a79071d0f396aea6378df6018054a2b445518811
[fw/altos] / altosdroid / src / org / altusmetrum / AltosDroid / AltosDroid.java
1 /*
2  * Copyright © 2012 Mike Beattie <mike@ethernal.org>
3  *
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.
7  *
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.
12  *
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.
16  */
17
18 package org.altusmetrum.AltosDroid;
19
20 import java.lang.ref.WeakReference;
21
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;
45
46 import org.altusmetrum.AltosLib.*;
47
48 /**
49  * This is the main Activity that displays the current chat session.
50  */
51 public class AltosDroid extends Activity {
52         // Debugging
53         private static final String TAG = "AltosDroid";
54         private static final boolean D = true;
55
56         // Message types received by our Handler
57         public static final int MSG_STATE_CHANGE    = 1;
58         public static final int MSG_TELEMETRY       = 2;
59
60         // Intent request codes
61         private static final int REQUEST_CONNECT_DEVICE = 1;
62         private static final int REQUEST_ENABLE_BT      = 2;
63
64         // Layout Views
65         private TextView mTitle;
66
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;
81
82         // Generic field for extras at the bottom
83         private TextView mTextView;
84
85         // Service
86         private boolean mIsBound   = false;
87         private Messenger mService = null;
88         final Messenger mMessenger = new Messenger(new IncomingHandler(this));
89
90         // TeleBT Config data
91         private AltosConfigData mConfigData = null;
92         // Local Bluetooth adapter
93         private BluetoothAdapter mBluetoothAdapter = null;
94
95         // Text to Speech
96         private AltosVoice mAltosVoice = null;
97
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); }
102
103                 @Override
104                 public void handleMessage(Message msg) {
105                         AltosDroid ad = mAltosDroid.get();
106                         switch (msg.what) {
107                         case MSG_STATE_CHANGE:
108                                 if(D) Log.d(TAG, "MSG_STATE_CHANGE: " + msg.arg1);
109                                 switch (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");
117                                         //TEST!
118                                         ad.mTextView.setText(Dumper.dump(ad.mConfigData));
119                                         break;
120                                 case TelemetryService.STATE_CONNECTING:
121                                         ad.mTitle.setText(R.string.title_connecting);
122                                         break;
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("");
128                                         break;
129                                 }
130                                 break;
131                         case MSG_TELEMETRY:
132                                 ad.update_ui((AltosState) msg.obj);
133                                 // TEST!
134                                 ad.mTextView.setText(Dumper.dump(msg.obj));
135                                 break;
136                         }
137                 }
138         };
139
140
141         private ServiceConnection mConnection = new ServiceConnection() {
142                 public void onServiceConnected(ComponentName className, IBinder service) {
143                         mService = new Messenger(service);
144                         try {
145                                 Message msg = Message.obtain(null, TelemetryService.MSG_REGISTER_CLIENT);
146                                 msg.replyTo = mMessenger;
147                                 mService.send(msg);
148                         } catch (RemoteException e) {
149                                 // In this case the service has crashed before we could even do anything with it
150                         }
151                 }
152
153                 public void onServiceDisconnected(ComponentName className) {
154                         // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
155                         mService = null;
156                 }
157         };
158
159
160         void doBindService() {
161                 bindService(new Intent(this, TelemetryService.class), mConnection, Context.BIND_AUTO_CREATE);
162                 mIsBound = true;
163         }
164
165         void doUnbindService() {
166                 if (mIsBound) {
167                         // If we have received the service, and hence registered with it, then now is the time to unregister.
168                         if (mService != null) {
169                                 try {
170                                         Message msg = Message.obtain(null, TelemetryService.MSG_UNREGISTER_CLIENT);
171                                         msg.replyTo = mMessenger;
172                                         mService.send(msg);
173                                 } catch (RemoteException e) {
174                                         // There is nothing special we need to do if the service has crashed.
175                                 }
176                         }
177                         // Detach our existing connection.
178                         unbindService(mConnection);
179                         mIsBound = false;
180                 }
181         }
182
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;
190                 if (!state.ascent)
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"));
201
202                 mAltosVoice.tell(state);
203         }
204
205         String pos(double p, String pos, String neg) {
206                 String  h = pos;
207                 if (p < 0) {
208                         h = neg;
209                         p = -p;
210                 }
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);
214         }
215
216         @Override
217         public void onCreate(Bundle savedInstanceState) {
218                 super.onCreate(savedInstanceState);
219                 if(D) Log.e(TAG, "+++ ON CREATE +++");
220
221                 // Get local Bluetooth adapter
222                 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
223
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();
227                         finish();
228                         return;
229                 }
230
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);
236
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);
241
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);
247
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);
261
262                 mAltosVoice = new AltosVoice(this);
263         }
264
265         @Override
266         public void onStart() {
267                 super.onStart();
268                 if(D) Log.e(TAG, "++ ON START ++");
269
270                 if (!mBluetoothAdapter.isEnabled()) {
271                         Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
272                         startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
273                 }
274
275                 // Start Telemetry Service
276                 startService(new Intent(AltosDroid.this, TelemetryService.class));
277
278                 doBindService();
279         }
280
281         @Override
282         public synchronized void onResume() {
283                 super.onResume();
284                 if(D) Log.e(TAG, "+ ON RESUME +");
285         }
286
287         @Override
288         public synchronized void onPause() {
289                 super.onPause();
290                 if(D) Log.e(TAG, "- ON PAUSE -");
291         }
292
293         @Override
294         public void onStop() {
295                 super.onStop();
296                 if(D) Log.e(TAG, "-- ON STOP --");
297
298                 doUnbindService();
299         }
300
301         @Override
302         public void onDestroy() {
303                 super.onDestroy();
304                 if(D) Log.e(TAG, "--- ON DESTROY ---");
305
306                 mAltosVoice.stop();
307         }
308
309
310
311
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) {
318                                 connectDevice(data);
319                         }
320                         break;
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
325                                 //setupChat();
326                         } else {
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();
331                                 finish();
332                         }
333                         break;
334                 }
335         }
336
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
343                 try {
344                         if (D) Log.d(TAG, "Connecting to " + device.getName());
345                         mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, device));
346                 } catch (RemoteException e) {
347                 }
348         }
349
350         @Override
351         public boolean onCreateOptionsMenu(Menu menu) {
352                 MenuInflater inflater = getMenuInflater();
353                 inflater.inflate(R.menu.option_menu, menu);
354                 return true;
355         }
356
357         void setFrequency(double freq) {
358                 try {
359                         mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
360                 } catch (RemoteException e) {
361                 }
362         }
363
364         void setFrequency(String freq) {
365                 try {
366                         setFrequency (Double.parseDouble(freq.split(" ")[0]));
367                 } catch (NumberFormatException e) {
368                 }
369         }
370
371         @Override
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);
379                         return true;
380                 case R.id.select_freq:
381                         // Set the TBT radio frequency
382
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)"
394                         };
395
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]);
402                                                  }
403                                          });
404                         AlertDialog alert = builder.create();
405                         alert.show();
406                         return true;
407                 }
408                 return false;
409         }
410
411 }