DockService.java revision 57d5585595810044e7727d4303214f69ff2d77d4
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.bluetooth;
18
19import com.android.settings.R;
20import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
21import com.android.settings.bluetooth.LocalBluetoothProfileManager.ServiceListener;
22
23import android.app.AlertDialog;
24import android.app.Notification;
25import android.app.Service;
26import android.bluetooth.BluetoothA2dp;
27import android.bluetooth.BluetoothAdapter;
28import android.bluetooth.BluetoothDevice;
29import android.bluetooth.BluetoothHeadset;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.SharedPreferences;
35import android.os.Handler;
36import android.os.HandlerThread;
37import android.os.IBinder;
38import android.os.Looper;
39import android.os.Message;
40import android.util.Log;
41import android.view.LayoutInflater;
42import android.view.View;
43import android.view.WindowManager;
44import android.widget.CheckBox;
45import android.widget.CompoundButton;
46
47import java.util.List;
48import java.util.Set;
49
50public 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 but connected
722                if (DEBUG) Log.d(TAG, "applyBtSettings - Disconnecting");
723                cachedDevice.disconnect(mProfiles[i]);
724            }
725            profileManager.setPreferred(device, mCheckedItems[i]);
726            if (DEBUG) {
727                if (mCheckedItems[i] != profileManager.isPreferred(device)) {
728                    Log.e(TAG, "Can't save prefered value");
729                }
730            }
731        }
732
733        if (callConnect) {
734            if (DEBUG) Log.d(TAG, "applyBtSettings - Connecting");
735            cachedDevice.connect();
736        }
737    }
738
739    private synchronized void handleDocked(final BluetoothDevice device, final int state,
740            final int startId) {
741        if (mBtManager.getDockAutoConnectSetting(device.getAddress())) {
742            // Setting == auto connect
743            initBtSettings(mContext, device, state, false);
744            applyBtSettings(mDevice, startId);
745        } else {
746            createDialog(mContext, device, state, startId);
747        }
748    }
749
750    private synchronized void handleUndocked(Context context, LocalBluetoothManager localManager,
751            BluetoothDevice device) {
752        mRunnable = null;
753        LocalBluetoothProfileManager.removeServiceListener(this);
754        if (mDialog != null) {
755            mDialog.dismiss();
756            mDialog = null;
757        }
758        mDevice = null;
759        mPendingDevice = null;
760        CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context,
761                localManager, device);
762        cachedBluetoothDevice.disconnect();
763    }
764
765    private static CachedBluetoothDevice getCachedBluetoothDevice(Context context,
766            LocalBluetoothManager localManager, BluetoothDevice device) {
767        CachedBluetoothDeviceManager cachedDeviceManager = localManager.getCachedDeviceManager();
768        CachedBluetoothDevice cachedBluetoothDevice = cachedDeviceManager.findDevice(device);
769        if (cachedBluetoothDevice == null) {
770            cachedBluetoothDevice = new CachedBluetoothDevice(context, device);
771        }
772        return cachedBluetoothDevice;
773    }
774
775    private boolean getSettingBool(String key) {
776        SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME,
777                Context.MODE_PRIVATE);
778        return sharedPref.getBoolean(key, false);
779    }
780
781    private int getSettingInt(String key, int defaultValue) {
782        SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME,
783                Context.MODE_PRIVATE);
784        return sharedPref.getInt(key, defaultValue);
785    }
786
787    private void setSettingBool(String key, boolean bool) {
788        SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME,
789                Context.MODE_PRIVATE).edit();
790        editor.putBoolean(key, bool);
791        editor.commit();
792    }
793
794    private void setSettingInt(String key, int value) {
795        SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME,
796                Context.MODE_PRIVATE).edit();
797        editor.putInt(key, value);
798        editor.commit();
799    }
800
801    private void removeSetting(String key) {
802        SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME,
803                Context.MODE_PRIVATE);
804        SharedPreferences.Editor editor = sharedPref.edit();
805        editor.remove(key);
806        editor.commit();
807        return;
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}
821