diff --git a/androidgcs/AndroidManifest.xml b/androidgcs/AndroidManifest.xml
index 068a01198..983042f34 100644
--- a/androidgcs/AndroidManifest.xml
+++ b/androidgcs/AndroidManifest.xml
@@ -1,71 +1,98 @@
-
+ package="org.openpilot.androidgcs"
+ android:versionCode="1"
+ android:versionName="1.0" >
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java
index 59380fbf0..113efa0c7 100644
--- a/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/HidUAVTalk.java
@@ -65,6 +65,8 @@ public class HidUAVTalk extends TelemetryTask {
private boolean readPending = false;
private boolean writePending = false;
private IntentFilter deviceAttachedFilter;
+ private boolean usbReceiverRegistered = false;
+ private boolean usbPermissionReceiverRegistered = false;
public HidUAVTalk(OPTelemetryService service) {
super(service);
@@ -74,8 +76,14 @@ public class HidUAVTalk extends TelemetryTask {
public void disconnect() {
CleanUpAndClose();
- telemService.unregisterReceiver(usbReceiver);
- telemService.unregisterReceiver(usbPermissionReceiver);
+ if(usbReceiverRegistered){
+ telemService.unregisterReceiver(usbReceiver);
+ usbReceiverRegistered = false;
+ }
+ if(usbPermissionReceiverRegistered){
+ telemService.unregisterReceiver(usbPermissionReceiver);
+ usbPermissionReceiverRegistered = false;
+ }
super.disconnect();
@@ -110,11 +118,13 @@ public class HidUAVTalk extends TelemetryTask {
permissionIntent = PendingIntent.getBroadcast(telemService, 0, new Intent(ACTION_USB_PERMISSION), 0);
permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
telemService.registerReceiver(usbPermissionReceiver, permissionFilter);
+ usbPermissionReceiverRegistered = true;
deviceAttachedFilter = new IntentFilter();
deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
deviceAttachedFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
telemService.registerReceiver(usbReceiver, deviceAttachedFilter);
+ usbReceiverRegistered = true;
// Go through all the devices plugged in
HashMap deviceList = usbManager.getDeviceList();
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java
index c03510a83..7a2003588 100644
--- a/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/OPTelemetryService.java
@@ -55,6 +55,8 @@ import android.widget.Toast;
import dalvik.system.DexClassLoader;
public class OPTelemetryService extends Service {
+ // Track lifecycle
+ private static int _startupRequests = 0;
// Logging settings
private final String TAG = OPTelemetryService.class.getSimpleName();
@@ -66,6 +68,7 @@ public class OPTelemetryService extends Service {
public final static String INTENT_CATEGORY_GCS = "org.openpilot.intent.category.GCS";
// Intent actions
+ public final static String INTENT_ACTION_TELEMETRYTASK_STARTED = "org.openpilot.intent.action.TELEMETRYTASK_STARTED";
public final static String INTENT_ACTION_CONNECTED = "org.openpilot.intent.action.CONNECTED";
public final static String INTENT_ACTION_DISCONNECTED = "org.openpilot.intent.action.DISCONNECTED";
@@ -139,6 +142,9 @@ public class OPTelemetryService extends Service {
throw new Error("Unsupported");
}
activeTelem.start();
+ Intent startedIntent = new Intent();
+ startedIntent.setAction(OPTelemetryService.INTENT_ACTION_TELEMETRYTASK_STARTED);
+ sendBroadcast(startedIntent,null);
break;
case MSG_DISCONNECT:
Toast.makeText(getApplicationContext(), "Disconnect requested", Toast.LENGTH_SHORT).show();
@@ -184,6 +190,9 @@ public class OPTelemetryService extends Service {
* and based on the stored preference will send itself a connect signal if needed.
*/
public void startup() {
+ synchronized (this) {
+ _startupRequests++;
+ }
Toast.makeText(getApplicationContext(), "Telemetry service starting", Toast.LENGTH_SHORT).show();
thread = new HandlerThread("TelemetryServiceHandler", Process.THREAD_PRIORITY_BACKGROUND);
@@ -232,11 +241,20 @@ public class OPTelemetryService extends Service {
telemTask = null;
try {
- activeTelem.join();
+ // Race condition - if we shut the service down before the telemetry task
+ // thread has started, this will hang so we need to check thread is runnable.
+ if(activeTelem.getState() == Thread.State.RUNNABLE){
+ activeTelem.join();
+ }else{
+ Log.d(TAG, "onDestroy() shut down telemetry task before it has started");
+ }
} catch (InterruptedException e) {
e.printStackTrace();
}
}
+ synchronized (this) {
+ _startupRequests = 0;
+ }
Log.d(TAG, "onDestory() shut down telemetry task");
Toast.makeText(this, "Telemetry service done", Toast.LENGTH_SHORT).show();
}
@@ -247,6 +265,9 @@ public class OPTelemetryService extends Service {
return telemTask.getTelemTaskIface();
return null;
}
+ public TelemetryTask getTelemetryTask(int id){
+ return telemTask;
+ }
public void openConnection() {
Toast.makeText(getApplicationContext(), "Requested open connection", Toast.LENGTH_SHORT).show();
Message msg = mServiceHandler.obtainMessage();
@@ -410,4 +431,8 @@ public class OPTelemetryService extends Service {
return true;
}
+
+ public static int getNumStartupRequests() {
+ return _startupRequests;
+ }
}
diff --git a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java
index ad23b2e00..dd1650487 100644
--- a/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java
+++ b/androidgcs/src/org/openpilot/androidgcs/telemetry/TelemetryTask.java
@@ -142,12 +142,16 @@ public abstract class TelemetryTask implements Runnable {
}
// Stop the master telemetry thread
- handler.post(new Runnable() {
- @Override
- public void run() {
- Looper.myLooper().quit();
- }
- });
+ // Check handler is not null: if we attempt to disconnect before
+ // the connect process has completed, handler may be null.
+ if(handler != null){
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Looper.myLooper().quit();
+ }
+ });
+ }
if (inputProcessThread != null) {
inputProcessThread.interrupt();
diff --git a/androidgcstests/.classpath b/androidgcstests/.classpath
new file mode 100644
index 000000000..affd72410
--- /dev/null
+++ b/androidgcstests/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/androidgcstests/.project b/androidgcstests/.project
new file mode 100644
index 000000000..94842a1ab
--- /dev/null
+++ b/androidgcstests/.project
@@ -0,0 +1,33 @@
+
+
+ androidgcstests
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/androidgcstests/AndroidManifest.xml b/androidgcstests/AndroidManifest.xml
new file mode 100644
index 000000000..8df0ba871
--- /dev/null
+++ b/androidgcstests/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidgcstests/proguard-project.txt b/androidgcstests/proguard-project.txt
new file mode 100644
index 000000000..f2fe1559a
--- /dev/null
+++ b/androidgcstests/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/androidgcstests/project.properties b/androidgcstests/project.properties
new file mode 100644
index 000000000..8937e94b9
--- /dev/null
+++ b/androidgcstests/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-14
diff --git a/androidgcstests/res/drawable-hdpi/ic_launcher.png b/androidgcstests/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..96a442e5b
Binary files /dev/null and b/androidgcstests/res/drawable-hdpi/ic_launcher.png differ
diff --git a/androidgcstests/res/drawable-ldpi/ic_launcher.png b/androidgcstests/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 000000000..99238729d
Binary files /dev/null and b/androidgcstests/res/drawable-ldpi/ic_launcher.png differ
diff --git a/androidgcstests/res/drawable-mdpi/ic_launcher.png b/androidgcstests/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..359047dfa
Binary files /dev/null and b/androidgcstests/res/drawable-mdpi/ic_launcher.png differ
diff --git a/androidgcstests/res/drawable-xhdpi/ic_launcher.png b/androidgcstests/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..71c6d760f
Binary files /dev/null and b/androidgcstests/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/androidgcstests/res/values/strings.xml b/androidgcstests/res/values/strings.xml
new file mode 100644
index 000000000..3e9bda27d
--- /dev/null
+++ b/androidgcstests/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ AndroidGCSTestTest
+
+
\ No newline at end of file
diff --git a/androidgcstests/src/org/openpilot/androidgcs/test/telemetryservice/OPTelemetryServiceTests.java b/androidgcstests/src/org/openpilot/androidgcs/test/telemetryservice/OPTelemetryServiceTests.java
new file mode 100644
index 000000000..bc3149543
--- /dev/null
+++ b/androidgcstests/src/org/openpilot/androidgcs/test/telemetryservice/OPTelemetryServiceTests.java
@@ -0,0 +1,207 @@
+/**
+ ******************************************************************************
+ * @file OPTelemetryServiceTests.java
+ * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2013.
+ * @brief Tests for the OPTelemetryService class
+ * @see The GNU Public License (GPL) Version 3
+ *
+ *****************************************************************************/
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package org.openpilot.androidgcs.test.telemetryservice;
+
+import org.openpilot.androidgcs.telemetry.HidUAVTalk;
+import org.openpilot.androidgcs.telemetry.TcpUAVTalk;
+import org.openpilot.androidgcs.telemetry.BluetoothUAVTalk;
+import org.openpilot.androidgcs.telemetry.OPTelemetryService;
+import org.openpilot.androidgcs.telemetry.OPTelemetryService.LocalBinder;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.ServiceTestCase;
+import android.test.mock.MockApplication;
+import android.test.mock.MockContext;
+
+public class OPTelemetryServiceTests extends
+ ServiceTestCase {
+
+ private static final int PREF_BLUETOOTH_CONN = 2;
+ private static final int PREF_TCP_CONN = 3;
+ private static final int PREF_USB_CONN = 4;
+ private Object _syncTelemetryTaskStarted = new Object();
+ private Object _syncTelemetryConnected = new Object();
+ private BroadcastReceiver _connectBroadcastReceiver;
+
+ public class MockAndroidGCSApplication extends MockApplication {
+
+ }
+
+ class MockAndroidGCSContext extends MockContext {
+
+ }
+
+ public OPTelemetryServiceTests() {
+ this(OPTelemetryService.class);
+ }
+
+ public OPTelemetryServiceTests(Class serviceClass) {
+ super(serviceClass);
+ _connectBroadcastReceiver = new BroadcastReceiver(){
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if(intent.getAction().equals(OPTelemetryService.INTENT_ACTION_CONNECTED)) {
+ if(_syncTelemetryConnected != null){
+ synchronized (_syncTelemetryConnected) {
+ _syncTelemetryConnected.notify();
+ }
+ }
+ }else if(intent.getAction().equals(OPTelemetryService.INTENT_ACTION_TELEMETRYTASK_STARTED)) {
+ // Not looked into why _syncTelemetryTaskStarted could possibly be null
+ // here but sometimes it is...
+ if(_syncTelemetryTaskStarted != null){
+ synchronized (_syncTelemetryTaskStarted) {
+ _syncTelemetryTaskStarted.notify();
+ }
+ }
+ }
+ }
+
+ };
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addCategory(OPTelemetryService.INTENT_CATEGORY_GCS);
+ filter.addAction(OPTelemetryService.INTENT_ACTION_CONNECTED);
+ filter.addAction(OPTelemetryService.INTENT_ACTION_TELEMETRYTASK_STARTED);
+
+ getContext().registerReceiver(_connectBroadcastReceiver, filter);
+
+// setApplication(new MockAndroidGCSApplication());
+// setContext(new MockAndroidGCSContext());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+// getContext().unregisterReceiver(_connectBroadcastReceiver);
+ }
+
+ public void testLifecycleSingleStart(){
+ bindService(new Intent(getContext(),
+ org.openpilot.androidgcs.telemetry.OPTelemetryService.class));
+
+ assertEquals(1, OPTelemetryService.getNumStartupRequests());
+
+ shutdownService();
+
+ assertEquals(0, OPTelemetryService.getNumStartupRequests());
+ }
+
+ public void testLifecycleMultiStart(){
+ bindService(new Intent(getContext(),
+ org.openpilot.androidgcs.telemetry.OPTelemetryService.class));
+ bindService(new Intent(getContext(),
+ org.openpilot.androidgcs.telemetry.OPTelemetryService.class));
+
+ assertEquals(1, OPTelemetryService.getNumStartupRequests());
+
+ shutdownService();
+
+ assertEquals(0, OPTelemetryService.getNumStartupRequests());
+ }
+
+ public void testStartBluetoothTelemetryTask(){
+ LocalBinder binder = startTelemetryTask(Integer.valueOf(PREF_BLUETOOTH_CONN));
+
+ assertTrue("Started wrong telemetry service type", binder.getTelemetryTask(0) instanceof BluetoothUAVTalk);
+ }
+
+ public void testStartBluetoothConnection() throws InterruptedException{
+ LocalBinder binder = startTelemetryTask(Integer.valueOf(PREF_BLUETOOTH_CONN));
+
+ synchronized (_syncTelemetryConnected) {
+ _syncTelemetryConnected.wait(2000);
+ }
+
+ assertTrue("Failed to connect to telemetry service", binder.isConnected());
+
+ assertTrue("Started wrong telemetry service type", binder.getTelemetryTask(0) instanceof BluetoothUAVTalk);
+ }
+
+ public void testStartTCPTelemetryTask(){
+ LocalBinder binder = startTelemetryTask(Integer.valueOf(PREF_TCP_CONN));
+
+ assertTrue("Started wrong telemetry service type", binder.getTelemetryTask(0) instanceof TcpUAVTalk);
+ }
+
+ public void testStartTCPConnection() throws InterruptedException{
+ LocalBinder binder = startTelemetryTask(Integer.valueOf(PREF_TCP_CONN));
+
+ synchronized (_syncTelemetryConnected) {
+ _syncTelemetryConnected.wait(2000);
+ }
+
+ assertTrue("Failed to connect to telemetry service", binder.isConnected());
+
+ assertTrue("Started wrong telemetry service type", binder.getTelemetryTask(0) instanceof TcpUAVTalk);
+ }
+
+ public void testStartUSBTelemetryTask(){
+ LocalBinder binder = startTelemetryTask(Integer.valueOf(PREF_USB_CONN));
+
+ assertTrue("Started wrong telemetry service type", binder.getTelemetryTask(0) instanceof HidUAVTalk);
+ }
+
+ public void testStartUSBConnection() throws InterruptedException{
+ LocalBinder binder = startTelemetryTask(Integer.valueOf(PREF_USB_CONN));
+
+ synchronized (_syncTelemetryConnected) {
+ _syncTelemetryConnected.wait(2000);
+ }
+
+ assertTrue("Failed to connect to telemetry service", binder.isConnected());
+
+ assertTrue("Started wrong telemetry service type", binder.getTelemetryTask(0) instanceof HidUAVTalk);
+ }
+
+ private LocalBinder startTelemetryTask(Integer telemetryType) {
+ LocalBinder binder = (LocalBinder) bindService(new Intent(getContext(),
+ org.openpilot.androidgcs.telemetry.OPTelemetryService.class));
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString("connection_type", telemetryType.toString());
+ editor.apply();
+
+ binder.openConnection();
+ synchronized (_syncTelemetryTaskStarted) {
+ try {
+ _syncTelemetryTaskStarted.wait(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return binder;
+ }
+}