DockService.java revision 2036ebd8896bbabbbe04db34c9e7d8a1be6fe32a
1/* 2 * Copyright (C) 2009 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.bluetooth; 18 19import com.android.settings.R; 20import com.android.settings.bluetooth.LocalBluetoothProfileManager.ServiceListener; 21 22import android.app.AlertDialog; 23import android.app.Notification; 24import android.app.Service; 25import android.bluetooth.BluetoothA2dp; 26import android.bluetooth.BluetoothAdapter; 27import android.bluetooth.BluetoothDevice; 28import android.bluetooth.BluetoothHeadset; 29import android.bluetooth.BluetoothProfile; 30import android.content.DialogInterface; 31import android.content.Intent; 32import android.content.IntentFilter; 33import android.content.SharedPreferences; 34import android.os.Handler; 35import android.os.HandlerThread; 36import android.os.IBinder; 37import android.os.Looper; 38import android.os.Message; 39import android.util.Log; 40import android.view.LayoutInflater; 41import android.view.View; 42import android.view.WindowManager; 43import android.widget.CheckBox; 44import android.widget.CompoundButton; 45 46import java.util.Collection; 47import java.util.List; 48import java.util.Set; 49 50public final class DockService extends Service implements ServiceListener { 51 52 private static final String TAG = "DockService"; 53 54 static final boolean DEBUG = false; 55 56 // Time allowed for the device to be undocked and redocked without severing 57 // the bluetooth connection 58 private static final long UNDOCKED_GRACE_PERIOD = 1000; 59 60 // Time allowed for the device to be undocked and redocked without turning 61 // off Bluetooth 62 private static final long DISABLE_BT_GRACE_PERIOD = 2000; 63 64 // Msg for user wanting the UI to setup the dock 65 private static final int MSG_TYPE_SHOW_UI = 111; 66 67 // Msg for device docked event 68 private static final int MSG_TYPE_DOCKED = 222; 69 70 // Msg for device undocked event 71 private static final int MSG_TYPE_UNDOCKED_TEMPORARY = 333; 72 73 // Msg for undocked command to be process after UNDOCKED_GRACE_PERIOD millis 74 // since MSG_TYPE_UNDOCKED_TEMPORARY 75 private static final int MSG_TYPE_UNDOCKED_PERMANENT = 444; 76 77 // Msg for disabling bt after DISABLE_BT_GRACE_PERIOD millis since 78 // MSG_TYPE_UNDOCKED_PERMANENT 79 private static final int MSG_TYPE_DISABLE_BT = 555; 80 81 private static final String SHARED_PREFERENCES_NAME = "dock_settings"; 82 83 private static final String KEY_DISABLE_BT_WHEN_UNDOCKED = "disable_bt_when_undock"; 84 85 private static final String KEY_DISABLE_BT = "disable_bt"; 86 87 private static final String KEY_CONNECT_RETRY_COUNT = "connect_retry_count"; 88 89 /* 90 * If disconnected unexpectedly, reconnect up to 6 times. Each profile counts 91 * as one time so it's only 3 times for both profiles on the car dock. 92 */ 93 private static final int MAX_CONNECT_RETRY = 6; 94 95 private static final int INVALID_STARTID = -100; 96 97 // Created in OnCreate() 98 private volatile Looper mServiceLooper; 99 private volatile ServiceHandler mServiceHandler; 100 private Runnable mRunnable; 101 private LocalBluetoothAdapter mLocalAdapter; 102 private CachedBluetoothDeviceManager mDeviceManager; 103 private LocalBluetoothProfileManager mProfileManager; 104 105 // Normally set after getting a docked event and unset when the connection 106 // is severed. One exception is that mDevice could be null if the service 107 // was started after the docked event. 108 private BluetoothDevice mDevice; 109 110 // Created and used for the duration of the dialog 111 private AlertDialog mDialog; 112 private LocalBluetoothProfile[] mProfiles; 113 private boolean[] mCheckedItems; 114 private int mStartIdAssociatedWithDialog; 115 116 // Set while BT is being enabled. 117 private BluetoothDevice mPendingDevice; 118 private int mPendingStartId; 119 private int mPendingTurnOnStartId = INVALID_STARTID; 120 private int mPendingTurnOffStartId = INVALID_STARTID; 121 122 @Override 123 public void onCreate() { 124 if (DEBUG) Log.d(TAG, "onCreate"); 125 126 LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); 127 if (manager == null) { 128 Log.e(TAG, "Can't get LocalBluetoothManager: exiting"); 129 return; 130 } 131 132 mLocalAdapter = manager.getBluetoothAdapter(); 133 mDeviceManager = manager.getCachedDeviceManager(); 134 mProfileManager = manager.getProfileManager(); 135 if (mProfileManager == null) { 136 Log.e(TAG, "Can't get LocalBluetoothProfileManager: exiting"); 137 return; 138 } 139 140 HandlerThread thread = new HandlerThread("DockService"); 141 thread.start(); 142 143 mServiceLooper = thread.getLooper(); 144 mServiceHandler = new ServiceHandler(mServiceLooper); 145 } 146 147 @Override 148 public void onDestroy() { 149 if (DEBUG) Log.d(TAG, "onDestroy"); 150 mRunnable = null; 151 if (mDialog != null) { 152 mDialog.dismiss(); 153 mDialog = null; 154 } 155 if (mProfileManager != null) { 156 mProfileManager.removeServiceListener(this); 157 } 158 if (mServiceLooper != null) { 159 mServiceLooper.quit(); 160 } 161 162 mLocalAdapter = null; 163 mDeviceManager = null; 164 mProfileManager = null; 165 mServiceLooper = null; 166 mServiceHandler = null; 167 } 168 169 @Override 170 public IBinder onBind(Intent intent) { 171 // not supported 172 return null; 173 } 174 175 private SharedPreferences getPrefs() { 176 return getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE); 177 } 178 179 @Override 180 public int onStartCommand(Intent intent, int flags, int startId) { 181 if (DEBUG) Log.d(TAG, "onStartCommand startId: " + startId + " flags: " + flags); 182 183 if (intent == null) { 184 // Nothing to process, stop. 185 if (DEBUG) Log.d(TAG, "START_NOT_STICKY - intent is null."); 186 187 // NOTE: We MUST not call stopSelf() directly, since we need to 188 // make sure the wake lock acquired by the Receiver is released. 189 DockEventReceiver.finishStartingService(this, startId); 190 return START_NOT_STICKY; 191 } 192 193 if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { 194 handleBtStateChange(intent, startId); 195 return START_NOT_STICKY; 196 } 197 198 /* 199 * This assumes that the intent sender has checked that this is a dock 200 * and that the intent is for a disconnect 201 */ 202 final SharedPreferences prefs = getPrefs(); 203 if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 204 BluetoothDevice disconnectedDevice = intent 205 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 206 int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0); 207 if (retryCount < MAX_CONNECT_RETRY) { 208 prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply(); 209 handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getHeadsetProfile(), startId); 210 } 211 return START_NOT_STICKY; 212 } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 213 BluetoothDevice disconnectedDevice = intent 214 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 215 216 int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0); 217 if (retryCount < MAX_CONNECT_RETRY) { 218 prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply(); 219 handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getA2dpProfile(), startId); 220 } 221 return START_NOT_STICKY; 222 } 223 224 Message msg = parseIntent(intent); 225 if (msg == null) { 226 // Bad intent 227 if (DEBUG) Log.d(TAG, "START_NOT_STICKY - Bad intent."); 228 DockEventReceiver.finishStartingService(this, startId); 229 return START_NOT_STICKY; 230 } 231 232 if (msg.what == MSG_TYPE_DOCKED) { 233 prefs.edit().remove(KEY_CONNECT_RETRY_COUNT).apply(); 234 } 235 236 msg.arg2 = startId; 237 processMessage(msg); 238 239 return START_NOT_STICKY; 240 } 241 242 private final class ServiceHandler extends Handler { 243 private ServiceHandler(Looper looper) { 244 super(looper); 245 } 246 247 @Override 248 public void handleMessage(Message msg) { 249 processMessage(msg); 250 } 251 } 252 253 // This method gets messages from both onStartCommand and mServiceHandler/mServiceLooper 254 private synchronized void processMessage(Message msg) { 255 int msgType = msg.what; 256 final int state = msg.arg1; 257 final int startId = msg.arg2; 258 BluetoothDevice device = null; 259 if (msg.obj != null) { 260 device = (BluetoothDevice) msg.obj; 261 } 262 263 if(DEBUG) Log.d(TAG, "processMessage: " + msgType + " state: " + state + " device = " 264 + (device == null ? "null" : device.toString())); 265 266 boolean deferFinishCall = false; 267 268 switch (msgType) { 269 case MSG_TYPE_SHOW_UI: 270 createDialog(device, state, startId); 271 break; 272 273 case MSG_TYPE_DOCKED: 274 deferFinishCall = msgTypeDocked(device, state, startId); 275 break; 276 277 case MSG_TYPE_UNDOCKED_PERMANENT: 278 deferFinishCall = msgTypeUndockedPermanent(device, startId); 279 break; 280 281 case MSG_TYPE_UNDOCKED_TEMPORARY: 282 msgTypeUndockedTemporary(device, state, startId); 283 break; 284 285 case MSG_TYPE_DISABLE_BT: 286 deferFinishCall = msgTypeDisableBluetooth(startId); 287 break; 288 } 289 290 if (mDialog == null && mPendingDevice == null && msgType != MSG_TYPE_UNDOCKED_TEMPORARY 291 && !deferFinishCall) { 292 // NOTE: We MUST not call stopSelf() directly, since we need to 293 // make sure the wake lock acquired by the Receiver is released. 294 DockEventReceiver.finishStartingService(this, startId); 295 } 296 } 297 298 private boolean msgTypeDisableBluetooth(int startId) { 299 if (DEBUG) { 300 Log.d(TAG, "BT DISABLE"); 301 } 302 final SharedPreferences prefs = getPrefs(); 303 if (mLocalAdapter.disable()) { 304 prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); 305 return false; 306 } else { 307 // disable() returned an error. Persist a flag to disable BT later 308 prefs.edit().putBoolean(KEY_DISABLE_BT, true).apply(); 309 mPendingTurnOffStartId = startId; 310 if(DEBUG) { 311 Log.d(TAG, "disable failed. try again later " + startId); 312 } 313 return true; 314 } 315 } 316 317 private void msgTypeUndockedTemporary(BluetoothDevice device, int state, 318 int startId) { 319 // Undocked event received. Queue a delayed msg to sever connection 320 Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state, 321 startId, device); 322 mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD); 323 } 324 325 private boolean msgTypeUndockedPermanent(BluetoothDevice device, int startId) { 326 // Grace period passed. Disconnect. 327 handleUndocked(device); 328 final SharedPreferences prefs = getPrefs(); 329 330 if (DEBUG) { 331 Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = " 332 + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)); 333 } 334 335 if (prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)) { 336 if (hasOtherConnectedDevices(device)) { 337 // Don't disable BT if something is connected 338 prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); 339 } else { 340 // BT was disabled when we first docked 341 if (DEBUG) { 342 Log.d(TAG, "QUEUED BT DISABLE"); 343 } 344 // Queue a delayed msg to disable BT 345 Message newMsg = mServiceHandler.obtainMessage( 346 MSG_TYPE_DISABLE_BT, 0, startId, null); 347 mServiceHandler.sendMessageDelayed(newMsg, 348 DISABLE_BT_GRACE_PERIOD); 349 return true; 350 } 351 } 352 return false; 353 } 354 355 private boolean msgTypeDocked(BluetoothDevice device, final int state, 356 final int startId) { 357 if (DEBUG) { 358 // TODO figure out why hasMsg always returns false if device 359 // is supplied 360 Log.d(TAG, "1 Has undock perm msg = " 361 + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice)); 362 Log.d(TAG, "2 Has undock perm msg = " 363 + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device)); 364 } 365 366 mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT); 367 mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT); 368 getPrefs().edit().remove(KEY_DISABLE_BT).apply(); 369 370 if (device != null && !device.equals(mDevice)) { 371 if (mDevice != null) { 372 // Not expected. Cleanup/undock existing 373 handleUndocked(mDevice); 374 } 375 376 mDevice = device; 377 378 // Register first in case LocalBluetoothProfileManager 379 // becomes ready after isManagerReady is called and it 380 // would be too late to register a service listener. 381 mProfileManager.addServiceListener(this); 382 if (mProfileManager.isManagerReady()) { 383 handleDocked(device, state, startId); 384 // Not needed after all 385 mProfileManager.removeServiceListener(this); 386 } else { 387 final BluetoothDevice d = device; 388 mRunnable = new Runnable() { 389 public void run() { 390 handleDocked(d, state, startId); // FIXME: WTF runnable here? 391 } 392 }; 393 return true; 394 } 395 } 396 return false; 397 } 398 399 synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) { 400 Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy(); 401 Set<BluetoothDevice> btDevices = mLocalAdapter.getBondedDevices(); 402 if (btDevices == null || cachedDevices == null || btDevices.isEmpty()) { 403 return false; 404 } 405 if(DEBUG) { 406 Log.d(TAG, "btDevices = " + btDevices.size()); 407 Log.d(TAG, "cachedDeviceUIs = " + cachedDevices.size()); 408 } 409 410 for (CachedBluetoothDevice deviceUI : cachedDevices) { 411 BluetoothDevice btDevice = deviceUI.getDevice(); 412 if (!btDevice.equals(dock) && btDevices.contains(btDevice) && deviceUI 413 .isConnected()) { 414 if(DEBUG) Log.d(TAG, "connected deviceUI = " + deviceUI.getName()); 415 return true; 416 } 417 } 418 return false; 419 } 420 421 private Message parseIntent(Intent intent) { 422 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 423 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1234); 424 425 if (DEBUG) { 426 Log.d(TAG, "Action: " + intent.getAction() + " State:" + state 427 + " Device: " + (device == null ? "null" : device.getAliasName())); 428 } 429 430 if (device == null) { 431 Log.w(TAG, "device is null"); 432 return null; 433 } 434 435 int msgType; 436 switch (state) { 437 case Intent.EXTRA_DOCK_STATE_UNDOCKED: 438 msgType = MSG_TYPE_UNDOCKED_TEMPORARY; 439 break; 440 case Intent.EXTRA_DOCK_STATE_DESK: 441 case Intent.EXTRA_DOCK_STATE_CAR: 442 if (DockEventReceiver.ACTION_DOCK_SHOW_UI.equals(intent.getAction())) { 443 msgType = MSG_TYPE_SHOW_UI; 444 } else { 445 msgType = MSG_TYPE_DOCKED; 446 } 447 break; 448 default: 449 return null; 450 } 451 452 return mServiceHandler.obtainMessage(msgType, state, 0, device); 453 } 454 455 private void createDialog(BluetoothDevice device, 456 int state, int startId) { 457 if (mDialog != null) { 458 // Shouldn't normally happen 459 mDialog.dismiss(); 460 mDialog = null; 461 } 462 mDevice = device; 463 switch (state) { 464 case Intent.EXTRA_DOCK_STATE_CAR: 465 case Intent.EXTRA_DOCK_STATE_DESK: 466 break; 467 default: 468 return; 469 } 470 471 startForeground(0, new Notification()); 472 473 // Device in a new dock. 474 boolean firstTime = !LocalBluetoothPreferences.hasDockAutoConnectSetting(this, device.getAddress()); 475 476 CharSequence[] items = initBtSettings(device, state, firstTime); 477 478 final AlertDialog.Builder ab = new AlertDialog.Builder(this); 479 ab.setTitle(getString(R.string.bluetooth_dock_settings_title)); 480 481 // Profiles 482 ab.setMultiChoiceItems(items, mCheckedItems, mMultiClickListener); 483 484 // Remember this settings 485 LayoutInflater inflater = (LayoutInflater) 486 getSystemService(LAYOUT_INFLATER_SERVICE); 487 float pixelScaleFactor = getResources().getDisplayMetrics().density; 488 View view = inflater.inflate(R.layout.remember_dock_setting, null); 489 CheckBox rememberCheckbox = (CheckBox) view.findViewById(R.id.remember); 490 491 // check "Remember setting" by default if no value was saved 492 boolean checked = firstTime || LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress()); 493 rememberCheckbox.setChecked(checked); 494 rememberCheckbox.setOnCheckedChangeListener(mCheckedChangeListener); 495 int viewSpacingLeft = (int) (14 * pixelScaleFactor); 496 int viewSpacingRight = (int) (14 * pixelScaleFactor); 497 ab.setView(view, viewSpacingLeft, 0 /* top */, viewSpacingRight, 0 /* bottom */); 498 if (DEBUG) { 499 Log.d(TAG, "Auto connect = " 500 + LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())); 501 } 502 503 // Ok Button 504 ab.setPositiveButton(getString(android.R.string.ok), mClickListener); 505 506 mStartIdAssociatedWithDialog = startId; 507 mDialog = ab.create(); 508 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 509 mDialog.setOnDismissListener(mDismissListener); 510 mDialog.show(); 511 } 512 513 // Called when the individual bt profiles are clicked. 514 private final DialogInterface.OnMultiChoiceClickListener mMultiClickListener = 515 new DialogInterface.OnMultiChoiceClickListener() { 516 public void onClick(DialogInterface dialog, int which, boolean isChecked) { 517 if (DEBUG) { 518 Log.d(TAG, "Item " + which + " changed to " + isChecked); 519 } 520 mCheckedItems[which] = isChecked; 521 } 522 }; 523 524 525 // Called when the "Remember" Checkbox is clicked 526 private final CompoundButton.OnCheckedChangeListener mCheckedChangeListener = 527 new CompoundButton.OnCheckedChangeListener() { 528 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 529 if (DEBUG) { 530 Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked); 531 } 532 if (mDevice != null) { 533 LocalBluetoothPreferences.saveDockAutoConnectSetting( 534 DockService.this, mDevice.getAddress(), isChecked); 535 } 536 } 537 }; 538 539 540 // Called when the dialog is dismissed 541 private final DialogInterface.OnDismissListener mDismissListener = 542 new DialogInterface.OnDismissListener() { 543 public void onDismiss(DialogInterface dialog) { 544 // NOTE: We MUST not call stopSelf() directly, since we need to 545 // make sure the wake lock acquired by the Receiver is released. 546 if (mPendingDevice == null) { 547 DockEventReceiver.finishStartingService( 548 DockService.this, mStartIdAssociatedWithDialog); 549 } 550 stopForeground(true); 551 } 552 }; 553 554 // Called when clicked on the OK button 555 private final DialogInterface.OnClickListener mClickListener = 556 new DialogInterface.OnClickListener() { 557 public void onClick(DialogInterface dialog, int which) { 558 if (which == DialogInterface.BUTTON_POSITIVE 559 && mDevice != null) { 560 if (!LocalBluetoothPreferences 561 .hasDockAutoConnectSetting( 562 DockService.this, 563 mDevice.getAddress())) { 564 LocalBluetoothPreferences 565 .saveDockAutoConnectSetting( 566 DockService.this, 567 mDevice.getAddress(), true); 568 } 569 570 applyBtSettings(mDevice, mStartIdAssociatedWithDialog); 571 } 572 } 573 }; 574 575 private CharSequence[] initBtSettings(BluetoothDevice device, 576 int state, boolean firstTime) { 577 // TODO Avoid hardcoding dock and profiles. Read from system properties 578 int numOfProfiles; 579 switch (state) { 580 case Intent.EXTRA_DOCK_STATE_DESK: 581 numOfProfiles = 1; 582 break; 583 case Intent.EXTRA_DOCK_STATE_CAR: 584 numOfProfiles = 2; 585 break; 586 default: 587 return null; 588 } 589 590 mProfiles = new LocalBluetoothProfile[numOfProfiles]; 591 mCheckedItems = new boolean[numOfProfiles]; 592 CharSequence[] items = new CharSequence[numOfProfiles]; 593 594 // FIXME: convert switch to something else 595 switch (state) { 596 case Intent.EXTRA_DOCK_STATE_CAR: 597 items[0] = getString(R.string.bluetooth_dock_settings_headset); 598 items[1] = getString(R.string.bluetooth_dock_settings_a2dp); 599 mProfiles[0] = mProfileManager.getHeadsetProfile(); 600 mProfiles[1] = mProfileManager.getA2dpProfile(); 601 if (firstTime) { 602 // Enable by default for car dock 603 mCheckedItems[0] = true; 604 mCheckedItems[1] = true; 605 } else { 606 mCheckedItems[0] = mProfiles[0].isPreferred(device); 607 mCheckedItems[1] = mProfiles[1].isPreferred(device); 608 } 609 break; 610 611 case Intent.EXTRA_DOCK_STATE_DESK: 612 items[0] = getString(R.string.bluetooth_dock_settings_a2dp); 613 mProfiles[0] = mProfileManager.getA2dpProfile(); 614 if (firstTime) { 615 // Disable by default for desk dock 616 mCheckedItems[0] = false; 617 } else { 618 mCheckedItems[0] = mProfiles[0].isPreferred(device); 619 } 620 break; 621 } 622 return items; 623 } 624 625 // TODO: move to background thread to fix strict mode warnings 626 private void handleBtStateChange(Intent intent, int startId) { 627 int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 628 synchronized (this) { 629 if(DEBUG) Log.d(TAG, "BtState = " + btState + " mPendingDevice = " + mPendingDevice); 630 if (btState == BluetoothAdapter.STATE_ON) { 631 handleBluetoothStateOn(startId); 632 } else if (btState == BluetoothAdapter.STATE_TURNING_OFF) { 633 // Remove the flag to disable BT if someone is turning off bt. 634 // The rational is that: 635 // a) if BT is off at undock time, no work needs to be done 636 // b) if BT is on at undock time, the user wants it on. 637 getPrefs().edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); 638 DockEventReceiver.finishStartingService(this, startId); 639 } else if (btState == BluetoothAdapter.STATE_OFF) { 640 // Bluetooth was turning off as we were trying to turn it on. 641 // Let's try again 642 if(DEBUG) Log.d(TAG, "Bluetooth = OFF mPendingDevice = " + mPendingDevice); 643 644 if (mPendingTurnOffStartId != INVALID_STARTID) { 645 DockEventReceiver.finishStartingService(this, mPendingTurnOffStartId); 646 getPrefs().edit().remove(KEY_DISABLE_BT).apply(); 647 mPendingTurnOffStartId = INVALID_STARTID; 648 } 649 650 if (mPendingDevice != null) { 651 mLocalAdapter.enable(); 652 mPendingTurnOnStartId = startId; 653 } else { 654 DockEventReceiver.finishStartingService(this, startId); 655 } 656 } 657 } 658 } 659 660 private void handleBluetoothStateOn(int startId) { 661 if (mPendingDevice != null) { 662 if (mPendingDevice.equals(mDevice)) { 663 if(DEBUG) { 664 Log.d(TAG, "applying settings"); 665 } 666 applyBtSettings(mPendingDevice, mPendingStartId); 667 } else if(DEBUG) { 668 Log.d(TAG, "mPendingDevice (" + mPendingDevice + ") != mDevice (" 669 + mDevice + ')'); 670 } 671 672 mPendingDevice = null; 673 DockEventReceiver.finishStartingService(this, mPendingStartId); 674 } else { 675 final SharedPreferences prefs = getPrefs(); 676 if (DEBUG) { 677 Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = " 678 + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)); 679 } 680 // Reconnect if docked and bluetooth was enabled by user. 681 Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 682 if (i != null) { 683 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, 684 Intent.EXTRA_DOCK_STATE_UNDOCKED); 685 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 686 BluetoothDevice device = i 687 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 688 if (device != null) { 689 connectIfEnabled(device); 690 } 691 } else if (prefs.getBoolean(KEY_DISABLE_BT, false) 692 && mLocalAdapter.disable()) { 693 mPendingTurnOffStartId = startId; 694 prefs.edit().remove(KEY_DISABLE_BT).apply(); 695 return; 696 } 697 } 698 } 699 700 if (mPendingTurnOnStartId != INVALID_STARTID) { 701 DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId); 702 mPendingTurnOnStartId = INVALID_STARTID; 703 } 704 705 DockEventReceiver.finishStartingService(this, startId); 706 } 707 708 private synchronized void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, 709 LocalBluetoothProfile profile, int startId) { 710 if (DEBUG) { 711 Log.d(TAG, "handling failed connect for " + disconnectedDevice); 712 } 713 714 // Reconnect if docked. 715 if (disconnectedDevice != null) { 716 // registerReceiver can't be called from a BroadcastReceiver 717 Intent intent = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 718 if (intent != null) { 719 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, 720 Intent.EXTRA_DOCK_STATE_UNDOCKED); 721 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 722 BluetoothDevice dockedDevice = intent 723 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 724 if (dockedDevice != null && dockedDevice.equals(disconnectedDevice)) { 725 CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( 726 dockedDevice); 727 cachedDevice.connectProfile(profile); 728 } 729 } 730 } 731 } 732 733 DockEventReceiver.finishStartingService(this, startId); 734 } 735 736 private synchronized void connectIfEnabled(BluetoothDevice device) { 737 CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( 738 device); 739 List<LocalBluetoothProfile> profiles = cachedDevice.getConnectableProfiles(); 740 for (LocalBluetoothProfile profile : profiles) { 741 if (profile.getPreferred(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { 742 cachedDevice.connect(false); 743 return; 744 } 745 } 746 } 747 748 private synchronized void applyBtSettings(BluetoothDevice device, int startId) { 749 if (device == null || mProfiles == null || mCheckedItems == null 750 || mLocalAdapter == null) { 751 return; 752 } 753 754 // Turn on BT if something is enabled 755 for (boolean enable : mCheckedItems) { 756 if (enable) { 757 int btState = mLocalAdapter.getBluetoothState(); 758 if (DEBUG) { 759 Log.d(TAG, "BtState = " + btState); 760 } 761 // May have race condition as the phone comes in and out and in the dock. 762 // Always turn on BT 763 mLocalAdapter.enable(); 764 765 // if adapter was previously OFF, TURNING_OFF, or TURNING_ON 766 if (btState != BluetoothAdapter.STATE_ON) { 767 if (mPendingDevice != null && mPendingDevice.equals(mDevice)) { 768 return; 769 } 770 771 mPendingDevice = device; 772 mPendingStartId = startId; 773 if (btState != BluetoothAdapter.STATE_TURNING_ON) { 774 getPrefs().edit().putBoolean( 775 KEY_DISABLE_BT_WHEN_UNDOCKED, true).apply(); 776 } 777 return; 778 } 779 } 780 } 781 782 mPendingDevice = null; 783 784 boolean callConnect = false; 785 CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( 786 device); 787 for (int i = 0; i < mProfiles.length; i++) { 788 LocalBluetoothProfile profile = mProfiles[i]; 789 if (DEBUG) Log.d(TAG, profile.toString() + " = " + mCheckedItems[i]); 790 791 if (mCheckedItems[i]) { 792 // Checked but not connected 793 callConnect = true; 794 } else if (!mCheckedItems[i]) { 795 // Unchecked, may or may not be connected. 796 int status = profile.getConnectionStatus(cachedDevice.getDevice()); 797 if (status == BluetoothProfile.STATE_CONNECTED) { 798 if (DEBUG) Log.d(TAG, "applyBtSettings - Disconnecting"); 799 cachedDevice.disconnect(mProfiles[i]); 800 } 801 } 802 profile.setPreferred(device, mCheckedItems[i]); 803 if (DEBUG) { 804 if (mCheckedItems[i] != profile.isPreferred(device)) { 805 Log.e(TAG, "Can't save preferred value"); 806 } 807 } 808 } 809 810 if (callConnect) { 811 if (DEBUG) Log.d(TAG, "applyBtSettings - Connecting"); 812 cachedDevice.connect(false); 813 } 814 } 815 816 private synchronized void handleDocked(BluetoothDevice device, int state, 817 int startId) { 818 if (LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())) { 819 // Setting == auto connect 820 initBtSettings(device, state, false); 821 applyBtSettings(mDevice, startId); 822 } else { 823 createDialog(device, state, startId); 824 } 825 } 826 827 private synchronized void handleUndocked(BluetoothDevice device) { 828 mRunnable = null; 829 mProfileManager.removeServiceListener(this); 830 if (mDialog != null) { 831 mDialog.dismiss(); 832 mDialog = null; 833 } 834 mDevice = null; 835 mPendingDevice = null; 836 CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(device); 837 cachedDevice.disconnect(); 838 } 839 840 private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice device) { 841 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 842 if (cachedDevice == null) { 843 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 844 } 845 return cachedDevice; 846 } 847 848 public synchronized void onServiceConnected() { 849 if (mRunnable != null) { 850 mRunnable.run(); 851 mRunnable = null; 852 mProfileManager.removeServiceListener(this); 853 } 854 } 855 856 public void onServiceDisconnected() { 857 // FIXME: shouldn't I do something on service disconnected too? 858 } 859} 860