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
17package com.example.bluetooth.health;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.DialogFragment;
23import android.bluetooth.BluetoothAdapter;
24import android.bluetooth.BluetoothDevice;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.ServiceConnection;
32import android.content.res.Resources;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.Message;
37import android.os.Messenger;
38import android.os.RemoteException;
39import android.util.Log;
40import android.view.View;
41import android.widget.Button;
42import android.widget.ImageView;
43import android.widget.TextView;
44import 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 */
51public 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
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
223    protected void onDestroy() {
224        super.onDestroy();
225        if (mHealthServiceBound) unbindService(mConnection);
226        unregisterReceiver(mReceiver);
227    }
228
229    @Override
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
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     */
263    public void setDevice(int position) {
264        mDevice = this.mAllBondedDevices[position];
265        mDeviceIndex = position;
266    }
267
268    private void connectChannel() {
269        sendMessageWithDevice(BluetoothHDPService.MSG_CONNECT_CHANNEL);
270    }
271
272    private void disconnectChannel() {
273        sendMessageWithDevice(BluetoothHDPService.MSG_DISCONNECT_CHANNEL);
274    }
275
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.
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}.
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.
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
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
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