Merge remote-tracking branch 'mjb/master'
authorKeith Packard <keithp@keithp.com>
Wed, 29 Aug 2012 18:29:24 +0000 (11:29 -0700)
committerKeith Packard <keithp@keithp.com>
Wed, 29 Aug 2012 18:29:24 +0000 (11:29 -0700)
Pull in Mike's fancy new AltosDroid bits

16 files changed:
altosdroid/AndroidManifest.xml
altosdroid/Makefile.am
altosdroid/res/layout/altosdroid.xml [new file with mode: 0644]
altosdroid/res/layout/main.xml
altosdroid/res/layout/telemetry_service_binding.xml [deleted file]
altosdroid/res/layout/telemetry_service_controller.xml [deleted file]
altosdroid/res/menu/option_menu.xml
altosdroid/res/values/strings.xml
altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java [deleted file]
altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java
altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java
altosdroid/src/org/altusmetrum/AltosDroid/TelemetryServiceActivities.java [deleted file]

index 96fe5ac70164c29a52412e03619af6c2721374a4..1239175968fb334ed0d44a400d727ef37c61fe17 100644 (file)
@@ -1,17 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
+<!--
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="org.altusmetrum.AltosDroid"
                   android:configChanges="orientation|keyboardHidden" />
 
 
-        <!-- Service Samples -->
-
         <service android:name=".TelemetryService" />
 
-        <activity android:name=".TelemetryServiceActivities$Controller"
-                android:label="@string/activity_telemetry_service_controller"
-                android:launchMode="singleTop">
-<!--
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.SAMPLE_CODE" />
-            </intent-filter>
--->
-        </activity>
-
-        <activity android:name="TelemetryServiceActivities$Binding"
-                android:label="@string/activity_telemetry_service_binding">
-<!--
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.SAMPLE_CODE" />
-            </intent-filter>
--->
-        </activity>
 
     </application>
 </manifest>
index 6ee984c2588f2aaa43956a16d803bff20fa9959a..36d28ca216ad1a0ef17c76bd4d3e90e4ca55fbe5 100644 (file)
@@ -24,9 +24,10 @@ ALTOSLIB=$(EXT_LIBDIR)/$(ALTOSLIB_JAR)
 SRC=\
        $(SRC_DIR)/AltosDroid.java \
        $(SRC_DIR)/TelemetryService.java \
-       $(SRC_DIR)/TelemetryServiceActivities.java \
-       $(SRC_DIR)/BluetoothChatService.java \
-       $(SRC_DIR)/DeviceListActivity.java
+       $(SRC_DIR)/TelemetryReader.java \
+       $(SRC_DIR)/AltosBluetooth.java \
+       $(SRC_DIR)/DeviceListActivity.java \
+       $(SRC_DIR)/Dumper.java
 
 all: $(all_target)
 
diff --git a/altosdroid/res/layout/altosdroid.xml b/altosdroid/res/layout/altosdroid.xml
new file mode 100644 (file)
index 0000000..33d89d5
--- /dev/null
@@ -0,0 +1,350 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0" >
+
+        <RelativeLayout
+            android:id="@+id/strut"
+            android:layout_width="10dip"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true" >
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/callsign_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_toLeftOf="@+id/strut" >
+
+            <TextView
+                android:id="@+id/callsign_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/callsign_label" />
+
+            <TextView
+                android:id="@+id/callsign_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_below="@id/callsign_label"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/state_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_toRightOf="@+id/strut" >
+
+            <TextView
+                android:id="@+id/state_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/state_label" />
+
+            <TextView
+                android:id="@+id/state_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/state_label"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/speed_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_below="@+id/callsign_container"
+            android:layout_toLeftOf="@+id/strut" >
+
+            <TextView
+                android:id="@+id/speed_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/speed_label" />
+
+            <TextView
+                android:id="@+id/speed_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/speed_label"
+                android:layout_toLeftOf="@+id/speed_units"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/speed_units"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignBaseline="@id/speed_value"
+                android:layout_alignParentRight="true"
+                android:layout_below="@id/speed_label"
+                android:gravity="right"
+                android:paddingLeft="10dip"
+                android:text="@string/speed_units"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/accel_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@+id/state_container"
+            android:layout_toRightOf="@+id/strut" >
+
+            <TextView
+                android:id="@+id/accel_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/accel_label" />
+
+            <TextView
+                android:id="@+id/accel_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/accel_label"
+                android:layout_toLeftOf="@+id/accel_units"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/accel_units"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignBaseline="@+id/accel_value"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/accel_label"
+                android:gravity="right"
+                android:paddingLeft="10dip"
+                android:text="@string/accel_units"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/range_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_below="@+id/speed_container"
+            android:layout_toLeftOf="@+id/strut" >
+
+            <TextView
+                android:id="@+id/range_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/range_label" />
+
+            <TextView
+                android:id="@+id/range_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/range_label"
+                android:layout_toLeftOf="@+id/range_units"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/range_units"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignBaseline="@+id/range_value"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/range_label"
+                android:gravity="right"
+                android:paddingLeft="10dip"
+                android:text="@string/range_units"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/altitude_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/accel_container"
+            android:layout_toRightOf="@id/strut" >
+
+            <TextView
+                android:id="@+id/altitude_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/altitude_label" />
+
+            <TextView
+                android:id="@+id/altitude_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/altitude_label"
+                android:layout_toLeftOf="@+id/altitude_units"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/altitude_units"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignBaseline="@+id/altitude_value"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/altitude_label"
+                android:gravity="right"
+                android:paddingLeft="10dip"
+                android:text="@string/altitude_units"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/azimuth_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_below="@id/range_container"
+            android:layout_toLeftOf="@id/strut" >
+
+            <TextView
+                android:id="@+id/azimuth_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/azimuth_label" />
+
+            <TextView
+                android:id="@+id/azimuth_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/azimuth_label"
+                android:layout_toLeftOf="@+id/azimuth_units"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/azimuth_units"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/azimuth_label"
+                android:gravity="right"
+                android:paddingLeft="10dip"
+                android:text="@string/azimuth_units"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/bearing_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@+id/altitude_container"
+            android:layout_toRightOf="@+id/strut" >
+
+            <TextView
+                android:id="@+id/bearing_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/bearing_label" />
+
+            <TextView
+                android:id="@+id/bearing_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/bearing_label"
+                android:layout_toLeftOf="@+id/bearing_units"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+            <TextView
+                android:id="@+id/bearing_units"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/bearing_label"
+                android:gravity="right"
+                android:paddingLeft="10dip"
+                android:text="@string/bearing_units"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/latitude_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/azimuth_container" >
+
+            <TextView
+                android:id="@+id/latitude_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/latitude_label" />
+
+            <TextView
+                android:id="@+id/latitude_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/latitude_label"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:id="@+id/longitude_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/latitude_container" >
+
+            <TextView
+                android:id="@+id/longitude_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/longitude_label" />
+
+            <TextView
+                android:id="@+id/longitude_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_below="@+id/longitude_label"
+                android:text=""
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        </RelativeLayout>
+        
+           <TextView
+               android:id="@+id/text"
+               android:layout_width="fill_parent"
+               android:layout_height="0dip"
+               android:layout_alignParentBottom="true"
+               android:layout_below="@id/longitude_container"
+               android:gravity="bottom"
+               android:scrollbars="vertical"
+               android:textSize="7dp"
+               android:typeface="monospace" />
+
+    </RelativeLayout>
index 070928a55a0c9f529fa7aa6390796c65ad5ed15b..00ca63c8da8ce10feed788c9963bd8623cb6cd86 100644 (file)
     <TextView
         android:id="@+id/in"
         android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_height="0dip"
         android:layout_weight="1"
         android:gravity="bottom"
         android:scrollbars="vertical"
         android:textSize="7dp"
         android:typeface="monospace" />
 
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        >
-
-        <EditText
-            android:id="@+id/edit_text_out"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom"
-            android:layout_weight="1"
-            android:inputType="text|textNoSuggestions" />
-
-        <Button android:id="@+id/button_send"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/send"
-        />
-    </LinearLayout>
 </LinearLayout>
diff --git a/altosdroid/res/layout/telemetry_service_binding.xml b/altosdroid/res/layout/telemetry_service_binding.xml
deleted file mode 100644 (file)
index 950d0d3..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- Demonstrates starting and stopping a local service.
-     See corresponding Java code com.android.sdk.app.LocalSerice.java. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="4dip"
-    android:gravity="center_horizontal"
-    android:layout_width="match_parent" android:layout_height="match_parent">
-
-    <TextView
-        android:layout_width="match_parent" android:layout_height="wrap_content"
-        android:layout_weight="0"
-        android:paddingBottom="4dip"
-        android:text="@string/telemetry_service_binding"/>
-
-    <Button android:id="@+id/bind"
-        android:layout_width="wrap_content" android:layout_height="wrap_content" 
-        android:text="@string/bind_service">
-        <requestFocus />
-    </Button>
-
-    <Button android:id="@+id/unbind"
-        android:layout_width="wrap_content" android:layout_height="wrap_content" 
-        android:text="@string/unbind_service">
-    </Button>
-
-</LinearLayout>
-
diff --git a/altosdroid/res/layout/telemetry_service_controller.xml b/altosdroid/res/layout/telemetry_service_controller.xml
deleted file mode 100644 (file)
index 189d2f6..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- Demonstrates starting and stopping a local service.
-     See corresponding Java code com.android.sdk.app.LocalSerice.java. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="4dip"
-    android:gravity="center_horizontal"
-    android:layout_width="match_parent" android:layout_height="match_parent">
-
-    <TextView
-        android:layout_width="match_parent" android:layout_height="wrap_content"
-        android:layout_weight="0"
-        android:paddingBottom="4dip"
-        android:text="@string/telemetry_service_controller"/>
-
-    <Button android:id="@+id/start"
-        android:layout_width="wrap_content" android:layout_height="wrap_content" 
-        android:text="@string/start_service">
-        <requestFocus />
-    </Button>
-
-    <Button android:id="@+id/stop"
-        android:layout_width="wrap_content" android:layout_height="wrap_content" 
-        android:text="@string/stop_service">
-    </Button>
-
-</LinearLayout>
-
index feb5668ea0e42c58f664735cab1b30f8783b1ec7..6946e298cdc5a3881d8ccec764898bf67e4435ad 100644 (file)
     <item android:id="@+id/connect_scan"
           android:icon="@android:drawable/ic_menu_search"
           android:title="@string/connect_device" />
-    <item android:id="@+id/telemetry_service_control"
-          android:icon="@android:drawable/ic_menu_manage"
-          android:title="@string/telemetry_service_control" />
-    <item android:id="@+id/telemetry_service_bind"
-          android:icon="@android:drawable/ic_menu_rotate"
-          android:title="@string/telemetry_service_bind" />
 </menu>
index 72a4ddec2b03c0932535d9f58ad41bd645217648..59f4f827dfb86f290997b7c0bef393df3bad65d5 100644 (file)
 <resources>
     <string name="app_name">AltosDroid</string>
 
-    <!--  BluetoothChat -->
-    <string name="send">Send</string>
-    <string name="not_connected">You are not connected to a device</string>
-    <string name="bt_not_enabled_leaving">Bluetooth was not enabled. Leaving Bluetooth Chat.</string>
+    <!--  AltosDroid -->
+    <string name="bt_not_enabled">Bluetooth was not enabled.</string>
     <string name="title_connecting">connecting…</string>
     <string name="title_connected_to">connected: </string>
     <string name="title_not_connected">not connected</string>
 
+    <!-- Options Menu -->
+    <string name="connect_device">Connect a device</string>
+
     <!--  DeviceListActivity -->
     <string name="scanning">scanning for devices…</string>
     <string name="select_device">select a device to connect</string>
     <string name="title_other_devices">Other Available Devices</string>
     <string name="button_scan">Scan for devices</string>
 
-    <!-- Options Menu -->
-    <string name="connect_device">Connect a device</string>
-    <string name="telemetry_service_control">Control Service</string>
-    <string name="telemetry_service_bind">(Un)Bind Service</string>
-
-
-
     <!-- Service -->
     <string name="telemetry_service_label">AltosDroid Telemetry Service</string>
     <string name="telemetry_service_started">Telemetry Service Started</string>
     <string name="telemetry_service_stopped">Telemetry Service Stopped</string>
 
-    <!-- Service control activity - temporary! -->
-    <string name="activity_telemetry_service_controller">Telemetry Service Controller</string>
-    <string name="telemetry_service_controller">Use the following buttons to start and stop the Telemetry
-        service.</string>
-    <string name="start_service">Start Service</string>
-    <string name="stop_service">Stop Service</string>
 
-    <string name="activity_telemetry_service_binding">Telemetry Service Binding</string>
-    <string name="telemetry_service_binding">This demonstrates how you can connect with a persistent
-        service.  Notice how it automatically starts for you, and play around with the
-        interaction between this and Local Service Controller.</string>
-    <string name="bind_service">Bind Service</string>
-    <string name="unbind_service">Unbind Service</string>
+       <!-- UI fields -->
+    <string name="callsign_label">Callsign</string>
+    <string name="state_label">State</string>
+    <string name="speed_label">Speed</string>
+    <string name="speed_units">m/s</string>
+    <string name="accel_label">Acceleration</string>
+    <string name="accel_units">m/s²</string>
+    <string name="range_label">Range</string>
+    <string name="range_units">m</string>
+    <string name="altitude_label">Altitude</string>
+    <string name="altitude_units">m</string>
+    <string name="azimuth_label">Azimuth</string>
+    <string name="azimuth_units">°</string>
+    <string name="bearing_label">Bearing</string>
+    <string name="bearing_units">°</string>
+    <string name="latitude_label">Latitude</string>
+    <string name="longitude_label">Longitude</string>
 
-    <string name="telemetry_service_connected">Connected to local service</string>
-    <string name="telemetry_service_disconnected">Disconnected from local service</string>
 
 </resources>
index 45438a6c9ba3cd3117e29d8f4c6ec93af329f560..4c8136aa4bc4cb510a27c60c4289c9889675ab20 100644 (file)
@@ -1,5 +1,6 @@
 /*
- * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
  *
  * 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
@@ -20,14 +21,14 @@ package org.altusmetrum.AltosDroid;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.reflect.Method;
+import java.util.UUID;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothSocket;
-import android.content.Context;
-import android.os.Bundle;
+//import android.os.Bundle;
 import android.os.Handler;
-import android.os.Message;
+//import android.os.Message;
 import android.util.Log;
 
 import org.altusmetrum.AltosLib.*;
@@ -38,112 +39,126 @@ public class AltosBluetooth extends AltosLink {
        private static final String TAG = "AltosBluetooth";
        private static final boolean D = true;
 
-       /**
-        * This thread runs while attempting to make an outgoing connection
-        * with a device. It runs straight through; the connection either
-        * succeeds or fails.
-        */
+       private ConnectThread    connect_thread = null;
+       private Thread           input_thread   = null;
+
+       private Handler          handler;
+
+       private BluetoothAdapter adapter;
+       private BluetoothDevice  device;
+       private BluetoothSocket  socket;
+       private InputStream      input;
+       private OutputStream     output;
+
+       // Constructor
+       public AltosBluetooth(BluetoothDevice in_device, Handler in_handler) {
+               adapter = BluetoothAdapter.getDefaultAdapter();
+               device = in_device;
+               handler = in_handler;
+
+               connect_thread = new ConnectThread(device);
+               connect_thread.start();
 
-       private BluetoothAdapter        adapter;
-       private ConnectThread           connect_thread;
-       private BluetoothSocket         socket;
-       private InputStream             input;
-       private OutputStream            output;
+       }
 
        private class ConnectThread extends Thread {
-               private final BluetoothDevice mmDevice;
-               private String mSocketType;
-               BluetoothSocket tmp_socket;
+               private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
 
-               public ConnectThread(BluetoothDevice device, boolean secure) {
-                       mmDevice = device;
-                       mSocketType = secure ? "Secure" : "Insecure";
+               public ConnectThread(BluetoothDevice device) {
+                       BluetoothSocket tmp_socket = null;
 
-                       // Get a BluetoothSocket for a connection with the
-                       // given BluetoothDevice
                        try {
-                               if (secure) {
-                                       Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
-                                       tmp_socket = (BluetoothSocket) m.invoke(device, 2);
-                                       // tmp = device.createRfcommSocket(2);
-                               } else {
-                                       Method m = device.getClass().getMethod("createInsecureRfcommSocket", new Class[] {int.class});
-                                       tmp_socket = (BluetoothSocket) m.invoke(device, 2);
-                                       // tmp = device.createInsecureRfcommSocket(2);
-                               }
-                       } catch (Exception e) {
-                               Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
+                               tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
+                       } catch (IOException e) {
                                e.printStackTrace();
                        }
+                       socket = tmp_socket;
                }
 
                public void run() {
-                       Log.i(TAG, "BEGIN connect_thread SocketType:" + mSocketType);
-                       setName("ConnectThread" + mSocketType);
+                       if (D) Log.d(TAG, "ConnectThread: BEGIN");
+                       setName("ConnectThread");
 
                        // Always cancel discovery because it will slow down a connection
                        adapter.cancelDiscovery();
 
-                       // Make a connection to the BluetoothSocket
-                       try {
-                               // This is a blocking call and will only return on a
-                               // successful connection or an exception
-                               tmp_socket.connect();
-                       } catch (IOException e) {
-                               // Close the socket
+                       synchronized (AltosBluetooth.this) {
+                               // Make a connection to the BluetoothSocket
                                try {
-                                       tmp_socket.close();
-                               } catch (IOException e2) {
-                                       Log.e(TAG, "unable to close() " + mSocketType +
-                                             " socket during connection failure", e2);
+                                       // This is a blocking call and will only return on a
+                                       // successful connection or an exception
+                                       socket.connect();
+
+                                       input = socket.getInputStream();
+                                       output = socket.getOutputStream();
+                               } catch (IOException e) {
+                                       // Close the socket
+                                       try {
+                                               socket.close();
+                                       } catch (IOException e2) {
+                                               if (D) Log.e(TAG, "ConnectThread: Failed to close() socket after failed connection");
+                                       }
+                                       input = null;
+                                       output = null;
+                                       AltosBluetooth.this.notifyAll();
+                                       handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED).sendToTarget();
+                                       if (D) Log.e(TAG, "ConnectThread: Failed to establish connection");
+                                       return;
                                }
-                               connection_failed();
-                               return;
-                       }
 
-                       try {
-                               synchronized (AltosBluetooth.this) {
-                                       input = tmp_socket.getInputStream();
-                                       output = tmp_socket.getOutputStream();
-                                       socket = tmp_socket;
-                                       // Reset the ConnectThread because we're done
-                                       AltosBluetooth.this.notify();
-                                       connect_thread = null;
-                               }
-                       } catch (Exception e) {
-                               Log.e(TAG, "Failed to finish connection", e);
-                               e.printStackTrace();
+                               input_thread = new Thread(AltosBluetooth.this);
+                               input_thread.start();
+
+                               // Configure the newly connected device for telemetry
+                               print("~\nE 0\n");
+                               set_monitor(false);
+
+                               // Let TelemetryService know we're connected
+                               handler.obtainMessage(TelemetryService.MSG_CONNECTED).sendToTarget();
+
+                               // Notify other waiting threads, now that we're connected
+                               AltosBluetooth.this.notifyAll();
+
+                               // Reset the ConnectThread because we're done
+                               connect_thread = null;
+
+                               if (D) Log.d(TAG, "ConnectThread: Connect completed");
                        }
                }
 
                public void cancel() {
                        try {
-                               if (tmp_socket != null)
-                                       tmp_socket.close();
+                               if (socket != null)
+                                       socket.close();
                        } catch (IOException e) {
-                               Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
+                               if (D) Log.e(TAG, "ConnectThread: close() of connect socket failed", e);
                        }
                }
        }
 
-       private synchronized void wait_connected() throws InterruptedException {
+       private synchronized void wait_connected() throws InterruptedException, IOException {
                if (input == null) {
                        wait();
+                       if (input == null) throw new IOException();
                }
        }
 
-       private void connection_failed() {
+       private void connection_lost() {
+               if (D) Log.e(TAG, "Connection lost during I/O");
+               handler.obtainMessage(TelemetryService.MSG_DISCONNECTED).sendToTarget();
        }
-       
+
        public void print(String data) {
                byte[] bytes = data.getBytes();
+               if (D) Log.d(TAG, "print(): begin");
                try {
                        wait_connected();
                        output.write(bytes);
+                       if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'");
                } catch (IOException e) {
-                       connection_failed();
+                       connection_lost();
                } catch (InterruptedException e) {
-                       connection_failed();
+                       connection_lost();
                }
        }
 
@@ -152,40 +167,58 @@ public class AltosBluetooth extends AltosLink {
                        wait_connected();
                        return input.read();
                } catch (IOException e) {
-                       connection_failed();
+                       connection_lost();
                } catch (java.lang.InterruptedException e) {
-                       connection_failed();
+                       connection_lost();
                }
                return AltosLink.ERROR;
        }
-                       
+
        public void close() {
+               if (D) Log.d(TAG, "close(): begin");
                synchronized(this) {
+                       if (D) Log.d(TAG, "close(): synched");
+
                        if (connect_thread != null) {
+                               if (D) Log.d(TAG, "close(): stopping connect_thread");
                                connect_thread.cancel();
                                connect_thread = null;
                        }
+                       if (D) Log.d(TAG, "close(): Closing socket");
+                       try {
+                               socket.close();
+                       } catch (IOException e) {
+                               if (D) Log.e(TAG, "close(): unable to close() socket");
+                       }
+                       if (input_thread != null) {
+                               if (D) Log.d(TAG, "close(): stopping input_thread");
+                               try {
+                                       if (D) Log.d(TAG, "close(): input_thread.interrupt().....");
+                                       input_thread.interrupt();
+                                       if (D) Log.d(TAG, "close(): input_thread.join().....");
+                                       input_thread.join();
+                               } catch (Exception e) {}
+                               input_thread = null;
+                       }
+                       input = null;
+                       output = null;
+                       notifyAll();
                }
        }
 
-       public void flush_output() {
-               super.flush_output();
-               /* any local work needed to flush bluetooth? */
-       }
 
-       public boolean can_cancel_reply() {
-               return false;
-       }
-       public boolean show_reply_timeout() {
-               return true;
-       }
-               
-       public void hide_reply_timeout() {
+       // We override this method so that we can add some debugging. Not 100% elegant, but more useful
+       // than debugging one char at a time above in getchar()!
+       public void add_reply(AltosLine line) throws InterruptedException {
+               if (D) Log.d(TAG, String.format("Got REPLY: %s", line.line));
+               super.add_reply(line);
        }
 
-       public AltosBluetooth(BluetoothDevice device) {
-               adapter = BluetoothAdapter.getDefaultAdapter();
-               connect_thread = new ConnectThread(device, true);
-               connect_thread.start();
-       }
-}
\ No newline at end of file
+       //public void flush_output() { super.flush_output(); }
+
+       // Stubs of required methods when extending AltosLink
+       public boolean can_cancel_reply()   { return false; }
+       public boolean show_reply_timeout() { return true; }
+       public void hide_reply_timeout()    { }
+
+}
index b4725e220c41d27a4a3ce90f4467ae25f6a2f199..ba424e7915182881399516d7ec27d1759790db67 100644 (file)
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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; version 2 of the License.
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * 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.altusmetrum.AltosDroid;
 
+import java.lang.ref.WeakReference;
+
 import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
 import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
-import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-import android.view.View;
 import android.view.Window;
-import android.view.View.OnClickListener;
-import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
-import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.Toast;
-import org.altusmetrum.AltosDroid.R;
+
 import org.altusmetrum.AltosLib.*;
 
 /**
  * This is the main Activity that displays the current chat session.
  */
 public class AltosDroid extends Activity {
-    // Debugging
-    private static final String TAG = "AltosDroid";
-    private static final boolean D = true;
-
-    // Message types sent from the BluetoothChatService Handler
-    public static final int MESSAGE_STATE_CHANGE = 1;
-    public static final int MESSAGE_READ = 2;
-    public static final int MESSAGE_WRITE = 3;
-    public static final int MESSAGE_DEVICE_NAME = 4;
-    public static final int MESSAGE_TOAST = 5;
-
-    // Key names received from the BluetoothChatService Handler
-    public static final String DEVICE_NAME = "device_name";
-    public static final String TOAST = "toast";
-
-    // Intent request codes
-    private static final int REQUEST_CONNECT_DEVICE = 1;
-    private static final int REQUEST_ENABLE_BT      = 2;
-
-    // Layout Views
-    private TextView mTitle;
-    private TextView mSerialView;
-    private EditText mOutEditText;
-    private Button mSendButton;
-
-    // Name of the connected device
-    private String mConnectedDeviceName = null;
-    // String buffer for outgoing messages
-    private StringBuffer mOutStringBuffer;
-    // Local Bluetooth adapter
-    private BluetoothAdapter mBluetoothAdapter = null;
-    // Member object for the chat services
-    private BluetoothChatService mChatService = null;
-
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if(D) Log.e(TAG, "+++ ON CREATE +++");
-
-        // Set up the window layout
-        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
-        setContentView(R.layout.main);
-        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
-
-        // Set up the custom title
-        mTitle = (TextView) findViewById(R.id.title_left_text);
-        mTitle.setText(R.string.app_name);
-        mTitle = (TextView) findViewById(R.id.title_right_text);
-
-        // Get local Bluetooth adapter
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        // If the adapter is null, then Bluetooth is not supported
-        if (mBluetoothAdapter == null) {
-            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
-            finish();
-            return;
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if(D) Log.e(TAG, "++ ON START ++");
-
-        // If BT is not on, request that it be enabled.
-        // setupChat() will then be called during onActivityResult
-        if (!mBluetoothAdapter.isEnabled()) {
-            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
-            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
-        // Otherwise, setup the chat session
-        } else {
-            if (mChatService == null) setupChat();
-        }
-    }
-
-    @Override
-    public synchronized void onResume() {
-        super.onResume();
-        if(D) Log.e(TAG, "+ ON RESUME +");
-
-        // Performing this check in onResume() covers the case in which BT was
-        // not enabled during onStart(), so we were paused to enable it...
-        // onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
-        if (mChatService != null) {
-            // Only if the state is STATE_NONE, do we know that we haven't started already
-            if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
-              // Start the Bluetooth chat services
-              mChatService.start();
-            }
-        }
-    }
-
-    @Override
-    public synchronized void onPause() {
-        super.onPause();
-        if(D) Log.e(TAG, "- ON PAUSE -");
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        if(D) Log.e(TAG, "-- ON STOP --");
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        // Stop the Bluetooth chat services
-        if (mChatService != null) mChatService.stop();
-        if(D) Log.e(TAG, "--- ON DESTROY ---");
-    }
-
-
-
-    private void setupChat() {
-        Log.d(TAG, "setupChat()");
-
-        mSerialView = (TextView) findViewById(R.id.in);
-        mSerialView.setMovementMethod(new ScrollingMovementMethod());
-        mSerialView.setClickable(false);
-        mSerialView.setLongClickable(false);
-
-        // Initialize the compose field with a listener for the return key
-        mOutEditText = (EditText) findViewById(R.id.edit_text_out);
-        mOutEditText.setOnEditorActionListener(mWriteListener);
-
-        // Initialize the send button with a listener that for click events
-        mSendButton = (Button) findViewById(R.id.button_send);
-        mSendButton.setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                // Send a message using content of the edit text widget
-                TextView view = (TextView) findViewById(R.id.edit_text_out);
-                String message = view.getText().toString();
-                sendMessage(message);
-            }
-        });
-
-        // Initialize the BluetoothChatService to perform bluetooth connections
-        mChatService = new BluetoothChatService(this, mHandler);
-
-        // Initialize the buffer for outgoing messages
-        mOutStringBuffer = new StringBuffer("");
-    }
-
-    /**
-     * Sends a message.
-     * @param message  A string of text to send.
-     */
-    private void sendMessage(String message) {
-        // Check that we're actually connected before trying anything
-        if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
-            Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
-            return;
-        }
-
-        // Check that there's actually something to send
-        if (message.length() > 0) {
-            // Get the message bytes and tell the BluetoothChatService to write
-            byte[] send = message.getBytes();
-            mChatService.write(send);
-
-            // Reset out string buffer to zero and clear the edit text field
-            mOutStringBuffer.setLength(0);
-            mOutEditText.setText(mOutStringBuffer);
-        }
-    }
-
-    // The action listener for the EditText widget, to listen for the return key
-    private TextView.OnEditorActionListener mWriteListener =
-        new TextView.OnEditorActionListener() {
-        public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
-            // If the action is a key-up event on the return key, send the message
-            if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
-                String message = view.getText().toString();
-                sendMessage(message);
-            }
-            if(D) Log.i(TAG, "END onEditorAction");
-            return true;
-        }
-    };
-
-    // The Handler that gets information back from the BluetoothChatService
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-            case MESSAGE_STATE_CHANGE:
-                if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
-                switch (msg.arg1) {
-                case BluetoothChatService.STATE_CONNECTED:
-                    mTitle.setText(R.string.title_connected_to);
-                    mTitle.append(mConnectedDeviceName);
-                    mSerialView.setText("");
-                    break;
-                case BluetoothChatService.STATE_CONNECTING:
-                    mTitle.setText(R.string.title_connecting);
-                    break;
-                case BluetoothChatService.STATE_READY:
-                case BluetoothChatService.STATE_NONE:
-                    mTitle.setText(R.string.title_not_connected);
-                    break;
-                }
-                break;
-            case MESSAGE_WRITE:
-                byte[] writeBuf = (byte[]) msg.obj;
-                // construct a string from the buffer
-                String writeMessage = new String(writeBuf);
-                mSerialView.append(writeMessage + '\n');
-                break;
-            case MESSAGE_READ:
-                byte[] readBuf = (byte[]) msg.obj;
-                // construct a string from the valid bytes in the buffer
-                String readMessage = new String(readBuf, 0, msg.arg1);
-                mSerialView.append(readMessage);
-                break;
-            case MESSAGE_DEVICE_NAME:
-                // save the connected device's name
-                mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
-                Toast.makeText(getApplicationContext(), "Connected to "
-                               + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
-                break;
-            case MESSAGE_TOAST:
-                Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
-                               Toast.LENGTH_SHORT).show();
-                break;
-            }
-        }
-    };
-
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if(D) Log.d(TAG, "onActivityResult " + resultCode);
-        switch (requestCode) {
-        case REQUEST_CONNECT_DEVICE:
-            // When DeviceListActivity returns with a device to connect to
-            if (resultCode == Activity.RESULT_OK) {
-                connectDevice(data);
-            }
-            break;
-        case REQUEST_ENABLE_BT:
-            // When the request to enable Bluetooth returns
-            if (resultCode == Activity.RESULT_OK) {
-                // Bluetooth is now enabled, so set up a chat session
-                setupChat();
-            } else {
-                // User did not enable Bluetooth or an error occured
-                Log.d(TAG, "BT not enabled");
-                Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
-                finish();
-            }
-        }
-    }
-
-    private void connectDevice(Intent data) {
-        // Get the device MAC address
-        String address = data.getExtras()
-            .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
-        // Get the BLuetoothDevice object
-        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
-        // Attempt to connect to the device
-        mChatService.connect(device);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuInflater inflater = getMenuInflater();
-        inflater.inflate(R.menu.option_menu, menu);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        Intent serverIntent = null;
-        switch (item.getItemId()) {
-        case R.id.telemetry_service_control:
-            serverIntent = new Intent(this, TelemetryServiceActivities.Controller.class);
-            startActivity(serverIntent);
-            return true;
-        case R.id.telemetry_service_bind:
-            serverIntent = new Intent(this, TelemetryServiceActivities.Binding.class);
-            startActivity(serverIntent);
-            return true;
-        case R.id.connect_scan:
-            // Launch the DeviceListActivity to see devices and do scan
-            serverIntent = new Intent(this, DeviceListActivity.class);
-            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
-            return true;
-        }
-        return false;
-    }
+       // Debugging
+       private static final String TAG = "AltosDroid";
+       private static final boolean D = true;
+
+       // Message types received by our Handler
+       public static final int MSG_STATE_CHANGE    = 1;
+       public static final int MSG_TELEMETRY       = 2;
+
+       // Intent request codes
+       private static final int REQUEST_CONNECT_DEVICE = 1;
+       private static final int REQUEST_ENABLE_BT      = 2;
+
+       // Layout Views
+       private TextView mTitle;
+       private TextView mSerialView;
+       private TextView mCallsignView;
+       private TextView mStateView;
+       private TextView mSpeedView;
+       private TextView mAccelView;
+       private TextView mRangeView;
+       private TextView mAltitudeView;
+       private TextView mAzimuthView;
+       private TextView mBearingView;
+       private TextView mLatitudeView;
+       private TextView mLongitudeView;
+
+       // Service
+       private boolean mIsBound   = false;
+       private Messenger mService = null;
+       final Messenger mMessenger = new Messenger(new IncomingHandler(this));
+
+       // TeleBT Config data
+       private AltosConfigData mConfigData = null;
+       // Local Bluetooth adapter
+       private BluetoothAdapter mBluetoothAdapter = null;
+
+       // Text to Speech
+       private TextToSpeech tts    = null;
+       private boolean tts_enabled = false;
+
+       // The Handler that gets information back from the Telemetry Service
+       static class IncomingHandler extends Handler {
+               private final WeakReference<AltosDroid> mAltosDroid;
+               IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); }
+
+               @Override
+               public void handleMessage(Message msg) {
+                       AltosDroid ad = mAltosDroid.get();
+                       switch (msg.what) {
+                       case MSG_STATE_CHANGE:
+                               if(D) Log.d(TAG, "MSG_STATE_CHANGE: " + msg.arg1);
+                               switch (msg.arg1) {
+                               case TelemetryService.STATE_CONNECTED:
+                                       ad.mConfigData = (AltosConfigData) msg.obj;
+                                       String str = String.format(" %s S/N: %d", ad.mConfigData.product, ad.mConfigData.serial);
+                                       ad.mTitle.setText(R.string.title_connected_to);
+                                       ad.mTitle.append(str);
+                                       Toast.makeText(ad.getApplicationContext(), "Connected to " + str, Toast.LENGTH_SHORT).show();
+                                       //TEST!
+                                       ad.mSerialView.setText(Dumper.dump(ad.mConfigData));
+                                       break;
+                               case TelemetryService.STATE_CONNECTING:
+                                       ad.mTitle.setText(R.string.title_connecting);
+                                       break;
+                               case TelemetryService.STATE_READY:
+                               case TelemetryService.STATE_NONE:
+                                       ad.mConfigData = null;
+                                       ad.mTitle.setText(R.string.title_not_connected);
+                                       ad.mSerialView.setText("");
+                                       break;
+                               }
+                               break;
+                       case MSG_TELEMETRY:
+                               ad.update_ui((AltosState) msg.obj);
+                               // TEST!
+                               ad.mSerialView.setText(Dumper.dump(msg.obj));
+                               break;
+                       }
+               }
+       };
+
+
+       private ServiceConnection mConnection = new ServiceConnection() {
+               public void onServiceConnected(ComponentName className, IBinder service) {
+                       mService = new Messenger(service);
+                       try {
+                               Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
+                               msg.replyTo = mMessenger;
+                               mService.send(msg);
+                       } catch (RemoteException e) {
+                               // In this case the service has crashed before we could even do anything with it
+                       }
+               }
+
+               public void onServiceDisconnected(ComponentName className) {
+                       // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
+                       mService = null;
+               }
+       };
+
+
+       void doBindService() {
+               bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
+               mIsBound = true;
+       }
+
+       void doUnbindService() {
+               if (mIsBound) {
+                       // If we have received the service, and hence registered with it, then now is the time to unregister.
+                       if (mService != null) {
+                               try {
+                                       Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
+                                       msg.replyTo = mMessenger;
+                                       mService.send(msg);
+                               } catch (RemoteException e) {
+                                       // There is nothing special we need to do if the service has crashed.
+                               }
+                       }
+                       // Detach our existing connection.
+                       unbindService(mConnection);
+                       mIsBound = false;
+               }
+       }
+
+       void update_ui(AltosState state) {
+               mCallsignView.setText(state.data.callsign);
+               mStateView.setText(state.data.state());
+               double speed = state.speed;
+               if (!state.ascent)
+                       speed = state.baro_speed;
+               mSpeedView.setText(String.format("%6.0f", speed));
+               mAccelView.setText(String.format("%6.0f", state.acceleration));
+               mRangeView.setText(String.format("%6.0f", state.range));
+               mAltitudeView.setText(String.format("%6.0f", state.height));
+               mAzimuthView.setText(String.format("%3.0f", state.elevation));
+               if (state.from_pad != null)
+                       mBearingView.setText(String.format("%3.0f", state.from_pad.bearing));
+               mLatitudeView.setText(pos(state.gps.lat, "N", "S"));
+               mLongitudeView.setText(pos(state.gps.lon, "W", "E"));
+       }
+
+       String pos(double p, String pos, String neg) {
+               String  h = pos;
+               if (p < 0) {
+                       h = neg;
+                       p = -p;
+               }
+               int deg = (int) Math.floor(p);
+               double min = (p - Math.floor(p)) * 60.0;
+               return String.format("%s %d° %9.6f", h, deg, min);
+       }
+
+       @Override
+       public void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+               if(D) Log.e(TAG, "+++ ON CREATE +++");
+
+               // Set up the window layout
+               requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
+               //setContentView(R.layout.main);
+               setContentView(R.layout.altosdroid);
+               getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
+
+               // Set up the custom title
+               mTitle = (TextView) findViewById(R.id.title_left_text);
+               mTitle.setText(R.string.app_name);
+               mTitle = (TextView) findViewById(R.id.title_right_text);
+
+               // Set up the temporary Text View
+               mSerialView = (TextView) findViewById(R.id.text);
+               mSerialView.setMovementMethod(new ScrollingMovementMethod());
+               mSerialView.setClickable(false);
+               mSerialView.setLongClickable(false);
+
+               mCallsignView  = (TextView) findViewById(R.id.callsign_value);
+               mStateView     = (TextView) findViewById(R.id.state_value);
+               mSpeedView     = (TextView) findViewById(R.id.speed_value);
+               mAccelView     = (TextView) findViewById(R.id.accel_value);
+               mRangeView     = (TextView) findViewById(R.id.range_value);
+               mAltitudeView  = (TextView) findViewById(R.id.altitude_value);
+               mAzimuthView   = (TextView) findViewById(R.id.azimuth_value);
+               mBearingView   = (TextView) findViewById(R.id.bearing_value);
+               mLatitudeView  = (TextView) findViewById(R.id.latitude_value);
+               mLongitudeView = (TextView) findViewById(R.id.longitude_value);
+
+               // Get local Bluetooth adapter
+               mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+               // If the adapter is null, then Bluetooth is not supported
+               if (mBluetoothAdapter == null) {
+                       Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
+                       finish();
+                       return;
+               }
+
+               // Enable Text to Speech
+               tts = new TextToSpeech(this, new OnInitListener() {
+                       public void onInit(int status) {
+                               if (status == TextToSpeech.SUCCESS) tts_enabled = true;
+                               if (tts_enabled) tts.speak("AltosDroid ready", TextToSpeech.QUEUE_ADD, null );
+                       }
+               });
+
+       }
+
+       @Override
+       public void onStart() {
+               super.onStart();
+               if(D) Log.e(TAG, "++ ON START ++");
+
+               if (!mBluetoothAdapter.isEnabled()) {
+                       Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+                       startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+               }
+
+               // Start Telemetry Service
+               startService(new Intent(AltosDroid.this, TelemetryService.class));
+
+               doBindService();
+       }
+
+       @Override
+       public synchronized void onResume() {
+               super.onResume();
+               if(D) Log.e(TAG, "+ ON RESUME +");
+       }
+
+       @Override
+       public synchronized void onPause() {
+               super.onPause();
+               if(D) Log.e(TAG, "- ON PAUSE -");
+       }
+
+       @Override
+       public void onStop() {
+               super.onStop();
+               if(D) Log.e(TAG, "-- ON STOP --");
+
+               doUnbindService();
+       }
+
+       @Override
+       public void onDestroy() {
+               super.onDestroy();
+               if(D) Log.e(TAG, "--- ON DESTROY ---");
+
+               if (tts != null) tts.shutdown();
+       }
+
+
+
+
+       public void onActivityResult(int requestCode, int resultCode, Intent data) {
+               if(D) Log.d(TAG, "onActivityResult " + resultCode);
+               switch (requestCode) {
+               case REQUEST_CONNECT_DEVICE:
+                       // When DeviceListActivity returns with a device to connect to
+                       if (resultCode == Activity.RESULT_OK) {
+                               connectDevice(data);
+                       }
+                       break;
+               case REQUEST_ENABLE_BT:
+                       // When the request to enable Bluetooth returns
+                       if (resultCode == Activity.RESULT_OK) {
+                               // Bluetooth is now enabled, so set up a chat session
+                               //setupChat();
+                       } else {
+                               // User did not enable Bluetooth or an error occured
+                               Log.e(TAG, "BT not enabled");
+                               stopService(new Intent(AltosDroid.this, TelemetryService.class));
+                               Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
+                               finish();
+                       }
+                       break;
+               }
+       }
+
+       private void connectDevice(Intent data) {
+               // Get the device MAC address
+               String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
+               // Get the BLuetoothDevice object
+               BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
+               // Attempt to connect to the device
+               try {
+                       if (D) Log.d(TAG, "Connecting to " + device.getName());
+                       mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, device));
+               } catch (RemoteException e) {
+               }
+       }
+
+       @Override
+       public boolean onCreateOptionsMenu(Menu menu) {
+               MenuInflater inflater = getMenuInflater();
+               inflater.inflate(R.menu.option_menu, menu);
+               return true;
+       }
+
+       @Override
+       public boolean onOptionsItemSelected(MenuItem item) {
+               Intent serverIntent = null;
+               switch (item.getItemId()) {
+               case R.id.connect_scan:
+                       // Launch the DeviceListActivity to see devices and do scan
+                       serverIntent = new Intent(this, DeviceListActivity.class);
+                       startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
+                       return true;
+               }
+               return false;
+       }
 
 }
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java b/altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java
deleted file mode 100644 (file)
index a93c08d..0000000
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.altusmetrum.AltosDroid;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.UUID;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothSocket;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * This class does all the work for setting up and managing Bluetooth
- * connections with other devices. It has a thread that listens for
- * incoming connections, a thread for connecting with a device, and a
- * thread for performing data transmissions when connected.
- */
-public class BluetoothChatService {
-    // Debugging
-    private static final String TAG = "BluetoothChatService";
-    private static final boolean D = true;
-
-    // Member fields
-    private final BluetoothAdapter mAdapter;
-    private final Handler mHandler;
-    private ConnectThread mConnectThread;
-    private ConnectedThread mConnectedThread;
-    private int mState;
-
-    // Constants that indicate the current connection state
-    public static final int STATE_NONE = 0;       // we're doing nothing
-    public static final int STATE_READY = 1;
-    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
-    public static final int STATE_CONNECTED = 3;  // now connected to a remote device
-
-    /**
-     * Constructor. Prepares a new BluetoothChat session.
-     * @param context  The UI Activity Context
-     * @param handler  A Handler to send messages back to the UI Activity
-     */
-    public BluetoothChatService(Context context, Handler handler) {
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mState = STATE_NONE;
-        mHandler = handler;
-    }
-
-    /**
-     * Set the current state of the chat connection
-     * @param state  An integer defining the current connection state
-     */
-    private synchronized void setState(int state) {
-        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
-        mState = state;
-
-        // Give the new state to the Handler so the UI Activity can update
-        mHandler.obtainMessage(AltosDroid.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
-    }
-
-    /**
-     * Return the current connection state. */
-    public synchronized int getState() {
-        return mState;
-    }
-
-    /**
-     * Start the chat service. Specifically start AcceptThread to begin a
-     * session in listening (server) mode. Called by the Activity onResume() */
-    public synchronized void start() {
-        if (D) Log.d(TAG, "start");
-
-        // Cancel any thread attempting to make a connection
-        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
-
-        // Cancel any thread currently running a connection
-        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
-
-        setState(STATE_READY);
-
-    }
-
-    /**
-     * Start the ConnectThread to initiate a connection to a remote device.
-     * @param device  The BluetoothDevice to connect
-     */
-    public synchronized void connect(BluetoothDevice device) {
-        if (D) Log.d(TAG, "connect to: " + device);
-
-        // Cancel any thread attempting to make a connection
-        if (mState == STATE_CONNECTING) {
-            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
-        }
-
-        // Cancel any thread currently running a connection
-        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
-
-        // Start the thread to connect with the given device
-        mConnectThread = new ConnectThread(device);
-        mConnectThread.start();
-        setState(STATE_CONNECTING);
-    }
-
-    /**
-     * Start the ConnectedThread to begin managing a Bluetooth connection
-     * @param socket  The BluetoothSocket on which the connection was made
-     * @param device  The BluetoothDevice that has been connected
-     */
-    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
-        if (D) Log.d(TAG, "connected");
-
-        // Cancel the thread that completed the connection
-        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
-
-        // Cancel any thread currently running a connection
-        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
-
-        // Start the thread to manage the connection and perform transmissions
-        mConnectedThread = new ConnectedThread(socket);
-        mConnectedThread.start();
-
-        // Send the name of the connected device back to the UI Activity
-        Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_DEVICE_NAME);
-        Bundle bundle = new Bundle();
-        bundle.putString(AltosDroid.DEVICE_NAME, device.getName());
-        msg.setData(bundle);
-        mHandler.sendMessage(msg);
-
-        setState(STATE_CONNECTED);
-    }
-
-    /**
-     * Stop all threads
-     */
-    public synchronized void stop() {
-        if (D) Log.d(TAG, "stop");
-
-        if (mConnectThread != null) {
-            mConnectThread.cancel();
-            mConnectThread = null;
-        }
-
-        if (mConnectedThread != null) {
-            mConnectedThread.cancel();
-            mConnectedThread = null;
-        }
-
-        setState(STATE_NONE);
-    }
-
-    /**
-     * Write to the ConnectedThread in an unsynchronized manner
-     * @param out The bytes to write
-     * @see ConnectedThread#write(byte[])
-     */
-    public void write(byte[] out) {
-        // Create temporary object
-        ConnectedThread r;
-        // Synchronize a copy of the ConnectedThread
-        synchronized (this) {
-            if (mState != STATE_CONNECTED) return;
-            r = mConnectedThread;
-        }
-        // Perform the write unsynchronized
-        r.write(out);
-    }
-
-    /**
-     * Indicate that the connection attempt failed and notify the UI Activity.
-     */
-    private void connectionFailed() {
-        // Send a failure message back to the Activity
-        Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_TOAST);
-        Bundle bundle = new Bundle();
-        bundle.putString(AltosDroid.TOAST, "Unable to connect device");
-        msg.setData(bundle);
-        mHandler.sendMessage(msg);
-
-        // Start the service over to restart listening mode
-        BluetoothChatService.this.start();
-    }
-
-    /**
-     * Indicate that the connection was lost and notify the UI Activity.
-     */
-    private void connectionLost() {
-        // Send a failure message back to the Activity
-        Message msg = mHandler.obtainMessage(AltosDroid.MESSAGE_TOAST);
-        Bundle bundle = new Bundle();
-        bundle.putString(AltosDroid.TOAST, "Device connection was lost");
-        msg.setData(bundle);
-        mHandler.sendMessage(msg);
-
-        // Start the service over to restart listening mode
-        BluetoothChatService.this.start();
-    }
-
-
-    /**
-     * This thread runs while attempting to make an outgoing connection
-     * with a device. It runs straight through; the connection either
-     * succeeds or fails.
-     */
-    private class ConnectThread extends Thread {
-        private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
-        private final BluetoothSocket mmSocket;
-        private final BluetoothDevice mmDevice;
-
-               public ConnectThread(BluetoothDevice device) {
-            mmDevice = device;
-            BluetoothSocket tmp = null;
-
-            try {
-                               tmp = mmDevice.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
-                       } catch (IOException e) {
-                               e.printStackTrace();
-                       }
-            mmSocket = tmp;
-        }
-
-        public void run() {
-            Log.i(TAG, "BEGIN mConnectThread");
-            setName("ConnectThread");
-
-            // Always cancel discovery because it will slow down a connection
-            mAdapter.cancelDiscovery();
-
-            // Make a connection to the BluetoothSocket
-            try {
-                // This is a blocking call and will only return on a
-                // successful connection or an exception
-                mmSocket.connect();
-            } catch (IOException e) {
-                // Close the socket
-                try {
-                    mmSocket.close();
-                } catch (IOException e2) {
-                    Log.e(TAG, "unable to close() socket during connection failure", e2);
-                }
-                connectionFailed();
-                return;
-            }
-
-            // Reset the ConnectThread because we're done
-            synchronized (BluetoothChatService.this) {
-                mConnectThread = null;
-            }
-
-            // Start the connected thread
-            connected(mmSocket, mmDevice);
-        }
-
-        public void cancel() {
-            try {
-                mmSocket.close();
-            } catch (IOException e) {
-                Log.e(TAG, "close() of connect socket failed", e);
-            }
-        }
-    }
-
-    /**
-     * This thread runs during a connection with a remote device.
-     * It handles all incoming and outgoing transmissions.
-     */
-    private class ConnectedThread extends Thread {
-        private final BluetoothSocket mmSocket;
-        private final InputStream mmInStream;
-        private final OutputStream mmOutStream;
-
-        public ConnectedThread(BluetoothSocket socket) {
-            Log.d(TAG, "create ConnectedThread");
-            mmSocket = socket;
-            InputStream tmpIn = null;
-            OutputStream tmpOut = null;
-
-            // Get the BluetoothSocket input and output streams
-            try {
-                tmpIn = socket.getInputStream();
-                tmpOut = socket.getOutputStream();
-            } catch (IOException e) {
-                Log.e(TAG, "temp sockets not created", e);
-            }
-
-            mmInStream = tmpIn;
-            mmOutStream = tmpOut;
-        }
-
-        public void run() {
-            Log.i(TAG, "BEGIN mConnectedThread");
-            byte[] buffer = new byte[1024];
-            int bytes;
-
-            // Keep listening to the InputStream while connected
-            while (true) {
-                try {
-                    // Read from the InputStream
-                    bytes = mmInStream.read(buffer);
-
-                    // Send the obtained bytes to the UI Activity
-                    mHandler.obtainMessage(AltosDroid.MESSAGE_READ, bytes, -1, buffer.clone())
-                            .sendToTarget();
-                } catch (IOException e) {
-                    Log.e(TAG, "disconnected", e);
-                    connectionLost();
-                    break;
-                }
-            }
-        }
-
-        /**
-         * Write to the connected OutStream.
-         * @param buffer  The bytes to write
-         */
-        public void write(byte[] buffer) {
-            try {
-                mmOutStream.write(buffer);
-                mmOutStream.write('\n');
-
-                // Share the sent message back to the UI Activity
-                mHandler.obtainMessage(AltosDroid.MESSAGE_WRITE, -1, -1, buffer)
-                        .sendToTarget();
-            } catch (IOException e) {
-                Log.e(TAG, "Exception during write", e);
-            }
-        }
-
-        public void cancel() {
-            try {
-                mmSocket.close();
-            } catch (IOException e) {
-                Log.e(TAG, "close() of connect socket failed", e);
-            }
-        }
-    }
-}
index af5d7f15650b52ef5a927e9f84d685336ed77ca0..7b9cbde74a84f0f4721231b37252e7846d1ee872 100644 (file)
@@ -45,159 +45,159 @@ import android.widget.AdapterView.OnItemClickListener;
  * Activity in the result Intent.
  */
 public class DeviceListActivity extends Activity {
-    // Debugging
-    private static final String TAG = "DeviceListActivity";
-    private static final boolean D = true;
-
-    // Return Intent extra
-    public static String EXTRA_DEVICE_ADDRESS = "device_address";
-
-    // Member fields
-    private BluetoothAdapter mBtAdapter;
-    private ArrayAdapter<String> mPairedDevicesArrayAdapter;
-    private ArrayAdapter<String> mNewDevicesArrayAdapter;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        // Setup the window
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-        setContentView(R.layout.device_list);
-
-        // Set result CANCELED incase the user backs out
-        setResult(Activity.RESULT_CANCELED);
-
-        // Initialize the button to perform device discovery
-        Button scanButton = (Button) findViewById(R.id.button_scan);
-        scanButton.setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                doDiscovery();
-                v.setVisibility(View.GONE);
-            }
-        });
-
-        // Initialize array adapters. One for already paired devices and
-        // one for newly discovered devices
-        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
-        mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
-
-        // Find and set up the ListView for paired devices
-        ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
-        pairedListView.setAdapter(mPairedDevicesArrayAdapter);
-        pairedListView.setOnItemClickListener(mDeviceClickListener);
-
-        // Find and set up the ListView for newly discovered devices
-        ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
-        newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
-        newDevicesListView.setOnItemClickListener(mDeviceClickListener);
-
-        // Register for broadcasts when a device is discovered
-        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
-        this.registerReceiver(mReceiver, filter);
-
-        // Register for broadcasts when discovery has finished
-        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-        this.registerReceiver(mReceiver, filter);
-
-        // Get the local Bluetooth adapter
-        mBtAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        // Get a set of currently paired devices
-        Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
-
-        // If there are paired devices, add each one to the ArrayAdapter
-        if (pairedDevices.size() > 0) {
-            findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
-            for (BluetoothDevice device : pairedDevices) {
-                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
-            }
-        } else {
-            String noDevices = getResources().getText(R.string.none_paired).toString();
-            mPairedDevicesArrayAdapter.add(noDevices);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        // Make sure we're not doing discovery anymore
-        if (mBtAdapter != null) {
-            mBtAdapter.cancelDiscovery();
-        }
-
-        // Unregister broadcast listeners
-        this.unregisterReceiver(mReceiver);
-    }
-
-    /**
-     * Start device discover with the BluetoothAdapter
-     */
-    private void doDiscovery() {
-        if (D) Log.d(TAG, "doDiscovery()");
-
-        // Indicate scanning in the title
-        setProgressBarIndeterminateVisibility(true);
-        setTitle(R.string.scanning);
-
-        // Turn on sub-title for new devices
-        findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
-
-        // If we're already discovering, stop it
-        if (mBtAdapter.isDiscovering()) {
-            mBtAdapter.cancelDiscovery();
-        }
-
-        // Request discover from BluetoothAdapter
-        mBtAdapter.startDiscovery();
-    }
-
-    // The on-click listener for all devices in the ListViews
-    private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
-        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
-            // Cancel discovery because it's costly and we're about to connect
-            mBtAdapter.cancelDiscovery();
-
-            // Get the device MAC address, which is the last 17 chars in the View
-            String info = ((TextView) v).getText().toString();
-            String address = info.substring(info.length() - 17);
-
-            // Create the result Intent and include the MAC address
-            Intent intent = new Intent();
-            intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
-
-            // Set result and finish this Activity
-            setResult(Activity.RESULT_OK, intent);
-            finish();
-        }
-    };
-
-    // The BroadcastReceiver that listens for discovered devices and
-    // changes the title when discovery is finished
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-
-            // When discovery finds a device
-            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
-                // Get the BluetoothDevice object from the Intent
-                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                // If it's already paired, skip it, because it's been listed already
-                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
-                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
-                }
-            // When discovery is finished, change the Activity title
-            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
-                setProgressBarIndeterminateVisibility(false);
-                setTitle(R.string.select_device);
-                if (mNewDevicesArrayAdapter.getCount() == 0) {
-                    String noDevices = getResources().getText(R.string.none_found).toString();
-                    mNewDevicesArrayAdapter.add(noDevices);
-                }
-            }
-        }
-    };
+       // Debugging
+       private static final String TAG = "DeviceListActivity";
+       private static final boolean D = true;
+
+       // Return Intent extra
+       public static String EXTRA_DEVICE_ADDRESS = "device_address";
+
+       // Member fields
+       private BluetoothAdapter mBtAdapter;
+       private ArrayAdapter<String> mPairedDevicesArrayAdapter;
+       private ArrayAdapter<String> mNewDevicesArrayAdapter;
+
+       @Override
+       protected void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+
+               // Setup the window
+               requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+               setContentView(R.layout.device_list);
+
+               // Set result CANCELED incase the user backs out
+               setResult(Activity.RESULT_CANCELED);
+
+               // Initialize the button to perform device discovery
+               Button scanButton = (Button) findViewById(R.id.button_scan);
+               scanButton.setOnClickListener(new OnClickListener() {
+                       public void onClick(View v) {
+                               doDiscovery();
+                               v.setVisibility(View.GONE);
+                       }
+               });
+
+               // Initialize array adapters. One for already paired devices and
+               // one for newly discovered devices
+               mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
+               mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
+
+               // Find and set up the ListView for paired devices
+               ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
+               pairedListView.setAdapter(mPairedDevicesArrayAdapter);
+               pairedListView.setOnItemClickListener(mDeviceClickListener);
+
+               // Find and set up the ListView for newly discovered devices
+               ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
+               newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
+               newDevicesListView.setOnItemClickListener(mDeviceClickListener);
+
+               // Register for broadcasts when a device is discovered
+               IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
+               this.registerReceiver(mReceiver, filter);
+
+               // Register for broadcasts when discovery has finished
+               filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
+               this.registerReceiver(mReceiver, filter);
+
+               // Get the local Bluetooth adapter
+               mBtAdapter = BluetoothAdapter.getDefaultAdapter();
+
+               // Get a set of currently paired devices
+               Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
+
+               // If there are paired devices, add each one to the ArrayAdapter
+               if (pairedDevices.size() > 0) {
+                       findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
+                       for (BluetoothDevice device : pairedDevices) {
+                               mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
+                       }
+               } else {
+                       String noDevices = getResources().getText(R.string.none_paired).toString();
+                       mPairedDevicesArrayAdapter.add(noDevices);
+               }
+       }
+
+       @Override
+       protected void onDestroy() {
+               super.onDestroy();
+
+               // Make sure we're not doing discovery anymore
+               if (mBtAdapter != null) {
+                       mBtAdapter.cancelDiscovery();
+               }
+
+               // Unregister broadcast listeners
+               this.unregisterReceiver(mReceiver);
+       }
+
+       /**
+       * Start device discover with the BluetoothAdapter
+       */
+       private void doDiscovery() {
+               if (D) Log.d(TAG, "doDiscovery()");
+
+               // Indicate scanning in the title
+               setProgressBarIndeterminateVisibility(true);
+               setTitle(R.string.scanning);
+
+               // Turn on sub-title for new devices
+               findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
+
+               // If we're already discovering, stop it
+               if (mBtAdapter.isDiscovering()) {
+                       mBtAdapter.cancelDiscovery();
+               }
+
+               // Request discover from BluetoothAdapter
+               mBtAdapter.startDiscovery();
+       }
+
+       // The on-click listener for all devices in the ListViews
+       private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
+               public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
+                       // Cancel discovery because it's costly and we're about to connect
+                       mBtAdapter.cancelDiscovery();
+
+                       // Get the device MAC address, which is the last 17 chars in the View
+                       String info = ((TextView) v).getText().toString();
+                       String address = info.substring(info.length() - 17);
+
+                       // Create the result Intent and include the MAC address
+                       Intent intent = new Intent();
+                       intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
+
+                       // Set result and finish this Activity
+                       setResult(Activity.RESULT_OK, intent);
+                       finish();
+               }
+       };
+
+       // The BroadcastReceiver that listens for discovered devices and
+       // changes the title when discovery is finished
+       private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+               @Override
+               public void onReceive(Context context, Intent intent) {
+                       String action = intent.getAction();
+
+                       // When discovery finds a device
+                       if (BluetoothDevice.ACTION_FOUND.equals(action)) {
+                               // Get the BluetoothDevice object from the Intent
+                               BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                               // If it's already paired, skip it, because it's been listed already
+                               if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
+                                       mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
+                               }
+                       // When discovery is finished, change the Activity title
+                       } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
+                               setProgressBarIndeterminateVisibility(false);
+                               setTitle(R.string.select_device);
+                               if (mNewDevicesArrayAdapter.getCount() == 0) {
+                                       String noDevices = getResources().getText(R.string.none_found).toString();
+                                       mNewDevicesArrayAdapter.add(noDevices);
+                               }
+                       }
+               }
+       };
 
 }
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java b/altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java
new file mode 100644 (file)
index 0000000..17e4cf5
--- /dev/null
@@ -0,0 +1,183 @@
+package org.altusmetrum.AltosDroid;\r
+\r
+       import java.lang.reflect.Array;\r
+       import java.lang.reflect.Field;\r
+       import java.util.HashMap;\r
+\r
+       public class Dumper {\r
+               private static Dumper instance = new Dumper();\r
+\r
+               protected static Dumper getInstance() {\r
+                       return instance;\r
+               }\r
+\r
+               class DumpContext {\r
+                       int maxDepth = 0;\r
+                       int maxArrayElements = 0;\r
+                       int callCount = 0;\r
+                       HashMap<String, String> ignoreList = new HashMap<String, String>();\r
+                       HashMap<Object, Integer> visited = new HashMap<Object, Integer>();\r
+               }\r
+\r
+               public static String dump(Object o) {\r
+                       return dump(o, 0, 0, null);\r
+               }\r
+\r
+               public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {\r
+                       DumpContext ctx = Dumper.getInstance().new DumpContext();\r
+                       ctx.maxDepth = maxDepth;\r
+                       ctx.maxArrayElements = maxArrayElements;\r
+\r
+                       if (ignoreList != null) {\r
+                               for (int i = 0; i < Array.getLength(ignoreList); i++) {\r
+                                       int colonIdx = ignoreList[i].indexOf(':');\r
+                                       if (colonIdx == -1)\r
+                                               ignoreList[i] = ignoreList[i] + ":";\r
+                                       ctx.ignoreList.put(ignoreList[i], ignoreList[i]);\r
+                               }\r
+                       }\r
+\r
+                       return dump(o, ctx);\r
+               }\r
+\r
+               protected static String dump(Object o, DumpContext ctx) {\r
+                       if (o == null) {\r
+                               return "<null>";\r
+                       }\r
+\r
+                       ctx.callCount++;\r
+                       StringBuffer tabs = new StringBuffer();\r
+                       for (int k = 0; k < ctx.callCount; k++) {\r
+                               tabs.append("\t");\r
+                       }\r
+                       StringBuffer buffer = new StringBuffer();\r
+                       @SuppressWarnings("rawtypes")\r
+                       Class oClass = o.getClass();\r
+\r
+                       String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);\r
+\r
+                       if (ctx.ignoreList.get(oSimpleName + ":") != null)\r
+                               return "<Ignored>";\r
+\r
+                       if (oClass.isArray()) {\r
+                               buffer.append("\n");\r
+                               buffer.append(tabs.toString().substring(1));\r
+                               buffer.append("[\n");\r
+                               int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));\r
+                               for (int i = 0; i < rowCount; i++) {\r
+                                       buffer.append(tabs.toString());\r
+                                       try {\r
+                                               Object value = Array.get(o, i);\r
+                                               buffer.append(dumpValue(value, ctx));\r
+                                       } catch (Exception e) {\r
+                                               buffer.append(e.getMessage());\r
+                                       }\r
+                                       if (i < Array.getLength(o) - 1)\r
+                                               buffer.append(",");\r
+                                       buffer.append("\n");\r
+                               }\r
+                               if (rowCount < Array.getLength(o)) {\r
+                                       buffer.append(tabs.toString());\r
+                                       buffer.append(Array.getLength(o) - rowCount + " more array elements...");\r
+                                       buffer.append("\n");\r
+                               }\r
+                               buffer.append(tabs.toString().substring(1));\r
+                               buffer.append("]");\r
+                       } else {\r
+                               buffer.append("\n");\r
+                               buffer.append(tabs.toString().substring(1));\r
+                               buffer.append("{\n");\r
+                               buffer.append(tabs.toString());\r
+                               buffer.append("hashCode: " + o.hashCode());\r
+                               buffer.append("\n");\r
+                               while (oClass != null && oClass != Object.class) {\r
+                                       Field[] fields = oClass.getDeclaredFields();\r
+\r
+                                       if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {\r
+                                               if (oClass != o.getClass()) {\r
+                                                       buffer.append(tabs.toString().substring(1));\r
+                                                       buffer.append("  Inherited from superclass " + oSimpleName + ":\n");\r
+                                               }\r
+\r
+                                               for (int i = 0; i < fields.length; i++) {\r
+\r
+                                                       String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());\r
+                                                       String fName = fields[i].getName();\r
+\r
+                                                       fields[i].setAccessible(true);\r
+                                                       buffer.append(tabs.toString());\r
+                                                       buffer.append(fName + "(" + fSimpleName + ")");\r
+                                                       buffer.append("=");\r
+\r
+                                                       if (ctx.ignoreList.get(":" + fName) == null &&\r
+                                                               ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&\r
+                                                               ctx.ignoreList.get(fSimpleName + ":") == null) {\r
+\r
+                                                               try {\r
+                                                                       Object value = fields[i].get(o);\r
+                                                                       buffer.append(dumpValue(value, ctx));\r
+                                                               } catch (Exception e) {\r
+                                                                       buffer.append(e.getMessage());\r
+                                                               }\r
+                                                               buffer.append("\n");\r
+                                                       } else {\r
+                                                               buffer.append("<Ignored>");\r
+                                                               buffer.append("\n");\r
+                                                       }\r
+                                               }\r
+                                               oClass = oClass.getSuperclass();\r
+                                               oSimpleName = oClass.getSimpleName();\r
+                                       } else {\r
+                                               oClass = null;\r
+                                               oSimpleName = "";\r
+                                       }\r
+                               }\r
+                               buffer.append(tabs.toString().substring(1));\r
+                               buffer.append("}");\r
+                       }\r
+                       ctx.callCount--;\r
+                       return buffer.toString();\r
+               }\r
+\r
+               protected static String dumpValue(Object value, DumpContext ctx) {\r
+                       if (value == null) {\r
+                               return "<null>";\r
+                       }\r
+                       if (value.getClass().isPrimitive() ||\r
+                               value.getClass() == java.lang.Short.class ||\r
+                               value.getClass() == java.lang.Long.class ||\r
+                               value.getClass() == java.lang.String.class ||\r
+                               value.getClass() == java.lang.Integer.class ||\r
+                               value.getClass() == java.lang.Float.class ||\r
+                               value.getClass() == java.lang.Byte.class ||\r
+                               value.getClass() == java.lang.Character.class ||\r
+                               value.getClass() == java.lang.Double.class ||\r
+                               value.getClass() == java.lang.Boolean.class) {\r
+\r
+                               return value.toString();\r
+\r
+                       } else {\r
+\r
+                               Integer visitedIndex = ctx.visited.get(value);\r
+                               if (visitedIndex == null) {\r
+                                       ctx.visited.put(value, ctx.callCount);\r
+                                       if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {\r
+                                               return dump(value, ctx);\r
+                                       } else {\r
+                                               return "<Reached max recursion depth>";\r
+                                       }\r
+                               } else {\r
+                                       return "<Previously visited - see hashCode " + value.hashCode() + ">";\r
+                               }\r
+                       }\r
+               }\r
+\r
+\r
+               private static String getSimpleNameWithoutArrayQualifier(@SuppressWarnings("rawtypes") Class clazz) {\r
+                       String simpleName = clazz.getSimpleName();\r
+                       int indexOfBracket = simpleName.indexOf('['); \r
+                       if (indexOfBracket != -1)\r
+                               return simpleName.substring(0, indexOfBracket);\r
+                       return simpleName;\r
+               }\r
+}\r
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java
new file mode 100644 (file)
index 0000000..c47e494
--- /dev/null
@@ -0,0 +1,94 @@
+/*\r
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>\r
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; version 2 of the License.\r
+ *\r
+ * This program is distributed in the hope that it will be useful, but\r
+ * WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+ * General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License along\r
+ * with this program; if not, write to the Free Software Foundation, Inc.,\r
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.\r
+ */\r
+\r
+\r
+package org.altusmetrum.AltosDroid;\r
+\r
+import java.text.*;\r
+import java.io.*;\r
+import java.util.concurrent.*;\r
+import android.util.Log;\r
+import android.os.Handler;\r
+\r
+import org.altusmetrum.AltosLib.*;\r
+\r
+\r
+public class TelemetryReader extends Thread {\r
+\r
+       private static final String TAG = "TelemetryReader";\r
+\r
+       int         crc_errors;\r
+\r
+       Handler     handler;\r
+\r
+       AltosLink   link;\r
+       AltosRecord previous;\r
+\r
+       LinkedBlockingQueue<AltosLine> telem;\r
+\r
+       public AltosRecord read() throws ParseException, AltosCRCException, InterruptedException, IOException {\r
+               AltosLine l = telem.take();\r
+               if (l.line == null)\r
+                       throw new IOException("IO error");\r
+               AltosRecord     next = AltosTelemetry.parse(l.line, previous);\r
+               previous = next;\r
+               return next;\r
+       }\r
+\r
+       public void close() {\r
+               previous = null;\r
+               link.remove_monitor(telem);\r
+               link = null;\r
+               telem.clear();\r
+               telem = null;\r
+       }\r
+\r
+       public void run() {\r
+               AltosState  state = null;\r
+\r
+               try {\r
+                       for (;;) {\r
+                               try {\r
+                                       AltosRecord record = read();\r
+                                       if (record == null)\r
+                                               break;\r
+                                       state = new AltosState(record, state);\r
+\r
+                                       handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();\r
+                               } catch (ParseException pp) {\r
+                                       Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage()));\r
+                               } catch (AltosCRCException ce) {\r
+                                       ++crc_errors;\r
+                               }\r
+                       }\r
+               } catch (InterruptedException ee) {\r
+               } catch (IOException ie) {\r
+               } finally {\r
+                       close();\r
+               }\r
+       }\r
+\r
+       public TelemetryReader (AltosLink in_link, Handler in_handler) {\r
+               link    = in_link;\r
+               handler = in_handler;\r
+\r
+               previous = null;\r
+               telem = new LinkedBlockingQueue<AltosLine>();\r
+               link.add_monitor(telem);\r
+       }\r
+}\r
index c0a32c9208ae6566dabed2dd7d9caaeefeddc6ae..ffe96946772a114f92c770c1cfb768b09200244a 100644 (file)
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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; version 2 of the License.
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * 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.altusmetrum.AltosDroid;
 
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
+import java.util.Timer;
+import java.util.TimerTask;
+
 import android.app.Notification;
-import android.app.NotificationManager;
+//import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
-import android.os.Binder;
+//import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
 import android.util.Log;
 import android.widget.Toast;
 
-// Need the following import to get access to the app resources, since this
-// class is in a sub-package.
-import org.altusmetrum.AltosDroid.R;
+import org.altusmetrum.AltosLib.*;
 
+public class TelemetryService extends Service {
 
+       private static final String TAG = "TelemetryService";
+       private static final boolean D = true;
+
+       static final int MSG_REGISTER_CLIENT   = 1;
+       static final int MSG_UNREGISTER_CLIENT = 2;
+       static final int MSG_CONNECT           = 3;
+       static final int MSG_CONNECTED         = 4;
+       static final int MSG_CONNECT_FAILED    = 5;
+       static final int MSG_DISCONNECTED      = 6;
+       static final int MSG_TELEMETRY         = 7;
+
+       public static final int STATE_NONE       = 0;
+       public static final int STATE_READY      = 1;
+       public static final int STATE_CONNECTING = 2;
+       public static final int STATE_CONNECTED  = 3;
+
+       // Unique Identification Number for the Notification.
+       // We use it on Notification start, and to cancel it.
+       private int NOTIFICATION = R.string.telemetry_service_label;
+       //private NotificationManager mNM;
+
+       // Timer - we wake up every now and then to decide if the service should stop
+       private Timer timer = new Timer();
+
+       ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
+       final Handler   mHandler   = new IncomingHandler(this);
+       final Messenger mMessenger = new Messenger(mHandler); // Target we publish for clients to send messages to IncomingHandler.
+
+       // Name of the connected device
+       private BluetoothDevice device           = null;
+       private AltosBluetooth  mAltosBluetooth  = null;
+       private AltosConfigData mConfigData      = null;
+       private TelemetryReader mTelemetryReader = null;
+
+       // internally track state of bluetooth connection
+       private int state = STATE_NONE;
+
+       // Handler of incoming messages from clients.
+       static class IncomingHandler extends Handler {
+               private final WeakReference<TelemetryService> service;
+               IncomingHandler(TelemetryService s) { service = new WeakReference<TelemetryService>(s); }
+
+               @Override
+               public void handleMessage(Message msg) {
+                       TelemetryService s = service.get();
+                       switch (msg.what) {
+                       case MSG_REGISTER_CLIENT:
+                               s.mClients.add(msg.replyTo);
+                               try {
+                                       // Now we try to send the freshly connected UI any relavant information about what
+                                       // we're talking to - Basically state and Config Data.
+                                       msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, s.state, -1, s.mConfigData));
+                               } catch (RemoteException e) {
+                                       s.mClients.remove(msg.replyTo);
+                               }
+                               if (D) Log.d(TAG, "Client bound to service");
+                               break;
+                       case MSG_UNREGISTER_CLIENT:
+                               s.mClients.remove(msg.replyTo);
+                               if (D) Log.d(TAG, "Client unbound from service");
+                               break;
+                       case MSG_CONNECT:
+                               if (D) Log.d(TAG, "Connect command received");
+                               s.device = (BluetoothDevice) msg.obj;
+                               s.startAltosBluetooth();
+                               break;
+                       case MSG_CONNECTED:
+                               if (D) Log.d(TAG, "Connected to device");
+                               s.connected();
+                               break;
+                       case MSG_CONNECT_FAILED:
+                               if (D) Log.d(TAG, "Connection failed... retrying");
+                               s.startAltosBluetooth();
+                               break;
+                       case MSG_DISCONNECTED:
+                               // Only do the following if we haven't been shutdown elsewhere..
+                               if (s.device != null) {
+                                       if (D) Log.d(TAG, "Disconnected from " + s.device.getName());
+                                       s.stopAltosBluetooth();
+                               }
+                               break;
+                       case MSG_TELEMETRY:
+                               s.sendMessageToClients(Message.obtain(null, AltosDroid.MSG_TELEMETRY, msg.obj));
+                               break;
+                       default:
+                               super.handleMessage(msg);
+                       }
+               }
+       }
+
+       private void sendMessageToClients(Message m) {
+               for (int i=mClients.size()-1; i>=0; i--) {
+                       try {
+                               mClients.get(i).send(m);
+                       } catch (RemoteException e) {
+                               mClients.remove(i);
+                       }
+               }
+       }
+
+       private void stopAltosBluetooth() {
+               if (D) Log.d(TAG, "stopAltosBluetooth(): begin");
+               setState(STATE_READY);
+               if (mTelemetryReader != null) {
+                       if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryReader");
+                       mTelemetryReader.interrupt();
+                       try {
+                               mTelemetryReader.join();
+                       } catch (InterruptedException e) {
+                       }
+                       mTelemetryReader = null;
+               }
+               if (mAltosBluetooth != null) {
+                       if (D) Log.d(TAG, "stopAltosBluetooth(): stopping AltosBluetooth");
+                       mAltosBluetooth.close();
+                       mAltosBluetooth = null;
+               }
+               device = null;
+               mConfigData = null;
+       }
+
+       private void startAltosBluetooth() {
+               if (mAltosBluetooth == null) {
+                       if (D) Log.d(TAG, String.format("startAltosBluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress()));
+                       mAltosBluetooth = new AltosBluetooth(device, mHandler);
+                       setState(STATE_CONNECTING);
+               } else {
+                       // This is a bit of a hack - if it appears we're still connected, we treat this as a restart.
+                       // So, to give a suitable delay to teardown/bringup, we just schedule a resend of a message
+                       // to ourselves in a few seconds time that will ultimately call this method again.
+                       // ... then we tear down the existing connection.
+                       // We do it this way around so that we don't lose a reference to the device when this method
+                       // is called on reception of MSG_CONNECT_FAILED in the handler above.
+                       mHandler.sendMessageDelayed(Message.obtain(null, MSG_CONNECT, device), 3000);
+                       stopAltosBluetooth();
+               }
+       }
+
+       private synchronized void setState(int s) {
+               if (D) Log.d(TAG, "setState(): " + state + " -> " + s);
+               state = s;
+
+               // This shouldn't be required - mConfigData should be null for any non-connected
+               // state, but to be safe and to reduce message size
+               AltosConfigData acd = (state == STATE_CONNECTED) ? mConfigData : null;
+
+               sendMessageToClients(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, state, -1, acd));
+       }
+
+       private void connected() {
+               try {
+                       mConfigData = mAltosBluetooth.config_data();
+               } catch (InterruptedException e) {
+               } catch (TimeoutException e) {
+                       // If this timed out, then we really want to retry it, but
+                       // probably safer to just retry the connection from scratch.
+                       mHandler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget();
+                       return;
+               }
+
+               setState(STATE_CONNECTED);
+
+               mTelemetryReader = new TelemetryReader(mAltosBluetooth, mHandler);
+               mTelemetryReader.start();
+       }
+
+
+       private void onTimerTick() {
+               if (D) Log.d(TAG, "Timer wakeup");
+               try {
+                       if (mClients.size() <= 0 && state != STATE_CONNECTED) {
+                               stopSelf();
+                       }
+               } catch (Throwable t) {
+                       Log.e(TAG, "Timer failed: ", t);
+               }
+       }
+
+
+       @Override
+       public void onCreate() {
+               // Create a reference to the NotificationManager so that we can update our notifcation text later
+               //mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
+
+               setState(STATE_READY);
+
+               // Start our timer - first event in 10 seconds, then every 10 seconds after that.
+               timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 10000L, 10000L);
+
+       }
+
+       @Override
+       public int onStartCommand(Intent intent, int flags, int startId) {
+               Log.i("TelemetryService", "Received start id " + startId + ": " + intent);
+
+               CharSequence text = getText(R.string.telemetry_service_started);
+
+               // Create notification to be displayed while the service runs
+               Notification notification = new Notification(R.drawable.am_status_c, text, 0);
+
+               // The PendingIntent to launch our activity if the user selects this notification
+               PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+                               new Intent(this, AltosDroid.class), 0);
+
+               // Set the info for the views that show in the notification panel.
+               notification.setLatestEventInfo(this, getText(R.string.telemetry_service_label), text, contentIntent);
+
+               // Set the notification to be in the "Ongoing" section.
+               notification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+               // Move us into the foreground.
+               startForeground(NOTIFICATION, notification);
+
+               // We want this service to continue running until it is explicitly
+               // stopped, so return sticky.
+               return START_STICKY;
+       }
+
+       @Override
+       public void onDestroy() {
+
+               // Stop the bluetooth Comms threads
+               stopAltosBluetooth();
+
+               // Demote us from the foreground, and cancel the persistent notification.
+               stopForeground(true);
+
+               // Stop our timer
+               if (timer != null) {timer.cancel();}
+
+               // Tell the user we stopped.
+               Toast.makeText(this, R.string.telemetry_service_stopped, Toast.LENGTH_SHORT).show();
+       }
+
+       @Override
+       public IBinder onBind(Intent intent) {
+               return mMessenger.getBinder();
+       }
 
-public class TelemetryService extends Service {
-    private NotificationManager mNM;
-
-    // Unique Identification Number for the Notification.
-    // We use it on Notification start, and to cancel it.
-    private int NOTIFICATION = R.string.telemetry_service_label;
-
-    /**
-     * Class for clients to access.  Because we know this service always
-     * runs in the same process as its clients, we don't need to deal with
-     * IPC.
-     */
-    public class TelemetryBinder extends Binder {
-        TelemetryService getService() {
-            return TelemetryService.this;
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        // Create a reference to the NotificationManager so that we can update our notifcation text later
-        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Log.i("TelemetryService", "Received start id " + startId + ": " + intent);
-
-        CharSequence text = getText(R.string.telemetry_service_started);
-
-        // Create notification to be displayed while the service runs
-        Notification notification = new Notification(R.drawable.am_status_c, text, 0);
-
-        // The PendingIntent to launch our activity if the user selects this notification
-        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
-                new Intent(this, TelemetryServiceActivities.Controller.class), 0);
-
-        // Set the info for the views that show in the notification panel.
-        notification.setLatestEventInfo(this, getText(R.string.telemetry_service_label), text, contentIntent);
-
-        // Set the notification to be in the "Ongoing" section.
-        notification.flags |= Notification.FLAG_ONGOING_EVENT;
-
-        // Move us into the foreground.
-        startForeground(NOTIFICATION, notification);
-
-        // We want this service to continue running until it is explicitly
-        // stopped, so return sticky.
-        return START_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        // Demote us from the foreground, and cancel the persistent notification.
-        stopForeground(true);
-
-        // Tell the user we stopped.
-        Toast.makeText(this, R.string.telemetry_service_stopped, Toast.LENGTH_SHORT).show();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-
-    // This is the object that receives interactions from clients.  See
-    // RemoteService for a more complete example.
-    private final IBinder mBinder = new TelemetryBinder();
 
 }
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryServiceActivities.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryServiceActivities.java
deleted file mode 100644 (file)
index 5191cfa..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.altusmetrum.AltosDroid;
-
-import org.altusmetrum.AltosDroid.R;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.Toast;
-
-public class TelemetryServiceActivities {
-    /**
-     * <p>Example of explicitly starting and stopping the local service.
-     * This demonstrates the implementation of a service that runs in the same
-     * process as the rest of the application, which is explicitly started and stopped
-     * as desired.</p>
-     * 
-     * <p>Note that this is implemented as an inner class only keep the sample
-     * all together; typically this code would appear in some separate class.
-     */
-    public static class Controller extends Activity {
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            setContentView(R.layout.telemetry_service_controller);
-
-            // Watch for button clicks.
-            Button button = (Button)findViewById(R.id.start);
-            button.setOnClickListener(mStartListener);
-            button = (Button)findViewById(R.id.stop);
-            button.setOnClickListener(mStopListener);
-        }
-
-        private OnClickListener mStartListener = new OnClickListener() {
-            public void onClick(View v) {
-                // Make sure the service is started.  It will continue running
-                // until someone calls stopService().  The Intent we use to find
-                // the service explicitly specifies our service component, because
-                // we want it running in our own process and don't want other
-                // applications to replace it.
-                startService(new Intent(Controller.this,
-                        TelemetryService.class));
-            }
-        };
-
-        private OnClickListener mStopListener = new OnClickListener() {
-            public void onClick(View v) {
-                // Cancel a previous call to startService().  Note that the
-                // service will not actually stop at this point if there are
-                // still bound clients.
-                stopService(new Intent(Controller.this,
-                        TelemetryService.class));
-            }
-        };
-    }
-
-    // ----------------------------------------------------------------------
-
-    /**
-     * Example of binding and unbinding to the local service.
-     * This demonstrates the implementation of a service which the client will
-     * bind to, receiving an object through which it can communicate with the service.</p>
-     * 
-     * <p>Note that this is implemented as an inner class only keep the sample
-     * all together; typically this code would appear in some separate class.
-     */
-    public static class Binding extends Activity {
-        private boolean mIsBound;
-
-
-        private TelemetryService mBoundService;
-        
-        private ServiceConnection mConnection = new ServiceConnection() {
-            public void onServiceConnected(ComponentName className, IBinder service) {
-                // This is called when the connection with the service has been
-                // established, giving us the service object we can use to
-                // interact with the service.  Because we have bound to a explicit
-                // service that we know is running in our own process, we can
-                // cast its IBinder to a concrete class and directly access it.
-                mBoundService = ((TelemetryService.TelemetryBinder)service).getService();
-                
-                // Tell the user about this for our demo.
-                Toast.makeText(Binding.this, R.string.telemetry_service_connected,
-                        Toast.LENGTH_SHORT).show();
-            }
-
-            public void onServiceDisconnected(ComponentName className) {
-                // This is called when the connection with the service has been
-                // unexpectedly disconnected -- that is, its process crashed.
-                // Because it is running in our same process, we should never
-                // see this happen.
-                mBoundService = null;
-                Toast.makeText(Binding.this, R.string.telemetry_service_disconnected,
-                        Toast.LENGTH_SHORT).show();
-            }
-        };
-        
-        void doBindService() {
-            // Establish a connection with the service.  We use an explicit
-            // class name because we want a specific service implementation that
-            // we know will be running in our own process (and thus won't be
-            // supporting component replacement by other applications).
-            bindService(new Intent(Binding.this, 
-                    TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
-            mIsBound = true;
-        }
-        
-        void doUnbindService() {
-            if (mIsBound) {
-                // Detach our existing connection.
-                unbindService(mConnection);
-                mIsBound = false;
-            }
-        }
-        
-        @Override
-        protected void onDestroy() {
-            super.onDestroy();
-            doUnbindService();
-        }
-
-
-        private OnClickListener mBindListener = new OnClickListener() {
-            public void onClick(View v) {
-                doBindService();
-            }
-        };
-
-        private OnClickListener mUnbindListener = new OnClickListener() {
-            public void onClick(View v) {
-                doUnbindService();
-            }
-        };
-        
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            setContentView(R.layout.telemetry_service_binding);
-
-            // Watch for button clicks.
-            Button button = (Button)findViewById(R.id.bind);
-            button.setOnClickListener(mBindListener);
-            button = (Button)findViewById(R.id.unbind);
-            button.setOnClickListener(mUnbindListener);
-        }
-    }
-}