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