2 * Copyright © 2012 Mike Beattie <mike@ethernal.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18 package org.altusmetrum.AltosDroid;
20 import java.lang.ref.WeakReference;
22 import android.app.Activity;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.content.Intent;
26 import android.content.Context;
27 import android.content.ComponentName;
28 import android.content.ServiceConnection;
29 import android.content.DialogInterface;
30 import android.os.IBinder;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.Messenger;
35 import android.os.RemoteException;
36 import android.text.method.ScrollingMovementMethod;
37 import android.util.Log;
38 import android.view.Menu;
39 import android.view.MenuInflater;
40 import android.view.MenuItem;
41 import android.view.Window;
42 import android.widget.TextView;
43 import android.widget.Toast;
44 import android.app.AlertDialog;
46 import org.altusmetrum.AltosLib.*;
49 * This is the main Activity that displays the current chat session.
51 public class AltosDroid extends Activity {
53 private static final String TAG = "AltosDroid";
54 private static final boolean D = true;
56 // Message types received by our Handler
57 public static final int MSG_STATE_CHANGE = 1;
58 public static final int MSG_TELEMETRY = 2;
60 // Intent request codes
61 private static final int REQUEST_CONNECT_DEVICE = 1;
62 private static final int REQUEST_ENABLE_BT = 2;
65 private TextView mTitle;
67 // Flight state values
68 private TextView mCallsignView;
69 private TextView mRSSIView;
70 private TextView mSerialView;
71 private TextView mFlightView;
72 private TextView mStateView;
73 private TextView mSpeedView;
74 private TextView mAccelView;
75 private TextView mRangeView;
76 private TextView mHeightView;
77 private TextView mElevationView;
78 private TextView mBearingView;
79 private TextView mLatitudeView;
80 private TextView mLongitudeView;
82 // Generic field for extras at the bottom
83 private TextView mTextView;
86 private boolean mIsBound = false;
87 private Messenger mService = null;
88 final Messenger mMessenger = new Messenger(new IncomingHandler(this));
91 private AltosConfigData mConfigData = null;
92 // Local Bluetooth adapter
93 private BluetoothAdapter mBluetoothAdapter = null;
96 private AltosVoice mAltosVoice = null;
98 // The Handler that gets information back from the Telemetry Service
99 static class IncomingHandler extends Handler {
100 private final WeakReference<AltosDroid> mAltosDroid;
101 IncomingHandler(AltosDroid ad) { mAltosDroid = new WeakReference<AltosDroid>(ad); }
104 public void handleMessage(Message msg) {
105 AltosDroid ad = mAltosDroid.get();
107 case MSG_STATE_CHANGE:
108 if(D) Log.d(TAG, "MSG_STATE_CHANGE: " + msg.arg1);
110 case TelemetryService.STATE_CONNECTED:
111 ad.mConfigData = (AltosConfigData) msg.obj;
112 String str = String.format(" %s S/N: %d", ad.mConfigData.product, ad.mConfigData.serial);
113 ad.mTitle.setText(R.string.title_connected_to);
114 ad.mTitle.append(str);
115 Toast.makeText(ad.getApplicationContext(), "Connected to " + str, Toast.LENGTH_SHORT).show();
116 ad.mAltosVoice.speak("Connected");
118 ad.mTextView.setText(Dumper.dump(ad.mConfigData));
120 case TelemetryService.STATE_CONNECTING:
121 ad.mTitle.setText(R.string.title_connecting);
123 case TelemetryService.STATE_READY:
124 case TelemetryService.STATE_NONE:
125 ad.mConfigData = null;
126 ad.mTitle.setText(R.string.title_not_connected);
127 ad.mTextView.setText("");
132 ad.update_ui((AltosState) msg.obj);
134 ad.mTextView.setText(Dumper.dump(msg.obj));
141 private ServiceConnection mConnection = new ServiceConnection() {
142 public void onServiceConnected(ComponentName className, IBinder service) {
143 mService = new Messenger(service);
145 Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
146 msg.replyTo = mMessenger;
148 } catch (RemoteException e) {
149 // In this case the service has crashed before we could even do anything with it
153 public void onServiceDisconnected(ComponentName className) {
154 // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
160 void doBindService() {
161 bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
165 void doUnbindService() {
167 // If we have received the service, and hence registered with it, then now is the time to unregister.
168 if (mService != null) {
170 Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
171 msg.replyTo = mMessenger;
173 } catch (RemoteException e) {
174 // There is nothing special we need to do if the service has crashed.
177 // Detach our existing connection.
178 unbindService(mConnection);
183 void update_ui(AltosState state) {
184 mCallsignView.setText(state.data.callsign);
185 mRSSIView.setText(String.format("%d", state.data.rssi));
186 mSerialView.setText(String.format("%d", state.data.serial));
187 mFlightView.setText(String.format("%d", state.data.flight));
188 mStateView.setText(state.data.state());
189 double speed = state.speed;
191 speed = state.baro_speed;
192 mSpeedView.setText(String.format("%6.0f m/s", speed));
193 mAccelView.setText(String.format("%6.0f m/s²", state.acceleration));
194 mRangeView.setText(String.format("%6.0f m", state.range));
195 mHeightView.setText(String.format("%6.0f m", state.height));
196 mElevationView.setText(String.format("%3.0f°", state.elevation));
197 if (state.from_pad != null)
198 mBearingView.setText(String.format("%3.0f°", state.from_pad.bearing));
199 mLatitudeView.setText(pos(state.gps.lat, "N", "S"));
200 mLongitudeView.setText(pos(state.gps.lon, "W", "E"));
202 mAltosVoice.tell(state);
205 String pos(double p, String pos, String neg) {
211 int deg = (int) Math.floor(p);
212 double min = (p - Math.floor(p)) * 60.0;
213 return String.format("%d° %9.6f\" %s", deg, min, h);
217 public void onCreate(Bundle savedInstanceState) {
218 super.onCreate(savedInstanceState);
219 if(D) Log.e(TAG, "+++ ON CREATE +++");
221 // Get local Bluetooth adapter
222 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
224 // If the adapter is null, then Bluetooth is not supported
225 if (mBluetoothAdapter == null) {
226 Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
231 // Set up the window layout
232 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
233 //setContentView(R.layout.main);
234 setContentView(R.layout.altosdroid);
235 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
237 // Set up the custom title
238 mTitle = (TextView) findViewById(R.id.title_left_text);
239 mTitle.setText(R.string.app_name);
240 mTitle = (TextView) findViewById(R.id.title_right_text);
242 // Set up the temporary Text View
243 mTextView = (TextView) findViewById(R.id.text);
244 mTextView.setMovementMethod(new ScrollingMovementMethod());
245 mTextView.setClickable(false);
246 mTextView.setLongClickable(false);
248 mCallsignView = (TextView) findViewById(R.id.callsign_value);
249 mRSSIView = (TextView) findViewById(R.id.rssi_value);
250 mSerialView = (TextView) findViewById(R.id.serial_value);
251 mFlightView = (TextView) findViewById(R.id.flight_value);
252 mStateView = (TextView) findViewById(R.id.state_value);
253 mSpeedView = (TextView) findViewById(R.id.speed_value);
254 mAccelView = (TextView) findViewById(R.id.accel_value);
255 mRangeView = (TextView) findViewById(R.id.range_value);
256 mHeightView = (TextView) findViewById(R.id.height_value);
257 mElevationView = (TextView) findViewById(R.id.elevation_value);
258 mBearingView = (TextView) findViewById(R.id.bearing_value);
259 mLatitudeView = (TextView) findViewById(R.id.latitude_value);
260 mLongitudeView = (TextView) findViewById(R.id.longitude_value);
262 mAltosVoice = new AltosVoice(this);
266 public void onStart() {
268 if(D) Log.e(TAG, "++ ON START ++");
270 if (!mBluetoothAdapter.isEnabled()) {
271 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
272 startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
275 // Start Telemetry Service
276 startService(new Intent(AltosDroid.this, TelemetryService.class));
282 public synchronized void onResume() {
284 if(D) Log.e(TAG, "+ ON RESUME +");
288 public synchronized void onPause() {
290 if(D) Log.e(TAG, "- ON PAUSE -");
294 public void onStop() {
296 if(D) Log.e(TAG, "-- ON STOP --");
302 public void onDestroy() {
304 if(D) Log.e(TAG, "--- ON DESTROY ---");
312 public void onActivityResult(int requestCode, int resultCode, Intent data) {
313 if(D) Log.d(TAG, "onActivityResult " + resultCode);
314 switch (requestCode) {
315 case REQUEST_CONNECT_DEVICE:
316 // When DeviceListActivity returns with a device to connect to
317 if (resultCode == Activity.RESULT_OK) {
321 case REQUEST_ENABLE_BT:
322 // When the request to enable Bluetooth returns
323 if (resultCode == Activity.RESULT_OK) {
324 // Bluetooth is now enabled, so set up a chat session
327 // User did not enable Bluetooth or an error occured
328 Log.e(TAG, "BT not enabled");
329 stopService(new Intent(AltosDroid.this, TelemetryService.class));
330 Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
337 private void connectDevice(Intent data) {
338 // Get the device MAC address
339 String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
340 // Get the BLuetoothDevice object
341 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
342 // Attempt to connect to the device
344 if (D) Log.d(TAG, "Connecting to " + device.getName());
345 mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, device));
346 } catch (RemoteException e) {
351 public boolean onCreateOptionsMenu(Menu menu) {
352 MenuInflater inflater = getMenuInflater();
353 inflater.inflate(R.menu.option_menu, menu);
357 void setFrequency(double freq) {
359 mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
360 } catch (RemoteException e) {
364 void setFrequency(String freq) {
366 setFrequency (Double.parseDouble(freq.split(" ")[0]));
367 } catch (NumberFormatException e) {
372 public boolean onOptionsItemSelected(MenuItem item) {
373 Intent serverIntent = null;
374 switch (item.getItemId()) {
375 case R.id.connect_scan:
376 // Launch the DeviceListActivity to see devices and do scan
377 serverIntent = new Intent(this, DeviceListActivity.class);
378 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
380 case R.id.select_freq:
381 // Set the TBT radio frequency
383 final String[] frequencies = {
384 "434.550 (Channel 0)",
385 "434.650 (Channel 1)",
386 "434.750 (Channel 2)",
387 "434.850 (Channel 3)",
388 "434.950 (Channel 4)",
389 "435.050 (Channel 5)",
390 "435.150 (Channel 6)",
391 "435.250 (Channel 7)",
392 "435.350 (Channel 8)",
393 "435.450 (Channel 9)"
396 AlertDialog.Builder builder = new AlertDialog.Builder(this);
397 builder.setTitle("Pick a frequency");
398 builder.setItems(frequencies,
399 new DialogInterface.OnClickListener() {
400 public void onClick(DialogInterface dialog, int item) {
401 setFrequency(frequencies[item]);
404 AlertDialog alert = builder.create();