blob: 6a19ec4c7f037e31e977547dc7ed8888cf1255de [file] [log] [blame]
The Android Open Source Projectde2d9f52008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2008 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.android.settings;
18
19import android.app.AlertDialog;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothHeadset;
22import android.bluetooth.BluetoothIntent;
23import android.bluetooth.DeviceClass;
24import android.bluetooth.IBluetoothDeviceCallback;
25import android.bluetooth.IBluetoothHeadsetCallback;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.SharedPreferences;
32import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
33import android.content.res.Configuration;
34import android.content.res.Resources;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Message;
38import android.os.SystemClock;
39import android.os.SystemProperties;
40import android.preference.CheckBoxPreference;
41import android.preference.EditTextPreference;
42import android.preference.Preference;
43import android.preference.PreferenceActivity;
44import android.preference.PreferenceScreen;
45import android.provider.Settings;
46import android.text.method.PasswordTransformationMethod;
47import android.util.Log;
48import android.view.ContextMenu;
49import android.view.KeyEvent;
50import android.view.LayoutInflater;
51import android.view.Menu;
52import android.view.MenuItem;
53import android.view.View;
54import android.view.ContextMenu.ContextMenuInfo;
55import android.view.View.OnKeyListener;
56import android.widget.AdapterView;
57import android.widget.EditText;
58import android.widget.Toast;
59import android.widget.AdapterView.AdapterContextMenuInfo;
60
61import java.util.HashMap;
62
63public class BluetoothSettings
64 extends PreferenceActivity
65 implements OnSharedPreferenceChangeListener, OnKeyListener,
66 View.OnCreateContextMenuListener {
67
68 private static final String TAG = "BluetoothSettings";
69
70 private static final int MENU_SCAN_ID = Menu.FIRST;
71 private static final int MENU_CLEAR_ID = Menu.FIRST + 1;
72
73 private static final int MENU_CONNECT = ContextMenu.FIRST;
74 private static final int MENU_DISCONNECT = ContextMenu.FIRST + 1;
75 private static final int MENU_PAIR = ContextMenu.FIRST + 2;
76 private static final int MENU_UNPAIR = ContextMenu.FIRST + 3;
77
78 private static final String BT_ENABLE = "bt_checkbox";
79 private static final String BT_VISIBILITY = "bt_visibility";
80 private static final String BT_NAME = "bt_name";
81
82 private static final String BT_KEY_PREFIX = "bt_dev_";
83 private static final int BT_KEY_LENGTH = BT_KEY_PREFIX.length();
84 private static final String FREEZE_ADDRESSES = "addresses";
85 private static final String FREEZE_TYPES = "types";
86 private static final String FREEZE_PIN = "pinText";
87 private static final String FREEZE_PIN_ADDRESS = "pinAddress";
88 private static final String FREEZE_RSSI = "rssi";
89 private static final String FREEZE_DISCOVERABLE_START = "dstart";
90
91 private static final int HANDLE_FAILED_TO_CONNECT = 1;
92 private static final int HANDLE_CONNECTING = 2;
93 private static final int HANDLE_CONNECTED = 3;
94 private static final int HANDLE_DISCONNECTED = 4;
95 private static final int HANDLE_PIN_REQUEST = 5;
96 private static final int HANDLE_DISCOVERABLE_TIMEOUT = 6;
97 private static final int HANDLE_INITIAL_SCAN = 7;
98 private static final int HANDLE_PAIRING_FAILED = 8;
99 private static final int HANDLE_PAIRING_PASSED = 9;
100 private static final int HANDLE_PAUSE_TIMEOUT = 10;
101
102
103
104 private static String STR_CONNECTED;
105 private static String STR_PAIRED;
106 private static String STR_PAIRED_NOT_NEARBY;
107 private static String STR_NOT_CONNECTED;
108 private static String STR_CONNECTING;
109 private static String STR_PAIRING;
110
111 private static final int WEIGHT_CONNECTED = 1;
112 private static final int WEIGHT_PAIRED = 0;
113 private static final int WEIGHT_UNKNOWN = -1;
114
115 private CheckBoxPreference mBTToggle;
116 private CheckBoxPreference mBTVisibility;
117 private EditTextPreference mBTName;
118 private ProgressCategory mBTDeviceList;
119 private AlertDialog mPinDialog;
120 private String mPinAddress;
121 private EditText mPinEdit;
122 private View mPinButton1;
123 private String mDisconnectAddress;
124
125 private BluetoothDevice mBluetooth;
126 private BluetoothHeadset mBluetoothHeadset;
127 private boolean mIsEnabled;
128 private String mLastConnected;
129 private static boolean sIsRunning;
130 private static DeviceCallback sDeviceCallback;
131 private IntentFilter mIntentFilter;
132 private Resources mRes;
133 private long mDiscoverableStartTime;
134 private int mDiscoverableTime;
135 private static final String DISCOVERABLE_TIME = "debug.bt.discoverable_time";
136 private static final int DISCOVERABLE_TIME_DEFAULT = 120;
137 private boolean mAutoDiscovery;
138 // After a few seconds after a pause, if the user doesn't restart the
139 // BT settings, then we need to cleanup a few things in the message handler
140 private static final int PAUSE_TIMEOUT = 3000;
141
142 private boolean mStartScan;
143 private static final String AUTO_DISCOVERY = "debug.bt.auto_discovery";
144 private HashMap<String,Preference> mDeviceMap;
145
146 @Override
147 protected void onCreate(Bundle icicle) {
148 super.onCreate(icicle);
149
150 addPreferencesFromResource(R.xml.bluetooth_settings);
151
152 // Deal with restarted activities by passing a static callback object to
153 // the Bluetooth service
154 if (sDeviceCallback == null) {
155 sDeviceCallback = new DeviceCallback();
156 }
157 sDeviceCallback.setHandler(mHandler);
158
159 mDiscoverableTime = SystemProperties.getInt(DISCOVERABLE_TIME, -1);
160 if (mDiscoverableTime <= 0) {
161 mDiscoverableTime= DISCOVERABLE_TIME_DEFAULT;
162 }
163 mAutoDiscovery = SystemProperties.getBoolean(AUTO_DISCOVERY, true);
164
165 if (!initBluetoothAPI()) {
166 finish();
167 return;
168 }
169 initUI();
170 getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
171
172 mDeviceMap = new HashMap<String,Preference>();
173 if (icicle != null && icicle.containsKey(FREEZE_ADDRESSES)) {
174 addDevices(icicle.getStringArray(FREEZE_ADDRESSES),
175 icicle.getStringArray(FREEZE_TYPES),
176 icicle.getIntArray(FREEZE_RSSI));
177 if (icicle.containsKey(FREEZE_PIN)) {
178 String savedPin = icicle.getString(FREEZE_PIN);
179 String pinAddress = icicle.getString(FREEZE_PIN_ADDRESS);
180 mPinDialog = showPinDialog(savedPin, pinAddress);
181 }
182 mDiscoverableStartTime = icicle.getLong(FREEZE_DISCOVERABLE_START);
183 } else {
184 mStartScan = true;
185 }
186 }
187
188 private void initUI() {
189 mBTToggle = (CheckBoxPreference) findPreference(BT_ENABLE);
190 mBTVisibility = (CheckBoxPreference) findPreference(BT_VISIBILITY);
191 mBTName = (EditTextPreference) findPreference(BT_NAME);
192 mBTDeviceList = (ProgressCategory) findPreference("bt_device_list");
193 mBTDeviceList.setOrderingAsAdded(false);
194 mRes = getResources();
195 if (mIsEnabled) {
196 String name = mBluetooth.getName();
197 if (name != null) {
198 mBTName.setSummary(name);
199 }
200 }
201 mBTVisibility.setEnabled(mIsEnabled);
202 mBTName.setEnabled(mIsEnabled);
203 STR_CONNECTED = mRes.getString(R.string.bluetooth_connected);
204 STR_PAIRED = mRes.getString(R.string.bluetooth_paired);
205 STR_PAIRED_NOT_NEARBY =
206 mRes.getString(R.string.bluetooth_paired_not_nearby);
207 STR_CONNECTING = mRes.getString(R.string.bluetooth_connecting);
208 STR_PAIRING = mRes.getString(R.string.bluetooth_pairing);
209 STR_NOT_CONNECTED = mRes.getString(R.string.bluetooth_not_connected);
210 getListView().setOnCreateContextMenuListener(this);
211 }
212
213 private boolean initBluetoothAPI() {
214 mIntentFilter =
215 new IntentFilter(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
216 mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
217 mIntentFilter.addAction(BluetoothIntent.BONDING_CREATED_ACTION);
218 mIntentFilter.addAction(BluetoothIntent.ENABLED_ACTION);
219 mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
220 mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
221 mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
222 mIntentFilter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
223 mIntentFilter.addAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
224 mIntentFilter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
225 mIntentFilter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
226 mIntentFilter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
227 mIntentFilter.addAction(BluetoothIntent.MODE_CHANGED_ACTION);
228
229 mBluetooth = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
230 mBluetoothHeadset = new BluetoothHeadset(this);
231 if (mBluetooth == null) { // If the environment doesn't support BT
232 return false;
233 }
234 mIsEnabled = mBluetooth.isEnabled();
235 return true;
236 }
237
238 @Override
239 protected void onResume() {
240 super.onResume();
241
242 sIsRunning = true;
243 mHandler.removeMessages(HANDLE_PAUSE_TIMEOUT);
244 registerReceiver(mReceiver, mIntentFilter);
245
246 mIsEnabled = mBluetooth.isEnabled();
247 updateStatus();
248 final boolean discoverable = mBluetooth.getMode() ==
249 BluetoothDevice.MODE_DISCOVERABLE;
250 mBTDeviceList.setProgress(mIsEnabled && mBluetooth.isDiscovering());
251 mBTVisibility.setChecked(mIsEnabled && discoverable);
252
253 if (discoverable) {
254 mHandler.sendMessage(
255 mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT));
256 }
257
258 if (mIsEnabled && mStartScan) {
259 // First attempt after 100ms
260 mHandler.sendMessageDelayed(
261 mHandler.obtainMessage(HANDLE_INITIAL_SCAN, 1), 100);
262 }
263 mStartScan = false;
264
265 // Check if headset status changed since we paused
266 String connected = mBluetoothHeadset.getHeadsetAddress();
267 if (connected != null) {
268 updateRemoteDeviceStatus(connected);
269 }
270 if (mLastConnected != null) {
271 updateRemoteDeviceStatus(mLastConnected);
272 }
273 }
274
275 @Override
276 protected void onPause() {
277 sIsRunning = false;
278
279 unregisterReceiver(mReceiver);
280
281 // Wait for a few seconds and cleanup any pending requests, states
282 mHandler.sendMessageDelayed(
283 mHandler.obtainMessage(HANDLE_PAUSE_TIMEOUT,
284 new Object[] { mBluetooth, mPinAddress }),
285 PAUSE_TIMEOUT);
286 super.onPause();
287 }
288
289 @Override
290 protected void onDestroy() {
291 mBluetoothHeadset.close();
292 sDeviceCallback.setHandler(null);
293
294 super.onDestroy();
295 }
296
297 @Override
298 public void onConfigurationChanged(Configuration c) {
299 super.onConfigurationChanged(c);
300 // Don't do anything on keyboardHidden/orientation change, as we need
301 // to make sure that we don't lose pairing request intents.
302 }
303
304 public static boolean isRunning() {
305 return sIsRunning;
306 }
307
308 @Override
309 protected void onSaveInstanceState(Bundle icicle) {
310 int deviceCount = mBTDeviceList.getPreferenceCount();
311 String [] addresses = new String[deviceCount];
312 String [] states = new String[deviceCount];
313 int [] weights = new int[deviceCount];
314 for (int i = 0; i < deviceCount; i++) {
315 BluetoothListItem p = (BluetoothListItem) mBTDeviceList.getPreference(i);
316 CharSequence summary = p.getSummary();
317 if (summary != null) {
318 states[i] = summary.toString();
319 } else {
320 states[i] = STR_NOT_CONNECTED;
321 }
322 addresses[i] = getAddressFromKey(p.getKey());
323 weights[i] = p.getWeight();
324 }
325 icicle.putStringArray(FREEZE_ADDRESSES, addresses);
326 icicle.putStringArray(FREEZE_TYPES, states);
327 icicle.putIntArray(FREEZE_RSSI, weights);
328 icicle.putLong(FREEZE_DISCOVERABLE_START, mDiscoverableStartTime);
329 if (mPinDialog != null && mPinDialog.isShowing()) {
330 icicle.putString(FREEZE_PIN, mPinEdit.getText().toString());
331 icicle.putString(FREEZE_PIN_ADDRESS, mPinAddress);
332 }
333 super.onSaveInstanceState(icicle);
334 }
335
336 @Override
337 public boolean onCreateOptionsMenu(Menu menu) {
338 super.onCreateOptionsMenu(menu);
339 menu.add(0, MENU_SCAN_ID, 0,
340 mRes.getString(R.string.bluetooth_scan_for_devices))
341 .setIcon(R.drawable.ic_menu_scan_bluetooth);
342 menu.add(0, MENU_CLEAR_ID, 0,
343 mRes.getString(R.string.bluetooth_clear_list))
344 .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
345 return true;
346 }
347
348 @Override
349 public boolean onOptionsItemSelected(MenuItem item) {
350 switch (item.getItemId()) {
351 case MENU_SCAN_ID:
352 startScanning();
353 return true;
354 case MENU_CLEAR_ID:
355 clearDevices();
356 return true;
357 }
358 return super.onOptionsItemSelected(item);
359 }
360
361 @Override
362 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
363 if (!(menuInfo instanceof AdapterContextMenuInfo)) {
364 return;
365 }
366 int position = ((AdapterContextMenuInfo)menuInfo).position;
367 Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
368 if (!(pref instanceof BluetoothListItem)) {
369 return;
370 }
371 String address = getAddressFromKey(pref.getKey());
372 // Setup the menu header
373 String name = mBluetooth.getRemoteName(address);
374 menu.setHeaderTitle(name != null? name : address);
375 int n = 0;
376 if (mBluetoothHeadset.isConnected(address)) {
377 menu.add(0, MENU_DISCONNECT, n++, R.string.bluetooth_disconnect);
378 } else {
379 menu.add(0, MENU_CONNECT, n++, R.string.bluetooth_connect);
380 }
381 if (mBluetooth.hasBonding(address)) {
382 menu.add(0, MENU_UNPAIR, n++, R.string.bluetooth_unpair);
383 } else {
384 menu.add(0, MENU_PAIR, n++, R.string.bluetooth_pair);
385 }
386 }
387
388 @Override
389 public boolean onContextItemSelected(MenuItem item) {
390 AdapterView.AdapterContextMenuInfo info;
391 if (!(item.getMenuInfo() instanceof AdapterContextMenuInfo)) {
392 return false;
393 }
394 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
395 Preference pref = (Preference) getPreferenceScreen().getRootAdapter().
396 getItem(info.position);
397 String address = getAddressFromKey(pref.getKey());
398 mBluetooth.cancelDiscovery();
399 switch (item.getItemId()) {
400 case MENU_DISCONNECT:
401 if (mBluetoothHeadset.isConnected(address)) {
402 mBluetoothHeadset.disconnectHeadset();
403 }
404 break;
405 case MENU_CONNECT:
406 if (!mBluetoothHeadset.isConnected(address)) {
407 updateRemoteDeviceStatus(address, STR_CONNECTING);
408 connect(pref, address);
409 }
410 break;
411 case MENU_UNPAIR:
412 if (mBluetooth.hasBonding(address)) {
413 mBluetooth.removeBonding(address);
414 updateRemoteDeviceStatus(address);
415 }
416 break;
417 case MENU_PAIR:
418 if (!mBluetooth.hasBonding(address)) {
419 pair(pref, address);
420 }
421 break;
422 }
423 return true;
424 }
425
426 private void startScanning() {
427 if (mIsEnabled && mBluetooth.isDiscovering()) {
428 return;
429 }
430 resetDeviceListUI();
431 if (mIsEnabled) {
432 mBluetooth.startDiscovery();
433 }
434 }
435
436 private void clearDevices() {
437 String [] addresses = mBluetooth.listBondings();
438 if (addresses != null) {
439 for (int i = 0; i < addresses.length; i++) {
440 unbond(addresses[i]);
441 }
442 }
443 resetDeviceListUI();
444 }
445
446 /* Update the Bluetooth toggle and visibility summary */
447 private void updateStatus() {
448 boolean started = mIsEnabled;
449 mBTToggle.setChecked(started);
450 }
451
452 private void updateRemoteDeviceStatus(String address) {
453 if (address != null) {
454 Preference device = mDeviceMap.get(address);
455 if (device == null) {
456 // This device is not in our discovered list
457 // Let's add the device, if BT is not shut down already
458 if (mIsEnabled) {
459 addDeviceToUI(address, null, null, WEIGHT_PAIRED);
460 }
461 return;
462 }
463 device.setEnabled(true);
464 if (address.equals(mBluetoothHeadset.getHeadsetAddress())) {
465 int state = mBluetoothHeadset.getState();
466 switch (state) {
467 case BluetoothHeadset.STATE_CONNECTED:
468 device.setSummary(STR_CONNECTED);
469 mLastConnected = address;
470 break;
471 case BluetoothHeadset.STATE_CONNECTING:
472 device.setSummary(STR_CONNECTING);
473 break;
474 case BluetoothHeadset.STATE_DISCONNECTED:
475 if (mBluetooth.hasBonding(address)) {
476 device.setSummary(STR_PAIRED);
477 }
478 break;
479 }
480 } else if (mBluetooth.hasBonding(address)) {
481 device.setSummary(STR_PAIRED);
482 } else {
483 device.setSummary(STR_NOT_CONNECTED);
484 }
485 }
486 }
487
488 private void updateRemoteDeviceStatus(String address, String summary) {
489 Preference device = mDeviceMap.get(address);
490 if (device != null) {
491 device.setEnabled(true);
492 device.setSummary(summary);
493 }
494 }
495
496 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
497 if (key.equals(BT_NAME)) {
498 String name = sharedPreferences.getString(key, null);
499 if (name == null) {
500 return;
501 }
502 if (mBluetooth.setName(name)) {
503 mBTName.setSummary(name);
504 }
505 }
506 }
507
508 private String getAddressFromKey(String key) {
509 if (key != null) {
510 return key.substring(BT_KEY_LENGTH);
511 }
512 return "";
513 }
514
515 private void sendPin(String pin) {
516 byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
517 if (pinBytes == null) {
518 mBluetooth.cancelPin(mPinAddress);
519 } else {
520 mBluetooth.setPin(mPinAddress, pinBytes);
521 }
522 mPinAddress = null;
523 }
524
525 private AlertDialog showPinDialog(String savedPin, String pinAddress) {
526 if (mPinDialog != null) {
527 return mPinDialog;
528 }
529 View view = LayoutInflater.from(this).inflate(
530 R.layout.bluetooth_pin_entry, null);
531 mPinEdit = (EditText) view.findViewById(R.id.text);
532 mPinEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
533 mPinEdit.setOnKeyListener(this);
534 mPinAddress = pinAddress;
535
536 if (savedPin != null) {
537 mPinEdit.setText(savedPin);
538 }
539
540 String remoteName = mBluetooth.getRemoteName(mPinAddress);
541 if (remoteName == null) {
542 remoteName = mPinAddress;
543 }
544
545 AlertDialog ad = new AlertDialog.Builder(this)
546 .setTitle(getString(R.string.bluetooth_notif_title))
547 .setMessage(getString(R.string.bluetooth_enter_pin_msg) + remoteName)
548 .setView(view)
549 .setPositiveButton(android.R.string.ok, mDisconnectListener)
550 .setNegativeButton(android.R.string.cancel, mDisconnectListener)
551 .setOnCancelListener(mCancelListener)
552 .show();
553 ad.setCanceledOnTouchOutside(false);
554 // Making an assumption here that the dialog buttons have the ids starting
555 // with ...button1 as below
556 mPinButton1 = ad.findViewById(com.android.internal.R.id.button1);
557 if (mPinButton1 != null) {
558 mPinButton1.setEnabled(savedPin != null? savedPin.length() > 0 : false);
559 }
560 return ad;
561 }
562
563 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
564 if (preference == mBTToggle) {
565 toggleBT();
566 return false;
567 } else if (preference == mBTVisibility) {
568 boolean vis = mBTVisibility.isChecked();
569 if (!vis) {
570 // Cancel discoverability
571 mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
572 mHandler.removeMessages(HANDLE_DISCOVERABLE_TIMEOUT);
573 } else {
574 mBluetooth.setMode(BluetoothDevice.MODE_DISCOVERABLE);
575 mBTVisibility.setSummaryOn(
576 getResources().getString(R.string.bluetooth_is_discoverable,
577 String.valueOf(mDiscoverableTime)));
578 mDiscoverableStartTime = SystemClock.elapsedRealtime();
579 mHandler.sendMessageDelayed(
580 mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
581 }
582 } else {
583 String key = preference.getKey();
584 if (key.startsWith(BT_KEY_PREFIX)) {
585 // Extract the device address from the key
586 String address = getAddressFromKey(key);
587 if (mBluetoothHeadset.isConnected(address)) {
588 askDisconnect(address);
589 } else if (mBluetooth.hasBonding(address)) {
590 if (mIsEnabled) {
591 mBluetooth.cancelDiscovery();
592 }
593 updateRemoteDeviceStatus(address, STR_CONNECTING);
594 connect(preference, address);
595 } else {
596 if (mIsEnabled) {
597 mBluetooth.cancelDiscovery();
598 }
599 pair(preference, address);
600 }
601 }
602 }
603 return false;
604 }
605
606 /* Handle the key input to the PIN entry dialog */
607 public boolean onKey(View v, int keyCode, KeyEvent event) {
608
609 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
610 || keyCode == KeyEvent.KEYCODE_ENTER) {
611 String pin = ((EditText)v).getText().toString();
612 if (pin != null && pin.length() > 0) {
613 sendPin(pin);
614 mPinDialog.dismiss();
615 return true;
616 }
617 } else if (mPinButton1 != null) {
618 boolean valid =
619 BluetoothDevice.convertPinToBytes(((EditText)v).getText().toString()) != null;
620 mPinButton1.setEnabled(valid);
621 }
622 return false;
623 }
624
625 private void askDisconnect(String address) {
626 String name = mBluetooth.getRemoteName(address);
627 if (name == null) {
628 name = mRes.getString(R.string.bluetooth_device);
629 }
630 String message = mRes.getString(R.string.bluetooth_disconnect_blank, name);
631
632 mDisconnectAddress = address;
633
634 AlertDialog ad = new AlertDialog.Builder(this)
635 .setTitle(message)
636 .setPositiveButton(android.R.string.ok, mDisconnectListener)
637 .setNegativeButton(android.R.string.cancel, null)
638 .show();
639 ad.setCanceledOnTouchOutside(false);
640
641 }
642
643 private void pairingDone(String address, boolean result) {
644 Preference pref = mDeviceMap.get(address);
645 if (pref != null) {
646 pref.setEnabled(true);
647 updateRemoteDeviceStatus(address);
648 } else if (result) {
649 // We've paired to a device that isn't in our list
650 addDeviceToUI(address, STR_PAIRED, mBluetooth.getRemoteName(address),
651 WEIGHT_PAIRED);
652 }
653 }
654
655 private void pair(Preference pref, String address) {
656 pref.setEnabled(false);
657 pref.setSummary(STR_PAIRING);
658 mBluetooth.createBonding(address, sDeviceCallback);
659 }
660
661 private void connect(Preference pref, String address) {
662 pref.setEnabled(false);
663 //TODO: Prompt the user to confirm they will disconnect current headset
664 disconnect();
665 mBluetoothHeadset.connectHeadset(address, mHeadsetCallback);
666 }
667
668 private void disconnect() {
669 int state = mBluetoothHeadset.getState();
670 if (state == BluetoothHeadset.STATE_CONNECTING ||
671 state == BluetoothHeadset.STATE_CONNECTED) {
672 mBluetoothHeadset.disconnectHeadset();
673 }
674 }
675
676 private void toggleBT() {
677 if (mIsEnabled) {
678 mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_stopping));
679 mBTDeviceList.setProgress(false);
680 // Force shutdown.
681 mBluetooth.cancelDiscovery();
682 mBluetooth.disable();
683 } else {
684 mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_enabling));
685 mBTToggle.setChecked(false);
686 mBTToggle.setEnabled(false);
687 if (!mBluetooth.enable()) {
688 mBTToggle.setEnabled(true);
689 }
690 }
691 }
692
693 private void addDeviceToUI(String address, String summary, String name,
694 int rssi) {
695
696 if (address == null) {
697 return;
698 }
699
700 BluetoothListItem p;
701 if (mDeviceMap.containsKey(address)) {
702 p = (BluetoothListItem) mDeviceMap.get(address);
703 if (summary != null && summary.equals(STR_NOT_CONNECTED)) {
704 if (mBluetooth.hasBonding(address)) {
705 summary = STR_PAIRED;
706 }
707 }
708 CharSequence oldSummary = p.getSummary();
709 if (oldSummary != null && oldSummary.equals(STR_CONNECTED)) {
710 summary = STR_CONNECTED; // Don't override connected with paired
711 mLastConnected = address;
712 }
713 } else {
714 p = new BluetoothListItem(this, null);
715 }
716 if (name == null) {
717 name = mBluetooth.getRemoteName(address);
718 }
719 if (name == null) {
720 name = address;
721 }
722
723 p.setTitle(name);
724 p.setSummary(summary);
725 p.setKey(BT_KEY_PREFIX + address);
726 // Enable the headset icon if it is most probably a headset class device
727 if (DeviceClass.getMajorClass(mBluetooth.getRemoteClass(address)) ==
728 DeviceClass.MAJOR_CLASS_AUDIO_VIDEO) {
729 p.setHeadset(true);
730 }
731 p.setWeight(rssi);
732 if (!mDeviceMap.containsKey(address)) {
733 mBTDeviceList.addPreference(p);
734 mDeviceMap.put(address, p);
735 }
736 }
737
738 private void addDevices(String [] addresses,
739 String[] deviceStatus, int[] rssi) {
740 for (int i = 0; i < addresses.length; i++) {
741 String status = deviceStatus[i];
742 String name = mBluetooth.getRemoteName(addresses[i]);
743 String address = addresses[i];
744 // Query the status if it's not known
745 if (status == null) {
746 if (mBluetoothHeadset.isConnected(addresses[i])) {
747 status = STR_CONNECTED;
748 mLastConnected = address;
749 } else if (mBluetooth.hasBonding(addresses[i])) {
750 status = STR_PAIRED;
751 } else {
752 status = STR_NOT_CONNECTED;
753 }
754 }
755 addDeviceToUI(address, status, name, rssi[i]);
756 }
757 }
758
759 private void removeDeviceFromUI(String address) {
760 Preference p = mDeviceMap.get(address);
761 if (p == null) {
762 return;
763 }
764 mBTDeviceList.removePreference(p);
765 mDeviceMap.remove(address);
766 }
767
768 private void updateDeviceName(String address, String name) {
769 Preference p = mDeviceMap.get(address);
770 if (p != null) {
771 p.setTitle(name);
772 }
773 }
774
775 private void resetDeviceListUI() {
776 mDeviceMap.clear();
777
778 while (mBTDeviceList.getPreferenceCount() > 0) {
779 mBTDeviceList.removePreference(mBTDeviceList.getPreference(0));
780 }
781 if (!mIsEnabled) {
782 return;
783 }
784
785 String connectedDevice = mBluetoothHeadset.getHeadsetAddress();
786 if (connectedDevice != null && mBluetoothHeadset.isConnected(connectedDevice)) {
787 addDeviceToUI(connectedDevice, STR_CONNECTED,
788 mBluetooth.getRemoteName(connectedDevice), WEIGHT_CONNECTED);
789 }
790 String [] bondedDevices = mBluetooth.listBondings();
791 if (bondedDevices != null) {
792 for (int i = 0; i < bondedDevices.length; i++) {
793 addDeviceToUI(bondedDevices[i], STR_PAIRED_NOT_NEARBY,
794 mBluetooth.getRemoteName(bondedDevices[i]), WEIGHT_PAIRED);
795 }
796 }
797 }
798
799 private void unbond(String address) {
800 mBluetooth.removeBonding(address);
801 }
802
803 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
804 @Override
805 public void onReceive(Context context, Intent intent) {
806 String action = intent.getAction();
807 String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
808 if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
809 mIsEnabled = true;
810 mBTToggle.setChecked(true);
811 mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_enabled));
812 mBTToggle.setEnabled(true);
813 String name = mBluetooth.getName();
814 if (name != null) {
815 mBTName.setSummary(name);
816 }
817 // save the "enabled" setting to database, so we can
818 // remember it on startup.
819 Settings.System.putInt(getContentResolver(),
820 Settings.System.BLUETOOTH_ON, 1);
821 resetDeviceListUI();
822 if (mAutoDiscovery) {
823 mBluetooth.startDiscovery();
824 }
825 } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
826 mIsEnabled = false;
827 mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_disabled));
828 resetDeviceListUI();
829 mBTVisibility.setChecked(false);
830 // save the "disabled" setting to database
831 Settings.System.putInt(getContentResolver(),
832 Settings.System.BLUETOOTH_ON, 0);
833 } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
834 if (address != null) {
835 int rssi = intent.getShortExtra(BluetoothIntent.RSSI,
836 (short) WEIGHT_UNKNOWN);
837 addDeviceToUI(address, STR_NOT_CONNECTED, null, rssi);
838 }
839 } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
840 String name = intent.getStringExtra(BluetoothIntent.NAME);
841 updateDeviceName(address, name);
842 } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
843 removeDeviceFromUI(address);
844 } else if (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
845 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PIN_REQUEST, address));
846 } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
847 int state = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
848 BluetoothHeadset.STATE_ERROR);
849 if (state == BluetoothHeadset.STATE_CONNECTED) {
850 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
851 } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
852 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
853 } else if (state == BluetoothHeadset.STATE_CONNECTING) {
854 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTING, address));
855 }
856 } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
857 mBTDeviceList.setProgress(true);
858 } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
859 mBTDeviceList.setProgress(false);
860 } else if (action.equals(BluetoothIntent.MODE_CHANGED_ACTION)) {
861 mBTVisibility.setChecked(
862 mBluetooth.getMode() == BluetoothDevice.MODE_DISCOVERABLE);
863 } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
864 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PAIRING_PASSED, address));
865 } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
866 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
867 } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION)) {
868 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
869 }
870 }
871 };
872
873
874 static class DeviceCallback extends IBluetoothDeviceCallback.Stub {
875 Handler messageHandler;
876
877 public void setHandler(Handler handler) {
878 synchronized (this) {
879 messageHandler = handler;
880 }
881 }
882
883 public void onCreateBondingResult(String address, int result) {
884 synchronized (this) {
885 if (messageHandler != null) {
886 if (result == BluetoothDevice.RESULT_FAILURE) {
887 messageHandler.sendMessage(messageHandler.obtainMessage(
888 HANDLE_PAIRING_FAILED, address));
889 } else {
890 messageHandler.sendMessage(messageHandler.obtainMessage(
891 HANDLE_PAIRING_PASSED, address));
892 }
893 }
894 }
895 }
896
897 public void onEnableResult(int result) { }
898 public void onGetRemoteServiceChannelResult(String address, int channel) { }
899 };
900
901 private IBluetoothHeadsetCallback mHeadsetCallback = new IBluetoothHeadsetCallback.Stub() {
902 public void onConnectHeadsetResult(String address, int resultCode) {
903 if (resultCode == BluetoothHeadset.RESULT_SUCCESS) {
904 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
905 } else {
906 // Make toast in UI thread
907 mHandler.sendMessage(mHandler.obtainMessage(HANDLE_FAILED_TO_CONNECT, resultCode,
908 -1, address));
909 }
910 }
911 };
912
913 private Handler mHandler = new Handler() {
914 @Override
915 public void handleMessage(Message msg) {
916 switch (msg.what) {
917 case HANDLE_CONNECTED:
918 case HANDLE_DISCONNECTED:
919 case HANDLE_CONNECTING:
920 updateRemoteDeviceStatus((String) msg.obj);
921 break;
922 case HANDLE_FAILED_TO_CONNECT:
923 updateRemoteDeviceStatus((String) msg.obj);
924 String name = mBluetooth.getRemoteName((String) msg.obj);
925 if (name == null) {
926 name = (String) msg.obj;
927 }
928 if (msg.arg1 == BluetoothHeadset.RESULT_FAILURE) {
929 Toast.makeText(BluetoothSettings.this,
930 mRes.getString(R.string.failed_to_connect, name),
931 Toast.LENGTH_SHORT).show();
932 }
933 break;
934 case HANDLE_PIN_REQUEST:
935 mPinDialog = showPinDialog(null, (String) msg.obj);
936 break;
937 case HANDLE_DISCOVERABLE_TIMEOUT:
938 long nowTime = SystemClock.elapsedRealtime();
939 int secondsLeft = mDiscoverableTime
940 - (int) (nowTime - mDiscoverableStartTime) / 1000;
941 if (secondsLeft > 0) {
942 mBTVisibility.setSummaryOn(
943 getResources().getString(R.string.bluetooth_is_discoverable,
944 String.valueOf(secondsLeft)));
945 sendMessageDelayed(obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
946 } else {
947 mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
948 mBTVisibility.setChecked(false);
949 }
950 break;
951 case HANDLE_INITIAL_SCAN:
952 if (mBluetoothHeadset.getState() == BluetoothHeadset.STATE_ERROR &&
953 ((Integer)msg.obj).intValue() < 2) {
954 // Second attempt after another 100ms
955 sendMessageDelayed(obtainMessage(HANDLE_INITIAL_SCAN, 2), 100);
956 } else {
957 resetDeviceListUI();
958 if (mAutoDiscovery) {
959 mBluetooth.cancelDiscovery();
960 mBluetooth.startDiscovery();
961 }
962 }
963 break;
964 case HANDLE_PAIRING_PASSED:
965 String addr = (String) msg.obj;
966 pairingDone(addr, true);
967 break;
968 case HANDLE_PAIRING_FAILED:
969 String address = (String) msg.obj;
970 pairingDone(address, false);
971 String pairName = mBluetooth.getRemoteName(address);
972 if (pairName == null) {
973 pairName = address;
974 }
975 Toast.makeText(BluetoothSettings.this,
976 mRes.getString(R.string.failed_to_pair, pairName),
977 Toast.LENGTH_SHORT).show();
978 break;
979 case HANDLE_PAUSE_TIMEOUT:
980 // Possibility of race condition, but not really harmful
981 if (!sIsRunning) {
982 Object[] params = (Object[]) msg.obj;
983 BluetoothDevice bluetooth = (BluetoothDevice) params[0];
984 if (bluetooth.isEnabled()) {
985 if (bluetooth.isDiscovering()) {
986 bluetooth.cancelDiscovery();
987 }
988 if (params[1] != null) {
989 bluetooth.cancelBondingProcess((String) params[1]);
990 }
991 bluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
992 }
993 }
994 break;
995 }
996 }
997 };
998
999 private DialogInterface.OnClickListener mDisconnectListener =
1000 new DialogInterface.OnClickListener() {
1001
1002 public void onClick(DialogInterface dialog, int which) {
1003 if (dialog == mPinDialog) {
1004 if (which == DialogInterface.BUTTON1) {
1005 String pin = mPinEdit.getText().toString();
1006 if (pin != null && pin.length() > 0) {
1007 sendPin(pin);
1008 } else {
1009 sendPin(null);
1010 }
1011 } else {
1012 sendPin(null);
1013 }
1014 mPinDialog = null;
1015 mPinEdit = null;
1016 } else {
1017 if (which == DialogInterface.BUTTON1) {
1018 disconnect();
1019 }
1020 }
1021 }
1022 };
1023
1024 private DialogInterface.OnCancelListener mCancelListener =
1025 new DialogInterface.OnCancelListener() {
1026 public void onCancel(DialogInterface dialog) {
1027 if (dialog == mPinDialog) {
1028 sendPin(null);
1029 }
1030 mPinDialog = null;
1031 mPinEdit = null;
1032 }
1033 };
1034}
1035