<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+<!--
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.altusmetrum.AltosDroid"
android:configChanges="orientation|keyboardHidden" />
- <!-- Service Samples -->
-
<service android:name=".TelemetryService" />
- <activity android:name=".TelemetryServiceActivities$Controller"
- android:label="@string/activity_telemetry_service_controller"
- android:launchMode="singleTop">
-<!--
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.SAMPLE_CODE" />
- </intent-filter>
--->
- </activity>
-
- <activity android:name="TelemetryServiceActivities$Binding"
- android:label="@string/activity_telemetry_service_binding">
-<!--
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.SAMPLE_CODE" />
- </intent-filter>
--->
- </activity>
</application>
</manifest>
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)
--- /dev/null
+<?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>
<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>
+++ /dev/null
-<?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>
-
+++ /dev/null
-<?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>
-
<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>
<resources>
<string name="app_name">AltosDroid</string>
- <!-- BluetoothChat -->
- <string name="send">Send</string>
- <string name="not_connected">You are not connected to a device</string>
- <string name="bt_not_enabled_leaving">Bluetooth was not enabled. Leaving Bluetooth Chat.</string>
+ <!-- AltosDroid -->
+ <string name="bt_not_enabled">Bluetooth was not enabled.</string>
<string name="title_connecting">connecting…</string>
<string name="title_connected_to">connected: </string>
<string name="title_not_connected">not connected</string>
+ <!-- Options Menu -->
+ <string name="connect_device">Connect a device</string>
+
<!-- DeviceListActivity -->
<string name="scanning">scanning for devices…</string>
<string name="select_device">select a device to connect</string>
<string name="title_other_devices">Other Available Devices</string>
<string name="button_scan">Scan for devices</string>
- <!-- Options Menu -->
- <string name="connect_device">Connect a device</string>
- <string name="telemetry_service_control">Control Service</string>
- <string name="telemetry_service_bind">(Un)Bind Service</string>
-
-
-
<!-- Service -->
<string name="telemetry_service_label">AltosDroid Telemetry Service</string>
<string name="telemetry_service_started">Telemetry Service Started</string>
<string name="telemetry_service_stopped">Telemetry Service Stopped</string>
- <!-- Service control activity - temporary! -->
- <string name="activity_telemetry_service_controller">Telemetry Service Controller</string>
- <string name="telemetry_service_controller">Use the following buttons to start and stop the Telemetry
- service.</string>
- <string name="start_service">Start Service</string>
- <string name="stop_service">Stop Service</string>
- <string name="activity_telemetry_service_binding">Telemetry Service Binding</string>
- <string name="telemetry_service_binding">This demonstrates how you can connect with a persistent
- service. Notice how it automatically starts for you, and play around with the
- interaction between this and Local Service Controller.</string>
- <string name="bind_service">Bind Service</string>
- <string name="unbind_service">Unbind Service</string>
+ <!-- UI fields -->
+ <string name="callsign_label">Callsign</string>
+ <string name="state_label">State</string>
+ <string name="speed_label">Speed</string>
+ <string name="speed_units">m/s</string>
+ <string name="accel_label">Acceleration</string>
+ <string name="accel_units">m/s²</string>
+ <string name="range_label">Range</string>
+ <string name="range_units">m</string>
+ <string name="altitude_label">Altitude</string>
+ <string name="altitude_units">m</string>
+ <string name="azimuth_label">Azimuth</string>
+ <string name="azimuth_units">°</string>
+ <string name="bearing_label">Bearing</string>
+ <string name="bearing_units">°</string>
+ <string name="latitude_label">Latitude</string>
+ <string name="longitude_label">Longitude</string>
- <string name="telemetry_service_connected">Connected to local service</string>
- <string name="telemetry_service_disconnected">Disconnected from local service</string>
</resources>
/*
- * 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
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.*;
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();
}
}
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() { }
+
+}
/*
- * 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;
+ }
}
+++ /dev/null
-/*
- * 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);
- }
- }
- }
-}
* 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);
+ }
+ }
+ }
+ };
}
--- /dev/null
+package org.altusmetrum.AltosDroid;\r
+\r
+ import java.lang.reflect.Array;\r
+ import java.lang.reflect.Field;\r
+ import java.util.HashMap;\r
+\r
+ public class Dumper {\r
+ private static Dumper instance = new Dumper();\r
+\r
+ protected static Dumper getInstance() {\r
+ return instance;\r
+ }\r
+\r
+ class DumpContext {\r
+ int maxDepth = 0;\r
+ int maxArrayElements = 0;\r
+ int callCount = 0;\r
+ HashMap<String, String> ignoreList = new HashMap<String, String>();\r
+ HashMap<Object, Integer> visited = new HashMap<Object, Integer>();\r
+ }\r
+\r
+ public static String dump(Object o) {\r
+ return dump(o, 0, 0, null);\r
+ }\r
+\r
+ public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {\r
+ DumpContext ctx = Dumper.getInstance().new DumpContext();\r
+ ctx.maxDepth = maxDepth;\r
+ ctx.maxArrayElements = maxArrayElements;\r
+\r
+ if (ignoreList != null) {\r
+ for (int i = 0; i < Array.getLength(ignoreList); i++) {\r
+ int colonIdx = ignoreList[i].indexOf(':');\r
+ if (colonIdx == -1)\r
+ ignoreList[i] = ignoreList[i] + ":";\r
+ ctx.ignoreList.put(ignoreList[i], ignoreList[i]);\r
+ }\r
+ }\r
+\r
+ return dump(o, ctx);\r
+ }\r
+\r
+ protected static String dump(Object o, DumpContext ctx) {\r
+ if (o == null) {\r
+ return "<null>";\r
+ }\r
+\r
+ ctx.callCount++;\r
+ StringBuffer tabs = new StringBuffer();\r
+ for (int k = 0; k < ctx.callCount; k++) {\r
+ tabs.append("\t");\r
+ }\r
+ StringBuffer buffer = new StringBuffer();\r
+ @SuppressWarnings("rawtypes")\r
+ Class oClass = o.getClass();\r
+\r
+ String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);\r
+\r
+ if (ctx.ignoreList.get(oSimpleName + ":") != null)\r
+ return "<Ignored>";\r
+\r
+ if (oClass.isArray()) {\r
+ buffer.append("\n");\r
+ buffer.append(tabs.toString().substring(1));\r
+ buffer.append("[\n");\r
+ int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));\r
+ for (int i = 0; i < rowCount; i++) {\r
+ buffer.append(tabs.toString());\r
+ try {\r
+ Object value = Array.get(o, i);\r
+ buffer.append(dumpValue(value, ctx));\r
+ } catch (Exception e) {\r
+ buffer.append(e.getMessage());\r
+ }\r
+ if (i < Array.getLength(o) - 1)\r
+ buffer.append(",");\r
+ buffer.append("\n");\r
+ }\r
+ if (rowCount < Array.getLength(o)) {\r
+ buffer.append(tabs.toString());\r
+ buffer.append(Array.getLength(o) - rowCount + " more array elements...");\r
+ buffer.append("\n");\r
+ }\r
+ buffer.append(tabs.toString().substring(1));\r
+ buffer.append("]");\r
+ } else {\r
+ buffer.append("\n");\r
+ buffer.append(tabs.toString().substring(1));\r
+ buffer.append("{\n");\r
+ buffer.append(tabs.toString());\r
+ buffer.append("hashCode: " + o.hashCode());\r
+ buffer.append("\n");\r
+ while (oClass != null && oClass != Object.class) {\r
+ Field[] fields = oClass.getDeclaredFields();\r
+\r
+ if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {\r
+ if (oClass != o.getClass()) {\r
+ buffer.append(tabs.toString().substring(1));\r
+ buffer.append(" Inherited from superclass " + oSimpleName + ":\n");\r
+ }\r
+\r
+ for (int i = 0; i < fields.length; i++) {\r
+\r
+ String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());\r
+ String fName = fields[i].getName();\r
+\r
+ fields[i].setAccessible(true);\r
+ buffer.append(tabs.toString());\r
+ buffer.append(fName + "(" + fSimpleName + ")");\r
+ buffer.append("=");\r
+\r
+ if (ctx.ignoreList.get(":" + fName) == null &&\r
+ ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&\r
+ ctx.ignoreList.get(fSimpleName + ":") == null) {\r
+\r
+ try {\r
+ Object value = fields[i].get(o);\r
+ buffer.append(dumpValue(value, ctx));\r
+ } catch (Exception e) {\r
+ buffer.append(e.getMessage());\r
+ }\r
+ buffer.append("\n");\r
+ } else {\r
+ buffer.append("<Ignored>");\r
+ buffer.append("\n");\r
+ }\r
+ }\r
+ oClass = oClass.getSuperclass();\r
+ oSimpleName = oClass.getSimpleName();\r
+ } else {\r
+ oClass = null;\r
+ oSimpleName = "";\r
+ }\r
+ }\r
+ buffer.append(tabs.toString().substring(1));\r
+ buffer.append("}");\r
+ }\r
+ ctx.callCount--;\r
+ return buffer.toString();\r
+ }\r
+\r
+ protected static String dumpValue(Object value, DumpContext ctx) {\r
+ if (value == null) {\r
+ return "<null>";\r
+ }\r
+ if (value.getClass().isPrimitive() ||\r
+ value.getClass() == java.lang.Short.class ||\r
+ value.getClass() == java.lang.Long.class ||\r
+ value.getClass() == java.lang.String.class ||\r
+ value.getClass() == java.lang.Integer.class ||\r
+ value.getClass() == java.lang.Float.class ||\r
+ value.getClass() == java.lang.Byte.class ||\r
+ value.getClass() == java.lang.Character.class ||\r
+ value.getClass() == java.lang.Double.class ||\r
+ value.getClass() == java.lang.Boolean.class) {\r
+\r
+ return value.toString();\r
+\r
+ } else {\r
+\r
+ Integer visitedIndex = ctx.visited.get(value);\r
+ if (visitedIndex == null) {\r
+ ctx.visited.put(value, ctx.callCount);\r
+ if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {\r
+ return dump(value, ctx);\r
+ } else {\r
+ return "<Reached max recursion depth>";\r
+ }\r
+ } else {\r
+ return "<Previously visited - see hashCode " + value.hashCode() + ">";\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ private static String getSimpleNameWithoutArrayQualifier(@SuppressWarnings("rawtypes") Class clazz) {\r
+ String simpleName = clazz.getSimpleName();\r
+ int indexOfBracket = simpleName.indexOf('['); \r
+ if (indexOfBracket != -1)\r
+ return simpleName.substring(0, indexOfBracket);\r
+ return simpleName;\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>\r
+ * Copyright © 2012 Mike Beattie <mike@ethernal.org>\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; version 2 of the License.\r
+ *\r
+ * This program is distributed in the hope that it will be useful, but\r
+ * WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+ * General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License along\r
+ * with this program; if not, write to the Free Software Foundation, Inc.,\r
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.\r
+ */\r
+\r
+\r
+package org.altusmetrum.AltosDroid;\r
+\r
+import java.text.*;\r
+import java.io.*;\r
+import java.util.concurrent.*;\r
+import android.util.Log;\r
+import android.os.Handler;\r
+\r
+import org.altusmetrum.AltosLib.*;\r
+\r
+\r
+public class TelemetryReader extends Thread {\r
+\r
+ private static final String TAG = "TelemetryReader";\r
+\r
+ int crc_errors;\r
+\r
+ Handler handler;\r
+\r
+ AltosLink link;\r
+ AltosRecord previous;\r
+\r
+ LinkedBlockingQueue<AltosLine> telem;\r
+\r
+ public AltosRecord read() throws ParseException, AltosCRCException, InterruptedException, IOException {\r
+ AltosLine l = telem.take();\r
+ if (l.line == null)\r
+ throw new IOException("IO error");\r
+ AltosRecord next = AltosTelemetry.parse(l.line, previous);\r
+ previous = next;\r
+ return next;\r
+ }\r
+\r
+ public void close() {\r
+ previous = null;\r
+ link.remove_monitor(telem);\r
+ link = null;\r
+ telem.clear();\r
+ telem = null;\r
+ }\r
+\r
+ public void run() {\r
+ AltosState state = null;\r
+\r
+ try {\r
+ for (;;) {\r
+ try {\r
+ AltosRecord record = read();\r
+ if (record == null)\r
+ break;\r
+ state = new AltosState(record, state);\r
+\r
+ handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();\r
+ } catch (ParseException pp) {\r
+ Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage()));\r
+ } catch (AltosCRCException ce) {\r
+ ++crc_errors;\r
+ }\r
+ }\r
+ } catch (InterruptedException ee) {\r
+ } catch (IOException ie) {\r
+ } finally {\r
+ close();\r
+ }\r
+ }\r
+\r
+ public TelemetryReader (AltosLink in_link, Handler in_handler) {\r
+ link = in_link;\r
+ handler = in_handler;\r
+\r
+ previous = null;\r
+ telem = new LinkedBlockingQueue<AltosLine>();\r
+ link.add_monitor(telem);\r
+ }\r
+}\r
/*
- * 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();
}
+++ /dev/null
-/*
- * 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);
- }
- }
-}