Home | History | Annotate | Download | only in bluetooth
      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