OSDN Git Service

am 8815f032: Merge "Always set right auth_type value in apn."
[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.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 }