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; + } +}