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