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