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 17 package com.android.settings.bluetooth; 18 19 import com.android.settings.R; 20 import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; 21 import com.android.settings.bluetooth.LocalBluetoothProfileManager.ServiceListener; 22 23 import android.app.AlertDialog; 24 import android.app.Notification; 25 import android.app.Service; 26 import android.bluetooth.BluetoothA2dp; 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothDevice; 29 import android.bluetooth.BluetoothHeadset; 30 import android.content.Context; 31 import android.content.DialogInterface; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.SharedPreferences; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.IBinder; 38 import android.os.Looper; 39 import android.os.Message; 40 import android.util.Log; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.WindowManager; 44 import android.widget.CheckBox; 45 import android.widget.CompoundButton; 46 47 import java.util.List; 48 import java.util.Set; 49 50 public 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, may or may not be connected. 722 int status = profileManager.getConnectionStatus(cachedDevice.getDevice()); 723 if (SettingsBtStatus.isConnectionStatusConnected(status)) { 724 if (DEBUG) Log.d(TAG, "applyBtSettings - Disconnecting"); 725 cachedDevice.disconnect(mProfiles[i]); 726 } 727 } 728 profileManager.setPreferred(device, mCheckedItems[i]); 729 if (DEBUG) { 730 if (mCheckedItems[i] != profileManager.isPreferred(device)) { 731 Log.e(TAG, "Can't save prefered value"); 732 } 733 } 734 } 735 736 if (callConnect) { 737 if (DEBUG) Log.d(TAG, "applyBtSettings - Connecting"); 738 cachedDevice.connect(); 739 } 740 } 741 742 private synchronized void handleDocked(final BluetoothDevice device, final int state, 743 final int startId) { 744 if (mBtManager.getDockAutoConnectSetting(device.getAddress())) { 745 // Setting == auto connect 746 initBtSettings(mContext, device, state, false); 747 applyBtSettings(mDevice, startId); 748 } else { 749 createDialog(mContext, device, state, startId); 750 } 751 } 752 753 private synchronized void handleUndocked(Context context, LocalBluetoothManager localManager, 754 BluetoothDevice device) { 755 mRunnable = null; 756 LocalBluetoothProfileManager.removeServiceListener(this); 757 if (mDialog != null) { 758 mDialog.dismiss(); 759 mDialog = null; 760 } 761 mDevice = null; 762 mPendingDevice = null; 763 CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, 764 localManager, device); 765 cachedBluetoothDevice.disconnect(); 766 } 767 768 private static CachedBluetoothDevice getCachedBluetoothDevice(Context context, 769 LocalBluetoothManager localManager, BluetoothDevice device) { 770 CachedBluetoothDeviceManager cachedDeviceManager = localManager.getCachedDeviceManager(); 771 CachedBluetoothDevice cachedBluetoothDevice = cachedDeviceManager.findDevice(device); 772 if (cachedBluetoothDevice == null) { 773 cachedBluetoothDevice = new CachedBluetoothDevice(context, device); 774 } 775 return cachedBluetoothDevice; 776 } 777 778 private boolean getSettingBool(String key) { 779 SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, 780 Context.MODE_PRIVATE); 781 return sharedPref.getBoolean(key, false); 782 } 783 784 private int getSettingInt(String key, int defaultValue) { 785 SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, 786 Context.MODE_PRIVATE); 787 return sharedPref.getInt(key, defaultValue); 788 } 789 790 private void setSettingBool(String key, boolean bool) { 791 SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, 792 Context.MODE_PRIVATE).edit(); 793 editor.putBoolean(key, bool); 794 editor.apply(); 795 } 796 797 private void setSettingInt(String key, int value) { 798 SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, 799 Context.MODE_PRIVATE).edit(); 800 editor.putInt(key, value); 801 editor.apply(); 802 } 803 804 private void removeSetting(String key) { 805 SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, 806 Context.MODE_PRIVATE); 807 SharedPreferences.Editor editor = sharedPref.edit(); 808 editor.remove(key); 809 editor.apply(); 810 } 811 812 public synchronized void onServiceConnected() { 813 if (mRunnable != null) { 814 mRunnable.run(); 815 mRunnable = null; 816 LocalBluetoothProfileManager.removeServiceListener(this); 817 } 818 } 819 820 public void onServiceDisconnected() { 821 } 822 } 823