diff --git a/.gitignore b/.gitignore index dacd5fc..94d52db 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,9 @@ files/pluginlib_helper/pluginlib_helper.cpp **/.catkin_tools **/devel/* **/logs/* + +# Android Studio stuff +**/.idea/* +*.iml + + diff --git a/example_workspace/src/hello_world_example_app/README.md b/example_workspace/src/hello_world_example_app/README.md index 377c579..c71bd97 100644 --- a/example_workspace/src/hello_world_example_app/README.md +++ b/example_workspace/src/hello_world_example_app/README.md @@ -1,36 +1,44 @@ -This is a hello ROS example app. It subscribes to `/chatter` topic and when a message is received, the following message is published in `/a_chatter`: +# Hello ROS (pubsub example - native Android) - data: "hello world from android ndk __COUNTER_VALUE__" +This is a hello ROS example app. It publishes a heartbeat to `/chatter` topic; when a message is received it is also logged using `ROS_INFO`. +The heartbeat looks like this: -USAGE -------- + data: "hello world from android ndk __COUNTER_VALUE__" -1. IP addresses are hardcoded, so you must edit the master URI and the android device ip address in the following file: +## Usage - app/src/main/cpp/main.cpp +1. Build the samples with `install` script using `--samples` option. +If you already have compiled the basic catkin workspace, you can enter the docker container (`docker/run.sh`) and build the samples using `build_catkin_workspace.sh` script using `-w` and `-p` options like this (replace paths with your current configuration if necessary): -2. Build the samples with do_everything script. + /opt/ros_android/scripts/build_catkin_workspace.sh -w /opt/ros_android/example_workspace/ -p /opt/ros_android/output/ -v 1 -b Debug -3. Install the app in your android device using adb -d install as explained here: +2. The APK file should be inside `app/build/outputs/apk/debug`. Install it in your android device using adb -d install as explained here: -4. Execute roscore in a terminal with ros sourced. Remember to export first your ip address: +3. Execute roscore in a terminal with ros sourced. Remember to export first your ip address: export ROS_IP=__YOUR_IP_ADDRESS__ -5. Subscribe to /a_chatter in another terminal with ros setup sourced: +4. Subscribe to /chatter in another terminal with ros setup sourced: - rostopic echo /a_chatter + rostopic echo /chatter + + You should see the heartbeat coming out. -6. Publish to /chatter, in another terminal with ros setup sourced and ROS_IP exported: +5. Optionally, publish to /chatter, in another terminal with ros setup sourced and ROS_IP exported: rostopic pub /chatter std_msgs/String "__YOUR_MESSAGE__" -r __PUBLISHING_RATE__ -If all is working well, you will receive multiple messages with an incresing counter value. -The message you have sent is logged in android, you can check the result with logcat: + The message you have sent is logged in Android, you can check the result with logcat: + + adb logcat + + You should see the heartbeat messages as well as any message you publish to `/chatter` topic in Android's log. + +### Remapping arguments - adb logcat +You can also remap topics as if you were launching a ROS console application. For example, to remap the topic enter the following: -The log will be like: + /chatter:=/custom_topic - 12-13 15:53:36.449 12078 12093 I ROSCPP_NDK_EXAMPLE: __YOUR_MESSAGE__ +In this case, you will see the output in `/custom_topic` instead. diff --git a/example_workspace/src/hello_world_example_app/app/src/main/cpp/CMakeLists.txt b/example_workspace/src/hello_world_example_app/app/CMakeLists.txt similarity index 62% rename from example_workspace/src/hello_world_example_app/app/src/main/cpp/CMakeLists.txt rename to example_workspace/src/hello_world_example_app/app/CMakeLists.txt index a1aabfd..d5cd503 100644 --- a/example_workspace/src/hello_world_example_app/app/src/main/cpp/CMakeLists.txt +++ b/example_workspace/src/hello_world_example_app/app/CMakeLists.txt @@ -20,29 +20,16 @@ project(hello_ros) find_package(catkin REQUIRED COMPONENTS roscpp std_msgs rosconsole) -include_directories(${catkin_INCLUDE_DIRS}) - -# build native_app_glue as a static lib -add_library(native_app_glue STATIC - ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) +include_directories(include ${catkin_INCLUDE_DIRS}) # now build app's shared lib set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -Werror") -# Export ANativeActivity_onCreate(), -# Refer to: https://github.com/android-ndk/ndk/issues/381. -set(CMAKE_SHARED_LINKER_FLAGS - "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") - -add_library(native-activity SHARED main.cpp) +add_library(native-activity SHARED src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.cpp src/main/cpp/main.cpp src/main/cpp/main_thread.cpp) -target_include_directories(native-activity PRIVATE - ${ANDROID_NDK}/sources/android/native_app_glue) # add lib dependencies target_link_libraries(native-activity android - native_app_glue ${catkin_LIBRARIES} - log ) diff --git a/example_workspace/src/hello_world_example_app/app/build.gradle b/example_workspace/src/hello_world_example_app/app/build.gradle index 5e79e6d..3d486db 100644 --- a/example_workspace/src/hello_world_example_app/app/build.gradle +++ b/example_workspace/src/hello_world_example_app/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 28 defaultConfig { - applicationId = 'com.example.hello_ros' + applicationId = 'org.ros.android.example.hello_ros' minSdkVersion 24 targetSdkVersion 26 externalNativeBuild { @@ -30,7 +30,7 @@ android { } externalNativeBuild { cmake { - path 'src/main/cpp/CMakeLists.txt' + path 'CMakeLists.txt' } } } diff --git a/example_workspace/src/hello_world_example_app/app/include/ros_android/main_thread.h b/example_workspace/src/hello_world_example_app/app/include/ros_android/main_thread.h new file mode 100644 index 0000000..4eff636 --- /dev/null +++ b/example_workspace/src/hello_world_example_app/app/include/ros_android/main_thread.h @@ -0,0 +1,40 @@ +#ifndef __ROS_ANDROID_MAIN_THREAD_H__ +#define __ROS_ANDROID_MAIN_THREAD_H__ + +#include +#include +#include + +namespace ros_android { +class MainThread { + public: + MainThread(); + explicit MainThread(std::string); + virtual ~MainThread() = 0; + + typedef std::shared_ptr Ptr; + static Ptr Instance(void); + + virtual void run() = 0; + virtual void stop() = 0; + bool check_ros_master(const std::vector> remapings); + private: + std::string node_name; + + static std::mutex s_instance_mutex; + static Ptr s_instance; +}; +} // namespace ros_android + +#define MY_ROS_ANDROID_MAIN_THREAD(class_name) \ + ros_android::MainThread::Ptr ros_android::MainThread::Instance() { \ + Ptr instance = s_instance; \ + if (!instance) { \ + std::lock_guard lock(s_instance_mutex); \ + if (!s_instance) { \ + instance = s_instance = std::make_shared(); \ + } \ + } \ + return instance; \ + } +#endif // #ifndef __ROS_ANDROID_MAIN_THREAD_H__ diff --git a/example_workspace/src/hello_world_example_app/app/src/main/AndroidManifest.xml b/example_workspace/src/hello_world_example_app/app/src/main/AndroidManifest.xml index d36cdb8..e8412fa 100644 --- a/example_workspace/src/hello_world_example_app/app/src/main/AndroidManifest.xml +++ b/example_workspace/src/hello_world_example_app/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ - @@ -9,22 +8,16 @@ - + android:hasCode="true"> - - - - @@ -33,4 +26,3 @@ - diff --git a/example_workspace/src/hello_world_example_app/app/src/main/cpp/main.cpp b/example_workspace/src/hello_world_example_app/app/src/main/cpp/main.cpp index b0471a3..f274ef0 100644 --- a/example_workspace/src/hello_world_example_app/app/src/main/cpp/main.cpp +++ b/example_workspace/src/hello_world_example_app/app/src/main/cpp/main.cpp @@ -1,94 +1,52 @@ -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include -#include "ros/ros.h" +#include +#include #include -#include +class HelloRos : public ros_android::MainThread { + public: + HelloRos() : ros_android::MainThread("hello_ros") {} -int loop_count_ = 0; -ros::Publisher chatter_pub; + virtual void run() override { + /* Write your main code here */ + ros::NodeHandle n; + ROS_INFO("Starting hello_ros main thread."); -void chatterCallback(const std_msgs::String::ConstPtr& msg){ - ROS_INFO("%s", msg->data.c_str()); - loop_count_++; - std_msgs::String msgo; - std::stringstream ss; - ss << "hello world from android ndk " << loop_count_; - msgo.data = ss.str(); - chatter_pub.publish(msgo); - ROS_INFO_STREAM(msg->data.c_str()); -} + // Creating a publisher and a subscriber; do a loopback in /chatter topic. + chatter_pub = n.advertise("chatter", 1000); + ros::Subscriber sub = n.subscribe("chatter", 1000, std::bind(&HelloRos::chatterCallback, this, std::placeholders::_1)); -void android_main(android_app *state) { + // Rate 1Hz + ros::WallRate loop_rate(1); - int argc = 3; + int loop_count = 0; + while (ros::ok()) { + ros::spinOnce(); - //*********************** NOTE: HARDCODE rosmaster ip addresses in __master, and hardcode the ip address of the android device in __ip ************************* - char* argv[] = {const_cast("nothing_important") , const_cast("__master:=http://10.34.0.120:11311"), const_cast("__ip:=10.34.0.121")}; - //********************************************************************************************************************************************************* + std_msgs::String msgo; + std::stringstream ss; + ss << "Hello world from android ndk " << loop_count++; + msgo.data = ss.str(); + chatter_pub.publish(msgo); - for (int i = 0; i < argc; i++) { - ROS_INFO("%s",argv[i]); + loop_rate.sleep(); + } } - ros::init(argc, &argv[0], "android_ndk_native_cpp"); - - ROS_INFO("GOING TO NODEHANDLE"); - std::string master_uri = ros::master::getURI(); - - if (ros::master::check()) { - ROS_INFO("ROS MASTER IS UP!"); - } else { - ROS_INFO("NO ROS MASTER."); + virtual void stop() override { + /* Write your clean-up code here */ + ros::shutdown(); } - ROS_INFO("%s", master_uri.c_str()); - - ros::NodeHandle n; - - ROS_INFO("GOING TO PUBLISHER"); - - // Creating a publisher and a subscriber - // When something is received in chatter topic, a message is published in a_chatter topic - chatter_pub = n.advertise("a_chatter", 1000); - ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); - - // Rate 1Hz - ros::WallRate loop_rate(1); - while(1) { - int events; - struct android_poll_source* source; + private: + ros::Publisher chatter_pub; - // Poll android events, without locking - while (ALooper_pollAll(0, NULL, &events, (void**)&source) >= 0) { - // Process this event - if (source != NULL) { - source->process(state, source); - } - - // Check if we are exiting. - if (state->destroyRequested != 0) { - ROS_INFO("APP DESTROYED BYE BYE"); - return; - } - } - - ros::spinOnce(); - - if (!ros::ok()) { - ROS_INFO("ROS ISN'T OK, BYE BYE"); - return; - } - - loop_rate.sleep(); + void chatterCallback(const std_msgs::String::ConstPtr& msg) { + ROS_INFO("Received message: %s", msg->data.c_str()); } -} +}; + +MY_ROS_ANDROID_MAIN_THREAD(HelloRos) diff --git a/example_workspace/src/hello_world_example_app/app/src/main/cpp/main_thread.cpp b/example_workspace/src/hello_world_example_app/app/src/main/cpp/main_thread.cpp new file mode 100644 index 0000000..0674425 --- /dev/null +++ b/example_workspace/src/hello_world_example_app/app/src/main/cpp/main_thread.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include +#include + + +namespace ros_android { + std::mutex MainThread::s_instance_mutex; + MainThread::Ptr MainThread::s_instance; + MainThread::MainThread() : node_name("ros_android_node") {} + MainThread::MainThread(std::string name) : node_name(name) {} + MainThread::~MainThread() {} + + bool MainThread::check_ros_master(const std::vector> remappings) { + bool rv; + + ros::init(remappings, node_name.c_str()); + rv = ros::master::check(); + if (rv) { + ROS_INFO("ROS MASTER IS UP!"); + } else { + ROS_INFO("NO ROS MASTER."); + } + ros::start(); + return rv; + } +} \ No newline at end of file diff --git a/example_workspace/src/hello_world_example_app/app/src/main/java/org/ros/android/example/hello_ros/MainActivity.java b/example_workspace/src/hello_world_example_app/app/src/main/java/org/ros/android/example/hello_ros/MainActivity.java new file mode 100644 index 0000000..412ac2c --- /dev/null +++ b/example_workspace/src/hello_world_example_app/app/src/main/java/org/ros/android/example/hello_ros/MainActivity.java @@ -0,0 +1,250 @@ +package org.ros.android.example.hello_ros; + +import android.Manifest; +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.util.Pair; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import java.lang.Runnable; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +public class MainActivity extends Activity { + static { + System.loadLibrary("native-activity"); + } + + private static final String IP_REGEX_EXPRESSION = + "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; + private final String TAG = "HELLO-WORLD-EXAMPLE"; + + private enum Status { + WAITING, RUNNING + } + + private class RosThread implements Runnable { + public RosThread() { + Log.i(TAG, "calling __RosThread"); + __RosThread(); + Log.i(TAG, "ended __RosThread"); + } + @Override + public native void run(); + public native void stop(); + public native boolean checkRosMaster(List> remappings); + private native void __RosThread(); + } + + private RosThread mainThread; + private EditText masterIP; + private EditText masterPort; + private CheckBox myIPCheckBox; + private Spinner myIP; + private EditText remappingArgs; + private Button runButton; + private TextView statusText; + private Status status; + private SharedPreferences sharedPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "OnCreate()"); + super.onCreate(savedInstanceState); + setContentView(R.layout.logging); + + masterIP = (EditText) findViewById(R.id.master_ip); + masterPort = (EditText) findViewById(R.id.master_port); + myIPCheckBox = (CheckBox) findViewById(R.id.my_ip_checkbox); + myIP = (Spinner) findViewById(R.id.my_ip); + remappingArgs = (EditText) findViewById(R.id.remapping_args); + runButton = (Button) findViewById(R.id.run_button); + statusText = (TextView) findViewById(R.id.status); + status = Status.WAITING; + + mainThread = new RosThread(); + + // Handle shared preferences + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); + String masterIPPreference = sharedPreferences.getString(getString(R.string.pref_master_ip_key), + getResources().getString(R.string.pref_master_ip_default)); + masterIP.setText(masterIPPreference); + + String masterPortPreference = sharedPreferences.getString(getString(R.string.pref_master_port_key), + getResources().getString(R.string.pref_master_port_default)); + masterPort.setText(masterPortPreference); + + String remappingsPreference = sharedPreferences.getString(getString(R.string.pref_remappings_key), + getResources().getString(R.string.pref_remappings_default)); + remappingArgs.setText(remappingsPreference); + + runButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + runButtonCallback(); + } + }); + + List ipAddressList = getLocalIpAddresses(); + ArrayAdapter adapter = + new ArrayAdapter(getApplicationContext(), android.R.layout.simple_spinner_dropdown_item, ipAddressList); + adapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); + myIP.setAdapter(adapter); + onCheckboxClicked(myIPCheckBox); + } + + @Override + protected void onResume() { + super.onResume(); + if (status == Status.RUNNING) { + new Thread(mainThread).start(); + } + } + + @Override + protected void onPause() { + super.onPause(); + mainThread.stop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mainThread.stop(); + } + + private void runButtonCallback() { + switch (status) { + case WAITING: + // Remapping arguments. + List> remappings = new ArrayList(); + + // Master IP. + String sMasterIP = masterIP.getText().toString(); + if (!sMasterIP.matches(IP_REGEX_EXPRESSION)) { + statusText.setText(R.string.status_masterIP_error); + break; + } + Log.i(TAG, "master ip is fine: " + sMasterIP); + + // Master port + String sMasterPort = masterPort.getText().toString(); + int iMasterPort = -1; + try { + iMasterPort = Integer.parseInt(sMasterPort); + } catch (Exception e) { + Log.w(TAG, "invalid master port."); + } + + if (iMasterPort < 0 || iMasterPort > 65535) { + statusText.setText(R.string.status_master_port_error); + break; + } + Log.i(TAG, "master port is fine: " + sMasterPort); + Pair master = new Pair("__master", "http://" + sMasterIP + ":" + sMasterPort); + remappings.add(master); + + // Device's IP. + if (myIPCheckBox.isChecked()) { + String sMyIP = String.valueOf(myIP.getSelectedItem()); + if (!sMyIP.matches(IP_REGEX_EXPRESSION)) { + statusText.setText(R.string.status_myIP_error); + break; + } + Log.i(TAG, "my ip is fine: " + sMyIP); + Pair ip = new Pair("__ip", sMyIP); + remappings.add(ip); + } + + // Parse remappings - basic error handling. + String sRemappingArgs = remappingArgs.getText().toString(); + if (!"".equals(sRemappingArgs)) { + try { + String[] remappingExtras = remappingArgs.getText().toString().split(" "); + for (String remapping : remappingExtras) { + String[] remappingBits = remapping.split(":="); + Pair remappingPair = new Pair(remappingBits[0], remappingBits[1]); + remappings.add(remappingPair); + } + } catch (Exception e) { + Log.e(TAG, "Could not parse remappings properly.", e); + } + } else { + Log.i(TAG, "No remapping arguments provided."); + } + + if (!mainThread.checkRosMaster(remappings)) { + statusText.setText(R.string.status_check_master_error); + break; + } + Log.i(TAG, "Master is ready"); + + statusText.setText(R.string.status_running); + runButton.setText(R.string.button_stop); + + new Thread(mainThread).start(); + status = Status.RUNNING; + + Editor sharedPreferencesEditor = sharedPreferences.edit(); + sharedPreferencesEditor.putString(getString(R.string.pref_master_ip_key), sMasterIP); + sharedPreferencesEditor.putString(getString(R.string.pref_master_port_key), sMasterPort); + sharedPreferencesEditor.putString(getString(R.string.pref_remappings_key), sRemappingArgs); + sharedPreferencesEditor.apply(); + break; + + case RUNNING: + statusText.setText(R.string.status_waiting); + runButton.setText(R.string.button_run); + + mainThread.stop(); + status = Status.WAITING; + break; + } + } + + public void onCheckboxClicked(View view) { + boolean checked = ((CheckBox) view).isChecked(); + + // Check which checkbox was clicked + switch(view.getId()) { + case R.id.my_ip_checkbox: + myIP.setEnabled(checked); + myIP.setClickable(checked); + break; + default: + break; + } + } + + private List getLocalIpAddresses() { + List availableIPAddresses = new ArrayList(); + try { + for (NetworkInterface netInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { + for (InetAddress inetAddress : Collections.list(netInterface.getInetAddresses())) { + if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { + availableIPAddresses.add(inetAddress.getHostAddress()); + } + } + } + } catch (Exception ex) { + Log.e("IP Address", ex.toString()); + } + return availableIPAddresses; + } +} diff --git a/example_workspace/src/hello_world_example_app/app/src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.cpp b/example_workspace/src/hello_world_example_app/app/src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.cpp new file mode 100644 index 0000000..4a6eeb9 --- /dev/null +++ b/example_workspace/src/hello_world_example_app/app/src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +#include +#include "org_ros_android_example_hello_ros_MainActivity_RosThread.h" + +using namespace ros_android; + +JNIEXPORT void JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread_run + (JNIEnv *, jobject) { + MainThread::Instance()->run(); +} + +JNIEXPORT void JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread_stop + (JNIEnv *, jobject) { + MainThread::Instance()->stop(); +} + +JNIEXPORT jboolean JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread_checkRosMaster + (JNIEnv *env, jobject, jobject remappings) { + // C++ vector to populate. + std::vector> remapping_map; + + // List required objects and IDs. + jclass cls_list = env->GetObjectClass(remappings); + jmethodID list_size_id = env->GetMethodID(cls_list, "size", "()I"); + jmethodID list_get_id = env->GetMethodID(cls_list, "get", "(I)Ljava/lang/Object;"); + jint list_size = env->CallIntMethod(remappings, list_size_id); + + // Pair required objects and IDs. + jclass cls_pair = env->FindClass("android/util/Pair"); + jfieldID pair_first_id = env->GetFieldID(cls_pair, "first", "Ljava/lang/Object;"); + jfieldID pair_second_id = env->GetFieldID(cls_pair, "second", "Ljava/lang/Object;"); + + for (jint i = 0; i < list_size; i++) { + jobject jpair = env->CallObjectMethod(remappings, list_get_id, i); + jstring jfirst = reinterpret_cast(env->GetObjectField(jpair, pair_first_id)); + jstring jsecond = reinterpret_cast(env->GetObjectField(jpair, pair_second_id)); + + std::pair c_pair(std::string(env->GetStringUTFChars(jfirst, nullptr)), + std::string(env->GetStringUTFChars(jsecond, nullptr))); + remapping_map.push_back(c_pair); + } + + + return MainThread::Instance()->check_ros_master(remapping_map); +} + +JNIEXPORT void JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread__1_1RosThread + (JNIEnv *, jobject) { + MainThread::Instance(); +} \ No newline at end of file diff --git a/example_workspace/src/hello_world_example_app/app/src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.h b/example_workspace/src/hello_world_example_app/app/src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.h new file mode 100644 index 0000000..8f5bb09 --- /dev/null +++ b/example_workspace/src/hello_world_example_app/app/src/main/jni/org_ros_android_example_hello_ros_MainActivity_RosThread.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_ros_android_example_hello_ros_MainActivity_RosThread */ + +#ifndef _Included_org_ros_android_example_hello_ros_MainActivity_RosThread +#define _Included_org_ros_android_example_hello_ros_MainActivity_RosThread +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_ros_android_example_hello_ros_MainActivity_RosThread + * Method: run + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread_run + (JNIEnv *, jobject); + +/* + * Class: org_ros_android_example_hello_ros_MainActivity_RosThread + * Method: stop + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread_stop + (JNIEnv *, jobject); + +/* + * Class: org_ros_android_example_hello_ros_MainActivity_RosThread + * Method: checkRosMaster + * Signature: (Ljava/lang/String;Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread_checkRosMaster + (JNIEnv *, jobject, jobject); + +/* + * Class: org_ros_android_example_hello_ros_MainActivity_RosThread + * Method: __RosThread + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_ros_android_example_hello_1ros_MainActivity_00024RosThread__1_1RosThread + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/example_workspace/src/hello_world_example_app/app/src/main/res/layout/logging.xml b/example_workspace/src/hello_world_example_app/app/src/main/res/layout/logging.xml new file mode 100644 index 0000000..f772907 --- /dev/null +++ b/example_workspace/src/hello_world_example_app/app/src/main/res/layout/logging.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + +