diff options
16 files changed, 1542 insertions, 1335 deletions
diff --git a/altosdroid/AndroidManifest.xml b/altosdroid/AndroidManifest.xml index 96fe5ac7..12391759 100644 --- a/altosdroid/AndroidManifest.xml +++ b/altosdroid/AndroidManifest.xml @@ -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" @@ -36,30 +38,8 @@                    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> diff --git a/altosdroid/Makefile.am b/altosdroid/Makefile.am index 6ee984c2..36d28ca2 100644 --- a/altosdroid/Makefile.am +++ b/altosdroid/Makefile.am @@ -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 index 00000000..33d89d52 --- /dev/null +++ b/altosdroid/res/layout/altosdroid.xml @@ -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> diff --git a/altosdroid/res/layout/main.xml b/altosdroid/res/layout/main.xml index 070928a5..00ca63c8 100644 --- a/altosdroid/res/layout/main.xml +++ b/altosdroid/res/layout/main.xml @@ -23,31 +23,11 @@      <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 index 950d0d3a..00000000 --- a/altosdroid/res/layout/telemetry_service_binding.xml +++ /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 index 189d2f6c..00000000 --- a/altosdroid/res/layout/telemetry_service_controller.xml +++ /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> - diff --git a/altosdroid/res/menu/option_menu.xml b/altosdroid/res/menu/option_menu.xml index feb5668e..6946e298 100644 --- a/altosdroid/res/menu/option_menu.xml +++ b/altosdroid/res/menu/option_menu.xml @@ -17,10 +17,4 @@      <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> diff --git a/altosdroid/res/values/strings.xml b/altosdroid/res/values/strings.xml index 72a4ddec..59f4f827 100644 --- a/altosdroid/res/values/strings.xml +++ b/altosdroid/res/values/strings.xml @@ -17,14 +17,15 @@  <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> @@ -34,33 +35,29 @@      <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> diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java index 45438a6c..4c8136aa 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java @@ -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()    { } + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index b4725e22..ba424e79 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -1,339 +1,362 @@  /* - * 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 index a93c08d6..00000000 --- a/altosdroid/src/org/altusmetrum/AltosDroid/BluetoothChatService.java +++ /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); -            } -        } -    } -} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java index af5d7f15..7b9cbde7 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java @@ -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 index 00000000..17e4cf5b --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/Dumper.java @@ -0,0 +1,183 @@ +package org.altusmetrum.AltosDroid;
 +
 +	import java.lang.reflect.Array;
 +	import java.lang.reflect.Field;
 +	import java.util.HashMap;
 +
 +	public class Dumper {
 +		private static Dumper instance = new Dumper();
 +
 +		protected static Dumper getInstance() {
 +			return instance;
 +		}
 +
 +		class DumpContext {
 +			int maxDepth = 0;
 +			int maxArrayElements = 0;
 +			int callCount = 0;
 +			HashMap<String, String> ignoreList = new HashMap<String, String>();
 +			HashMap<Object, Integer> visited = new HashMap<Object, Integer>();
 +		}
 +
 +		public static String dump(Object o) {
 +			return dump(o, 0, 0, null);
 +		}
 +
 +		public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {
 +			DumpContext ctx = Dumper.getInstance().new DumpContext();
 +			ctx.maxDepth = maxDepth;
 +			ctx.maxArrayElements = maxArrayElements;
 +
 +			if (ignoreList != null) {
 +				for (int i = 0; i < Array.getLength(ignoreList); i++) {
 +					int colonIdx = ignoreList[i].indexOf(':');
 +					if (colonIdx == -1)
 +						ignoreList[i] = ignoreList[i] + ":";
 +					ctx.ignoreList.put(ignoreList[i], ignoreList[i]);
 +				}
 +			}
 +
 +			return dump(o, ctx);
 +		}
 +
 +		protected static String dump(Object o, DumpContext ctx) {
 +			if (o == null) {
 +				return "<null>";
 +			}
 +
 +			ctx.callCount++;
 +			StringBuffer tabs = new StringBuffer();
 +			for (int k = 0; k < ctx.callCount; k++) {
 +				tabs.append("\t");
 +			}
 +			StringBuffer buffer = new StringBuffer();
 +			@SuppressWarnings("rawtypes")
 +			Class oClass = o.getClass();
 +
 +			String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);
 +
 +			if (ctx.ignoreList.get(oSimpleName + ":") != null)
 +				return "<Ignored>";
 +
 +			if (oClass.isArray()) {
 +				buffer.append("\n");
 +				buffer.append(tabs.toString().substring(1));
 +				buffer.append("[\n");
 +				int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));
 +				for (int i = 0; i < rowCount; i++) {
 +					buffer.append(tabs.toString());
 +					try {
 +						Object value = Array.get(o, i);
 +						buffer.append(dumpValue(value, ctx));
 +					} catch (Exception e) {
 +						buffer.append(e.getMessage());
 +					}
 +					if (i < Array.getLength(o) - 1)
 +						buffer.append(",");
 +					buffer.append("\n");
 +				}
 +				if (rowCount < Array.getLength(o)) {
 +					buffer.append(tabs.toString());
 +					buffer.append(Array.getLength(o) - rowCount + " more array elements...");
 +					buffer.append("\n");
 +				}
 +				buffer.append(tabs.toString().substring(1));
 +				buffer.append("]");
 +			} else {
 +				buffer.append("\n");
 +				buffer.append(tabs.toString().substring(1));
 +				buffer.append("{\n");
 +				buffer.append(tabs.toString());
 +				buffer.append("hashCode: " + o.hashCode());
 +				buffer.append("\n");
 +				while (oClass != null && oClass != Object.class) {
 +					Field[] fields = oClass.getDeclaredFields();
 +
 +					if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {
 +						if (oClass != o.getClass()) {
 +							buffer.append(tabs.toString().substring(1));
 +							buffer.append("  Inherited from superclass " + oSimpleName + ":\n");
 +						}
 +
 +						for (int i = 0; i < fields.length; i++) {
 +
 +							String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());
 +							String fName = fields[i].getName();
 +
 +							fields[i].setAccessible(true);
 +							buffer.append(tabs.toString());
 +							buffer.append(fName + "(" + fSimpleName + ")");
 +							buffer.append("=");
 +
 +							if (ctx.ignoreList.get(":" + fName) == null &&
 +								ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&
 +								ctx.ignoreList.get(fSimpleName + ":") == null) {
 +
 +								try {
 +									Object value = fields[i].get(o);
 +									buffer.append(dumpValue(value, ctx));
 +								} catch (Exception e) {
 +									buffer.append(e.getMessage());
 +								}
 +								buffer.append("\n");
 +							} else {
 +								buffer.append("<Ignored>");
 +								buffer.append("\n");
 +							}
 +						}
 +						oClass = oClass.getSuperclass();
 +						oSimpleName = oClass.getSimpleName();
 +					} else {
 +						oClass = null;
 +						oSimpleName = "";
 +					}
 +				}
 +				buffer.append(tabs.toString().substring(1));
 +				buffer.append("}");
 +			}
 +			ctx.callCount--;
 +			return buffer.toString();
 +		}
 +
 +		protected static String dumpValue(Object value, DumpContext ctx) {
 +			if (value == null) {
 +				return "<null>";
 +			}
 +			if (value.getClass().isPrimitive() ||
 +				value.getClass() == java.lang.Short.class ||
 +				value.getClass() == java.lang.Long.class ||
 +				value.getClass() == java.lang.String.class ||
 +				value.getClass() == java.lang.Integer.class ||
 +				value.getClass() == java.lang.Float.class ||
 +				value.getClass() == java.lang.Byte.class ||
 +				value.getClass() == java.lang.Character.class ||
 +				value.getClass() == java.lang.Double.class ||
 +				value.getClass() == java.lang.Boolean.class) {
 +
 +				return value.toString();
 +
 +			} else {
 +
 +				Integer visitedIndex = ctx.visited.get(value);
 +				if (visitedIndex == null) {
 +					ctx.visited.put(value, ctx.callCount);
 +					if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {
 +						return dump(value, ctx);
 +					} else {
 +						return "<Reached max recursion depth>";
 +					}
 +				} else {
 +					return "<Previously visited - see hashCode " + value.hashCode() + ">";
 +				}
 +			}
 +		}
 +
 +
 +		private static String getSimpleNameWithoutArrayQualifier(@SuppressWarnings("rawtypes") Class clazz) {
 +			String simpleName = clazz.getSimpleName();
 +			int indexOfBracket = simpleName.indexOf('['); 
 +			if (indexOfBracket != -1)
 +				return simpleName.substring(0, indexOfBracket);
 +			return simpleName;
 +		}
 +}
 diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java new file mode 100644 index 00000000..c47e4942 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java @@ -0,0 +1,94 @@ +/*
 + * 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
 + * 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.
 + */
 +
 +
 +package org.altusmetrum.AltosDroid;
 +
 +import java.text.*;
 +import java.io.*;
 +import java.util.concurrent.*;
 +import android.util.Log;
 +import android.os.Handler;
 +
 +import org.altusmetrum.AltosLib.*;
 +
 +
 +public class TelemetryReader extends Thread {
 +
 +	private static final String TAG = "TelemetryReader";
 +
 +	int         crc_errors;
 +
 +	Handler     handler;
 +
 +	AltosLink   link;
 +	AltosRecord previous;
 +
 +	LinkedBlockingQueue<AltosLine> telem;
 +
 +	public AltosRecord read() throws ParseException, AltosCRCException, InterruptedException, IOException {
 +		AltosLine l = telem.take();
 +		if (l.line == null)
 +			throw new IOException("IO error");
 +		AltosRecord	next = AltosTelemetry.parse(l.line, previous);
 +		previous = next;
 +		return next;
 +	}
 +
 +	public void close() {
 +		previous = null;
 +		link.remove_monitor(telem);
 +		link = null;
 +		telem.clear();
 +		telem = null;
 +	}
 +
 +	public void run() {
 +		AltosState  state = null;
 +
 +		try {
 +			for (;;) {
 +				try {
 +					AltosRecord record = read();
 +					if (record == null)
 +						break;
 +					state = new AltosState(record, state);
 +
 +					handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();
 +				} catch (ParseException pp) {
 +					Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage()));
 +				} catch (AltosCRCException ce) {
 +					++crc_errors;
 +				}
 +			}
 +		} catch (InterruptedException ee) {
 +		} catch (IOException ie) {
 +		} finally {
 +			close();
 +		}
 +	}
 +
 +	public TelemetryReader (AltosLink in_link, Handler in_handler) {
 +		link    = in_link;
 +		handler = in_handler;
 +
 +		previous = null;
 +		telem = new LinkedBlockingQueue<AltosLine>();
 +		link.add_monitor(telem);
 +	}
 +}
 diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java index c0a32c92..ffe96946 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java @@ -1,104 +1,285 @@  /* - * 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 index 5191cfa9..00000000 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryServiceActivities.java +++ /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); -        } -    } -}  | 
