1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.bluetooth.health; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.app.DialogFragment; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothDevice; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.ServiceConnection; 32 import android.content.res.Resources; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.Message; 37 import android.os.Messenger; 38 import android.os.RemoteException; 39 import android.util.Log; 40 import android.view.View; 41 import android.widget.Button; 42 import android.widget.ImageView; 43 import android.widget.TextView; 44 import android.widget.Toast; 45 46 /** 47 * Main user interface for the Sample application. All Bluetooth health-related 48 * operations happen in {@link BluetoothHDPService}. This activity passes messages to and from 49 * the service. 50 */ 51 public class BluetoothHDPActivity extends Activity { 52 private static final String TAG = "BluetoothHealthActivity"; 53 54 // Use the appropriate IEEE 11073 data types based on the devices used. 55 // Below are some examples. Refer to relevant Bluetooth HDP specifications for detail. 56 // 0x1007 - blood pressure meter 57 // 0x1008 - body thermometer 58 // 0x100F - body weight scale 59 private static final int HEALTH_PROFILE_SOURCE_DATA_TYPE = 0x1007; 60 61 private static final int REQUEST_ENABLE_BT = 1; 62 63 private TextView mConnectIndicator; 64 private ImageView mDataIndicator; 65 private TextView mStatusMessage; 66 67 private BluetoothAdapter mBluetoothAdapter; 68 private BluetoothDevice[] mAllBondedDevices; 69 private BluetoothDevice mDevice; 70 private int mDeviceIndex = 0; 71 private Resources mRes; 72 private Messenger mHealthService; 73 private boolean mHealthServiceBound; 74 75 // Handles events sent by {@link HealthHDPService}. 76 private Handler mIncomingHandler = new Handler() { 77 @Override 78 public void handleMessage(Message msg) { 79 switch (msg.what) { 80 // Application registration complete. 81 case BluetoothHDPService.STATUS_HEALTH_APP_REG: 82 mStatusMessage.setText( 83 String.format(mRes.getString(R.string.status_reg), 84 msg.arg1)); 85 break; 86 // Application unregistration complete. 87 case BluetoothHDPService.STATUS_HEALTH_APP_UNREG: 88 mStatusMessage.setText( 89 String.format(mRes.getString(R.string.status_unreg), 90 msg.arg1)); 91 break; 92 // Reading data from HDP device. 93 case BluetoothHDPService.STATUS_READ_DATA: 94 mStatusMessage.setText(mRes.getString(R.string.read_data)); 95 mDataIndicator.setImageLevel(1); 96 break; 97 // Finish reading data from HDP device. 98 case BluetoothHDPService.STATUS_READ_DATA_DONE: 99 mStatusMessage.setText(mRes.getString(R.string.read_data_done)); 100 mDataIndicator.setImageLevel(0); 101 break; 102 // Channel creation complete. Some devices will automatically establish 103 // connection. 104 case BluetoothHDPService.STATUS_CREATE_CHANNEL: 105 mStatusMessage.setText( 106 String.format(mRes.getString(R.string.status_create_channel), 107 msg.arg1)); 108 mConnectIndicator.setText(R.string.connected); 109 break; 110 // Channel destroy complete. This happens when either the device disconnects or 111 // there is extended inactivity. 112 case BluetoothHDPService.STATUS_DESTROY_CHANNEL: 113 mStatusMessage.setText( 114 String.format(mRes.getString(R.string.status_destroy_channel), 115 msg.arg1)); 116 mConnectIndicator.setText(R.string.disconnected); 117 break; 118 default: 119 super.handleMessage(msg); 120 } 121 } 122 }; 123 124 private final Messenger mMessenger = new Messenger(mIncomingHandler); 125 126 @Override onCreate(Bundle savedInstanceState)127 public void onCreate(Bundle savedInstanceState) { 128 super.onCreate(savedInstanceState); 129 130 // Check for Bluetooth availability on the Android platform. 131 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 132 if (mBluetoothAdapter == null) { 133 Toast.makeText(this, R.string.bluetooth_not_available, Toast.LENGTH_LONG); 134 finish(); 135 return; 136 } 137 setContentView(R.layout.console); 138 mConnectIndicator = (TextView) findViewById(R.id.connect_ind); 139 mStatusMessage = (TextView) findViewById(R.id.status_msg); 140 mDataIndicator = (ImageView) findViewById(R.id.data_ind); 141 mRes = getResources(); 142 mHealthServiceBound = false; 143 144 // Initiates application registration through {@link BluetoothHDPService}. 145 Button registerAppButton = (Button) findViewById(R.id.button_register_app); 146 registerAppButton.setOnClickListener(new View.OnClickListener() { 147 public void onClick(View v) { 148 sendMessage(BluetoothHDPService.MSG_REG_HEALTH_APP, 149 HEALTH_PROFILE_SOURCE_DATA_TYPE); 150 } 151 }); 152 153 // Initiates application unregistration through {@link BluetoothHDPService}. 154 Button unregisterAppButton = (Button) findViewById(R.id.button_unregister_app); 155 unregisterAppButton.setOnClickListener(new View.OnClickListener() { 156 public void onClick(View v) { 157 sendMessage(BluetoothHDPService.MSG_UNREG_HEALTH_APP, 0); 158 } 159 }); 160 161 // Initiates channel creation through {@link BluetoothHDPService}. Some devices will 162 // initiate the channel connection, in which case, it is not necessary to do this in the 163 // application. When pressed, the user is asked to select from one of the bonded devices 164 // to connect to. 165 Button connectButton = (Button) findViewById(R.id.button_connect_channel); 166 connectButton.setOnClickListener(new View.OnClickListener() { 167 public void onClick(View v) { 168 mAllBondedDevices = 169 (BluetoothDevice[]) mBluetoothAdapter.getBondedDevices().toArray( 170 new BluetoothDevice[0]); 171 172 if (mAllBondedDevices.length > 0) { 173 int deviceCount = mAllBondedDevices.length; 174 if (mDeviceIndex < deviceCount) mDevice = mAllBondedDevices[mDeviceIndex]; 175 else { 176 mDeviceIndex = 0; 177 mDevice = mAllBondedDevices[0]; 178 } 179 String[] deviceNames = new String[deviceCount]; 180 int i = 0; 181 for (BluetoothDevice device : mAllBondedDevices) { 182 deviceNames[i++] = device.getName(); 183 } 184 SelectDeviceDialogFragment deviceDialog = 185 SelectDeviceDialogFragment.newInstance(deviceNames, mDeviceIndex); 186 deviceDialog.show(getFragmentManager(), "deviceDialog"); 187 } 188 } 189 }); 190 191 // Initiates channel disconnect through {@link BluetoothHDPService}. 192 Button disconnectButton = (Button) findViewById(R.id.button_disconnect_channel); 193 disconnectButton.setOnClickListener(new View.OnClickListener() { 194 public void onClick(View v) { 195 disconnectChannel(); 196 } 197 }); 198 registerReceiver(mReceiver, initIntentFilter()); 199 } 200 201 // Sets up communication with {@link BluetoothHDPService}. 202 private ServiceConnection mConnection = new ServiceConnection() { 203 public void onServiceConnected(ComponentName name, IBinder service) { 204 mHealthServiceBound = true; 205 Message msg = Message.obtain(null, BluetoothHDPService.MSG_REG_CLIENT); 206 msg.replyTo = mMessenger; 207 mHealthService = new Messenger(service); 208 try { 209 mHealthService.send(msg); 210 } catch (RemoteException e) { 211 Log.w(TAG, "Unable to register client to service."); 212 e.printStackTrace(); 213 } 214 } 215 216 public void onServiceDisconnected(ComponentName name) { 217 mHealthService = null; 218 mHealthServiceBound = false; 219 } 220 }; 221 222 @Override onDestroy()223 protected void onDestroy() { 224 super.onDestroy(); 225 if (mHealthServiceBound) unbindService(mConnection); 226 unregisterReceiver(mReceiver); 227 } 228 229 @Override onStart()230 protected void onStart() { 231 super.onStart(); 232 // If Bluetooth is not on, request that it be enabled. 233 if (!mBluetoothAdapter.isEnabled()) { 234 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 235 startActivityForResult(enableIntent, REQUEST_ENABLE_BT); 236 } else { 237 initialize(); 238 } 239 } 240 241 /** 242 * Ensures user has turned on Bluetooth on the Android device. 243 */ 244 @Override onActivityResult(int requestCode, int resultCode, Intent data)245 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 246 switch (requestCode) { 247 case REQUEST_ENABLE_BT: 248 if (resultCode == Activity.RESULT_OK) { 249 initialize(); 250 } else { 251 finish(); 252 return; 253 } 254 } 255 } 256 257 /** 258 * Used by {@link SelectDeviceDialogFragment} to record the bonded Bluetooth device selected 259 * by the user. 260 * 261 * @param position Position of the bonded Bluetooth device in the array. 262 */ setDevice(int position)263 public void setDevice(int position) { 264 mDevice = this.mAllBondedDevices[position]; 265 mDeviceIndex = position; 266 } 267 connectChannel()268 private void connectChannel() { 269 sendMessageWithDevice(BluetoothHDPService.MSG_CONNECT_CHANNEL); 270 } 271 disconnectChannel()272 private void disconnectChannel() { 273 sendMessageWithDevice(BluetoothHDPService.MSG_DISCONNECT_CHANNEL); 274 } 275 initialize()276 private void initialize() { 277 // Starts health service. 278 Intent intent = new Intent(this, BluetoothHDPService.class); 279 startService(intent); 280 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 281 } 282 283 // Intent filter and broadcast receive to handle Bluetooth on event. initIntentFilter()284 private IntentFilter initIntentFilter() { 285 IntentFilter filter = new IntentFilter(); 286 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 287 return filter; 288 } 289 290 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 291 @Override 292 public void onReceive(Context context, Intent intent) { 293 final String action = intent.getAction(); 294 if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 295 if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) == 296 BluetoothAdapter.STATE_ON) { 297 initialize(); 298 } 299 } 300 } 301 }; 302 303 // Sends a message to {@link BluetoothHDPService}. sendMessage(int what, int value)304 private void sendMessage(int what, int value) { 305 if (mHealthService == null) { 306 Log.d(TAG, "Health Service not connected."); 307 return; 308 } 309 310 try { 311 mHealthService.send(Message.obtain(null, what, value, 0)); 312 } catch (RemoteException e) { 313 Log.w(TAG, "Unable to reach service."); 314 e.printStackTrace(); 315 } 316 } 317 318 // Sends an update message, along with an HDP BluetoothDevice object, to 319 // {@link BluetoothHDPService}. The BluetoothDevice object is needed by the channel creation 320 // method. sendMessageWithDevice(int what)321 private void sendMessageWithDevice(int what) { 322 if (mHealthService == null) { 323 Log.d(TAG, "Health Service not connected."); 324 return; 325 } 326 327 try { 328 mHealthService.send(Message.obtain(null, what, mDevice)); 329 } catch (RemoteException e) { 330 Log.w(TAG, "Unable to reach service."); 331 e.printStackTrace(); 332 } 333 } 334 335 /** 336 * Dialog to display a list of bonded Bluetooth devices for user to select from. This is 337 * needed only for channel connection initiated from the application. 338 */ 339 public static class SelectDeviceDialogFragment extends DialogFragment { 340 newInstance(String[] names, int position)341 public static SelectDeviceDialogFragment newInstance(String[] names, int position) { 342 SelectDeviceDialogFragment frag = new SelectDeviceDialogFragment(); 343 Bundle args = new Bundle(); 344 args.putStringArray("names", names); 345 args.putInt("position", position); 346 frag.setArguments(args); 347 return frag; 348 } 349 350 @Override onCreateDialog(Bundle savedInstanceState)351 public Dialog onCreateDialog(Bundle savedInstanceState) { 352 String[] deviceNames = getArguments().getStringArray("names"); 353 int position = getArguments().getInt("position", -1); 354 if (position == -1) position = 0; 355 return new AlertDialog.Builder(getActivity()) 356 .setTitle(R.string.select_device) 357 .setPositiveButton(R.string.ok, 358 new DialogInterface.OnClickListener() { 359 public void onClick(DialogInterface dialog, int which) { 360 ((BluetoothHDPActivity) getActivity()).connectChannel(); 361 } 362 }) 363 .setSingleChoiceItems(deviceNames, position, 364 new DialogInterface.OnClickListener() { 365 public void onClick(DialogInterface dialog, int which) { 366 ((BluetoothHDPActivity) getActivity()).setDevice(which); 367 } 368 } 369 ) 370 .create(); 371 } 372 } 373 } 374