OSDN Git Service

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