OSDN Git Service

Merge "Updated BT imaging icons"
authorJustin Ho <justinho@google.com>
Wed, 2 Mar 2011 16:58:05 +0000 (08:58 -0800)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Wed, 2 Mar 2011 16:58:05 +0000 (08:58 -0800)
44 files changed:
AndroidManifest.xml
res/xml/bluetooth_settings.xml
src/com/android/settings/TetherSettings.java
src/com/android/settings/WirelessSettings.java
src/com/android/settings/bluetooth/A2dpProfile.java [new file with mode: 0644]
src/com/android/settings/bluetooth/BluetoothCallback.java [new file with mode: 0644]
src/com/android/settings/bluetooth/BluetoothDeviceFilter.java [new file with mode: 0644]
src/com/android/settings/bluetooth/BluetoothDevicePreference.java
src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java [new file with mode: 0644]
src/com/android/settings/bluetooth/BluetoothEnabler.java
src/com/android/settings/bluetooth/BluetoothEventManager.java [new file with mode: 0644]
src/com/android/settings/bluetooth/BluetoothEventRedirector.java [deleted file]
src/com/android/settings/bluetooth/BluetoothFindNearby.java
src/com/android/settings/bluetooth/BluetoothNamePreference.java
src/com/android/settings/bluetooth/BluetoothPairingDialog.java
src/com/android/settings/bluetooth/BluetoothPairingRequest.java
src/com/android/settings/bluetooth/BluetoothProfilePreference.java
src/com/android/settings/bluetooth/BluetoothSettings.java
src/com/android/settings/bluetooth/CachedBluetoothDevice.java
src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
src/com/android/settings/bluetooth/DevicePickerActivity.java
src/com/android/settings/bluetooth/DevicePickerFragment.java
src/com/android/settings/bluetooth/DeviceProfilesSettings.java
src/com/android/settings/bluetooth/DockEventReceiver.java
src/com/android/settings/bluetooth/DockService.java
src/com/android/settings/bluetooth/HeadsetProfile.java [new file with mode: 0644]
src/com/android/settings/bluetooth/HidProfile.java [new file with mode: 0644]
src/com/android/settings/bluetooth/LocalBluetoothAdapter.java [new file with mode: 0644]
src/com/android/settings/bluetooth/LocalBluetoothManager.java
src/com/android/settings/bluetooth/LocalBluetoothPreferences.java [new file with mode: 0644]
src/com/android/settings/bluetooth/LocalBluetoothProfile.java [new file with mode: 0644]
src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
src/com/android/settings/bluetooth/OppProfile.java [new file with mode: 0644]
src/com/android/settings/bluetooth/PanProfile.java [new file with mode: 0644]
src/com/android/settings/bluetooth/RequestPermissionActivity.java
src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java
src/com/android/settings/bluetooth/SettingsBtStatus.java [deleted file]
src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java [new file with mode: 0644]
src/com/android/settings/bluetooth/Utils.java [new file with mode: 0644]
src/com/android/settings/widget/SettingsAppWidgetProvider.java
tests/src/com/android/settings/SettingsHookTests.java
tests/src/com/android/settings/bluetooth/Utf8ByteLengthFilterTest.java [moved from tests/src/com/android/settings/tests/Utf8ByteLengthFilterTest.java with 91% similarity]

index f6e063b..7f23ce0 100644 (file)
         </activity>
 
         <receiver
+            android:name=".bluetooth.BluetoothDiscoveryReceiver">
+            <intent-filter>
+                <action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
+                <action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+
+        <receiver
             android:name=".bluetooth.DockEventReceiver">
             <intent-filter>
                 <action android:name="android.intent.action.DOCK_EVENT" />
index b441ab4..042eed5 100644 (file)
@@ -60,7 +60,6 @@
     <PreferenceCategory
         android:key="bt_device_list"
         android:title="@string/bluetooth_preference_paired_devices"
-        android:dependency="bt_checkbox"
         android:orderingFromXml="false" />
 
     <Preference
index 2ff3254..af83157 100644 (file)
@@ -35,8 +35,6 @@ import android.os.Environment;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.PreferenceScreen;
-import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.webkit.WebView;
@@ -49,7 +47,6 @@ import java.util.Locale;
  * Displays preferences for Tethering.
  */
 public class TetherSettings extends SettingsPreferenceFragment {
-    private static final String TAG = "TetheringSettings";
 
     private static final String USB_TETHER_SETTINGS = "usb_tether_settings";
     private static final String ENABLE_WIFI_AP = "enable_wifi_ap";
@@ -66,8 +63,6 @@ public class TetherSettings extends SettingsPreferenceFragment {
     private WebView mView;
     private CheckBoxPreference mUsbTether;
 
-    private CheckBoxPreference mEnableWifiAp;
-    private PreferenceScreen mWifiApSettings;
     private WifiApEnabler mWifiApEnabler;
 
     private CheckBoxPreference mBluetoothTether;
@@ -95,9 +90,9 @@ public class TetherSettings extends SettingsPreferenceFragment {
                     BluetoothProfile.PAN);
         }
 
-
-        mEnableWifiAp = (CheckBoxPreference) findPreference(ENABLE_WIFI_AP);
-        mWifiApSettings = (PreferenceScreen) findPreference(WIFI_AP_SETTINGS);
+        CheckBoxPreference enableWifiAp =
+                (CheckBoxPreference) findPreference(ENABLE_WIFI_AP);
+        Preference wifiApSettings = findPreference(WIFI_AP_SETTINGS);
         mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS);
         mBluetoothTether = (CheckBoxPreference) findPreference(ENABLE_BLUETOOTH_TETHERING);
         mTetherHelp = (PreferenceScreen) findPreference(TETHERING_HELP);
@@ -118,8 +113,8 @@ public class TetherSettings extends SettingsPreferenceFragment {
         }
 
         if (!wifiAvailable) {
-            getPreferenceScreen().removePreference(mEnableWifiAp);
-            getPreferenceScreen().removePreference(mWifiApSettings);
+            getPreferenceScreen().removePreference(enableWifiAp);
+            getPreferenceScreen().removePreference(wifiApSettings);
         }
 
         if (!bluetoothAvailable) {
@@ -132,7 +127,7 @@ public class TetherSettings extends SettingsPreferenceFragment {
             }
         }
 
-        mWifiApEnabler = new WifiApEnabler(activity, mEnableWifiAp);
+        mWifiApEnabler = new WifiApEnabler(activity, enableWifiAp);
         mView = new WebView(activity);
     }
 
@@ -154,22 +149,22 @@ public class TetherSettings extends SettingsPreferenceFragment {
             // check for the full language + country resource, if not there, try just language
             final AssetManager am = getActivity().getAssets();
             String path = HELP_PATH.replace("%y", locale.getLanguage().toLowerCase());
-            path = path.replace("%z", "_"+locale.getCountry().toLowerCase());
+            path = path.replace("%z", '_'+locale.getCountry().toLowerCase());
             boolean useCountry = true;
             InputStream is = null;
             try {
                 is = am.open(path);
-            } catch (Exception e) {
+            } catch (Exception ignored) {
                 useCountry = false;
             } finally {
                 if (is != null) {
                     try {
                         is.close();
-                    } catch (Exception e) {}
+                    } catch (Exception ignored) {}
                 }
             }
             String url = HELP_URL.replace("%y", locale.getLanguage().toLowerCase());
-            url = url.replace("%z", (useCountry ? "_"+locale.getCountry().toLowerCase() : ""));
+            url = url.replace("%z", useCountry ? '_'+locale.getCountry().toLowerCase() : "");
             if ((mUsbRegexs.length != 0) && (mWifiRegexs.length == 0)) {
                 url = url.replace("%x", USB_HELP_MODIFIER);
             } else if ((mWifiRegexs.length != 0) && (mUsbRegexs.length == 0)) {
@@ -271,10 +266,8 @@ public class TetherSettings extends SettingsPreferenceFragment {
             String[] errored) {
         ConnectivityManager cm =
                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
-        boolean usbTethered = false;
         boolean usbAvailable = false;
         int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
-        boolean usbErrored = false;
         boolean massStorageActive =
                 Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState());
         for (String s : available) {
@@ -287,11 +280,13 @@ public class TetherSettings extends SettingsPreferenceFragment {
                 }
             }
         }
+        boolean usbTethered = false;
         for (String s : tethered) {
             for (String regex : mUsbRegexs) {
                 if (s.matches(regex)) usbTethered = true;
             }
         }
+        boolean usbErrored = false;
         for (String s: errored) {
             for (String regex : mUsbRegexs) {
                 if (s.matches(regex)) usbErrored = true;
@@ -329,25 +324,23 @@ public class TetherSettings extends SettingsPreferenceFragment {
             String[] errored) {
         ConnectivityManager cm =
                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
-        boolean bluetoothTethered = false;
-        boolean bluetoothAvailable = false;
         int bluetoothError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
-        boolean bluetoothErrored = false;
         for (String s : available) {
             for (String regex : mBluetoothRegexs) {
                 if (s.matches(regex)) {
-                    bluetoothAvailable = true;
                     if (bluetoothError == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                         bluetoothError = cm.getLastTetherError(s);
                     }
                 }
             }
         }
+        boolean bluetoothTethered = false;
         for (String s : tethered) {
             for (String regex : mBluetoothRegexs) {
                 if (s.matches(regex)) bluetoothTethered = true;
             }
         }
+        boolean bluetoothErrored = false;
         for (String s: errored) {
             for (String regex : mBluetoothRegexs) {
                 if (s.matches(regex)) bluetoothErrored = true;
@@ -458,7 +451,7 @@ public class TetherSettings extends SettingsPreferenceFragment {
         return super.onPreferenceTreeClick(screen, preference);
     }
 
-    private String findIface(String[] ifaces, String[] regexes) {
+    private static String findIface(String[] ifaces, String[] regexes) {
         for (String iface : ifaces) {
             for (String regex : regexes) {
                 if (iface.matches(regex)) {
index b1b7e6e..4b92749 100644 (file)
@@ -19,6 +19,8 @@ package com.android.settings;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.settings.bluetooth.BluetoothEnabler;
+import com.android.settings.bluetooth.LocalBluetoothAdapter;
+import com.android.settings.bluetooth.LocalBluetoothManager;
 import com.android.settings.wifi.WifiEnabler;
 import com.android.settings.nfc.NfcEnabler;
 
@@ -103,7 +105,8 @@ public class WirelessSettings extends SettingsPreferenceFragment {
         mAirplaneModeEnabler = new AirplaneModeEnabler(activity, airplane);
         mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
         mWifiEnabler = new WifiEnabler(activity, wifi);
-        mBtEnabler = new BluetoothEnabler(activity, bt);
+        mBtEnabler = new BluetoothEnabler(activity,
+                LocalBluetoothManager.getInstance(activity).getBluetoothAdapter(), bt);
         mNfcEnabler = new NfcEnabler(activity, nfc);
 
         String toggleable = Settings.System.getString(activity.getContentResolver(),
diff --git a/src/com/android/settings/bluetooth/A2dpProfile.java b/src/com/android/settings/bluetooth/A2dpProfile.java
new file mode 100644 (file)
index 0000000..96225d8
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/**
+ * A2dpProfile handles Bluetooth A2DP.
+ * TODO: add null checks around calls to mService object.
+ */
+final class A2dpProfile implements LocalBluetoothProfile {
+    private BluetoothA2dp mService;
+
+    static final ParcelUuid[] SINK_UUIDS = {
+        BluetoothUuid.AudioSink,
+        BluetoothUuid.AdvAudioDist,
+    };
+
+    static final String NAME = "A2DP";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 1;
+
+    // These callbacks run on the main thread.
+    private final class A2dpServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothA2dp) proxy;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mService = null;
+        }
+    }
+
+    A2dpProfile(Context context) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        adapter.getProfileProxy(context, new A2dpServiceListener(),
+                BluetoothProfile.A2DP);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    private List<BluetoothDevice> getConnectedDevices() {
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        List<BluetoothDevice> sinks = getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    boolean isA2dpPlaying() {
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (!sinks.isEmpty()) {
+            if (mService.isA2dpPlaying(sinks.get(0))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isProfileReady() {
+        return mService != null;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource() {
+        return R.string.bluetooth_profile_a2dp;
+    }
+
+    public int getDisconnectResource() {
+        return R.string.bluetooth_disconnect_a2dp_profile;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = mService.getConnectionState(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_a2dp_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_a2dp_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_headphones_a2dp;
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothCallback.java b/src/com/android/settings/bluetooth/BluetoothCallback.java
new file mode 100644 (file)
index 0000000..3ce9adf
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+/**
+ * BluetoothCallback provides a callback interface for the settings
+ * UI to receive events from {@link BluetoothEventManager}.
+ */
+interface BluetoothCallback {
+    void onBluetoothStateChanged(int bluetoothState);
+    void onScanningStateChanged(boolean started);
+    void onDeviceAdded(CachedBluetoothDevice cachedDevice);
+    void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
+    void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
new file mode 100644 (file)
index 0000000..00e342c
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+/**
+ * BluetoothDeviceFilter contains a static method that returns a
+ * Filter object that returns whether or not the BluetoothDevice
+ * passed to it matches the specified filter type constant from
+ * {@link android.bluetooth.BluetoothDevicePicker}.
+ */
+final class BluetoothDeviceFilter {
+    private static final String TAG = "BluetoothDeviceFilter";
+
+    /** The filter interface to external classes. */
+    interface Filter {
+        boolean matches(BluetoothDevice device);
+    }
+
+    /** All filter singleton (referenced directly). */
+    static final Filter ALL_FILTER = new AllFilter();
+
+    /** Bonded devices only filter (referenced directly). */
+    static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
+
+    /** Table of singleton filter objects. */
+    private static final Filter[] FILTERS = {
+            ALL_FILTER,             // FILTER_TYPE_ALL
+            new AudioFilter(),      // FILTER_TYPE_AUDIO
+            new TransferFilter(),   // FILTER_TYPE_TRANSFER
+            new PanuFilter(),       // FILTER_TYPE_PANU
+            new NapFilter()         // FILTER_TYPE_NAP
+    };
+
+    /** Private constructor. */
+    private BluetoothDeviceFilter() {
+    }
+
+    /**
+     * Returns the singleton {@link Filter} object for the specified type,
+     * or {@link #ALL_FILTER} if the type value is out of range.
+     *
+     * @param filterType a constant from BluetoothDevicePicker
+     * @return a singleton object implementing the {@link Filter} interface.
+     */
+    static Filter getFilter(int filterType) {
+        if (filterType >= 0 && filterType < FILTERS.length) {
+            return FILTERS[filterType];
+        } else {
+            Log.w(TAG, "Invalid filter type " + filterType + " for device picker");
+            return ALL_FILTER;
+        }
+    }
+
+    /** Filter that matches all devices. */
+    private static final class AllFilter implements Filter {
+        public boolean matches(BluetoothDevice device) {
+            return true;
+        }
+    }
+
+    /** Filter that matches only bonded devices. */
+    private static final class BondedDeviceFilter implements Filter {
+        public boolean matches(BluetoothDevice device) {
+            return device.getBondState() == BluetoothDevice.BOND_BONDED;
+        }
+    }
+
+    /** Parent class of filters based on UUID and/or Bluetooth class. */
+    private abstract static class ClassUuidFilter implements Filter {
+        abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
+
+        public boolean matches(BluetoothDevice device) {
+            return matches(device.getUuids(), device.getBluetoothClass());
+        }
+    }
+
+    /** Filter that matches devices that support AUDIO profiles. */
+    private static final class AudioFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) {
+                    return true;
+                }
+                if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) {
+                    return true;
+                }
+            } else if (btClass != null) {
+                if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) ||
+                        btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /** Filter that matches devices that support Object Transfer. */
+    private static final class TransferFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+                    return true;
+                }
+            }
+            return btClass != null
+                    && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP);
+        }
+    }
+
+    /** Filter that matches devices that support PAN User (PANU) profile. */
+    private static final class PanuFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) {
+                    return true;
+                }
+            }
+            return btClass != null
+                    && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU);
+        }
+    }
+
+    /** Filter that matches devices that support NAP profile. */
+    private static final class NapFilter extends ClassUuidFilter {
+        @Override
+        boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+            if (uuids != null) {
+                if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) {
+                    return true;
+                }
+            }
+            return btClass != null
+                    && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP);
+        }
+    }
+}
index 6a0b881..5f791d9 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.R;
-
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.graphics.drawable.Drawable;
 import android.preference.Preference;
+import android.text.TextUtils;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+import com.android.settings.R;
 
-import java.util.Map;
+import java.util.List;
 
 /**
  * BluetoothDevicePreference is the preference type used to display each remote
  * Bluetooth device in the Bluetooth Settings screen.
  */
-public class BluetoothDevicePreference extends Preference implements
+public final class BluetoothDevicePreference extends Preference implements
         CachedBluetoothDevice.Callback, OnClickListener {
     private static final String TAG = "BluetoothDevicePreference";
 
@@ -48,11 +53,7 @@ public class BluetoothDevicePreference extends Preference implements
 
     private OnClickListener mOnSettingsClickListener;
 
-    /**
-     * Cached local copy of whether the device is busy. This is only updated
-     * from {@link #onDeviceAttributesChanged()}.
-     */
-    private boolean mIsBusy;
+    private AlertDialog mDisconnectDialog;
 
     public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) {
         super(context);
@@ -60,19 +61,19 @@ public class BluetoothDevicePreference extends Preference implements
         if (sDimAlpha == Integer.MIN_VALUE) {
             TypedValue outValue = new TypedValue();
             context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
-            sDimAlpha = (int) ((outValue.getFloat() * 255) * 0.5);
+            sDimAlpha = (int) (outValue.getFloat() * 255);
         }
 
         mCachedDevice = cachedDevice;
 
         setWidgetLayoutResource(R.layout.preference_bluetooth);
 
-        cachedDevice.registerCallback(this);
+        mCachedDevice.registerCallback(this);
 
         onDeviceAttributesChanged();
     }
 
-    public CachedBluetoothDevice getCachedDevice() {
+    CachedBluetoothDevice getCachedDevice() {
         return mCachedDevice;
     }
 
@@ -84,42 +85,30 @@ public class BluetoothDevicePreference extends Preference implements
     protected void onPrepareForRemoval() {
         super.onPrepareForRemoval();
         mCachedDevice.unregisterCallback(this);
+        if (mDisconnectDialog != null) {
+            mDisconnectDialog.dismiss();
+            mDisconnectDialog = null;
+        }
     }
 
     public void onDeviceAttributesChanged() {
-
         /*
          * The preference framework takes care of making sure the value has
-         * changed before proceeding.
+         * changed before proceeding. It will also call notifyChanged() if
+         * any preference info has changed from the previous value.
          */
-
         setTitle(mCachedDevice.getName());
 
-        /*
-         * TODO: Showed "Paired" even though it was "Connected". This may be
-         * related to BluetoothHeadset not bound to the actual
-         * BluetoothHeadsetService when we got here.
-         */
-        setSummary(mCachedDevice.getSummary());
+        setSummary(getConnectionSummary());
 
         // Used to gray out the item
-        mIsBusy = mCachedDevice.isBusy();
-
-        // Data has changed
-        notifyChanged();
+        setEnabled(!mCachedDevice.isBusy());
 
-        // This could affect ordering, so notify that also
+        // This could affect ordering, so notify that
         notifyHierarchyChanged();
     }
 
     @Override
-    public boolean isEnabled() {
-        // Temp fix until we have 2053751 fixed in the framework
-        setEnabled(true);
-        return super.isEnabled() && !mIsBusy;
-    }
-
-    @Override
     protected void onBindView(View view) {
         // Disable this view if the bluetooth enable/disable preference view is off
         if (null != findPreferenceInHierarchy("bt_checkbox")) {
@@ -129,17 +118,17 @@ public class BluetoothDevicePreference extends Preference implements
         super.onBindView(view);
 
         ImageView btClass = (ImageView) view.findViewById(android.R.id.icon);
-        btClass.setImageResource(mCachedDevice.getBtClassDrawable());
-        btClass.setAlpha(!mIsBusy ? 255 : sDimAlpha);
+        btClass.setImageResource(getBtClassDrawable());
+        btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);
 
         mDeviceSettings = (ImageView) view.findViewById(R.id.deviceDetails);
         if (mOnSettingsClickListener != null) {
             mDeviceSettings.setOnClickListener(this);
             mDeviceSettings.setTag(mCachedDevice);
-            mDeviceSettings.setAlpha(!mIsBusy ? 255 : sDimAlpha);
+            mDeviceSettings.setAlpha(isEnabled() ? 255 : sDimAlpha);
         } else { // Hide the settings icon and divider
             mDeviceSettings.setVisibility(View.GONE);
-            ImageView divider = (ImageView) view.findViewById(R.id.divider);
+            View divider = view.findViewById(R.id.divider);
             if (divider != null) {
                 divider.setVisibility(View.GONE);
             }
@@ -148,24 +137,40 @@ public class BluetoothDevicePreference extends Preference implements
         LayoutInflater inflater = (LayoutInflater)
                 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         ViewGroup profilesGroup = (ViewGroup) view.findViewById(R.id.profileIcons);
-        Map<Profile, Drawable> profileIcons = mCachedDevice.getProfileIcons();
-        for (Profile profile : profileIcons.keySet()) {
-            Drawable icon = profileIcons.get(profile);
-            inflater.inflate(R.layout.profile_icon_small, profilesGroup, true);
-            ImageView imageView =
-                    (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1);
-            imageView.setImageDrawable(icon);
-            boolean profileEnabled = mCachedDevice.isConnectedProfile(profile);
-            imageView.setAlpha(profileEnabled ? 255 : sDimAlpha);
+        for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) {
+            int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass());
+            if (iconResource != 0) {
+                Drawable icon = getContext().getResources().getDrawable(iconResource);
+                inflater.inflate(R.layout.profile_icon_small, profilesGroup, true);
+                ImageView imageView =
+                        (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1);
+                imageView.setImageDrawable(icon);
+                boolean profileEnabled = mCachedDevice.isConnectedProfile(profile);
+                imageView.setAlpha(profileEnabled ? 255 : sDimAlpha);
+            }
         }
     }
 
     public void onClick(View v) {
         if (v == mDeviceSettings) {
-            if (mOnSettingsClickListener != null) mOnSettingsClickListener.onClick(v);
+            if (mOnSettingsClickListener != null) {
+                mOnSettingsClickListener.onClick(v);
+            }
         }
     }
 
+    public boolean equals(Object o) {
+        if ((o == null) || !(o instanceof BluetoothDevicePreference)) {
+            return false;
+        }
+        return mCachedDevice.equals(
+                ((BluetoothDevicePreference) o).mCachedDevice);
+    }
+
+    public int hashCode() {
+        return mCachedDevice.hashCode();
+    }
+
     @Override
     public int compareTo(Preference another) {
         if (!(another instanceof BluetoothDevicePreference)) {
@@ -173,7 +178,112 @@ public class BluetoothDevicePreference extends Preference implements
             return 1;
         }
 
-        return mCachedDevice.compareTo(((BluetoothDevicePreference) another).mCachedDevice);
+        return mCachedDevice
+                .compareTo(((BluetoothDevicePreference) another).mCachedDevice);
+    }
+
+    void onClicked() {
+        int bondState = mCachedDevice.getBondState();
+
+        if (mCachedDevice.isConnected()) {
+            askDisconnect();
+        } else if (bondState == BluetoothDevice.BOND_BONDED) {
+            mCachedDevice.connect(true);
+        } else if (bondState == BluetoothDevice.BOND_NONE) {
+            pair();
+        }
+    }
+
+    // Show disconnect confirmation dialog for a device.
+    private void askDisconnect() {
+        Context context = getContext();
+        String name = mCachedDevice.getName();
+        if (TextUtils.isEmpty(name)) {
+            name = context.getString(R.string.bluetooth_device);
+        }
+        String message = context.getString(R.string.bluetooth_disconnect_blank, name);
+
+        DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
+                mCachedDevice.disconnect();
+            }
+        };
+
+        mDisconnectDialog = Utils.showDisconnectDialog(context,
+                mDisconnectDialog, disconnectListener, name, message);
+    }
+
+    private void pair() {
+        if (!mCachedDevice.startPairing()) {
+            Utils.showError(getContext(), mCachedDevice.getName(),
+                    R.string.bluetooth_pairing_error_message);
+        }
+    }
+
+    private int getConnectionSummary() {
+        final CachedBluetoothDevice cachedDevice = mCachedDevice;
+        final BluetoothDevice device = cachedDevice.getDevice();
+
+        // if any profiles are connected or busy, return that status
+        for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+            int connectionStatus = profile.getConnectionStatus(device);
+
+            if (connectionStatus != BluetoothProfile.STATE_DISCONNECTED) {
+                return Utils.getConnectionStateSummary(connectionStatus);
+            }
+        }
+
+        switch (cachedDevice.getBondState()) {
+            case BluetoothDevice.BOND_BONDED:
+                return R.string.bluetooth_paired;
+            case BluetoothDevice.BOND_BONDING:
+                return R.string.bluetooth_pairing;
+            case BluetoothDevice.BOND_NONE:
+                return R.string.bluetooth_not_connected;
+            default:
+                return 0;
+        }
     }
 
+    private int getBtClassDrawable() {
+        BluetoothClass btClass = mCachedDevice.getBtClass();
+        if (btClass != null) {
+            switch (btClass.getMajorDeviceClass()) {
+                case BluetoothClass.Device.Major.COMPUTER:
+                    return R.drawable.ic_bt_laptop;
+
+                case BluetoothClass.Device.Major.PHONE:
+                    return R.drawable.ic_bt_cellphone;
+
+                case BluetoothClass.Device.Major.PERIPHERAL:
+                    return HidProfile.getHidClassDrawable(btClass);
+
+                case BluetoothClass.Device.Major.IMAGING:
+                    return R.drawable.ic_bt_imaging;
+
+                default:
+                    // unrecognized device class; continue
+            }
+        } else {
+            Log.w(TAG, "mBtClass is null");
+        }
+
+        List<LocalBluetoothProfile> profiles = mCachedDevice.getProfiles();
+        for (LocalBluetoothProfile profile : profiles) {
+            int resId = profile.getDrawableResource(btClass);
+            if (resId != 0) {
+                return resId;
+            }
+        }
+        if (btClass != null) {
+            if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+                return R.drawable.ic_bt_headphones_a2dp;
+
+            }
+            if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+                return R.drawable.ic_bt_headset_hfp;
+            }
+        }
+        return 0;
+    }
 }
index 166e4ae..40bf5bc 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.R;
-
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.os.Handler;
 import android.os.SystemProperties;
+import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
-import android.preference.CheckBoxPreference;
-import android.provider.Settings;
-import android.util.Log;
+
+import com.android.settings.R;
 
 /**
  * BluetoothDiscoverableEnabler is a helper to manage the "Discoverable"
  * checkbox. It sets/unsets discoverability and keeps track of how much time
  * until the the discoverability is automatically turned off.
  */
-public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener {
-    private static final String TAG = "BluetoothDiscoverableEnabler";
+final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener {
 
     private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
             "debug.bt.discoverable_time";
 
-    static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120;
-    static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300;
-    static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600;
+    private static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120;
+    private static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300;
+    private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600;
     static final int DISCOVERABLE_TIMEOUT_NEVER = 0;
 
-    static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP =
-        "discoverable_end_timestamp";
-
     private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin";
     private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin";
     private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour";
@@ -63,7 +56,7 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
     private final CheckBoxPreference mCheckBoxPreference;
     private final ListPreference mTimeoutListPreference;
 
-    private final LocalBluetoothManager mLocalManager;
+    private final LocalBluetoothAdapter mLocalAdapter;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -84,7 +77,7 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
         }
     };
 
-    public BluetoothDiscoverableEnabler(Context context,
+    BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter,
             CheckBoxPreference checkBoxPreference, ListPreference timeoutListPreference) {
         mContext = context;
         mUiHandler = new Handler();
@@ -95,15 +88,15 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
         // we actually want to persist this since can't infer from BT device state
         mTimeoutListPreference.setPersistent(true);
 
-        mLocalManager = LocalBluetoothManager.getInstance(context);
-        if (mLocalManager == null) {
+        mLocalAdapter = adapter;
+        if (adapter == null) {
             // Bluetooth not supported
             checkBoxPreference.setEnabled(false);
         }
     }
 
     public void resume() {
-        if (mLocalManager == null) {
+        if (mLocalAdapter == null) {
             return;
         }
 
@@ -111,11 +104,11 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
         mContext.registerReceiver(mReceiver, filter);
         mCheckBoxPreference.setOnPreferenceChangeListener(this);
         mTimeoutListPreference.setOnPreferenceChangeListener(this);
-        handleModeChanged(mLocalManager.getBluetoothAdapter().getScanMode());
+        handleModeChanged(mLocalAdapter.getScanMode());
     }
 
     public void pause() {
-        if (mLocalManager == null) {
+        if (mLocalAdapter == null) {
             return;
         }
 
@@ -137,43 +130,37 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
         return true;
     }
 
-    private void setEnabled(final boolean enable) {
-        BluetoothAdapter manager = mLocalManager.getBluetoothAdapter();
-
+    private void setEnabled(boolean enable) {
         if (enable) {
             int timeout = getDiscoverableTimeout();
-            manager.setDiscoverableTimeout(timeout);
+            mLocalAdapter.setDiscoverableTimeout(timeout);
 
             long endTimestamp = System.currentTimeMillis() + timeout * 1000L;
-            persistDiscoverableEndTimestamp(endTimestamp);
+            LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp);
 
             updateCountdownSummary();
 
-            manager.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
+            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
         } else {
-            manager.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
         }
     }
 
     private void updateTimerDisplay(int timeout) {
         if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) {
             mCheckBoxPreference.setSummaryOn(
-                mContext.getResources()
-                .getString(R.string.bluetooth_is_discoverable_always));
+                mContext.getString(R.string.bluetooth_is_discoverable_always));
         } else {
             mCheckBoxPreference.setSummaryOn(
-                mContext.getResources()
-                .getString(R.string.bluetooth_is_discoverable, timeout));
+                mContext.getString(R.string.bluetooth_is_discoverable, timeout));
         }
     }
 
     private int getDiscoverableTimeout() {
         int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1);
         if (timeout < 0) {
-            String timeoutValue = null;
-            if (mTimeoutListPreference != null && mTimeoutListPreference.getValue() != null) {
-                timeoutValue = mTimeoutListPreference.getValue().toString();
-            } else {
+            String timeoutValue = mTimeoutListPreference.getValue();
+            if (timeoutValue == null) {
                 mTimeoutListPreference.setValue(VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES);
                 return DISCOVERABLE_TIMEOUT_TWO_MINUTES;
             }
@@ -192,12 +179,6 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
         return timeout;
     }
 
-    private void persistDiscoverableEndTimestamp(long endTimestamp) {
-        SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit();
-        editor.putLong(SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp);
-        editor.apply();
-    }
-
     private void handleModeChanged(int mode) {
         if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
             mCheckBoxPreference.setChecked(true);
@@ -208,14 +189,13 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
     }
 
     private void updateCountdownSummary() {
-        int mode = mLocalManager.getBluetoothAdapter().getScanMode();
+        int mode = mLocalAdapter.getScanMode();
         if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
             return;
         }
 
         long currentTimestamp = System.currentTimeMillis();
-        long endTimestamp = mLocalManager.getSharedPreferences().getLong(
-                SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
+        long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext);
 
         if (currentTimestamp > endTimestamp) {
             // We're still in discoverable mode, but maybe there isn't a timeout.
@@ -231,6 +211,4 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan
             mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000);
         }
     }
-
-
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java b/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java
new file mode 100644 (file)
index 0000000..fbb6827
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * BluetoothDiscoveryReceiver updates a timestamp when the
+ * Bluetooth adapter starts or finishes discovery mode. This
+ * is used to decide whether to open an alert dialog or
+ * create a notification when we receive a pairing request.
+ *
+ * <p>Note that the discovery start/finish intents are also handled
+ * by {@link BluetoothEventManager} to update the UI, if visible.
+ */
+public final class BluetoothDiscoveryReceiver extends BroadcastReceiver {
+    private static final String TAG = "BluetoothDiscoveryReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        Log.v(TAG, "Received: " + action);
+
+        if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) ||
+                action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
+            LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
+        }
+    }
+}
index a7b9d71..9aeb1b9 100644 (file)
@@ -34,13 +34,14 @@ import android.widget.Toast;
  * preference. It turns on/off Bluetooth and ensures the summary of the
  * preference reflects the current state.
  */
-public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
+public final class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
     private final Context mContext;
     private final CheckBoxPreference mCheckBox;
     private final CharSequence mOriginalSummary;
 
-    private final LocalBluetoothManager mLocalManager;
+    private final LocalBluetoothAdapter mLocalAdapter;
     private final IntentFilter mIntentFilter;
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -49,14 +50,15 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
         }
     };
 
-    public BluetoothEnabler(Context context, CheckBoxPreference checkBox) {
+    public BluetoothEnabler(Context context, LocalBluetoothAdapter adapter,
+            CheckBoxPreference checkBox) {
         mContext = context;
         mCheckBox = checkBox;
         mOriginalSummary = checkBox.getSummary();
         checkBox.setPersistent(false);
 
-        mLocalManager = LocalBluetoothManager.getInstance(context);
-        if (mLocalManager == null) {
+        mLocalAdapter = adapter;
+        if (adapter == null) {
             // Bluetooth is not supported
             checkBox.setEnabled(false);
         }
@@ -64,19 +66,19 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
     }
 
     public void resume() {
-        if (mLocalManager == null) {
+        if (mLocalAdapter == null) {
             return;
         }
 
         // Bluetooth state is not sticky, so set it manually
-        handleStateChanged(mLocalManager.getBluetoothState());
+        handleStateChanged(mLocalAdapter.getBluetoothState());
 
         mContext.registerReceiver(mReceiver, mIntentFilter);
         mCheckBox.setOnPreferenceChangeListener(this);
     }
 
     public void pause() {
-        if (mLocalManager == null) {
+        if (mLocalAdapter == null) {
             return;
         }
 
@@ -95,14 +97,14 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
             return false;
         }
 
-        mLocalManager.setBluetoothEnabled(enable);
+        mLocalAdapter.setBluetoothEnabled(enable);
         mCheckBox.setEnabled(false);
 
         // Don't update UI to opposite state until we're sure
         return false;
     }
 
-    /* package */ void handleStateChanged(int state) {
+    void handleStateChanged(int state) {
         switch (state) {
             case BluetoothAdapter.STATE_TURNING_ON:
                 mCheckBox.setSummary(R.string.wifi_starting);
diff --git a/src/com/android/settings/bluetooth/BluetoothEventManager.java b/src/com/android/settings/bluetooth/BluetoothEventManager.java
new file mode 100644 (file)
index 0000000..70e35f9
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
+ * API and dispatches the event on the UI thread to the right class in the
+ * Settings.
+ */
+final class BluetoothEventManager {
+    private static final String TAG = "BluetoothEventManager";
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private LocalBluetoothProfileManager mProfileManager;
+    private final IntentFilter mIntentFilter;
+    private final Map<String, Handler> mHandlerMap;
+
+    private final Collection<BluetoothCallback> mCallbacks =
+            new ArrayList<BluetoothCallback>();
+
+    interface Handler {
+        void onReceive(Context context, Intent intent, BluetoothDevice device);
+    }
+
+    void addHandler(String action, Handler handler) {
+        mHandlerMap.put(action, handler);
+        mIntentFilter.addAction(action);
+    }
+
+    // Set profile manager after construction due to circular dependency
+    void setProfileManager(LocalBluetoothProfileManager manager) {
+        mProfileManager = manager;
+    }
+
+    BluetoothEventManager(LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mIntentFilter = new IntentFilter();
+        mHandlerMap = new HashMap<String, Handler>();
+
+        // Bluetooth on/off broadcasts
+        addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
+
+        // Discovery broadcasts
+        addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
+        addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
+        addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
+        addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
+        addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
+
+        // Pairing broadcasts
+        addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
+        addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
+
+        // Fine-grained state broadcasts
+        addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
+        addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
+
+        // Dock event broadcasts
+        addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
+    }
+
+    /**
+     * A Bluetooth-related activity is now in the foreground. Register to
+     * start receiving Bluetooth events.
+     * @param context a Context object for the current Activity
+     */
+    void resume(Context context) {
+        if (mLocalAdapter.syncBluetoothState()) {
+            // adapter state changed while we were paused: send callbacks
+            int newState = mLocalAdapter.getState();
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onBluetoothStateChanged(newState);
+                }
+            }
+        }
+        context.registerReceiver(mBroadcastReceiver, mIntentFilter);
+    }
+
+    void pause(Context context) {
+        context.unregisterReceiver(mBroadcastReceiver);
+    }
+
+    /** Register to start receiving callbacks for Bluetooth events. */
+    void registerCallback(BluetoothCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    /** Unregister to stop receiving callbacks for Bluetooth events. */
+    void unregisterCallback(BluetoothCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    // This can't be called from a broadcast receiver where the filter is set in the Manifest.
+    private static String getDockedDeviceAddress(Context context) {
+        // This works only because these broadcast intents are "sticky"
+        Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+        if (i != null) {
+            int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+            if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                if (device != null) {
+                    return device.getAddress();
+                }
+            }
+        }
+        return null;
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.v(TAG, "Received " + intent.getAction());
+
+            String action = intent.getAction();
+            BluetoothDevice device = intent
+                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            Handler handler = mHandlerMap.get(action);
+            if (handler != null) {
+                handler.onReceive(context, intent, device);
+            }
+        }
+    };
+
+    private class AdapterStateChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                                    BluetoothAdapter.ERROR);
+            // update local profiles and get paired devices
+            mLocalAdapter.setBluetoothStateInt(state);
+            // send callback to update UI and possibly start scanning
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onBluetoothStateChanged(state);
+                }
+            }
+        }
+    }
+
+    private class ScanningStateChangedHandler implements Handler {
+        private final boolean mStarted;
+
+        ScanningStateChangedHandler(boolean started) {
+            mStarted = started;
+        }
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onScanningStateChanged(mStarted);
+                }
+            }
+            mDeviceManager.onScanningStateChanged(mStarted);
+            LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
+        }
+    }
+
+    private class DeviceFoundHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
+            BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
+            String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
+            // TODO Pick up UUID. They should be available for 2.1 devices.
+            // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
+                        + cachedDevice);
+                // callback to UI to create Preference for new device
+                dispatchDeviceAdded(cachedDevice);
+            }
+            cachedDevice.setRssi(rssi);
+            cachedDevice.setBtClass(btClass);
+            cachedDevice.setName(name);
+            cachedDevice.setVisible(true);
+        }
+    }
+
+    private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+        synchronized (mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onDeviceAdded(cachedDevice);
+            }
+        }
+    }
+
+    private class DeviceDisappearedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
+                return;
+            }
+            if (mDeviceManager.onDeviceDisappeared(cachedDevice)) {
+                synchronized (mCallbacks) {
+                    for (BluetoothCallback callback : mCallbacks) {
+                        callback.onDeviceDeleted(cachedDevice);
+                    }
+                }
+            }
+        }
+    }
+
+    private class NameChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            mDeviceManager.onDeviceNameUpdated(device);
+        }
+    }
+
+    private class BondStateChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            if (device == null) {
+                Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+                return;
+            }
+            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                                               BluetoothDevice.ERROR);
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "CachedBluetoothDevice for device " + device +
+                        " not found, calling readPairedDevices().");
+                if (!readPairedDevices()) {
+                    Log.e(TAG, "Got bonding state changed for " + device +
+                            ", but we have no record of that device.");
+                    return;
+                }
+                cachedDevice = mDeviceManager.findDevice(device);
+                if (cachedDevice == null) {
+                    Log.e(TAG, "Got bonding state changed for " + device +
+                            ", but device not added in cache.");
+                    return;
+                }
+            }
+
+            synchronized (mCallbacks) {
+                for (BluetoothCallback callback : mCallbacks) {
+                    callback.onDeviceBondStateChanged(cachedDevice, bondState);
+                }
+            }
+            cachedDevice.onBondingStateChanged(bondState);
+
+            if (bondState == BluetoothDevice.BOND_NONE) {
+                if (device.isBluetoothDock()) {
+                    // After a dock is unpaired, we will forget the settings
+                    LocalBluetoothPreferences
+                            .removeDockAutoConnectSetting(context, device.getAddress());
+
+                    // if the device is undocked, remove it from the list as well
+                    if (!device.getAddress().equals(getDockedDeviceAddress(context))) {
+                        mDeviceManager.onDeviceDisappeared(cachedDevice);
+                    }
+                }
+                int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
+                        BluetoothDevice.ERROR);
+
+                showUnbondMessage(context, cachedDevice.getName(), reason);
+            }
+        }
+
+        /**
+         * Called when we have reached the unbonded state.
+         *
+         * @param reason one of the error reasons from
+         *            BluetoothDevice.UNBOND_REASON_*
+         */
+        private void showUnbondMessage(Context context, String name, int reason) {
+            int errorMsg;
+
+            switch(reason) {
+            case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
+                errorMsg = R.string.bluetooth_pairing_pin_error_message;
+                break;
+            case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
+                errorMsg = R.string.bluetooth_pairing_rejected_error_message;
+                break;
+            case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
+                errorMsg = R.string.bluetooth_pairing_device_down_error_message;
+                break;
+            case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
+            case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
+            case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
+            case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
+                errorMsg = R.string.bluetooth_pairing_error_message;
+                break;
+            default:
+                Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
+                return;
+            }
+            Utils.showError(context, name, errorMsg);
+        }
+    }
+
+    private class ClassChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            mDeviceManager.onBtClassChanged(device);
+        }
+    }
+
+    private class UuidChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            mDeviceManager.onUuidChanged(device);
+        }
+    }
+
+    private class PairingCancelHandler implements Handler {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            if (device == null) {
+                Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
+                return;
+            }
+            int errorMsg = R.string.bluetooth_pairing_error_message;
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            Utils.showError(context, cachedDevice.getName(), errorMsg);
+        }
+    }
+
+    private class DockEventHandler implements Handler {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            // Remove if unpair device upon undocking
+            int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
+            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
+            if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
+                    CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+                    if (cachedDevice != null) {
+                        mDeviceManager.onDeviceDisappeared(cachedDevice);
+                    }
+                }
+            }
+        }
+    }
+
+    boolean readPairedDevices() {
+        Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
+        if (bondedDevices == null) {
+            return false;
+        }
+
+        boolean deviceAdded = false;
+        for (BluetoothDevice device : bondedDevices) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+                dispatchDeviceAdded(cachedDevice);
+                deviceAdded = true;
+            }
+        }
+
+        return deviceAdded;
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
deleted file mode 100644 (file)
index 3174750..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.bluetooth;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth
- * API and dispatches the event on the UI thread to the right class in the
- * Settings.
- */
-class BluetoothEventRedirector {
-    private static final String TAG = "BluetoothEventRedirector";
-
-    /* package */ final LocalBluetoothManager mManager;
-
-    private final ThreadPoolExecutor mSerialExecutor = new ThreadPoolExecutor(
-        0, 1, 1000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
-
-    private final Handler mHandler = new Handler();
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Log.v(TAG, "Received " + intent.getAction());
-
-            String action = intent.getAction();
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
-                                        BluetoothAdapter.ERROR);
-                mManager.setBluetoothStateInt(state);
-            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
-                PendingResult pr = goAsync();  // so loading shared prefs doesn't kill animation
-                persistDiscoveringTimestamp(pr, true);
-            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
-                PendingResult pr = goAsync();  // so loading shared prefs doesn't kill animation
-                persistDiscoveringTimestamp(pr, false);
-            } else if (action.equals(BluetoothDevice.ACTION_FOUND)) {
-                short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
-                BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
-                String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
-                // TODO Pick up UUID. They should be available for 2.1 devices.
-                // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
-                mManager.getCachedDeviceManager().onDeviceAppeared(device, rssi, btClass, name);
-
-            } else if (action.equals(BluetoothDevice.ACTION_DISAPPEARED)) {
-                mManager.getCachedDeviceManager().onDeviceDisappeared(device);
-
-            } else if (action.equals(BluetoothDevice.ACTION_NAME_CHANGED)) {
-                mManager.getCachedDeviceManager().onDeviceNameUpdated(device);
-
-            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
-                int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
-                                                   BluetoothDevice.ERROR);
-                CachedBluetoothDeviceManager cachedDeviceMgr = mManager.getCachedDeviceManager();
-                cachedDeviceMgr.onBondingStateChanged(device, bondState);
-                if (bondState == BluetoothDevice.BOND_NONE) {
-                    if (device.isBluetoothDock()) {
-                        // After a dock is unpaired, we will forget the
-                        // settings
-                        mManager.removeDockAutoConnectSetting(device.getAddress());
-
-                        // if the device is undocked, remove it from the list as
-                        // well
-                        if (!device.getAddress().equals(getDockedDeviceAddress(context))) {
-                            cachedDeviceMgr.onDeviceDisappeared(device);
-                        }
-                    }
-                    int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
-                            BluetoothDevice.ERROR);
-                    cachedDeviceMgr.showUnbondMessage(device, reason);
-                }
-
-            } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
-                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
-                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
-                if (newState == BluetoothProfile.STATE_DISCONNECTED &&
-                        oldState == BluetoothProfile.STATE_CONNECTING) {
-                    Log.i(TAG, "Failed to connect BT headset");
-                }
-
-                mManager.getCachedDeviceManager().onProfileStateChanged(device,
-                    Profile.HEADSET, newState);
-            } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
-                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
-                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
-                if (newState == BluetoothProfile.STATE_DISCONNECTED &&
-                        oldState == BluetoothProfile.STATE_CONNECTING) {
-                    Log.i(TAG, "Failed to connect BT A2DP");
-                }
-
-                mManager.getCachedDeviceManager().onProfileStateChanged(device,
-                        Profile.A2DP, newState);
-            } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
-                final int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
-                final int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
-                if (newState == BluetoothProfile.STATE_DISCONNECTED &&
-                        oldState == BluetoothProfile.STATE_CONNECTING) {
-                    Log.i(TAG, "Failed to connect BT HID");
-                }
-
-                mManager.getCachedDeviceManager().onProfileStateChanged(device,
-                        Profile.HID, newState);
-
-            } else if (action.equals(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED)) {
-                final int role = intent.getIntExtra(
-                        BluetoothPan.EXTRA_LOCAL_ROLE, 0);
-                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
-                    final int newState = intent.getIntExtra(
-                            BluetoothPan.EXTRA_STATE, 0);
-                    final int oldState = intent.getIntExtra(
-                            BluetoothPan.EXTRA_PREVIOUS_STATE, 0);
-                    if (newState == BluetoothPan.STATE_DISCONNECTED &&
-                            oldState == BluetoothPan.STATE_CONNECTING) {
-                        Log.i(TAG, "Failed to connect BT PAN");
-                    }
-                    mManager.getCachedDeviceManager().onProfileStateChanged(device,
-                            Profile.PAN, newState);
-                }
-            } else if (action.equals(BluetoothDevice.ACTION_CLASS_CHANGED)) {
-                mManager.getCachedDeviceManager().onBtClassChanged(device);
-
-            } else if (action.equals(BluetoothDevice.ACTION_UUID)) {
-                mManager.getCachedDeviceManager().onUuidChanged(device);
-
-            } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) {
-                int errorMsg = R.string.bluetooth_pairing_error_message;
-                mManager.showError(device, errorMsg);
-
-            } else if (action.equals(Intent.ACTION_DOCK_EVENT)) {
-                // Remove if unpair device upon undocking
-                int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
-                int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
-                if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                    if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
-                        mManager.getCachedDeviceManager().onDeviceDisappeared(device);
-                    }
-                }
-            }
-        }
-    };
-
-    public BluetoothEventRedirector(LocalBluetoothManager localBluetoothManager) {
-        mManager = localBluetoothManager;
-    }
-
-    public void registerReceiver() {
-        IntentFilter filter = new IntentFilter();
-
-        // Bluetooth on/off broadcasts
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-
-        // Discovery broadcasts
-        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
-        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-        filter.addAction(BluetoothDevice.ACTION_DISAPPEARED);
-        filter.addAction(BluetoothDevice.ACTION_FOUND);
-        filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
-
-        // Pairing broadcasts
-        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL);
-
-        // Fine-grained state broadcasts
-        filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_UUID);
-
-        // Dock event broadcasts
-        filter.addAction(Intent.ACTION_DOCK_EVENT);
-
-        mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
-    }
-
-    public void stop() {
-        mManager.getContext().unregisterReceiver(mBroadcastReceiver);
-    }
-
-    // This can't be called from a broadcast receiver where the filter is set in the Manifest.
-    /* package */ String getDockedDeviceAddress(Context context) {
-        // This works only because these broadcast intents are "sticky"
-        Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
-        if (i != null) {
-            int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
-            if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if (device != null) {
-                    return device.getAddress();
-                }
-            }
-        }
-        return null;
-    }
-
-    /* package */ void persistDiscoveringTimestamp(
-        final BroadcastReceiver.PendingResult pr, final boolean newState) {
-        // Load the shared preferences and edit it on a background
-        // thread (but serialized!), but then post back to the main
-        // thread to run the onScanningStateChanged callbacks which
-        // update the UI...
-        mSerialExecutor.submit(new Runnable() {
-                public void run() {
-                    SharedPreferences.Editor editor = mManager.getSharedPreferences().edit();
-                    editor.putLong(
-                        LocalBluetoothManager.SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP,
-                        System.currentTimeMillis());
-                    editor.apply();
-                    mHandler.post(new Runnable() {
-                            public void run() {
-                                mManager.onScanningStateChanged(newState);
-                                pr.finish();
-                            }
-                        });
-                }
-            });
-    }
-}
index f1b876e..066f4f6 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.util.Log;
 
 import com.android.settings.R;
 
 /**
  * Fragment to scan and show the discoverable devices.
  */
-public class BluetoothFindNearby extends DeviceListPreferenceFragment {
+public final class BluetoothFindNearby extends DeviceListPreferenceFragment {
 
-    private static final String TAG = "BluetoothFindNearby";
-
-    void addPreferencesForActivity(Activity activity) {
+    @Override
+    void addPreferencesForActivity() {
         addPreferencesFromResource(R.xml.device_picker);
     }
 
@@ -38,35 +35,37 @@ public class BluetoothFindNearby extends DeviceListPreferenceFragment {
     public void onResume() {
         super.onResume();
         if (mSelectedDevice != null) {
-            CachedBluetoothDevice device =
-                    mLocalManager.getCachedDeviceManager().findDevice(mSelectedDevice);
-            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
+            CachedBluetoothDeviceManager manager = mLocalManager.getCachedDeviceManager();
+            CachedBluetoothDevice device = manager.findDevice(mSelectedDevice);
+            if (device != null && device.getBondState() == BluetoothDevice.BOND_BONDED) {
                 // selected device was paired, so return from this screen
                 finish();
                 return;
             }
         }
-        mLocalManager.startScanning(true);
+        mLocalAdapter.startScanning(true);
     }
 
+    @Override
     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
-        mLocalManager.stopScanning();
+        mLocalAdapter.stopScanning();
         super.onDevicePreferenceClick(btPreference);
     }
 
-    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice,
-            int bondState) {
+    public void onDeviceBondStateChanged(CachedBluetoothDevice
+            cachedDevice, int bondState) {
         if (bondState == BluetoothDevice.BOND_BONDED) {
             // return from scan screen after successful auto-pairing
             finish();
         }
     }
 
-    void onBluetoothStateChanged(int bluetoothState) {
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
         super.onBluetoothStateChanged(bluetoothState);
 
         if (bluetoothState == BluetoothAdapter.STATE_ON) {
-                mLocalManager.startScanning(false);
+                mLocalAdapter.startScanning(false);
         }
     }
 }
index c99ab4c..f41689e 100644 (file)
@@ -22,12 +22,12 @@ import android.app.Dialog;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.preference.EditTextPreference;
 import android.text.Editable;
 import android.text.InputFilter;
-import android.text.Spanned;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.widget.Button;
@@ -38,13 +38,13 @@ import android.widget.EditText;
  * Bluetooth name. It asks the user for a name, and persists it via the
  * Bluetooth API.
  */
-public class BluetoothNamePreference extends EditTextPreference implements TextWatcher {
-    private static final String TAG = "BluetoothNamePreference";
+public final class BluetoothNamePreference extends EditTextPreference implements TextWatcher {
+//    private static final String TAG = "BluetoothNamePreference";
     private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248;
 
-    private LocalBluetoothManager mLocalManager;
+    private final LocalBluetoothAdapter mLocalAdapter;
 
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -61,7 +61,7 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW
     public BluetoothNamePreference(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mLocalManager = LocalBluetoothManager.getInstance(context);
+        mLocalAdapter = LocalBluetoothManager.getInstance(context).getBluetoothAdapter();
 
         setSummaryToName();
     }
@@ -97,16 +97,17 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW
     }
 
     private void setSummaryToName() {
-        BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
-        if (adapter.isEnabled()) {
-            setSummary(adapter.getName());
+        if (mLocalAdapter != null && mLocalAdapter.isEnabled()) {
+            setSummary(mLocalAdapter.getName());
         }
     }
 
     @Override
     protected boolean persistString(String value) {
-        BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
-        adapter.setName(value);
+        // Persist with Bluez instead of shared preferences
+        if (mLocalAdapter != null) {
+            mLocalAdapter.setName(value);
+        }
         return true;
     }
 
@@ -116,8 +117,8 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW
 
         // The dialog should be created by now
         EditText et = getEditText();
-        if (et != null) {
-            et.setText(mLocalManager.getBluetoothAdapter().getName());
+        if (et != null && mLocalAdapter != null) {
+            et.setText(mLocalAdapter.getName());
         }
     }
 
@@ -125,7 +126,7 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW
     public void afterTextChanged(Editable s) {
         Dialog d = getDialog();
         if (d instanceof AlertDialog) {
-            ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0);
+            ((AlertDialog) d).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(s.length() > 0);
         }
     }
 
@@ -139,67 +140,4 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW
         // not used
     }
 
-    /**
-     * This filter will constrain edits so that the text length is not
-     * greater than the specified number of bytes using UTF-8 encoding.
-     * <p>The JNI method used by {@link android.server.BluetoothService}
-     * to convert UTF-16 to UTF-8 doesn't support surrogate pairs,
-     * therefore code points outside of the basic multilingual plane
-     * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters,
-     * rather than a single 4-byte UTF-8 encoding. Dalvik implements this
-     * conversion in {@code convertUtf16ToUtf8()} in
-     * {@code dalvik/vm/UtfString.c}.
-     * <p>This JNI method is unlikely to change in the future due to
-     * backwards compatibility requirements. It's also unclear whether
-     * the installed base of Bluetooth devices would correctly handle the
-     * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6.
-     * However, this filter will still work in scenarios where surrogate
-     * pairs are encoded as 4 bytes, with the caveat that the maximum
-     * length will be constrained more conservatively than necessary.
-     */
-    public static class Utf8ByteLengthFilter implements InputFilter {
-        private int mMaxBytes;
-
-        public Utf8ByteLengthFilter(int maxBytes) {
-            mMaxBytes = maxBytes;
-        }
-
-        public CharSequence filter(CharSequence source, int start, int end,
-                                   Spanned dest, int dstart, int dend) {
-            int srcByteCount = 0;
-            // count UTF-8 bytes in source substring
-            for (int i = start; i < end; i++) {
-                char c = source.charAt(i);
-                srcByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3);
-            }
-            int destLen = dest.length();
-            int destByteCount = 0;
-            // count UTF-8 bytes in destination excluding replaced section
-            for (int i = 0; i < destLen; i++) {
-                if (i < dstart || i >= dend) {
-                    char c = dest.charAt(i);
-                    destByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3);
-                }
-            }
-            int keepBytes = mMaxBytes - destByteCount;
-            if (keepBytes <= 0) {
-                return "";
-            } else if (keepBytes >= srcByteCount) {
-                return null; // use original dest string
-            } else {
-                // find end position of largest sequence that fits in keepBytes
-                for (int i = start; i < end; i++) {
-                    char c = source.charAt(i);
-                    keepBytes -= (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3);
-                    if (keepBytes < 0) {
-                        return source.subSequence(start, i);
-                    }
-                }
-                // If the entire substring fits, we should have returned null
-                // above, so this line should not be reached. If for some
-                // reason it is, return null to use the original dest string.
-                return null;
-            }
-        }
-    }
 }
index 1822e73..1b443c4 100644 (file)
@@ -42,33 +42,37 @@ import com.android.settings.R;
  * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
  * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
  */
-public class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener,
+public final class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener,
         TextWatcher {
     private static final String TAG = "BluetoothPairingDialog";
 
     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
-    private LocalBluetoothManager mLocalManager;
     private BluetoothDevice mDevice;
     private int mType;
     private String mPairingKey;
     private EditText mPairingView;
     private Button mOkButton;
 
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    /**
+     * Dismiss the dialog if the bond state changes to bonded or none,
+     * or if pairing was canceled for {@link #mDevice}.
+     */
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+            String action = intent.getAction();
+            if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                                                    BluetoothDevice.ERROR);
                 if (bondState == BluetoothDevice.BOND_BONDED ||
                         bondState == BluetoothDevice.BOND_NONE) {
-                    dismissDialog();
+                    dismiss();
                 }
-            } else if(BluetoothDevice.ACTION_PAIRING_CANCEL.equals(intent.getAction())) {
+            } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 if (device == null || device.equals(mDevice)) {
-                    dismissDialog();
+                    dismiss();
                 }
             }
         }
@@ -81,48 +85,63 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter
         Intent intent = getIntent();
         if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST))
         {
-            Log.e(TAG,
-                  "Error: this activity may be started only with intent " +
+            Log.e(TAG, "Error: this activity may be started only with intent " +
                   BluetoothDevice.ACTION_PAIRING_REQUEST);
             finish();
+            return;
         }
 
-        mLocalManager = LocalBluetoothManager.getInstance(this);
+        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
+        if (manager == null) {
+            Log.e(TAG, "Error: BluetoothAdapter not supported by system");
+            finish();
+            return;
+        }
+        CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager();
+
         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
-        if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) {
-            createUserEntryDialog();
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY) {
-            createUserEntryDialog();
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION){
-            int passkey =
-                intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
-            if (passkey == BluetoothDevice.ERROR) {
-                Log.e(TAG, "Invalid ConfirmationPasskey received, not showing any dialog");
-                return;
-            }
-            mPairingKey = String.format("%06d", passkey);
-            createConfirmationDialog();
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) {
-            createConsentDialog();
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY ||
-                   mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
-            int pairingKey =
-                intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
-            if (pairingKey == BluetoothDevice.ERROR) {
-                Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
-                return;
-            }
-            if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
-                mPairingKey = String.format("%06d", pairingKey);
-            } else {
-                mPairingKey = String.format("%04d", pairingKey);
-            }
-            createDisplayPasskeyOrPinDialog();
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) {
-            createConsentDialog();
-        } else {
-            Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
+
+        switch (mType) {
+            case BluetoothDevice.PAIRING_VARIANT_PIN:
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+                createUserEntryDialog(deviceManager);
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+                int passkey =
+                    intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
+                if (passkey == BluetoothDevice.ERROR) {
+                    Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
+                    return;
+                }
+                mPairingKey = String.format("%06d", passkey);
+                createConfirmationDialog(deviceManager);
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_CONSENT:
+            case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+                createConsentDialog(deviceManager);
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+                int pairingKey =
+                    intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
+                if (pairingKey == BluetoothDevice.ERROR) {
+                    Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog");
+                    return;
+                }
+                if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
+                    mPairingKey = String.format("%06d", pairingKey);
+                } else {
+                    mPairingKey = String.format("%04d", pairingKey);
+                }
+                createDisplayPasskeyOrPinDialog(deviceManager);
+                break;
+
+            default:
+                Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
         }
 
         /*
@@ -133,67 +152,79 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter
         registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
     }
 
-    private void createUserEntryDialog() {
+    private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) {
         final AlertController.AlertParams p = mAlertParams;
         p.mIconId = android.R.drawable.ic_dialog_info;
         p.mTitle = getString(R.string.bluetooth_pairing_request);
-        p.mView = createView();
+        p.mView = createView(deviceManager);
         p.mPositiveButtonText = getString(android.R.string.ok);
         p.mPositiveButtonListener = this;
         p.mNegativeButtonText = getString(android.R.string.cancel);
         p.mNegativeButtonListener = this;
         setupAlert();
 
-        mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+        mOkButton = mAlert.getButton(BUTTON_POSITIVE);
         mOkButton.setEnabled(false);
     }
 
-    private View createView() {
+    private View createView(CachedBluetoothDeviceManager deviceManager) {
         View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
-
-        String name = mLocalManager.getCachedDeviceManager().getName(mDevice);
+        String name = deviceManager.getName(mDevice);
         TextView messageView = (TextView) view.findViewById(R.id.message);
         mPairingView = (EditText) view.findViewById(R.id.text);
         mPairingView.addTextChangedListener(this);
 
-        if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) {
-            messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name));
-            // Maximum of 16 characters in a PIN adb sync
-            mPairingView.setFilters(new InputFilter[] {
-                    new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) });
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY){
-            messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name));
-            // Maximum of 6 digits for passkey
-            mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER |
-                    InputType.TYPE_NUMBER_FLAG_SIGNED);
-            mPairingView.setFilters(new InputFilter[] {
-                    new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)});
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
-            mPairingView.setVisibility(View.GONE);
-            messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name,
-                    mPairingKey));
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) {
-            mPairingView.setVisibility(View.GONE);
-            messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name));
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY ||
-                    mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
-            mPairingView.setVisibility(View.GONE);
-            messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name,
-                    mPairingKey));
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) {
-            mPairingView.setVisibility(View.GONE);
-            messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name));
-        } else {
-            Log.e(TAG, "Incorrect pairing type received, not creating view");
+        switch (mType) {
+            case BluetoothDevice.PAIRING_VARIANT_PIN:
+                messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name));
+                // Maximum of 16 characters in a PIN adb sync
+                mPairingView.setFilters(new InputFilter[] {
+                        new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) });
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+                messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name));
+                // Maximum of 6 digits for passkey
+                mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER |
+                        InputType.TYPE_NUMBER_FLAG_SIGNED);
+                mPairingView.setFilters(new InputFilter[] {
+                        new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)});
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+                mPairingView.setVisibility(View.GONE);
+                messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name,
+                        mPairingKey));
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_CONSENT:
+                mPairingView.setVisibility(View.GONE);
+                messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name));
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+                mPairingView.setVisibility(View.GONE);
+                messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name,
+                        mPairingKey));
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+                mPairingView.setVisibility(View.GONE);
+                messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name));
+                break;
+
+            default:
+                Log.e(TAG, "Incorrect pairing type received, not creating view");
         }
         return view;
     }
 
-    private void createConfirmationDialog() {
+    private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) {
         final AlertController.AlertParams p = mAlertParams;
         p.mIconId = android.R.drawable.ic_dialog_info;
         p.mTitle = getString(R.string.bluetooth_pairing_request);
-        p.mView = createView();
+        p.mView = createView(deviceManager);
         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
         p.mPositiveButtonListener = this;
         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
@@ -201,11 +232,11 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter
         setupAlert();
     }
 
-    private void createConsentDialog() {
+    private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) {
         final AlertController.AlertParams p = mAlertParams;
         p.mIconId = android.R.drawable.ic_dialog_info;
         p.mTitle = getString(R.string.bluetooth_pairing_request);
-        p.mView = createView();
+        p.mView = createView(deviceManager);
         p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
         p.mPositiveButtonListener = this;
         p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
@@ -213,11 +244,12 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter
         setupAlert();
     }
 
-    private void createDisplayPasskeyOrPinDialog() {
+    private void createDisplayPasskeyOrPinDialog(
+            CachedBluetoothDeviceManager deviceManager) {
         final AlertController.AlertParams p = mAlertParams;
         p.mIconId = android.R.drawable.ic_dialog_info;
         p.mTitle = getString(R.string.bluetooth_pairing_request);
-        p.mView = createView();
+        p.mView = createView(deviceManager);
         p.mNegativeButtonText = getString(android.R.string.cancel);
         p.mNegativeButtonListener = this;
         setupAlert();
@@ -244,32 +276,37 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter
         }
     }
 
-    private void dismissDialog() {
-        this.dismiss();
-    }
-
     private void onPair(String value) {
-        if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) {
-            byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
-            if (pinBytes == null) {
-                return;
-            }
-            mDevice.setPin(pinBytes);
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY) {
-            int passkey = Integer.parseInt(value);
-            mDevice.setPasskey(passkey);
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
-            mDevice.setPairingConfirmation(true);
-        } else if (mType ==  BluetoothDevice.PAIRING_VARIANT_CONSENT) {
-            mDevice.setPairingConfirmation(true);
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
-            // Do Nothing.
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
-            // Do Nothing
-        } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) {
-            mDevice.setRemoteOutOfBandData();
-        } else {
-            Log.e(TAG, "Incorrect pairing type received");
+        switch (mType) {
+            case BluetoothDevice.PAIRING_VARIANT_PIN:
+                byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
+                if (pinBytes == null) {
+                    return;
+                }
+                mDevice.setPin(pinBytes);
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
+                int passkey = Integer.parseInt(value);
+                mDevice.setPasskey(passkey);
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
+            case BluetoothDevice.PAIRING_VARIANT_CONSENT:
+                mDevice.setPairingConfirmation(true);
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+            case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+                // Do nothing.
+                break;
+
+            case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+                mDevice.setRemoteOutOfBandData();
+                break;
+
+            default:
+                Log.e(TAG, "Incorrect pairing type received");
         }
     }
 
@@ -279,11 +316,12 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter
 
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
+            case BUTTON_POSITIVE:
                 onPair(mPairingView.getText().toString());
                 break;
 
-            case DialogInterface.BUTTON_NEGATIVE:
+            case BUTTON_NEGATIVE:
+            default:
                 onCancel();
                 break;
         }
index 6037c82..de96d71 100644 (file)
@@ -35,17 +35,15 @@ import android.os.PowerManager;
  * confirmation entry dialog. Otherwise it puts a Notification in the status bar, which can
  * be clicked to bring up the Pairing entry dialog.
  */
-public class BluetoothPairingRequest extends BroadcastReceiver {
+public final class BluetoothPairingRequest extends BroadcastReceiver {
 
-    public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+    private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
 
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
         if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
-
-            LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context);
-
+            // convert broadcast intent into activity intent (same action string)
             BluetoothDevice device =
                     intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
@@ -68,39 +66,35 @@ public class BluetoothPairingRequest extends BroadcastReceiver {
                     (PowerManager)context.getSystemService(Context.POWER_SERVICE);
             String deviceAddress = device != null ? device.getAddress() : null;
             if (powerManager.isScreenOn() &&
-                localManager.shouldShowDialogInForeground(deviceAddress)) {
+                    LocalBluetoothPreferences.shouldShowDialogInForeground(context, deviceAddress)) {
                 // Since the screen is on and the BT-related activity is in the foreground,
                 // just open the dialog
                 context.startActivity(pairingIntent);
-
             } else {
-
                 // Put up a notification that leads to the dialog
                 Resources res = context.getResources();
-                Notification notification = new Notification(
-                        android.R.drawable.stat_sys_data_bluetooth,
-                        res.getString(R.string.bluetooth_notif_ticker),
-                        System.currentTimeMillis());
+                Notification.Builder builder = new Notification.Builder(context)
+                        .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+                        .setTicker(res.getString(R.string.bluetooth_notif_ticker));
 
                 PendingIntent pending = PendingIntent.getActivity(context, 0,
                         pairingIntent, PendingIntent.FLAG_ONE_SHOT);
 
                 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
                 if (TextUtils.isEmpty(name)) {
-                    name = (device != null ? device.getName() :
-                            context.getString(android.R.string.unknownName));
+                    name = device != null ? device.getName() :
+                            context.getString(android.R.string.unknownName);
                 }
 
-                notification.setLatestEventInfo(context,
-                        res.getString(R.string.bluetooth_notif_title),
-                        res.getString(R.string.bluetooth_notif_message, name),
-                        pending);
-                notification.flags |= Notification.FLAG_AUTO_CANCEL;
-                notification.defaults |= Notification.DEFAULT_SOUND;
+                builder.setContentTitle(res.getString(R.string.bluetooth_notif_title))
+                        .setContentText(res.getString(R.string.bluetooth_notif_message, name))
+                        .setContentIntent(pending)
+                        .setAutoCancel(true)
+                        .setDefaults(Notification.DEFAULT_SOUND);
 
                 NotificationManager manager = (NotificationManager)
                         context.getSystemService(Context.NOTIFICATION_SERVICE);
-                manager.notify(NOTIFICATION_ID, notification);
+                manager.notify(NOTIFICATION_ID, builder.getNotification());
             }
 
         } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) {
index c74012a..e334867 100644 (file)
@@ -16,9 +16,6 @@
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
-import com.android.settings.R;
-
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.preference.Preference;
@@ -26,22 +23,24 @@ import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.ImageView;
 
+import com.android.settings.R;
+
 /**
  * BluetoothProfilePreference is the preference type used to display each profile for a
  * particular bluetooth device.
  */
-public class BluetoothProfilePreference extends Preference implements OnClickListener {
+final class BluetoothProfilePreference extends Preference implements OnClickListener {
 
-    private static final String TAG = "BluetoothProfilePreference";
+//    private static final String TAG = "BluetoothProfilePreference";
 
     private Drawable mProfileDrawable;
     private boolean mExpanded;
     private ImageView mProfileExpandView;
-    private final Profile mProfile;
+    private final LocalBluetoothProfile mProfile;
 
     private OnClickListener mOnExpandClickListener;
 
-    public BluetoothProfilePreference(Context context, Profile profile) {
+    BluetoothProfilePreference(Context context, LocalBluetoothProfile profile) {
         super(context);
 
         mProfile = profile;
@@ -75,14 +74,14 @@ public class BluetoothProfilePreference extends Preference implements OnClickLis
         btProfile.setImageDrawable(mProfileDrawable);
 
         mProfileExpandView = (ImageView) view.findViewById(R.id.profileExpand);
-        if (mProfile == Profile.PAN) {
-            mProfileExpandView.setVisibility(View.GONE);
-        } else {
+        if (mProfile.isAutoConnectable()) {
             mProfileExpandView.setOnClickListener(this);
             mProfileExpandView.setTag(mProfile);
             mProfileExpandView.setImageResource(mExpanded
                     ? com.android.internal.R.drawable.expander_open_holo_dark
                     : com.android.internal.R.drawable.expander_close_holo_dark);
+        } else {
+            mProfileExpandView.setVisibility(View.GONE);
         }
     }
 
index 07121e9..c6ba9af 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import android.app.Activity;
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
-import android.preference.PreferenceCategory;
+import android.preference.PreferenceActivity;
 import android.preference.PreferenceScreen;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
@@ -34,9 +32,7 @@ import com.android.settings.R;
  * BluetoothSettings is the Settings screen for Bluetooth configuration and
  * connection management.
  */
-public class BluetoothSettings extends DeviceListPreferenceFragment
-        implements LocalBluetoothManager.Callback, View.OnClickListener {
-
+public final class BluetoothSettings extends DeviceListPreferenceFragment {
     private static final String TAG = "BluetoothSettings";
 
     private static final String KEY_BT_CHECKBOX = "bt_checkbox";
@@ -53,13 +49,20 @@ public class BluetoothSettings extends DeviceListPreferenceFragment
     private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
             "android.btopp.intent.action.OPEN_RECEIVED_FILES";
 
-    void addPreferencesForActivity(Activity activity) {
+    /** Initialize the filter to show bonded devices only. */
+    public BluetoothSettings() {
+        super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
+    }
+
+    @Override
+    void addPreferencesForActivity() {
         addPreferencesFromResource(R.xml.bluetooth_settings);
 
-        mEnabler = new BluetoothEnabler(activity,
+        mEnabler = new BluetoothEnabler(getActivity(), mLocalAdapter,
                 (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
 
-        mDiscoverableEnabler = new BluetoothDiscoverableEnabler(activity,
+        mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
+                mLocalAdapter,
                 (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE),
                     (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT));
 
@@ -88,6 +91,27 @@ public class BluetoothSettings extends DeviceListPreferenceFragment
         mEnabler.pause();
     }
 
+    private final View.OnClickListener mListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            // User clicked on advanced options icon for a device in the list
+            if (v.getTag() instanceof CachedBluetoothDevice) {
+                CachedBluetoothDevice
+                        device = (CachedBluetoothDevice) v.getTag();
+
+                Preference pref = new Preference(getActivity());
+                pref.setTitle(device.getName());
+                pref.setFragment(DeviceProfilesSettings.class.getName());
+                pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE,
+                        device.getDevice());
+                ((PreferenceActivity) getActivity())
+                        .onPreferenceStartFragment(BluetoothSettings.this,
+                                pref);
+            } else {
+                Log.w(TAG, "onClick() called for other View: " + v);
+            }
+        }
+    };
+
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
             Preference preference) {
@@ -105,9 +129,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment
         if (bondState == BluetoothDevice.BOND_BONDED) {
             // add to "Paired devices" list after remote-initiated pairing
             if (mDevicePreferenceMap.get(cachedDevice) == null) {
-                if (addDevicePreference(cachedDevice)) {
-                    createDevicePreference(cachedDevice);
-                }
+                createDevicePreference(cachedDevice);
             }
         } else if (bondState == BluetoothDevice.BOND_NONE) {
             // remove unpaired device from paired devices list
@@ -116,21 +138,11 @@ public class BluetoothSettings extends DeviceListPreferenceFragment
     }
 
     /**
-     * Additional check to only add paired devices to list.
-     */
-    boolean addDevicePreference(CachedBluetoothDevice cachedDevice) {
-        if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
-            return super.addDevicePreference(cachedDevice);
-        } else {
-            return false;
-        }
-    }
-
-    /**
      * Add a listener, which enables the advanced settings icon.
      * @param preference the newly added preference
      */
+    @Override
     void initDevicePreference(BluetoothDevicePreference preference) {
-        preference.setOnSettingsClickListener(this);
+        preference.setOnSettingsClickListener(mListener);
     }
 }
index 11885ac..0bc816c 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.R;
-
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
-
-import android.app.AlertDialog;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
+import android.bluetooth.BluetoothProfile;
 import android.os.ParcelUuid;
 import android.os.SystemClock;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * CachedBluetoothDevice represents a remote Bluetooth device. It contains
@@ -47,27 +35,23 @@ import java.util.Map;
  * functionality that can be performed on the device (connect, pair, disconnect,
  * etc.).
  */
-class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
+final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
     private static final String TAG = "CachedBluetoothDevice";
-    private static final boolean D = LocalBluetoothManager.D;
-    private static final boolean V = LocalBluetoothManager.V;
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Utils.V;
 
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final LocalBluetoothProfileManager mProfileManager;
     private final BluetoothDevice mDevice;
     private String mName;
     private short mRssi;
     private BluetoothClass mBtClass;
-    private Context mContext;
 
-    private List<Profile> mProfiles = new ArrayList<Profile>();
+    private final List<LocalBluetoothProfile> mProfiles =
+            new ArrayList<LocalBluetoothProfile>();
 
     private boolean mVisible;
 
-    private final LocalBluetoothManager mLocalManager;
-
-    private AlertDialog mDialog = null;
-
-    private List<Callback> mCallbacks = new ArrayList<Callback>();
+    private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
 
     /**
      * When we connect to multiple profiles, we only want to display a single
@@ -95,186 +79,62 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
      * @param profile Profile to describe
      * @return Description of the device and profile
      */
-    private String describe(Profile profile) {
+    private String describe(LocalBluetoothProfile profile) {
         StringBuilder sb = new StringBuilder();
         sb.append("Address:").append(mDevice);
         if (profile != null) {
-            sb.append(" Profile:").append(profile.name());
+            sb.append(" Profile:").append(profile);
         }
 
         return sb.toString();
     }
 
-    public void onProfileStateChanged(Profile profile, int newProfileState) {
-        if (D) {
-            Log.d(TAG, "onProfileStateChanged: profile " + profile.toString() +
+    void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
+        if (Utils.D) {
+            Log.d(TAG, "onProfileStateChanged: profile " + profile +
                     " newProfileState " + newProfileState);
         }
 
-        final LocalBluetoothProfileManager pm =
-                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
-        if (pm == null) return;
-        int newState = pm.convertState(newProfileState);
-
-        if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED) {
+        if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
             if (!mProfiles.contains(profile)) {
                 mProfiles.add(profile);
             }
         }
     }
 
-    CachedBluetoothDevice(Context context, BluetoothDevice device) {
-        mLocalManager = LocalBluetoothManager.getInstance(context);
-        if (mLocalManager == null) {
-            throw new IllegalStateException(
-                    "Cannot use CachedBluetoothDevice without Bluetooth hardware");
-        }
-
+    CachedBluetoothDevice(LocalBluetoothAdapter adapter,
+            LocalBluetoothProfileManager profileManager,
+            BluetoothDevice device) {
+        mLocalAdapter = adapter;
+        mProfileManager = profileManager;
         mDevice = device;
-        mContext = context;
-
         fillData();
     }
 
-    public void onClicked() {
-        int bondState = getBondState();
-
-        if (isConnected()) {
-            askDisconnect();
-        } else if (bondState == BluetoothDevice.BOND_BONDED) {
-            connect(true);
-        } else if (bondState == BluetoothDevice.BOND_NONE) {
-            pair();
-        }
-    }
-
-    public void disconnect() {
-        for (Profile profile : mProfiles) {
+    void disconnect() {
+        for (LocalBluetoothProfile profile : mProfiles) {
             disconnect(profile);
         }
     }
 
-    public void disconnect(Profile profile) {
-        LocalBluetoothProfileManager profileManager =
-                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
-        if (profileManager.disconnect(mDevice)) {
-            if (D) {
+    void disconnect(LocalBluetoothProfile profile) {
+        if (profile.disconnect(mDevice)) {
+            if (Utils.D) {
                 Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
             }
         }
     }
 
-    public void askDisconnect() {
-        Context context = mLocalManager.getForegroundActivity();
-        if (context == null) {
-            // Cannot ask, since we need an activity context
-            disconnect();
+    void connect(boolean connectAllProfiles) {
+        if (!ensurePaired()) {
             return;
         }
 
-        Resources res = context.getResources();
-
-        String name = getName();
-        if (TextUtils.isEmpty(name)) {
-            name = res.getString(R.string.bluetooth_device);
-        }
-        String message = res.getString(R.string.bluetooth_disconnect_blank, name);
-
-        DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                disconnect();
-            }
-        };
-
-        showDisconnectDialog(context, disconnectListener, message);
-    }
-
-    public void askDisconnect(final Profile profile) {
-        Context context = mLocalManager.getForegroundActivity();
-        if (context == null) {
-            // Cannot ask, since we need an activity context
-            disconnect(profile);
-            return;
-        }
-
-        Resources res = context.getResources();
-
-        String name = getName();
-        if (TextUtils.isEmpty(name)) {
-            name = res.getString(R.string.bluetooth_device);
-        }
-        int disconnectMessage;
-        switch (profile) {
-            case A2DP:
-                disconnectMessage = R.string.bluetooth_disconnect_a2dp_profile;
-                break;
-            case HEADSET:
-                disconnectMessage = R.string.bluetooth_disconnect_headset_profile;
-                break;
-            case HID:
-                disconnectMessage = R.string.bluetooth_disconnect_hid_profile;
-                break;
-            case PAN:
-                disconnectMessage = R.string.bluetooth_disconnect_pan_profile;
-                break;
-            default:
-                Log.w(TAG, "askDisconnect: unexpected profile " + profile);
-                disconnectMessage = R.string.bluetooth_disconnect_blank;
-                break;
-        }
-        String message = res.getString(disconnectMessage, name);
-
-        DialogInterface.OnClickListener disconnectListener =
-                new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                disconnect(profile);
-            }
-        };
-
-        showDisconnectDialog(context, disconnectListener, message);
-    }
-
-    private void showDisconnectDialog(Context context,
-            DialogInterface.OnClickListener disconnectListener,
-            String message) {
-        if (mDialog == null) {
-            mDialog = new AlertDialog.Builder(context)
-                    .setPositiveButton(android.R.string.ok, disconnectListener)
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .create();
-        } else {
-            if (mDialog.isShowing()) {
-                mDialog.dismiss();
-            }
-            // use disconnectListener for the correct profile(s)
-            CharSequence okText = context.getText(android.R.string.ok);
-            mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
-                    okText, disconnectListener);
-        }
-        mDialog.setTitle(getName());
-        mDialog.setMessage(message);
-        mDialog.show();
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        if (mDialog != null) {
-            mDialog.dismiss();
-            mDialog = null;
-        }
-
-        super.finalize();
-    }
-
-    public void connect(boolean connectAllProfiles) {
-        if (!ensurePaired()) return;
-
         mConnectAttempted = SystemClock.elapsedRealtime();
-
         connectWithoutResettingTimer(connectAllProfiles);
     }
 
-    /*package*/ void onBondingDockConnect() {
+    void onBondingDockConnect() {
         // Attempt to connect if UUIDs are available. Otherwise,
         // we will connect when the ACTION_UUID intent arrives.
         connect(false);
@@ -282,7 +142,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
 
     private void connectWithoutResettingTimer(boolean connectAllProfiles) {
         // Try to initialize the profiles if they were not.
-        if (mProfiles.size() == 0) {
+        if (mProfiles.isEmpty()) {
             if (!updateProfiles()) {
                 // If UUIDs are not available yet, connect will be happen
                 // upon arrival of the ACTION_UUID intent.
@@ -295,98 +155,85 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         mIsConnectingErrorPossible = true;
 
         int preferredProfiles = 0;
-        for (Profile profile : mProfiles) {
+        for (LocalBluetoothProfile profile : mProfiles) {
             if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
-                LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                        .getProfileManager(mLocalManager, profile);
-                if (profileManager.isPreferred(mDevice)) {
+                if (profile.isPreferred(mDevice)) {
                     ++preferredProfiles;
-                    connectInt(this, profile);
+                    connectInt(profile);
                 }
             }
         }
         if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
 
         if (preferredProfiles == 0) {
-            connectAllAutoConnectableProfiles();
+            connectAutoConnectableProfiles();
         }
     }
 
-    private void connectAllAutoConnectableProfiles() {
-        if (!ensurePaired()) return;
-
+    private void connectAutoConnectableProfiles() {
+        if (!ensurePaired()) {
+            return;
+        }
         // Reset the only-show-one-error-dialog tracking variable
         mIsConnectingErrorPossible = true;
 
-        for (Profile profile : mProfiles) {
+        for (LocalBluetoothProfile profile : mProfiles) {
             if (profile.isAutoConnectable()) {
-                LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                        .getProfileManager(mLocalManager, profile);
-                profileManager.setPreferred(mDevice, true);
-                connectInt(this, profile);
+                profile.setPreferred(mDevice, true);
+                connectInt(profile);
             }
         }
     }
 
-    public void connect(Profile profile) {
+    /**
+     * Connect this device to the specified profile.
+     *
+     * @param profile the profile to use with the remote device
+     */
+    void connectProfile(LocalBluetoothProfile profile) {
         mConnectAttempted = SystemClock.elapsedRealtime();
         // Reset the only-show-one-error-dialog tracking variable
         mIsConnectingErrorPossible = true;
-        connectInt(this, profile);
+        connectInt(profile);
     }
 
-    private boolean connectInt(CachedBluetoothDevice cachedDevice, Profile profile) {
-        if (!cachedDevice.ensurePaired()) return false;
-
-        LocalBluetoothProfileManager profileManager =
-                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
-
-        if (profileManager.connect(cachedDevice.mDevice)) {
-            if (D) {
+    private void connectInt(LocalBluetoothProfile profile) {
+        if (!ensurePaired()) {
+            return;
+        }
+        if (profile.connect(mDevice)) {
+            if (Utils.D) {
                 Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
             }
-            return true;
+            return;
         }
-        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + cachedDevice.mName);
-
-        return false;
-    }
-
-    public void showConnectingError() {
-        if (!mIsConnectingErrorPossible) return;
-        mIsConnectingErrorPossible = false;
-
-        mLocalManager.showError(mDevice,
-                R.string.bluetooth_connecting_error_message);
+        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
     }
 
     private boolean ensurePaired() {
         if (getBondState() == BluetoothDevice.BOND_NONE) {
-            pair();
+            startPairing();
             return false;
         } else {
             return true;
         }
     }
 
-    public void pair() {
-        BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
-
+    boolean startPairing() {
         // Pairing is unreliable while scanning, so cancel discovery
-        if (adapter.isDiscovering()) {
-            adapter.cancelDiscovery();
+        if (mLocalAdapter.isDiscovering()) {
+            mLocalAdapter.cancelDiscovery();
         }
 
         if (!mDevice.createBond()) {
-            mLocalManager.showError(mDevice,
-                    R.string.bluetooth_pairing_error_message);
-            return;
+            return false;
         }
 
         mConnectAfterPairing = true;  // auto-connect after pairing
+        return true;
     }
 
-    public void unpair() {
+    void unpair() {
         disconnect();
 
         int state = getBondState();
@@ -396,14 +243,14 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         }
 
         if (state != BluetoothDevice.BOND_NONE) {
-            final BluetoothDevice dev = getDevice();
+            final BluetoothDevice dev = mDevice;
             if (dev != null) {
                 final boolean successful = dev.removeBond();
                 if (successful) {
-                    if (D) {
+                    if (Utils.D) {
                         Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
                     }
-                } else if (V) {
+                } else if (Utils.V) {
                     Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
                             describe(null));
                 }
@@ -411,6 +258,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         }
     }
 
+    // TODO: do any of these need to run async on a background thread?
     private void fillData() {
         fetchName();
         fetchBtClass();
@@ -421,15 +269,15 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         dispatchAttributesChanged();
     }
 
-    public BluetoothDevice getDevice() {
+    BluetoothDevice getDevice() {
         return mDevice;
     }
 
-    public String getName() {
+    String getName() {
         return mName;
     }
 
-    public void setName(String name) {
+    void setName(String name) {
         if (!mName.equals(name)) {
             if (TextUtils.isEmpty(name)) {
                 // TODO: use friendly name for unknown device (bug 1181856)
@@ -442,7 +290,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         }
     }
 
-    public void refreshName() {
+    void refreshName() {
         fetchName();
         dispatchAttributesChanged();
     }
@@ -452,15 +300,15 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
 
         if (TextUtils.isEmpty(mName)) {
             mName = mDevice.getAddress();
-            if (DEBUG) Log.d(TAG, "Default to address. Device has no name (yet) " + mName);
+            if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
         }
     }
 
-    public void refresh() {
+    void refresh() {
         dispatchAttributesChanged();
     }
 
-    public boolean isVisible() {
+    boolean isVisible() {
         return mVisible;
     }
 
@@ -471,7 +319,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         }
     }
 
-    public int getBondState() {
+    int getBondState() {
         return mDevice.getBondState();
     }
 
@@ -487,11 +335,10 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
      *
      * @return Whether it is connected.
      */
-    public boolean isConnected() {
-        for (Profile profile : mProfiles) {
-            int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
-                    .getConnectionStatus(mDevice);
-            if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+    boolean isConnected() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            int status = profile.getConnectionStatus(mDevice);
+            if (status == BluetoothProfile.STATE_CONNECTED) {
                 return true;
             }
         }
@@ -499,81 +346,21 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         return false;
     }
 
-    public boolean isConnectedProfile(Profile profile) {
-        int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
-                .getConnectionStatus(mDevice);
-        if (SettingsBtStatus.isConnectionStatusConnected(status)) {
-            return true;
-        }
+    boolean isConnectedProfile(LocalBluetoothProfile profile) {
+        int status = profile.getConnectionStatus(mDevice);
+        return status == BluetoothProfile.STATE_CONNECTED;
 
-        return false;
     }
 
-    public boolean isBusy() {
-        for (Profile profile : mProfiles) {
-            int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
-                    .getConnectionStatus(mDevice);
-            if (SettingsBtStatus.isConnectionStatusBusy(status)) {
+    boolean isBusy() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            int status = profile.getConnectionStatus(mDevice);
+            if (status == BluetoothProfile.STATE_CONNECTING
+                    || status == BluetoothProfile.STATE_DISCONNECTING) {
                 return true;
             }
         }
-
-        if (getBondState() == BluetoothDevice.BOND_BONDING) {
-            return true;
-        }
-
-        return false;
-    }
-
-    public int getBtClassDrawable() {
-        if (mBtClass != null) {
-            switch (mBtClass.getMajorDeviceClass()) {
-                case BluetoothClass.Device.Major.COMPUTER:
-                    return R.drawable.ic_bt_laptop;
-
-                case BluetoothClass.Device.Major.PHONE:
-                    return R.drawable.ic_bt_cellphone;
-
-                case BluetoothClass.Device.Major.PERIPHERAL:
-                    return getHidClassDrawable();
-
-                case BluetoothClass.Device.Major.IMAGING:
-                    return R.drawable.ic_bt_imaging;
-            }
-        } else {
-            Log.w(TAG, "mBtClass is null");
-        }
-
-        if (mProfiles.size() > 0) {
-            if (mProfiles.contains(Profile.A2DP)) {
-                return R.drawable.ic_bt_headphones_a2dp;
-            } else if (mProfiles.contains(Profile.HEADSET)) {
-                return R.drawable.ic_bt_headset_hfp;
-            }
-        } else if (mBtClass != null) {
-            if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
-                return R.drawable.ic_bt_headphones_a2dp;
-
-            }
-            if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
-                return R.drawable.ic_bt_headset_hfp;
-            }
-        }
-        return 0;
-    }
-
-    private int getHidClassDrawable() {
-        switch (mBtClass.getDeviceClass()) {
-            case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
-            case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
-                return R.drawable.ic_bt_keyboard_hid;
-
-            case BluetoothClass.Device.PERIPHERAL_POINTING:
-                return R.drawable.ic_bt_pointing_hid;
-
-            default:
-                return R.drawable.ic_bt_misc_hid;
-        }
+        return getBondState() == BluetoothDevice.BOND_BONDING;
     }
 
     /**
@@ -587,11 +374,10 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         ParcelUuid[] uuids = mDevice.getUuids();
         if (uuids == null) return false;
 
-        BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
-        ParcelUuid[] localUuids = adapter.getUuids();
+        ParcelUuid[] localUuids = mLocalAdapter.getUuids();
         if (localUuids == null) return false;
 
-        LocalBluetoothProfileManager.updateProfiles(uuids, localUuids, mProfiles);
+        mProfileManager.updateProfiles(uuids, localUuids, mProfiles);
 
         if (DEBUG) {
             Log.e(TAG, "updating profiles for " + mDevice.getName());
@@ -599,8 +385,8 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
 
             if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
             Log.v(TAG, "UUID:");
-            for (int i = 0; i < uuids.length; i++) {
-                Log.v(TAG, "  " + uuids[i]);
+            for (ParcelUuid uuid : uuids) {
+                Log.v(TAG, "  " + uuid);
             }
         }
         return true;
@@ -610,7 +396,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
      * Refreshes the UI for the BT class, including fetching the latest value
      * for the class.
      */
-    public void refreshBtClass() {
+    void refreshBtClass() {
         fetchBtClass();
         dispatchAttributesChanged();
     }
@@ -618,7 +404,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
     /**
      * Refreshes the UI when framework alerts us of a UUID change.
      */
-    public void onUuidChanged() {
+    void onUuidChanged() {
         updateProfiles();
 
         if (DEBUG) {
@@ -630,7 +416,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
          * If a connect was attempted earlier without any UUID, we will do the
          * connect now.
          */
-        if (mProfiles.size() > 0
+        if (!mProfiles.isEmpty()
                 && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
                         .elapsedRealtime()) {
             connectWithoutResettingTimer(false);
@@ -638,7 +424,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         dispatchAttributesChanged();
     }
 
-    public void onBondingStateChanged(int bondState) {
+    void onBondingStateChanged(int bondState) {
         if (bondState == BluetoothDevice.BOND_NONE) {
             mProfiles.clear();
             mConnectAfterPairing = false;  // cancel auto-connect
@@ -656,52 +442,25 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         }
     }
 
-    public void setBtClass(BluetoothClass btClass) {
+    void setBtClass(BluetoothClass btClass) {
         if (btClass != null && mBtClass != btClass) {
             mBtClass = btClass;
             dispatchAttributesChanged();
         }
     }
 
-    public int getSummary() {
-        for (Profile profile : mProfiles) {
-            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                    .getProfileManager(mLocalManager, profile);
-            int connectionStatus = profileManager.getConnectionStatus(mDevice);
-
-            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) ||
-                    connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING ||
-                    connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) {
-                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
-            }
-        }
-
-        return SettingsBtStatus.getPairingStatusSummary(getBondState());
+    BluetoothClass getBtClass() {
+        return mBtClass;
     }
 
-    public Map<Profile, Drawable> getProfileIcons() {
-        Map<Profile, Drawable> drawables = new HashMap<Profile, Drawable>();
-
-        for (Profile profile : mProfiles) {
-            int iconResource;
-            if (profile == Profile.HID && mBtClass != null) {
-                iconResource = getHidClassDrawable();
-            } else {
-                LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                        .getProfileManager(mLocalManager, profile);
-                iconResource = profileManager.getDrawableResource();
-            }
-            if (iconResource != 0) {
-                drawables.put(profile, mContext.getResources().getDrawable(iconResource));
-            }
-        }
-
-        return drawables;
+    List<LocalBluetoothProfile> getProfiles() {
+        return Collections.unmodifiableList(mProfiles);
     }
 
-    public List<Profile> getConnectableProfiles() {
-        ArrayList<Profile> connectableProfiles = new ArrayList<Profile>();
-        for (Profile profile : mProfiles) {
+    List<LocalBluetoothProfile> getConnectableProfiles() {
+        List<LocalBluetoothProfile> connectableProfiles =
+                new ArrayList<LocalBluetoothProfile>();
+        for (LocalBluetoothProfile profile : mProfiles) {
             if (profile.isConnectable()) {
                 connectableProfiles.add(profile);
             }
@@ -709,36 +468,13 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         return connectableProfiles;
     }
 
-    public void onClickedAdvancedOptions(SettingsPreferenceFragment fragment) {
-        // TODO: Verify if there really is a case when there's no foreground
-        // activity
-
-        // Intent intent = new Intent();
-        // // Need an activity context to open this in our task
-        // Context context = mLocalManager.getForegroundActivity();
-        // if (context == null) {
-        // // Fallback on application context, and open in a new task
-        // context = mLocalManager.getContext();
-        // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        // }
-        // intent.setClass(context, ConnectSpecificProfilesActivity.class);
-        // intent.putExtra(ConnectSpecificProfilesActivity.EXTRA_DEVICE,
-        // mDevice);
-        // context.startActivity(intent);
-        Preference pref = new Preference(fragment.getActivity());
-        pref.setTitle(getName());
-        pref.setFragment(DeviceProfilesSettings.class.getName());
-        pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, mDevice);
-        ((PreferenceActivity) fragment.getActivity()).onPreferenceStartFragment(fragment, pref);
-    }
-
-    public void registerCallback(Callback callback) {
+    void registerCallback(Callback callback) {
         synchronized (mCallbacks) {
             mCallbacks.add(callback);
         }
     }
 
-    public void unregisterCallback(Callback callback) {
+    void unregisterCallback(Callback callback) {
         synchronized (mCallbacks) {
             mCallbacks.remove(callback);
         }
@@ -760,9 +496,8 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
     @Override
     public boolean equals(Object o) {
         if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
-            throw new ClassCastException();
+            return false;
         }
-
         return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
     }
 
@@ -771,11 +506,12 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         return mDevice.getAddress().hashCode();
     }
 
+    // This comparison uses non-final fields so the sort order may change
+    // when device attributes change (such as bonding state). Settings
+    // will completely refresh the device list when this happens.
     public int compareTo(CachedBluetoothDevice another) {
-        int comparison;
-
         // Connected above not connected
-        comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+        int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
         if (comparison != 0) return comparison;
 
         // Paired above not paired
@@ -792,7 +528,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
         if (comparison != 0) return comparison;
 
         // Fallback on name
-        return getName().compareTo(another.getName());
+        return mName.compareTo(another.mName);
     }
 
     public interface Callback {
index 3ee8bc2..ab71ece 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.util.Log;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.LocalBluetoothManager.Callback;
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 /**
  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
  */
-class CachedBluetoothDeviceManager {
-    private static final String TAG = "CachedBluetoothDeviceManager";
-
-    final LocalBluetoothManager mLocalManager;
-    final List<Callback> mCallbacks;
-
-    final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
-
-    public CachedBluetoothDeviceManager(LocalBluetoothManager localManager) {
-        mLocalManager = localManager;
-        mCallbacks = localManager.getCallbacks();
-    }
+final class CachedBluetoothDeviceManager {
+//    private static final String TAG = "CachedBluetoothDeviceManager";
 
-    private synchronized boolean readPairedDevices() {
-        BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
-        Set<BluetoothDevice> bondedDevices = adapter.getBondedDevices();
-        if (bondedDevices == null) return false;
-
-        boolean deviceAdded = false;
-        for (BluetoothDevice device : bondedDevices) {
-            CachedBluetoothDevice cachedDevice = findDevice(device);
-            if (cachedDevice == null) {
-                cachedDevice = new CachedBluetoothDevice(mLocalManager.getContext(), device);
-                mCachedDevices.add(cachedDevice);
-                dispatchDeviceAdded(cachedDevice);
-                deviceAdded = true;
-            }
-        }
-
-        return deviceAdded;
-    }
+    private final List<CachedBluetoothDevice> mCachedDevices =
+            new ArrayList<CachedBluetoothDevice>();
 
-    public synchronized List<CachedBluetoothDevice> getCachedDevicesCopy() {
+    public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
         return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
     }
 
-    void onBluetoothStateChanged(boolean enabled) {
-        if (enabled) {
-            readPairedDevices();
-        }
-    }
-
-    public synchronized void onDeviceAppeared(BluetoothDevice device, short rssi,
-            BluetoothClass btClass, String name) {
-        boolean deviceAdded = false;
-
-        CachedBluetoothDevice cachedDevice = findDevice(device);
-        if (cachedDevice == null) {
-            cachedDevice = new CachedBluetoothDevice(mLocalManager.getContext(), device);
-            mCachedDevices.add(cachedDevice);
-            deviceAdded = true;
-        }
-        cachedDevice.setRssi(rssi);
-        cachedDevice.setBtClass(btClass);
-        cachedDevice.setName(name);
-        cachedDevice.setVisible(true);
-
-        if (deviceAdded) {
-            dispatchDeviceAdded(cachedDevice);
-        }
-    }
-
-    public synchronized void onDeviceDisappeared(BluetoothDevice device) {
-        CachedBluetoothDevice cachedDevice = findDevice(device);
-        if (cachedDevice == null) return;
-
+    public boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
         cachedDevice.setVisible(false);
-        checkForDeviceRemoval(cachedDevice);
+        return checkForDeviceRemoval(cachedDevice);
     }
 
-    private void checkForDeviceRemoval(CachedBluetoothDevice cachedDevice) {
+    private boolean checkForDeviceRemoval(
+            CachedBluetoothDevice cachedDevice) {
         if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
                 !cachedDevice.isVisible()) {
             // If device isn't paired, remove it altogether
             mCachedDevices.remove(cachedDevice);
-            dispatchDeviceDeleted(cachedDevice);
+            return true;  // dispatch device deleted
         }
+        return false;
     }
 
-    public synchronized void onDeviceNameUpdated(BluetoothDevice device) {
+    public void onDeviceNameUpdated(BluetoothDevice device) {
         CachedBluetoothDevice cachedDevice = findDevice(device);
         if (cachedDevice != null) {
             cachedDevice.refreshName();
         }
     }
 
-    public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) {
-
-        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
-            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
-
+    /**
+     * Search for existing {@link CachedBluetoothDevice} or return null
+     * if this device isn't in the cache. Use {@link #addDevice}
+     * to create and return a new {@link CachedBluetoothDevice} for
+     * a newly discovered {@link BluetoothDevice}.
+     *
+     * @param device the address of the Bluetooth device
+     * @return the cached device object for this device, or null if it has
+     *   not been previously seen
+     */
+    CachedBluetoothDevice findDevice(BluetoothDevice device) {
+        for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
             if (cachedDevice.getDevice().equals(device)) {
                 return cachedDevice;
             }
         }
-
         return null;
     }
 
     /**
+     * Create and return a new {@link CachedBluetoothDevice}. This assumes
+     * that {@link #findDevice} has already been called and returned null.
+     * @param device the address of the new Bluetooth device
+     * @return the newly created CachedBluetoothDevice object
+     */
+    CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
+            LocalBluetoothProfileManager profileManager,
+            BluetoothDevice device) {
+        CachedBluetoothDevice newDevice = new CachedBluetoothDevice(adapter, profileManager,
+                device);
+        mCachedDevices.add(newDevice);
+        return newDevice;
+    }
+
+    /**
      * Attempts to get the name of a remote device, otherwise returns the address.
      *
      * @param device The remote device.
@@ -139,122 +100,23 @@ class CachedBluetoothDeviceManager {
      */
     public String getName(BluetoothDevice device) {
         CachedBluetoothDevice cachedDevice = findDevice(device);
-        if (cachedDevice != null) return cachedDevice.getName();
-
-        String name = device.getName();
-        if (name != null) return name;
-
-        return device.getAddress();
-    }
-
-    private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        synchronized (mCallbacks) {
-            for (Callback callback : mCallbacks) {
-                callback.onDeviceAdded(cachedDevice);
-            }
-        }
-
-        // TODO: divider between prev paired/connected and scanned
-    }
-
-    private void dispatchDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        synchronized (mCallbacks) {
-            for (Callback callback : mCallbacks) {
-                callback.onDeviceDeleted(cachedDevice);
-            }
-        }
-    }
-
-    private void dispatchDeviceBondStateChanged(
-            CachedBluetoothDevice cachedDevice, int bondState) {
-        synchronized (mCallbacks) {
-            for (Callback callback : mCallbacks) {
-                callback.onDeviceBondStateChanged(cachedDevice, bondState);
-            }
-        }
-    }
-
-    public synchronized void onBondingStateChanged(BluetoothDevice device, int bondState) {
-        CachedBluetoothDevice cachedDevice = findDevice(device);
-        if (cachedDevice == null) {
-            if (!readPairedDevices()) {
-                Log.e(TAG, "Got bonding state changed for " + device +
-                        ", but we have no record of that device.");
-                return;
-            }
-            cachedDevice = findDevice(device);
-            if (cachedDevice == null) {
-                Log.e(TAG, "Got bonding state changed for " + device +
-                        ", but device not added in cache.");
-                return;
-            }
+        if (cachedDevice != null) {
+            return cachedDevice.getName();
         }
 
-        dispatchDeviceBondStateChanged(cachedDevice, bondState);
-        cachedDevice.onBondingStateChanged(bondState);
-    }
-
-    /**
-     * Called when we have reached the un-bond state.
-     *
-     * @param device The remote device.
-     * @param reason The reason, one of the error reasons from
-     *            BluetoothDevice.UNBOND_REASON_*
-     */
-    public synchronized void showUnbondMessage(BluetoothDevice device, int reason) {
-        int errorMsg;
-
-        switch(reason) {
-        case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
-            errorMsg = R.string.bluetooth_pairing_pin_error_message;
-            mLocalManager.showError(device, errorMsg);
-            break;
-        case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
-            errorMsg = R.string.bluetooth_pairing_rejected_error_message;
-            mLocalManager.showError(device, errorMsg);
-            break;
-        case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
-            errorMsg = R.string.bluetooth_pairing_device_down_error_message;
-            mLocalManager.showError(device, errorMsg);
-            break;
-        case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
-        case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
-        case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
-        case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
-            errorMsg = R.string.bluetooth_pairing_error_message;
-            mLocalManager.showError(device, errorMsg);
-            break;
-        default:
-            Log.w(TAG, "showUnbondMessage: Not displaying any message for reason:" + reason);
-            break;
+        String name = device.getName();
+        if (name != null) {
+            return name;
         }
-    }
 
-    public synchronized void onProfileStateChanged(BluetoothDevice device, Profile profile,
-            int newProfileState) {
-        CachedBluetoothDevice cachedDevice = findDevice(device);
-        if (cachedDevice == null) return;
-
-        cachedDevice.onProfileStateChanged(profile, newProfileState);
-        cachedDevice.refresh();
-    }
-
-    public synchronized void onConnectingError(BluetoothDevice device) {
-        CachedBluetoothDevice cachedDevice = findDevice(device);
-        if (cachedDevice == null) return;
-
-        /*
-         * Go through the device's delegate so we don't spam the user with
-         * errors connecting to different profiles, and instead make sure the
-         * user sees a single error for his single 'connect' action.
-         */
-        cachedDevice.showConnectingError();
+        return device.getAddress();
     }
 
     public synchronized void onScanningStateChanged(boolean started) {
         if (!started) return;
 
         // If starting a new scan, clear old visibility
+        // Iterate in reverse order since devices may be removed.
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
             cachedDevice.setVisible(false);
index 46fff6e..a978e23 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothDevicePicker;
-import android.bluetooth.BluetoothUuid;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.Bundle;
-import android.os.ParcelUuid;
 import android.preference.Preference;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceScreen;
 import android.util.Log;
-import android.view.View;
 
 import com.android.settings.ProgressCategory;
 import com.android.settings.SettingsPreferenceFragment;
 
-import java.util.List;
+import java.util.Collection;
 import java.util.WeakHashMap;
 
 /**
@@ -48,105 +38,96 @@ import java.util.WeakHashMap;
  * @see DevicePickerFragment
  * @see BluetoothFindNearby
  */
-public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFragment
-        implements LocalBluetoothManager.Callback, View.OnClickListener {
+public abstract class DeviceListPreferenceFragment extends
+        SettingsPreferenceFragment implements BluetoothCallback {
 
     private static final String TAG = "DeviceListPreferenceFragment";
 
-    static final String KEY_BT_DEVICE_LIST = "bt_device_list";
-    static final String KEY_BT_SCAN = "bt_scan";
+    private static final String KEY_BT_DEVICE_LIST = "bt_device_list";
+    private static final String KEY_BT_SCAN = "bt_scan";
 
-    int mFilterType;
+    private BluetoothDeviceFilter.Filter mFilter;
 
-    BluetoothDevice mSelectedDevice = null;
+    BluetoothDevice mSelectedDevice;
 
+    LocalBluetoothAdapter mLocalAdapter;
     LocalBluetoothManager mLocalManager;
 
     private PreferenceCategory mDeviceList;
 
-    WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
+    final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
             new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>();
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                onBluetoothStateChanged(mLocalManager.getBluetoothState());
-            }
-        }
-    };
+    DeviceListPreferenceFragment() {
+        mFilter = BluetoothDeviceFilter.ALL_FILTER;
+    }
+
+    DeviceListPreferenceFragment(BluetoothDeviceFilter.Filter filter) {
+        mFilter = filter;
+    }
+
+    final void setFilter(int filterType) {
+        mFilter = BluetoothDeviceFilter.getFilter(filterType);
+    }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        // We delay calling super.onActivityCreated(). See WifiSettings.java for more info.
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
-        final Activity activity = getActivity();
-        mLocalManager = LocalBluetoothManager.getInstance(activity);
+        mLocalManager = LocalBluetoothManager.getInstance(getActivity());
         if (mLocalManager == null) {
-            finish();
+            Log.e(TAG, "Bluetooth is not supported on this device");
+            return;
         }
+        mLocalAdapter = mLocalManager.getBluetoothAdapter();
 
-        mFilterType = BluetoothDevicePicker.FILTER_TYPE_ALL;
-
-        if (getPreferenceScreen() != null) getPreferenceScreen().removeAll();
-
-        addPreferencesForActivity(activity);
+        addPreferencesForActivity();
 
         mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST);
-
-        super.onActivityCreated(savedInstanceState);
+        if (mDeviceList == null) {
+            Log.e(TAG, "Could not find device list preference object!");
+        }
     }
 
     /** Add preferences from the subclass. */
-    abstract void addPreferencesForActivity(Activity activity);
+    abstract void addPreferencesForActivity();
 
     @Override
     public void onResume() {
         super.onResume();
 
-        mLocalManager.registerCallback(this);
-
-        updateProgressUi(mLocalManager.getBluetoothAdapter().isDiscovering());
-
-        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
-        getActivity().registerReceiver(mReceiver, intentFilter);
         mLocalManager.setForegroundActivity(getActivity());
+        mLocalManager.getEventManager().registerCallback(this);
+
+        updateProgressUi(mLocalAdapter.isDiscovering());
     }
 
     @Override
     public void onPause() {
         super.onPause();
-        mLocalManager.stopScanning();
+
+        mLocalAdapter.stopScanning();
         mLocalManager.setForegroundActivity(null);
+        mLocalManager.getEventManager().unregisterCallback(this);
+
         mDevicePreferenceMap.clear();
         mDeviceList.removeAll();
-        getActivity().unregisterReceiver(mReceiver);
-
-        mLocalManager.unregisterCallback(this);
     }
 
     void addDevices() {
-        List<CachedBluetoothDevice> cachedDevices =
+        Collection<CachedBluetoothDevice> cachedDevices =
                 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
         for (CachedBluetoothDevice cachedDevice : cachedDevices) {
             onDeviceAdded(cachedDevice);
         }
     }
 
-    public void onClick(View v) {
-        // User clicked on advanced options icon for a device in the list
-        if (v.getTag() instanceof CachedBluetoothDevice) {
-            CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
-            device.onClickedAdvancedOptions(this);
-        }
-    }
-
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
             Preference preference) {
 
         if (KEY_BT_SCAN.equals(preference.getKey())) {
-            mLocalManager.startScanning(true);
+            mLocalAdapter.startScanning(true);
             return true;
         }
 
@@ -162,7 +143,7 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra
     }
 
     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
-        btPreference.getCachedDevice().onClicked();
+        btPreference.onClicked();
     }
 
     public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
@@ -171,71 +152,11 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra
             return;
         }
 
-        if (addDevicePreference(cachedDevice)) {
+        if (mFilter.matches(cachedDevice.getDevice())) {
             createDevicePreference(cachedDevice);
         }
      }
 
-    /**
-     * Determine whether to add the new device to the list.
-     * @param cachedDevice the newly discovered device
-     * @return true if the device should be added; false otherwise
-     */
-    boolean addDevicePreference(CachedBluetoothDevice cachedDevice) {
-        ParcelUuid[] uuids = cachedDevice.getDevice().getUuids();
-        BluetoothClass bluetoothClass = cachedDevice.getDevice().getBluetoothClass();
-
-        switch(mFilterType) {
-        case BluetoothDevicePicker.FILTER_TYPE_TRANSFER:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.OPP_PROFILE_UUIDS))  return true;
-            }
-            if (bluetoothClass != null
-                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_OPP)) {
-                return true;
-            }
-            break;
-        case BluetoothDevicePicker.FILTER_TYPE_AUDIO:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.A2DP_SINK_PROFILE_UUIDS))  return true;
-
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.HEADSET_PROFILE_UUIDS))  return true;
-            } else if (bluetoothClass != null) {
-                if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) return true;
-
-                if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) return true;
-            }
-            break;
-        case BluetoothDevicePicker.FILTER_TYPE_PANU:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.PANU_PROFILE_UUIDS))  return true;
-
-            }
-            if (bluetoothClass != null
-                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_PANU)) {
-                return true;
-            }
-            break;
-        case BluetoothDevicePicker.FILTER_TYPE_NAP:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.NAP_PROFILE_UUIDS))  return true;
-            }
-            if (bluetoothClass != null
-                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_NAP)) {
-                return true;
-            }
-            break;
-        default:
-            return true;
-        }
-        return false;
-    }
-
     void createDevicePreference(CachedBluetoothDevice cachedDevice) {
         BluetoothDevicePreference preference = new BluetoothDevicePreference(
                 getActivity(), cachedDevice);
@@ -252,7 +173,8 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra
     void initDevicePreference(BluetoothDevicePreference preference) { }
 
     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
+        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(
+                cachedDevice);
         if (preference != null) {
             mDeviceList.removePreference(preference);
         }
@@ -268,15 +190,9 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra
         }
     }
 
-    void onBluetoothStateChanged(int bluetoothState) {
+    public void onBluetoothStateChanged(int bluetoothState) {
         if (bluetoothState == BluetoothAdapter.STATE_OFF) {
             updateProgressUi(false);
         }
     }
-
-    void sendDevicePickedIntent(BluetoothDevice device) {
-        Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        getActivity().sendBroadcast(intent);
-    }
 }
index c29fafb..8f6e0df 100644 (file)
@@ -25,7 +25,7 @@ import android.os.Bundle;
  * Activity for Bluetooth device picker dialog. The device picker logic
  * is implemented in the {@link BluetoothSettings} fragment.
  */
-public class DevicePickerActivity extends Activity {
+public final class DevicePickerActivity extends Activity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
index d3e3d69..126df02 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothDevicePicker;
 import android.content.Intent;
-import android.util.Log;
+import android.os.Bundle;
 
 import com.android.settings.R;
 
@@ -29,36 +28,42 @@ import com.android.settings.R;
  * BluetoothSettings is the Settings screen for Bluetooth configuration and
  * connection management.
  */
-public class DevicePickerFragment extends DeviceListPreferenceFragment {
-
-    private static final String TAG = "BluetoothDevicePicker";
+public final class DevicePickerFragment extends DeviceListPreferenceFragment {
 
     private boolean mNeedAuth;
     private String mLaunchPackage;
     private String mLaunchClass;
 
-    void addPreferencesForActivity(Activity activity) {
-        Intent intent = activity.getIntent();
+    @Override
+    void addPreferencesForActivity() {
+        addPreferencesFromResource(R.xml.device_picker);
+
+        Intent intent = getActivity().getIntent();
         mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
-        mFilterType = intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
-                BluetoothDevicePicker.FILTER_TYPE_ALL);
+        setFilter(intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
+                BluetoothDevicePicker.FILTER_TYPE_ALL));
         mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE);
         mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS);
+    }
 
-        activity.setTitle(activity.getString(R.string.device_picker));
-        addPreferencesFromResource(R.xml.device_picker);
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getActivity().setTitle(getString(R.string.device_picker));
     }
 
     @Override
     public void onResume() {
         super.onResume();
         addDevices();
-        mLocalManager.startScanning(true);
+        mLocalAdapter.startScanning(true);
     }
 
+    @Override
     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
-        mLocalManager.stopScanning();
-        mLocalManager.persistSelectedDeviceInPicker(mSelectedDevice.getAddress());
+        mLocalAdapter.stopScanning();
+        LocalBluetoothPreferences.persistSelectedDeviceInPicker(
+                getActivity(), mSelectedDevice.getAddress());
         if ((btPreference.getCachedDevice().getBondState() ==
                 BluetoothDevice.BOND_BONDED) || !mNeedAuth) {
             sendDevicePickedIntent(mSelectedDevice);
@@ -79,15 +84,16 @@ public class DevicePickerFragment extends DeviceListPreferenceFragment {
         }
     }
 
-    void onBluetoothStateChanged(int bluetoothState) {
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
         super.onBluetoothStateChanged(bluetoothState);
 
         if (bluetoothState == BluetoothAdapter.STATE_ON) {
-                mLocalManager.startScanning(false);
+                mLocalAdapter.startScanning(false);
         }
     }
 
-    void sendDevicePickedIntent(BluetoothDevice device) {
+    private void sendDevicePickedIntent(BluetoothDevice device) {
         Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         if (mLaunchPackage != null && mLaunchClass != null) {
index f39eabd..307125c 100644 (file)
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
-
-import android.bluetooth.BluetoothClass;
+import android.app.AlertDialog;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothUuid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.DialogInterface;
 import android.os.Bundle;
-import android.os.ParcelUuid;
 import android.preference.CheckBoxPreference;
 import android.preference.EditTextPreference;
 import android.preference.Preference;
@@ -34,6 +31,9 @@ import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
 import java.util.HashMap;
 
 /**
@@ -41,7 +41,7 @@ import java.util.HashMap;
  * for a particular device, and allows them to be individually connected
  * (or disconnected).
  */
-public class DeviceProfilesSettings extends SettingsPreferenceFragment
+public final class DeviceProfilesSettings extends SettingsPreferenceFragment
         implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener,
                 View.OnClickListener {
     private static final String TAG = "DeviceProfilesSettings";
@@ -56,11 +56,15 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
 
     private LocalBluetoothManager mManager;
     private CachedBluetoothDevice mCachedDevice;
+    private LocalBluetoothProfileManager mProfileManager;
 
     private PreferenceGroup mProfileContainer;
     private EditTextPreference mDeviceNamePref;
-    private final HashMap<String,CheckBoxPreference> mAutoConnectPrefs
-            = new HashMap<String,CheckBoxPreference>();
+
+    private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs
+            = new HashMap<LocalBluetoothProfile, CheckBoxPreference>();
+
+    private AlertDialog mDisconnectDialog;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -74,51 +78,54 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
             device = args.getParcelable(EXTRA_DEVICE);
         }
 
+        addPreferencesFromResource(R.xml.bluetooth_device_advanced);
+        getPreferenceScreen().setOrderingAsAdded(false);
+        mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
+        mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE);
+
         if (device == null) {
             Log.w(TAG, "Activity started without a remote Bluetooth device");
             finish();
-            return;
+            return;  // TODO: test this failure path
         }
 
         mManager = LocalBluetoothManager.getInstance(getActivity());
-        mCachedDevice = mManager.getCachedDeviceManager().findDevice(device);
+        CachedBluetoothDeviceManager deviceManager =
+                mManager.getCachedDeviceManager();
+        mProfileManager = mManager.getProfileManager();
+        mCachedDevice = deviceManager.findDevice(device);
         if (mCachedDevice == null) {
             Log.w(TAG, "Device not found, cannot connect to it");
             finish();
-            return;
+            return;  // TODO: test this failure path
         }
 
-        addPreferencesFromResource(R.xml.bluetooth_device_advanced);
-        getPreferenceScreen().setOrderingAsAdded(false);
-
-        mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
-
-        mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE);
-        mDeviceNamePref.setSummary(mCachedDevice.getName());
-        mDeviceNamePref.setText(mCachedDevice.getName());
+        String deviceName = mCachedDevice.getName();
+        mDeviceNamePref.setSummary(deviceName);
+        mDeviceNamePref.setText(deviceName);
         mDeviceNamePref.setOnPreferenceChangeListener(this);
 
         // Set the title of the screen
-        findPreference(KEY_TITLE).setTitle(getResources()
-                .getString(R.string.bluetooth_device_advanced_title, mCachedDevice.getName()));
+        findPreference(KEY_TITLE).setTitle(
+                getString(R.string.bluetooth_device_advanced_title,
+                        deviceName));
 
         // Add a preference for each profile
         addPreferencesForProfiles();
     }
 
-    private boolean isObjectPushSupported(BluetoothDevice device) {
-        ParcelUuid[] uuids = device.getUuids();
-        BluetoothClass bluetoothClass = device.getBluetoothClass();
-        return (uuids != null && BluetoothUuid.containsAnyUuid(uuids,
-                LocalBluetoothProfileManager.OPP_PROFILE_UUIDS)) ||
-                (bluetoothClass != null && bluetoothClass.doesClassMatch(
-                        BluetoothClass.PROFILE_OPP));
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mDisconnectDialog != null) {
+            mDisconnectDialog.dismiss();
+            mDisconnectDialog = null;
+        }
     }
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-
         outState.putParcelable(EXTRA_DEVICE, mCachedDevice.getDevice());
     }
 
@@ -141,7 +148,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
     }
 
     private void addPreferencesForProfiles() {
-        for (Profile profile : mCachedDevice.getConnectableProfiles()) {
+        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
             Preference pref = createProfilePreference(profile);
             mProfileContainer.addPreference(pref);
         }
@@ -155,21 +162,18 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
      * @return A preference that allows the user to choose whether this profile
      *         will be connected to.
      */
-    private Preference createProfilePreference(Profile profile) {
+    private Preference createProfilePreference(LocalBluetoothProfile profile) {
         BluetoothProfilePreference pref = new BluetoothProfilePreference(getActivity(), profile);
         pref.setKey(profile.toString());
-        pref.setTitle(profile.localizedString);
+        pref.setTitle(profile.getNameResource());
         pref.setExpanded(false);
         pref.setPersistent(false);
-        pref.setOrder(getProfilePreferenceIndex(profile));
+        pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal()));
         pref.setOnExpandClickListener(this);
 
-        LocalBluetoothProfileManager profileManager =
-                LocalBluetoothProfileManager.getProfileManager(mManager, profile);
-        int iconResource = profileManager.getDrawableResource();
+        int iconResource = profile.getDrawableResource(null);  // FIXME: get BT class for this?
         if (iconResource != 0) {
-            pref.setProfileDrawable(mManager.getContext()
-                    .getResources().getDrawable(iconResource));
+            pref.setProfileDrawable(getResources().getDrawable(iconResource));
         }
 
         /**
@@ -186,7 +190,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
     public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
         String key = preference.getKey();
         if (preference instanceof BluetoothProfilePreference) {
-            onProfileClicked(Profile.valueOf(key));
+            onProfileClicked(mProfileManager.getProfileByName(key));
             return true;
         } else if (key.equals(KEY_UNPAIR)) {
             unpairDevice();
@@ -194,7 +198,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
             return true;
         }
 
-        return false;
+        return super.onPreferenceTreeClick(screen, preference);
     }
 
     public boolean onPreferenceChange(Preference preference, Object newValue) {
@@ -202,10 +206,8 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
             mCachedDevice.setName((String) newValue);
         } else if (preference instanceof CheckBoxPreference) {
             boolean autoConnect = (Boolean) newValue;
-            Profile prof = getProfileOf(preference);
-            LocalBluetoothProfileManager
-                    .getProfileManager(mManager, prof)
-                    .setPreferred(mCachedDevice.getDevice(),
+            LocalBluetoothProfile prof = getProfileOf(preference);
+            prof.setPreferred(mCachedDevice.getDevice(),
                             autoConnect);
             return true;
         } else {
@@ -215,20 +217,44 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
         return true;
     }
 
-    private void onProfileClicked(Profile profile) {
+    private void onProfileClicked(LocalBluetoothProfile profile) {
         BluetoothDevice device = mCachedDevice.getDevice();
-        LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                .getProfileManager(mManager, profile);
 
-        int status = profileManager.getConnectionStatus(device);
+        int status = profile.getConnectionStatus(device);
         boolean isConnected =
-                SettingsBtStatus.isConnectionStatusConnected(status);
+                status == BluetoothProfile.STATE_CONNECTED;
 
         if (isConnected) {
-            mCachedDevice.askDisconnect(profile);
+            askDisconnect(getActivity(), profile);
         } else {
-            mCachedDevice.connect(profile);
+            mCachedDevice.connectProfile(profile);
+        }
+    }
+
+    private void askDisconnect(Context context,
+            final LocalBluetoothProfile profile) {
+        // local reference for callback
+        final CachedBluetoothDevice device = mCachedDevice;
+        String name = device.getName();
+        if (TextUtils.isEmpty(name)) {
+            name = context.getString(R.string.bluetooth_device);
+        }
+        int disconnectMessage = profile.getDisconnectResource();
+        if (disconnectMessage == 0) {
+            Log.w(TAG, "askDisconnect: unexpected profile " + profile);
+            disconnectMessage = R.string.bluetooth_disconnect_blank;
         }
+        String message = context.getString(disconnectMessage, name);
+
+        DialogInterface.OnClickListener disconnectListener =
+                new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
+                device.disconnect(profile);
+            }
+        };
+
+        mDisconnectDialog = Utils.showDisconnectDialog(context,
+                mDisconnectDialog, disconnectListener, name, message);
     }
 
     public void onDeviceAttributesChanged() {
@@ -242,7 +268,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
 //        transaction.setBreadCrumbTitle(deviceName);
 //        transaction.commit();
 
-        findPreference(KEY_TITLE).setTitle(getResources().getString(
+        findPreference(KEY_TITLE).setTitle(getString(
                 R.string.bluetooth_device_advanced_title,
                 deviceName));
         mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE);
@@ -253,7 +279,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
     }
 
     private void refreshProfiles() {
-        for (Profile profile : mCachedDevice.getConnectableProfiles()) {
+        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
             Preference profilePref = findPreference(profile.toString());
             if (profilePref == null) {
                 profilePref = createProfilePreference(profile);
@@ -264,78 +290,43 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
         }
     }
 
-    private void refreshProfilePreference(Preference profilePref, Profile profile) {
+    private void refreshProfilePreference(Preference profilePref, LocalBluetoothProfile profile) {
         BluetoothDevice device = mCachedDevice.getDevice();
-        LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                .getProfileManager(mManager, profile);
-
-        int connectionStatus = profileManager.getConnectionStatus(device);
 
         /*
          * Gray out checkbox while connecting and disconnecting
          */
         profilePref.setEnabled(!mCachedDevice.isBusy());
-        profilePref.setSummary(getProfileSummary(profileManager, profile, device,
-                connectionStatus, isDeviceOnline()));
-        // TODO:
-        //profilePref.setChecked(profileManager.isPreferred(device));
+        profilePref.setSummary(profile.getSummaryResourceForDevice(device));
     }
 
-    private Profile getProfileOf(Preference pref) {
-        if (!(pref instanceof CheckBoxPreference)) return null;
+    private LocalBluetoothProfile getProfileOf(Preference pref) {
+        if (!(pref instanceof CheckBoxPreference)) {
+            return null;
+        }
         String key = pref.getKey();
         if (TextUtils.isEmpty(key)) return null;
 
         try {
-            return Profile.valueOf(pref.getKey());
-        } catch (IllegalArgumentException e) {
+            return mProfileManager.getProfileByName(pref.getKey());
+        } catch (IllegalArgumentException ignored) {
             return null;
         }
     }
 
-    private static int getProfileSummary(LocalBluetoothProfileManager profileManager,
-            Profile profile, BluetoothDevice device, int connectionStatus, boolean onlineMode) {
-        if (!onlineMode || connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) {
-            return getProfileSummaryForSettingPreference(profile);
-        } else {
-            return profileManager.getSummary(device);
-        }
-    }
-
-    /**
-     * Gets the summary that describes when checked, it will become a preferred profile.
-     *
-     * @param profile The profile to get the summary for.
-     * @return The summary.
-     */
-    private static final int getProfileSummaryForSettingPreference(Profile profile) {
-        switch (profile) {
-            case A2DP:
-                return R.string.bluetooth_a2dp_profile_summary_use_for;
-            case HEADSET:
-                return R.string.bluetooth_headset_profile_summary_use_for;
-            case HID:
-                return R.string.bluetooth_hid_profile_summary_use_for;
-            case PAN:
-                return R.string.bluetooth_pan_profile_summary_use_for;
-            default:
-                return 0;
-        }
-    }
-
     public void onClick(View v) {
-        if (v.getTag() instanceof Profile) {
-            Profile prof = (Profile) v.getTag();
-            CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof.toString());
+        if (v.getTag() instanceof LocalBluetoothProfile) {
+            LocalBluetoothProfile prof = (LocalBluetoothProfile) v.getTag();
+            CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof);
             if (autoConnectPref == null) {
                 autoConnectPref = new CheckBoxPreference(getActivity());
                 autoConnectPref.setLayoutResource(com.android.internal.R.layout.preference_child);
                 autoConnectPref.setKey(prof.toString());
                 autoConnectPref.setTitle(R.string.bluetooth_auto_connect);
-                autoConnectPref.setOrder(getProfilePreferenceIndex(prof) + 1);
+                autoConnectPref.setOrder(getProfilePreferenceIndex(prof.getOrdinal()) + 1);
                 autoConnectPref.setChecked(getAutoConnect(prof));
                 autoConnectPref.setOnPreferenceChangeListener(this);
-                mAutoConnectPrefs.put(prof.name(), autoConnectPref);
+                mAutoConnectPrefs.put(prof, autoConnectPref);
             }
             BluetoothProfilePreference profilePref =
                     (BluetoothProfilePreference) findPreference(prof.toString());
@@ -349,19 +340,14 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
         }
     }
 
-    private int getProfilePreferenceIndex(Profile prof) {
-        return mProfileContainer.getOrder() + prof.ordinal() * 10;
+    private int getProfilePreferenceIndex(int profIndex) {
+        return mProfileContainer.getOrder() + profIndex * 10;
     }
 
     private void unpairDevice() {
         mCachedDevice.unpair();
     }
 
-    private boolean isDeviceOnline() {
-        // TODO: Verify
-        return mCachedDevice.isConnected() || mCachedDevice.isBusy();
-    }
-
     private void setIncomingFileTransfersAllowed(boolean allow) {
         // TODO: make an IPC call into BluetoothOpp to update
         Log.d(TAG, "Set allow incoming = " + allow);
@@ -372,8 +358,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment
         return true;
     }
 
-    private boolean getAutoConnect(Profile prof) {
-        return LocalBluetoothProfileManager.getProfileManager(mManager, prof)
-                .isPreferred(mCachedDevice.getDevice());
+    private boolean getAutoConnect(LocalBluetoothProfile prof) {
+        return prof.isPreferred(mCachedDevice.getDevice());
     }
 }
index 0410998..6ecbef6 100644 (file)
@@ -28,7 +28,7 @@ import android.content.Intent;
 import android.os.PowerManager;
 import android.util.Log;
 
-public class DockEventReceiver extends BroadcastReceiver {
+public final class DockEventReceiver extends BroadcastReceiver {
 
     private static final boolean DEBUG = DockService.DEBUG;
 
@@ -39,11 +39,9 @@ public class DockEventReceiver extends BroadcastReceiver {
 
     private static final int EXTRA_INVALID = -1234;
 
-    private static final Object mStartingServiceSync = new Object();
+    private static final Object sStartingServiceSync = new Object();
 
-    private static final long WAKELOCK_TIMEOUT = 5000;
-
-    private static PowerManager.WakeLock mStartingService;
+    private static PowerManager.WakeLock sStartingService;
 
     @Override
     public void onReceive(Context context, Intent intent) {
@@ -75,7 +73,7 @@ public class DockEventReceiver extends BroadcastReceiver {
                     beginStartingService(context, i);
                     break;
                 default:
-                    if (DEBUG) Log.e(TAG, "Unknown state");
+                    Log.e(TAG, "Unknown state: " + state);
                     break;
             }
         } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction()) ||
@@ -118,15 +116,15 @@ public class DockEventReceiver extends BroadcastReceiver {
      * Start the service to process the current event notifications, acquiring
      * the wake lock before returning to ensure that the service will run.
      */
-    public static void beginStartingService(Context context, Intent intent) {
-        synchronized (mStartingServiceSync) {
-            if (mStartingService == null) {
+    private static void beginStartingService(Context context, Intent intent) {
+        synchronized (sStartingServiceSync) {
+            if (sStartingService == null) {
                 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-                mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                sStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                         "StartingDockService");
             }
 
-            mStartingService.acquire(WAKELOCK_TIMEOUT);
+            sStartingService.acquire();
 
             if (context.startService(intent) == null) {
                 Log.e(TAG, "Can't start DockService");
@@ -139,10 +137,13 @@ public class DockEventReceiver extends BroadcastReceiver {
      * releasing the wake lock if the service is now stopping.
      */
     public static void finishStartingService(Service service, int startId) {
-        synchronized (mStartingServiceSync) {
-            if (mStartingService != null) {
-                if (DEBUG) Log.d(TAG, "stopSelf id = "+ startId);
-                service.stopSelfResult(startId);
+        synchronized (sStartingServiceSync) {
+            if (sStartingService != null) {
+                if (DEBUG) Log.d(TAG, "stopSelf id = " + startId);
+                if (service.stopSelfResult(startId)) {
+                    Log.d(TAG, "finishStartingService: stopping service");
+                    sStartingService.release();
+                }
             }
         }
     }
index 8104652..b457706 100644 (file)
@@ -17,7 +17,6 @@
 package com.android.settings.bluetooth;
 
 import com.android.settings.R;
-import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
 import com.android.settings.bluetooth.LocalBluetoothProfileManager.ServiceListener;
 
 import android.app.AlertDialog;
@@ -27,7 +26,7 @@ import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.content.Context;
+import android.bluetooth.BluetoothProfile;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -44,12 +43,11 @@ import android.view.WindowManager;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
-public class DockService extends Service implements AlertDialog.OnMultiChoiceClickListener,
-        DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
-        CompoundButton.OnCheckedChangeListener, ServiceListener {
+public final class DockService extends Service implements ServiceListener {
 
     private static final String TAG = "DockService";
 
@@ -82,14 +80,11 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
 
     private static final String SHARED_PREFERENCES_NAME = "dock_settings";
 
-    private static final String SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED =
-        "disable_bt_when_undock";
+    private static final String KEY_DISABLE_BT_WHEN_UNDOCKED = "disable_bt_when_undock";
 
-    private static final String SHARED_PREFERENCES_KEY_DISABLE_BT =
-        "disable_bt";
+    private static final String KEY_DISABLE_BT = "disable_bt";
 
-    private static final String SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT =
-        "connect_retry_count";
+    private static final String KEY_CONNECT_RETRY_COUNT = "connect_retry_count";
 
     /*
      * If disconnected unexpectedly, reconnect up to 6 times. Each profile counts
@@ -103,8 +98,9 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
     private volatile Looper mServiceLooper;
     private volatile ServiceHandler mServiceHandler;
     private Runnable mRunnable;
-    private DockService mContext;
-    private LocalBluetoothManager mBtManager;
+    private LocalBluetoothAdapter mLocalAdapter;
+    private CachedBluetoothDeviceManager mDeviceManager;
+    private LocalBluetoothProfileManager mProfileManager;
 
     // Normally set after getting a docked event and unset when the connection
     // is severed. One exception is that mDevice could be null if the service
@@ -113,7 +109,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
 
     // Created and used for the duration of the dialog
     private AlertDialog mDialog;
-    private Profile[] mProfiles;
+    private LocalBluetoothProfile[] mProfiles;
     private boolean[] mCheckedItems;
     private int mStartIdAssociatedWithDialog;
 
@@ -127,8 +123,19 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
     public void onCreate() {
         if (DEBUG) Log.d(TAG, "onCreate");
 
-        mBtManager = LocalBluetoothManager.getInstance(this);
-        mContext = this;
+        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
+        if (manager == null) {
+            Log.e(TAG, "Can't get LocalBluetoothManager: exiting");
+            return;
+        }
+
+        mLocalAdapter = manager.getBluetoothAdapter();
+        mDeviceManager = manager.getCachedDeviceManager();
+        mProfileManager = manager.getProfileManager();
+        if (mProfileManager == null) {
+            Log.e(TAG, "Can't get LocalBluetoothProfileManager: exiting");
+            return;
+        }
 
         HandlerThread thread = new HandlerThread("DockService");
         thread.start();
@@ -141,12 +148,22 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
     public void onDestroy() {
         if (DEBUG) Log.d(TAG, "onDestroy");
         mRunnable = null;
-        LocalBluetoothProfileManager.removeServiceListener(this);
         if (mDialog != null) {
             mDialog.dismiss();
             mDialog = null;
         }
-        mServiceLooper.quit();
+        if (mProfileManager != null) {
+            mProfileManager.removeServiceListener(this);
+        }
+        if (mServiceLooper != null) {
+            mServiceLooper.quit();
+        }
+
+        mLocalAdapter = null;
+        mDeviceManager = null;
+        mProfileManager = null;
+        mServiceLooper = null;
+        mServiceHandler = null;
     }
 
     @Override
@@ -155,9 +172,13 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         return null;
     }
 
+    private SharedPreferences getPrefs() {
+        return getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
+    }
+
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (DEBUG) Log.d(TAG, "onStartCommand startId:" + startId + " flags: " + flags);
+        if (DEBUG) Log.d(TAG, "onStartCommand startId: " + startId + " flags: " + flags);
 
         if (intent == null) {
             // Nothing to process, stop.
@@ -178,24 +199,24 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
          * This assumes that the intent sender has checked that this is a dock
          * and that the intent is for a disconnect
          */
+        final SharedPreferences prefs = getPrefs();
         if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
             BluetoothDevice disconnectedDevice = intent
                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-            int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0);
+            int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0);
             if (retryCount < MAX_CONNECT_RETRY) {
-                setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1);
-                handleUnexpectedDisconnect(disconnectedDevice, Profile.HEADSET, startId);
+                prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply();
+                handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getHeadsetProfile(), startId);
             }
             return START_NOT_STICKY;
         } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
             BluetoothDevice disconnectedDevice = intent
                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
-            int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0);
+            int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0);
             if (retryCount < MAX_CONNECT_RETRY) {
-                setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1);
-                handleUnexpectedDisconnect(disconnectedDevice, Profile.A2DP, startId);
+                prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply();
+                handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getA2dpProfile(), startId);
             }
             return START_NOT_STICKY;
         }
@@ -209,7 +230,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         }
 
         if (msg.what == MSG_TYPE_DOCKED) {
-            removeSetting(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT);
+            prefs.edit().remove(KEY_CONNECT_RETRY_COUNT).apply();
         }
 
         msg.arg2 = startId;
@@ -219,7 +240,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
     }
 
     private final class ServiceHandler extends Handler {
-        public ServiceHandler(Looper looper) {
+        private ServiceHandler(Looper looper) {
             super(looper);
         }
 
@@ -234,7 +255,6 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         int msgType = msg.what;
         final int state = msg.arg1;
         final int startId = msg.arg2;
-        boolean deferFinishCall = false;
         BluetoothDevice device = null;
         if (msg.obj != null) {
             device = (BluetoothDevice) msg.obj;
@@ -243,102 +263,27 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         if(DEBUG) Log.d(TAG, "processMessage: " + msgType + " state: " + state + " device = "
                 + (device == null ? "null" : device.toString()));
 
+        boolean deferFinishCall = false;
+
         switch (msgType) {
             case MSG_TYPE_SHOW_UI:
-                if (mDialog != null) {
-                    // Shouldn't normally happen
-                    mDialog.dismiss();
-                    mDialog = null;
-                }
-                mDevice = device;
-                createDialog(mContext, mDevice, state, startId);
+                createDialog(device, state, startId);
                 break;
 
             case MSG_TYPE_DOCKED:
-                if (DEBUG) {
-                    // TODO figure out why hasMsg always returns false if device
-                    // is supplied
-                    Log.d(TAG, "1 Has undock perm msg = "
-                            + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice));
-                    Log.d(TAG, "2 Has undock perm msg = "
-                            + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device));
-                }
-
-                mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT);
-                mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT);
-                removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT);
-
-                if (!device.equals(mDevice)) {
-                    if (mDevice != null) {
-                        // Not expected. Cleanup/undock existing
-                        handleUndocked(mContext, mBtManager, mDevice);
-                    }
-
-                    mDevice = device;
-
-                    // Register first in case LocalBluetoothProfileManager
-                    // becomes ready after isManagerReady is called and it
-                    // would be too late to register a service listener.
-                    LocalBluetoothProfileManager.addServiceListener(this);
-                    if (LocalBluetoothProfileManager.isManagerReady()) {
-                        handleDocked(device, state, startId);
-                        // Not needed after all
-                        LocalBluetoothProfileManager.removeServiceListener(this);
-                    } else {
-                        final BluetoothDevice d = device;
-                        mRunnable = new Runnable() {
-                            public void run() {
-                                handleDocked(d, state, startId);
-                            }
-                        };
-                        deferFinishCall = true;
-                    }
-                }
+                deferFinishCall = msgTypeDocked(device, state, startId);
                 break;
 
             case MSG_TYPE_UNDOCKED_PERMANENT:
-                // Grace period passed. Disconnect.
-                handleUndocked(mContext, mBtManager, device);
-
-                if (DEBUG) {
-                    Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = "
-                            + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED));
-                }
-
-                if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)) {
-                    // BT was disabled when we first docked
-                    if (!hasOtherConnectedDevices(device)) {
-                        if(DEBUG) Log.d(TAG, "QUEUED BT DISABLE");
-                        // Queue a delayed msg to disable BT
-                        Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_DISABLE_BT, 0,
-                                startId, null);
-                        mServiceHandler.sendMessageDelayed(newMsg, DISABLE_BT_GRACE_PERIOD);
-                        deferFinishCall = true;
-                    } else {
-                        // Don't disable BT if something is connected
-                        removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED);
-                    }
-                }
+                deferFinishCall = msgTypeUndockedPermanent(device, startId);
                 break;
 
             case MSG_TYPE_UNDOCKED_TEMPORARY:
-                // Undocked event received. Queue a delayed msg to sever connection
-                Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state,
-                        startId, device);
-                mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD);
+                msgTypeUndockedTemporary(device, state, startId);
                 break;
 
             case MSG_TYPE_DISABLE_BT:
-                if(DEBUG) Log.d(TAG, "BT DISABLE");
-                if (mBtManager.getBluetoothAdapter().disable()) {
-                    removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED);
-                } else {
-                    // disable() returned an error. Persist a flag to disable BT later
-                    setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT, true);
-                    mPendingTurnOffStartId = startId;
-                    deferFinishCall = true;
-                    if(DEBUG) Log.d(TAG, "disable failed. try again later " + startId);
-                }
+                deferFinishCall = msgTypeDisableBluetooth(startId);
                 break;
         }
 
@@ -346,24 +291,127 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
                 && !deferFinishCall) {
             // NOTE: We MUST not call stopSelf() directly, since we need to
             // make sure the wake lock acquired by the Receiver is released.
-            DockEventReceiver.finishStartingService(DockService.this, startId);
+            DockEventReceiver.finishStartingService(this, startId);
         }
     }
 
-    public synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) {
-        List<CachedBluetoothDevice> cachedDevices = mBtManager.getCachedDeviceManager()
-                .getCachedDevicesCopy();
-        Set<BluetoothDevice> btDevices = mBtManager.getBluetoothAdapter().getBondedDevices();
-        if (btDevices == null || cachedDevices == null || btDevices.size() == 0) {
+    private boolean msgTypeDisableBluetooth(int startId) {
+        if (DEBUG) {
+            Log.d(TAG, "BT DISABLE");
+        }
+        final SharedPreferences prefs = getPrefs();
+        if (mLocalAdapter.disable()) {
+            prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply();
             return false;
+        } else {
+            // disable() returned an error. Persist a flag to disable BT later
+            prefs.edit().putBoolean(KEY_DISABLE_BT, true).apply();
+            mPendingTurnOffStartId = startId;
+            if(DEBUG) {
+                Log.d(TAG, "disable failed. try again later " + startId);
+            }
+            return true;
         }
-        if(DEBUG) Log.d(TAG, "btDevices = " + btDevices.size());
-        if(DEBUG) Log.d(TAG, "cachedDevices = " + cachedDevices.size());
+    }
 
-        for (CachedBluetoothDevice device : cachedDevices) {
-            BluetoothDevice btDevice = device.getDevice();
-            if (!btDevice.equals(dock) && btDevices.contains(btDevice) && device.isConnected()) {
-                if(DEBUG) Log.d(TAG, "connected device = " + device.getName());
+    private void msgTypeUndockedTemporary(BluetoothDevice device, int state,
+            int startId) {
+        // Undocked event received. Queue a delayed msg to sever connection
+        Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state,
+                startId, device);
+        mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD);
+    }
+
+    private boolean msgTypeUndockedPermanent(BluetoothDevice device, int startId) {
+        // Grace period passed. Disconnect.
+        handleUndocked(device);
+        final SharedPreferences prefs = getPrefs();
+
+        if (DEBUG) {
+            Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = "
+                    + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false));
+        }
+
+        if (prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)) {
+            if (hasOtherConnectedDevices(device)) {
+                // Don't disable BT if something is connected
+                prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply();
+            } else {
+                // BT was disabled when we first docked
+                if (DEBUG) {
+                    Log.d(TAG, "QUEUED BT DISABLE");
+                }
+                // Queue a delayed msg to disable BT
+                Message newMsg = mServiceHandler.obtainMessage(
+                        MSG_TYPE_DISABLE_BT, 0, startId, null);
+                mServiceHandler.sendMessageDelayed(newMsg,
+                        DISABLE_BT_GRACE_PERIOD);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean msgTypeDocked(BluetoothDevice device, final int state,
+            final int startId) {
+        if (DEBUG) {
+            // TODO figure out why hasMsg always returns false if device
+            // is supplied
+            Log.d(TAG, "1 Has undock perm msg = "
+                    + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice));
+            Log.d(TAG, "2 Has undock perm msg = "
+                    + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device));
+        }
+
+        mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT);
+        mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT);
+        getPrefs().edit().remove(KEY_DISABLE_BT).apply();
+
+        if (device != null && !device.equals(mDevice)) {
+            if (mDevice != null) {
+                // Not expected. Cleanup/undock existing
+                handleUndocked(mDevice);
+            }
+
+            mDevice = device;
+
+            // Register first in case LocalBluetoothProfileManager
+            // becomes ready after isManagerReady is called and it
+            // would be too late to register a service listener.
+            mProfileManager.addServiceListener(this);
+            if (mProfileManager.isManagerReady()) {
+                handleDocked(device, state, startId);
+                // Not needed after all
+                mProfileManager.removeServiceListener(this);
+            } else {
+                final BluetoothDevice d = device;
+                mRunnable = new Runnable() {
+                    public void run() {
+                        handleDocked(d, state, startId);  // FIXME: WTF runnable here?
+                    }
+                };
+                return true;
+            }
+        }
+        return false;
+    }
+
+    synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) {
+        Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy();
+        Set<BluetoothDevice> btDevices = mLocalAdapter.getBondedDevices();
+        if (btDevices == null || cachedDevices == null || btDevices.isEmpty()) {
+            return false;
+        }
+        if(DEBUG) {
+            Log.d(TAG, "btDevices = " + btDevices.size());
+            Log.d(TAG, "cachedDeviceUIs = " + cachedDevices.size());
+        }
+
+        for (CachedBluetoothDevice deviceUI : cachedDevices) {
+            BluetoothDevice btDevice = deviceUI.getDevice();
+            if (!btDevice.equals(dock) && btDevices.contains(btDevice) && deviceUI
+                    .isConnected()) {
+                if(DEBUG) Log.d(TAG, "connected deviceUI = " + deviceUI.getName());
                 return true;
             }
         }
@@ -404,96 +452,128 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         return mServiceHandler.obtainMessage(msgType, state, 0, device);
     }
 
-    private boolean createDialog(DockService service, BluetoothDevice device, int state,
-            int startId) {
+    private void createDialog(BluetoothDevice device,
+            int state, int startId) {
+        if (mDialog != null) {
+            // Shouldn't normally happen
+            mDialog.dismiss();
+            mDialog = null;
+        }
+        mDevice = device;
         switch (state) {
             case Intent.EXTRA_DOCK_STATE_CAR:
             case Intent.EXTRA_DOCK_STATE_DESK:
                 break;
             default:
-                return false;
+                return;
         }
 
         startForeground(0, new Notification());
 
         // Device in a new dock.
-        boolean firstTime = !mBtManager.hasDockAutoConnectSetting(device.getAddress());
+        boolean firstTime = !LocalBluetoothPreferences.hasDockAutoConnectSetting(this, device.getAddress());
 
-        CharSequence[] items = initBtSettings(service, device, state, firstTime);
+        CharSequence[] items = initBtSettings(device, state, firstTime);
 
-        final AlertDialog.Builder ab = new AlertDialog.Builder(service);
-        ab.setTitle(service.getString(R.string.bluetooth_dock_settings_title));
+        final AlertDialog.Builder ab = new AlertDialog.Builder(this);
+        ab.setTitle(getString(R.string.bluetooth_dock_settings_title));
 
         // Profiles
-        ab.setMultiChoiceItems(items, mCheckedItems, service);
+        ab.setMultiChoiceItems(items, mCheckedItems, mMultiClickListener);
 
         // Remember this settings
-        LayoutInflater inflater = (LayoutInflater) service
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        float pixelScaleFactor = service.getResources().getDisplayMetrics().density;
+        LayoutInflater inflater = (LayoutInflater)
+                getSystemService(LAYOUT_INFLATER_SERVICE);
+        float pixelScaleFactor = getResources().getDisplayMetrics().density;
         View view = inflater.inflate(R.layout.remember_dock_setting, null);
         CheckBox rememberCheckbox = (CheckBox) view.findViewById(R.id.remember);
 
         // check "Remember setting" by default if no value was saved
-        boolean checked = firstTime || mBtManager.getDockAutoConnectSetting(device.getAddress());
+        boolean checked = firstTime || LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress());
         rememberCheckbox.setChecked(checked);
-        rememberCheckbox.setOnCheckedChangeListener(this);
+        rememberCheckbox.setOnCheckedChangeListener(mCheckedChangeListener);
         int viewSpacingLeft = (int) (14 * pixelScaleFactor);
         int viewSpacingRight = (int) (14 * pixelScaleFactor);
         ab.setView(view, viewSpacingLeft, 0 /* top */, viewSpacingRight, 0 /* bottom */);
         if (DEBUG) {
             Log.d(TAG, "Auto connect = "
-                    + mBtManager.getDockAutoConnectSetting(device.getAddress()));
+                    + LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress()));
         }
 
         // Ok Button
-        ab.setPositiveButton(service.getString(android.R.string.ok), service);
+        ab.setPositiveButton(getString(android.R.string.ok), mClickListener);
 
         mStartIdAssociatedWithDialog = startId;
         mDialog = ab.create();
         mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-        mDialog.setOnDismissListener(service);
+        mDialog.setOnDismissListener(mDismissListener);
         mDialog.show();
-        return true;
     }
 
     // Called when the individual bt profiles are clicked.
-    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
-        if (DEBUG) Log.d(TAG, "Item " + which + " changed to " + isChecked);
-        mCheckedItems[which] = isChecked;
-    }
+    private final DialogInterface.OnMultiChoiceClickListener mMultiClickListener =
+            new DialogInterface.OnMultiChoiceClickListener() {
+                public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Item " + which + " changed to " + isChecked);
+                    }
+                    mCheckedItems[which] = isChecked;
+                }
+            };
+
 
     // Called when the "Remember" Checkbox is clicked
-    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-        if (DEBUG) Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked);
-        if (mDevice != null) {
-            mBtManager.saveDockAutoConnectSetting(mDevice.getAddress(), isChecked);
-        }
-    }
+    private final CompoundButton.OnCheckedChangeListener mCheckedChangeListener =
+            new CompoundButton.OnCheckedChangeListener() {
+                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked);
+                    }
+                    if (mDevice != null) {
+                        LocalBluetoothPreferences.saveDockAutoConnectSetting(
+                                DockService.this, mDevice.getAddress(), isChecked);
+                    }
+                }
+            };
+
 
     // Called when the dialog is dismissed
-    public void onDismiss(DialogInterface dialog) {
-        // NOTE: We MUST not call stopSelf() directly, since we need to
-        // make sure the wake lock acquired by the Receiver is released.
-        if (mPendingDevice == null) {
-            DockEventReceiver.finishStartingService(mContext, mStartIdAssociatedWithDialog);
-        }
-        mContext.stopForeground(true);
-    }
+    private final DialogInterface.OnDismissListener mDismissListener =
+            new DialogInterface.OnDismissListener() {
+                public void onDismiss(DialogInterface dialog) {
+                    // NOTE: We MUST not call stopSelf() directly, since we need to
+                    // make sure the wake lock acquired by the Receiver is released.
+                    if (mPendingDevice == null) {
+                        DockEventReceiver.finishStartingService(
+                                DockService.this, mStartIdAssociatedWithDialog);
+                    }
+                    stopForeground(true);
+                }
+            };
 
     // Called when clicked on the OK button
-    public void onClick(DialogInterface dialog, int which) {
-        if (which == DialogInterface.BUTTON_POSITIVE && mDevice != null) {
-            if (!mBtManager.hasDockAutoConnectSetting(mDevice.getAddress())) {
-                mBtManager.saveDockAutoConnectSetting(mDevice.getAddress(), true);
-            }
+    private final DialogInterface.OnClickListener mClickListener =
+            new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    if (which == DialogInterface.BUTTON_POSITIVE
+                            && mDevice != null) {
+                        if (!LocalBluetoothPreferences
+                                .hasDockAutoConnectSetting(
+                                        DockService.this,
+                                        mDevice.getAddress())) {
+                            LocalBluetoothPreferences
+                                    .saveDockAutoConnectSetting(
+                                            DockService.this,
+                                            mDevice.getAddress(), true);
+                        }
 
-            applyBtSettings(mDevice, mStartIdAssociatedWithDialog);
-        }
-    }
+                        applyBtSettings(mDevice, mStartIdAssociatedWithDialog);
+                    }
+                }
+            };
 
-    private CharSequence[] initBtSettings(DockService service, BluetoothDevice device, int state,
-            boolean firstTime) {
+    private CharSequence[] initBtSettings(BluetoothDevice device,
+            int state, boolean firstTime) {
         // TODO Avoid hardcoding dock and profiles. Read from system properties
         int numOfProfiles;
         switch (state) {
@@ -507,96 +587,54 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
                 return null;
         }
 
-        mProfiles = new Profile[numOfProfiles];
+        mProfiles = new LocalBluetoothProfile[numOfProfiles];
         mCheckedItems = new boolean[numOfProfiles];
         CharSequence[] items = new CharSequence[numOfProfiles];
 
+        // FIXME: convert switch to something else
         switch (state) {
             case Intent.EXTRA_DOCK_STATE_CAR:
-                items[0] = service.getString(R.string.bluetooth_dock_settings_headset);
-                items[1] = service.getString(R.string.bluetooth_dock_settings_a2dp);
-                mProfiles[0] = Profile.HEADSET;
-                mProfiles[1] = Profile.A2DP;
+                items[0] = getString(R.string.bluetooth_dock_settings_headset);
+                items[1] = getString(R.string.bluetooth_dock_settings_a2dp);
+                mProfiles[0] = mProfileManager.getHeadsetProfile();
+                mProfiles[1] = mProfileManager.getA2dpProfile();
                 if (firstTime) {
                     // Enable by default for car dock
                     mCheckedItems[0] = true;
                     mCheckedItems[1] = true;
                 } else {
-                    mCheckedItems[0] = LocalBluetoothProfileManager.getProfileManager(mBtManager,
-                            Profile.HEADSET).isPreferred(device);
-                    mCheckedItems[1] = LocalBluetoothProfileManager.getProfileManager(mBtManager,
-                            Profile.A2DP).isPreferred(device);
+                    mCheckedItems[0] = mProfiles[0].isPreferred(device);
+                    mCheckedItems[1] = mProfiles[1].isPreferred(device);
                 }
                 break;
 
             case Intent.EXTRA_DOCK_STATE_DESK:
-                items[0] = service.getString(R.string.bluetooth_dock_settings_a2dp);
-                mProfiles[0] = Profile.A2DP;
+                items[0] = getString(R.string.bluetooth_dock_settings_a2dp);
+                mProfiles[0] = mProfileManager.getA2dpProfile();
                 if (firstTime) {
                     // Disable by default for desk dock
                     mCheckedItems[0] = false;
                 } else {
-                    mCheckedItems[0] = LocalBluetoothProfileManager.getProfileManager(mBtManager,
-                            Profile.A2DP).isPreferred(device);
+                    mCheckedItems[0] = mProfiles[0].isPreferred(device);
                 }
                 break;
         }
         return items;
     }
 
+    // TODO: move to background thread to fix strict mode warnings
     private void handleBtStateChange(Intent intent, int startId) {
         int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
         synchronized (this) {
             if(DEBUG) Log.d(TAG, "BtState = " + btState + " mPendingDevice = " + mPendingDevice);
             if (btState == BluetoothAdapter.STATE_ON) {
-                if (mPendingDevice != null) {
-                    if (mPendingDevice.equals(mDevice)) {
-                        if(DEBUG) Log.d(TAG, "applying settings");
-                        applyBtSettings(mPendingDevice, mPendingStartId);
-                    } else if(DEBUG) {
-                        Log.d(TAG, "mPendingDevice  (" + mPendingDevice + ") != mDevice ("
-                                + mDevice + ")");
-                    }
-
-                    mPendingDevice = null;
-                    DockEventReceiver.finishStartingService(mContext, mPendingStartId);
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = "
-                                + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED));
-                    }
-                    // Reconnect if docked and bluetooth was enabled by user.
-                    Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
-                    if (i != null) {
-                        int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE,
-                                Intent.EXTRA_DOCK_STATE_UNDOCKED);
-                        if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                            BluetoothDevice device = i
-                                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                            if (device != null) {
-                                connectIfEnabled(device);
-                            }
-                        } else if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT)
-                                && mBtManager.getBluetoothAdapter().disable()) {
-                            mPendingTurnOffStartId = startId;
-                            removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT);
-                            return;
-                        }
-                    }
-                }
-
-                if (mPendingTurnOnStartId != INVALID_STARTID) {
-                    DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId);
-                    mPendingTurnOnStartId = INVALID_STARTID;
-                }
-
-                DockEventReceiver.finishStartingService(this, startId);
+                handleBluetoothStateOn(startId);
             } else if (btState == BluetoothAdapter.STATE_TURNING_OFF) {
                 // Remove the flag to disable BT if someone is turning off bt.
                 // The rational is that:
                 // a) if BT is off at undock time, no work needs to be done
                 // b) if BT is on at undock time, the user wants it on.
-                removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED);
+                getPrefs().edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply();
                 DockEventReceiver.finishStartingService(this, startId);
             } else if (btState == BluetoothAdapter.STATE_OFF) {
                 // Bluetooth was turning off as we were trying to turn it on.
@@ -605,12 +643,12 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
 
                 if (mPendingTurnOffStartId != INVALID_STARTID) {
                     DockEventReceiver.finishStartingService(this, mPendingTurnOffStartId);
-                    removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT);
+                    getPrefs().edit().remove(KEY_DISABLE_BT).apply();
                     mPendingTurnOffStartId = INVALID_STARTID;
                 }
 
                 if (mPendingDevice != null) {
-                    mBtManager.getBluetoothAdapter().enable();
+                    mLocalAdapter.enable();
                     mPendingTurnOnStartId = startId;
                 } else {
                     DockEventReceiver.finishStartingService(this, startId);
@@ -619,86 +657,124 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         }
     }
 
-    private void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, Profile profile,
-            int startId) {
-        synchronized (this) {
-            if (DEBUG) Log.d(TAG, "handling failed connect for " + disconnectedDevice);
+    private void handleBluetoothStateOn(int startId) {
+        if (mPendingDevice != null) {
+            if (mPendingDevice.equals(mDevice)) {
+                if(DEBUG) {
+                    Log.d(TAG, "applying settings");
+                }
+                applyBtSettings(mPendingDevice, mPendingStartId);
+            } else if(DEBUG) {
+                Log.d(TAG, "mPendingDevice  (" + mPendingDevice + ") != mDevice ("
+                        + mDevice + ')');
+            }
+
+            mPendingDevice = null;
+            DockEventReceiver.finishStartingService(this, mPendingStartId);
+        } else {
+            final SharedPreferences prefs = getPrefs();
+            if (DEBUG) {
+                Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = "
+                        + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false));
+            }
+            // Reconnect if docked and bluetooth was enabled by user.
+            Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+            if (i != null) {
+                int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                        Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                    BluetoothDevice device = i
+                            .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (device != null) {
+                        connectIfEnabled(device);
+                    }
+                } else if (prefs.getBoolean(KEY_DISABLE_BT, false)
+                        && mLocalAdapter.disable()) {
+                    mPendingTurnOffStartId = startId;
+                    prefs.edit().remove(KEY_DISABLE_BT).apply();
+                    return;
+                }
+            }
+        }
+
+        if (mPendingTurnOnStartId != INVALID_STARTID) {
+            DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId);
+            mPendingTurnOnStartId = INVALID_STARTID;
+        }
+
+        DockEventReceiver.finishStartingService(this, startId);
+    }
+
+    private synchronized void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice,
+            LocalBluetoothProfile profile, int startId) {
+        if (DEBUG) {
+            Log.d(TAG, "handling failed connect for " + disconnectedDevice);
+        }
 
             // Reconnect if docked.
             if (disconnectedDevice != null) {
                 // registerReceiver can't be called from a BroadcastReceiver
-                Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
-                if (i != null) {
-                    int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                Intent intent = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+                if (intent != null) {
+                    int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
                             Intent.EXTRA_DOCK_STATE_UNDOCKED);
                     if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                        BluetoothDevice dockedDevice = i
+                        BluetoothDevice dockedDevice = intent
                                 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                         if (dockedDevice != null && dockedDevice.equals(disconnectedDevice)) {
-                            CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext,
-                                    mBtManager, dockedDevice);
-                            cachedDevice.connect(profile);
+                            CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(
+                                    dockedDevice);
+                            cachedDevice.connectProfile(profile);
                         }
                     }
                 }
             }
 
             DockEventReceiver.finishStartingService(this, startId);
-        }
     }
 
     private synchronized void connectIfEnabled(BluetoothDevice device) {
-        CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, device);
-        List<Profile> profiles = cachedDevice.getConnectableProfiles();
-        for (int i = 0; i < profiles.size(); i++) {
-            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                    .getProfileManager(mBtManager, profiles.get(i));
-            int auto;
-            if (Profile.A2DP == profiles.get(i)) {
-                auto = BluetoothA2dp.PRIORITY_AUTO_CONNECT;
-            } else if (Profile.HEADSET == profiles.get(i)) {
-                auto = BluetoothHeadset.PRIORITY_AUTO_CONNECT;
-            } else {
-                continue;
-            }
-
-            if (profileManager.getPreferred(device) == auto) {
+        CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(
+                device);
+        List<LocalBluetoothProfile> profiles = cachedDevice.getConnectableProfiles();
+        for (LocalBluetoothProfile profile : profiles) {
+            if (profile.getPreferred(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
                 cachedDevice.connect(false);
-                break;
+                return;
             }
         }
     }
 
-    private synchronized void applyBtSettings(final BluetoothDevice device, int startId) {
-        if (device == null || mProfiles == null || mCheckedItems == null)
+    private synchronized void applyBtSettings(BluetoothDevice device, int startId) {
+        if (device == null || mProfiles == null || mCheckedItems == null
+                || mLocalAdapter == null) {
             return;
+        }
 
         // Turn on BT if something is enabled
-        synchronized (this) {
-            for (boolean enable : mCheckedItems) {
-                if (enable) {
-                    int btState = mBtManager.getBluetoothState();
-                    if(DEBUG) Log.d(TAG, "BtState = " + btState);
-                    // May have race condition as the phone comes in and out and in the dock.
-                    // Always turn on BT
-                    mBtManager.getBluetoothAdapter().enable();
-
-                    switch (btState) {
-                        case BluetoothAdapter.STATE_OFF:
-                        case BluetoothAdapter.STATE_TURNING_OFF:
-                        case BluetoothAdapter.STATE_TURNING_ON:
-                            if (mPendingDevice != null && mPendingDevice.equals(mDevice)) {
-                                return;
-                            }
-
-                            mPendingDevice = device;
-                            mPendingStartId = startId;
-                            if (btState != BluetoothAdapter.STATE_TURNING_ON) {
-                                setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED,
-                                        true);
-                            }
-                            return;
+        for (boolean enable : mCheckedItems) {
+            if (enable) {
+                int btState = mLocalAdapter.getBluetoothState();
+                if (DEBUG) {
+                    Log.d(TAG, "BtState = " + btState);
+                }
+                // May have race condition as the phone comes in and out and in the dock.
+                // Always turn on BT
+                mLocalAdapter.enable();
+
+                // if adapter was previously OFF, TURNING_OFF, or TURNING_ON
+                if (btState != BluetoothAdapter.STATE_ON) {
+                    if (mPendingDevice != null && mPendingDevice.equals(mDevice)) {
+                        return;
+                    }
+
+                    mPendingDevice = device;
+                    mPendingStartId = startId;
+                    if (btState != BluetoothAdapter.STATE_TURNING_ON) {
+                        getPrefs().edit().putBoolean(
+                                KEY_DISABLE_BT_WHEN_UNDOCKED, true).apply();
                     }
+                    return;
                 }
             }
         }
@@ -706,28 +782,26 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         mPendingDevice = null;
 
         boolean callConnect = false;
-        CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager,
+        CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(
                 device);
         for (int i = 0; i < mProfiles.length; i++) {
-            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
-                    .getProfileManager(mBtManager, mProfiles[i]);
-
-            if (DEBUG) Log.d(TAG, mProfiles[i].toString() + " = " + mCheckedItems[i]);
+            LocalBluetoothProfile profile = mProfiles[i];
+            if (DEBUG) Log.d(TAG, profile.toString() + " = " + mCheckedItems[i]);
 
             if (mCheckedItems[i]) {
                 // Checked but not connected
                 callConnect = true;
             } else if (!mCheckedItems[i]) {
                 // Unchecked, may or may not be connected.
-                int status = profileManager.getConnectionStatus(cachedDevice.getDevice());
-                if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+                int status = profile.getConnectionStatus(cachedDevice.getDevice());
+                if (status == BluetoothProfile.STATE_CONNECTED) {
                     if (DEBUG) Log.d(TAG, "applyBtSettings - Disconnecting");
                     cachedDevice.disconnect(mProfiles[i]);
                 }
             }
-            profileManager.setPreferred(device, mCheckedItems[i]);
+            profile.setPreferred(device, mCheckedItems[i]);
             if (DEBUG) {
-                if (mCheckedItems[i] != profileManager.isPreferred(device)) {
+                if (mCheckedItems[i] != profile.isPreferred(device)) {
                     Log.e(TAG, "Can't save preferred value");
                 }
             }
@@ -739,84 +813,47 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli
         }
     }
 
-    private synchronized void handleDocked(final BluetoothDevice device, final int state,
-            final int startId) {
-        if (mBtManager.getDockAutoConnectSetting(device.getAddress())) {
+    private synchronized void handleDocked(BluetoothDevice device, int state,
+            int startId) {
+        if (LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())) {
             // Setting == auto connect
-            initBtSettings(mContext, device, state, false);
+            initBtSettings(device, state, false);
             applyBtSettings(mDevice, startId);
         } else {
-            createDialog(mContext, device, state, startId);
+            createDialog(device, state, startId);
         }
     }
 
-    private synchronized void handleUndocked(Context context, LocalBluetoothManager localManager,
-            BluetoothDevice device) {
+    private synchronized void handleUndocked(BluetoothDevice device) {
         mRunnable = null;
-        LocalBluetoothProfileManager.removeServiceListener(this);
+        mProfileManager.removeServiceListener(this);
         if (mDialog != null) {
             mDialog.dismiss();
             mDialog = null;
         }
         mDevice = null;
         mPendingDevice = null;
-        CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context,
-                localManager, device);
-        cachedBluetoothDevice.disconnect();
+        CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(device);
+        cachedDevice.disconnect();
     }
 
-    private static CachedBluetoothDevice getCachedBluetoothDevice(Context context,
-            LocalBluetoothManager localManager, BluetoothDevice device) {
-        CachedBluetoothDeviceManager cachedDeviceManager = localManager.getCachedDeviceManager();
-        CachedBluetoothDevice cachedBluetoothDevice = cachedDeviceManager.findDevice(device);
-        if (cachedBluetoothDevice == null) {
-            cachedBluetoothDevice = new CachedBluetoothDevice(context, device);
+    private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+        if (cachedDevice == null) {
+            cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
         }
-        return cachedBluetoothDevice;
-    }
-
-    private boolean getSettingBool(String key) {
-        SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME,
-                Context.MODE_PRIVATE);
-        return sharedPref.getBoolean(key, false);
-    }
-
-    private int getSettingInt(String key, int defaultValue) {
-        SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME,
-                Context.MODE_PRIVATE);
-        return sharedPref.getInt(key, defaultValue);
-    }
-
-    private void setSettingBool(String key, boolean bool) {
-        SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME,
-                Context.MODE_PRIVATE).edit();
-        editor.putBoolean(key, bool);
-        editor.apply();
-    }
-
-    private void setSettingInt(String key, int value) {
-        SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME,
-                Context.MODE_PRIVATE).edit();
-        editor.putInt(key, value);
-        editor.apply();
-    }
-
-    private void removeSetting(String key) {
-        SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME,
-                Context.MODE_PRIVATE);
-        SharedPreferences.Editor editor = sharedPref.edit();
-        editor.remove(key);
-        editor.apply();
+        return cachedDevice;
     }
 
     public synchronized void onServiceConnected() {
         if (mRunnable != null) {
             mRunnable.run();
             mRunnable = null;
-            LocalBluetoothProfileManager.removeServiceListener(this);
+            mProfileManager.removeServiceListener(this);
         }
     }
 
     public void onServiceDisconnected() {
+        // FIXME: shouldn't I do something on service disconnected too?
     }
 }
diff --git a/src/com/android/settings/bluetooth/HeadsetProfile.java b/src/com/android/settings/bluetooth/HeadsetProfile.java
new file mode 100644 (file)
index 0000000..dac47b7
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HeadsetProfile handles Bluetooth HFP and Headset profiles.
+ */
+final class HeadsetProfile implements LocalBluetoothProfile {
+    private static final String TAG = "HeadsetProfile";
+
+    private BluetoothHeadset mService;
+    private boolean mProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final ParcelUuid[] UUIDS = {
+        BluetoothUuid.HSP,
+        BluetoothUuid.Handsfree,
+    };
+
+    static final String NAME = "HEADSET";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 0;
+
+    // These callbacks run on the main thread.
+    private final class HeadsetServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothHeadset) proxy;
+            mProfileReady = true;
+            // We just bound to the service, so refresh the UI of the
+            // headset device.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            if (deviceList.isEmpty()) {
+                return;
+            }
+            BluetoothDevice firstDevice = deviceList.get(0);
+            CachedBluetoothDevice device = mDeviceManager.findDevice(firstDevice);
+            // we may add a new device here, but generally this should not happen
+            if (device == null) {
+                Log.w(TAG, "HeadsetProfile found new device: " + firstDevice);
+                device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, firstDevice);
+            }
+            device.onProfileStateChanged(HeadsetProfile.this,
+                    BluetoothProfile.STATE_CONNECTED);
+
+            mProfileManager.callServiceConnectedListeners();
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mProfileReady = false;
+            mService = null;
+            mProfileManager.callServiceDisconnectedListeners();
+        }
+    }
+
+    // TODO(): The calls must get queued if mService becomes null.
+    // It can happen when the phone app crashes for some reason.
+    // All callers should have service listeners. Dock Service is the only
+    // one right now.
+    HeadsetProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(),
+                BluetoothProfile.HEADSET);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
+            // Downgrade priority as user is disconnecting the headset.
+            if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+            return mService.disconnect(device);
+        } else {
+            return false;
+        }
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public synchronized boolean isProfileReady() {
+        return mProfileReady;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource() {
+        return R.string.bluetooth_profile_headset;
+    }
+
+    public int getDisconnectResource() {
+        return R.string.bluetooth_disconnect_headset_profile;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = mService.getConnectionState(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_headset_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_headset_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_headset_hfp;
+    }
+}
diff --git a/src/com/android/settings/bluetooth/HidProfile.java b/src/com/android/settings/bluetooth/HidProfile.java
new file mode 100644 (file)
index 0000000..9185059
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/**
+ * HidProfile handles Bluetooth HID profile.
+ */
+final class HidProfile implements LocalBluetoothProfile {
+    private BluetoothInputDevice mService;
+    private boolean mProfileReady;
+
+    static final String NAME = "HID";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 3;
+
+    // These callbacks run on the main thread.
+    private final class InputDeviceServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothInputDevice) proxy;
+            mProfileReady = true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mProfileReady = false;
+            mService = null;
+        }
+    }
+
+    HidProfile(Context context, LocalBluetoothAdapter adapter) {
+        adapter.getProfileProxy(context, new InputDeviceServiceListener(),
+                BluetoothProfile.INPUT_DEVICE);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mProfileReady;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource() {
+        return R.string.bluetooth_profile_hid;
+    }
+
+    public int getDisconnectResource() {
+        return R.string.bluetooth_disconnect_hid_profile;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = mService.getConnectionState(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_hid_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_hid_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        if (btClass == null) {
+            return R.drawable.ic_bt_keyboard_hid;
+        }
+        return getHidClassDrawable(btClass);
+    }
+
+    static int getHidClassDrawable(BluetoothClass btClass) {
+        switch (btClass.getDeviceClass()) {
+            case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
+            case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
+                return R.drawable.ic_bt_keyboard_hid;
+            case BluetoothClass.Device.PERIPHERAL_POINTING:
+                return R.drawable.ic_bt_pointing_hid;
+            default:
+                return R.drawable.ic_bt_misc_hid;
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java b/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java
new file mode 100644 (file)
index 0000000..013171c
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.Set;
+
+/**
+ * LocalBluetoothAdapter provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothAdapter}, specifically
+ * those related to state transitions of the adapter itself.
+ *
+ * <p>Connection and bonding state changes affecting specific devices
+ * are handled by {@link CachedBluetoothDeviceManager},
+ * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}.
+ */
+public final class LocalBluetoothAdapter {
+    private static final String TAG = "LocalBluetoothAdapter";
+
+    /** This class does not allow direct access to the BluetoothAdapter. */
+    private final BluetoothAdapter mAdapter;
+
+    private LocalBluetoothProfileManager mProfileManager;
+
+    private static LocalBluetoothAdapter sInstance;
+
+    private int mState = BluetoothAdapter.ERROR;
+
+    private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
+
+    private long mLastScan;
+
+    private LocalBluetoothAdapter(BluetoothAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    void setProfileManager(LocalBluetoothProfileManager manager) {
+        mProfileManager = manager;
+    }
+
+    /**
+     * Get the singleton instance of the LocalBluetoothAdapter. If this device
+     * doesn't support Bluetooth, then null will be returned. Callers must be
+     * prepared to handle a null return value.
+     * @return the LocalBluetoothAdapter object, or null if not supported
+     */
+    static synchronized LocalBluetoothAdapter getInstance() {
+        if (sInstance == null) {
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (adapter != null) {
+                sInstance = new LocalBluetoothAdapter(adapter);
+            }
+        }
+
+        return sInstance;
+    }
+
+    // Pass-through BluetoothAdapter methods that we can intercept if necessary
+
+    void cancelDiscovery() {
+        mAdapter.cancelDiscovery();
+    }
+
+    boolean enable() {
+        return mAdapter.enable();
+    }
+
+    boolean disable() {
+        return mAdapter.disable();
+    }
+
+    void getProfileProxy(Context context,
+            BluetoothProfile.ServiceListener listener, int profile) {
+        mAdapter.getProfileProxy(context, listener, profile);
+    }
+
+    Set<BluetoothDevice> getBondedDevices() {
+        return mAdapter.getBondedDevices();
+    }
+
+    String getName() {
+        return mAdapter.getName();
+    }
+
+    int getScanMode() {
+        return mAdapter.getScanMode();
+    }
+
+    int getState() {
+        return mAdapter.getState();
+    }
+
+    ParcelUuid[] getUuids() {
+        return mAdapter.getUuids();
+    }
+
+    boolean isDiscovering() {
+        return mAdapter.isDiscovering();
+    }
+
+    boolean isEnabled() {
+        return mAdapter.isEnabled();
+    }
+
+    void setDiscoverableTimeout(int timeout) {
+        mAdapter.setDiscoverableTimeout(timeout);
+    }
+
+    void setName(String name) {
+        mAdapter.setName(name);
+    }
+
+    void setScanMode(int mode) {
+        mAdapter.setScanMode(mode);
+    }
+
+    boolean setScanMode(int mode, int duration) {
+        return mAdapter.setScanMode(mode, duration);
+    }
+
+    void startScanning(boolean force) {
+        // Only start if we're not already scanning
+        if (!mAdapter.isDiscovering()) {
+            if (!force) {
+                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
+                // unless forced
+                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
+                    return;
+                }
+
+                // If we are playing music, don't scan unless forced.
+                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
+                if (a2dp != null && a2dp.isA2dpPlaying()) {
+                    return;
+                }
+            }
+
+            if (mAdapter.startDiscovery()) {
+                mLastScan = System.currentTimeMillis();
+            }
+        }
+    }
+
+    void stopScanning() {
+        if (mAdapter.isDiscovering()) {
+            mAdapter.cancelDiscovery();
+        }
+    }
+
+    public synchronized int getBluetoothState() {
+        // Always sync state, in case it changed while paused
+        syncBluetoothState();
+        return mState;
+    }
+
+    synchronized void setBluetoothStateInt(int state) {
+        mState = state;
+
+        if (state == BluetoothAdapter.STATE_ON) {
+            // if mProfileManager hasn't been constructed yet, it will
+            // get the adapter UUIDs in its constructor when it is.
+            if (mProfileManager != null) {
+                mProfileManager.setBluetoothStateOn();
+            }
+        }
+    }
+
+    // Returns true if the state changed; false otherwise.
+    boolean syncBluetoothState() {
+        int currentState = mAdapter.getState();
+        if (currentState != mState) {
+            setBluetoothStateInt(mAdapter.getState());
+            return true;
+        }
+        return false;
+    }
+
+    public void setBluetoothEnabled(boolean enabled) {
+        boolean success = enabled
+                ? mAdapter.enable()
+                : mAdapter.disable();
+
+        if (success) {
+            setBluetoothStateInt(enabled
+                ? BluetoothAdapter.STATE_TURNING_ON
+                : BluetoothAdapter.STATE_TURNING_OFF);
+        } else {
+            if (Utils.V) {
+                Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
+                        "success for enabled: " + enabled);
+            }
+
+            syncBluetoothState();
+        }
+    }
+}
index f3c5e6f..0c04e22 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.R;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.ParcelUuid;
-import android.util.Config;
 import android.util.Log;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.List;
 
-// TODO: have some notion of shutting down.  Maybe a minute after they leave BT settings?
 /**
  * LocalBluetoothManager provides a simplified interface on top of a subset of
- * the Bluetooth API.
+ * the Bluetooth API. Note that {@link #getInstance} will return null
+ * if there is no Bluetooth adapter on this device, and callers must be
+ * prepared to handle this case.
  */
-public class LocalBluetoothManager {
+public final class LocalBluetoothManager {
     private static final String TAG = "LocalBluetoothManager";
-    static final boolean V = Config.LOGV;
-    static final boolean D = Config.LOGD;
-
-    private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
 
     /** Singleton instance. */
     private static LocalBluetoothManager sInstance;
 
-    private Context mContext;
-    /** If a BT-related activity is in the foreground, this will be it. */
-    private Activity mForegroundActivity;
-    private AlertDialog mErrorDialog = null;
-
-    private BluetoothAdapter mAdapter;
-
-    private CachedBluetoothDeviceManager mCachedDeviceManager;
-    private BluetoothA2dp mBluetoothA2dp;
-
-    private int mState = BluetoothAdapter.ERROR;
+    private final Context mContext;
 
-    private final List<Callback> mCallbacks = new ArrayList<Callback>();
-
-    private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
-
-    // If a device was picked from the device picker or was in discoverable mode
-    // in the last 60 seconds, show the pairing dialogs in foreground instead
-    // of raising notifications
-    private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000;
-
-    public static final String SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP =
-        "last_discovering_time";
-
-    private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE =
-        "last_selected_device";
+    /** If a BT-related activity is in the foreground, this will be it. */
+    private Context mForegroundActivity;
 
-    private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME =
-        "last_selected_device_time";
+    private final LocalBluetoothAdapter mLocalAdapter;
 
-    private static final String SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock";
+    private final CachedBluetoothDeviceManager mCachedDeviceManager;
 
-    private long mLastScan;
+    /** The Bluetooth profile manager. */
+    private final LocalBluetoothProfileManager mProfileManager;
 
-    private LocalBluetoothManager() { }
+    /** The broadcast receiver event manager. */
+    private final BluetoothEventManager mEventManager;
 
-    public static LocalBluetoothManager getInstance(Context context) {
-        synchronized (LocalBluetoothManager.class) {
-            if (sInstance == null) {
-                sInstance = new LocalBluetoothManager();
-                if (!sInstance.init(context)) {
-                    return null;
-                }
-                LocalBluetoothProfileManager.init(sInstance);
+    public static synchronized LocalBluetoothManager getInstance(Context context) {
+        if (sInstance == null) {
+            LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+            if (adapter == null) {
+                return null;
             }
-
-            return sInstance;
-        }
-    }
-
-    private boolean init(Context context) {
-        // This will be around as long as this process is
-        mContext = context.getApplicationContext();
-
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        if (mAdapter == null) {
-            return false;
+            // This will be around as long as this process is
+            Context appContext = context.getApplicationContext();
+            sInstance = new LocalBluetoothManager(adapter, appContext);
         }
 
-        mCachedDeviceManager = new CachedBluetoothDeviceManager(this);
-
-        new BluetoothEventRedirector(this).registerReceiver();
+        return sInstance;
+    }
 
-        mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+    private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
+        mContext = context;
+        mLocalAdapter = adapter;
 
-        return true;
+        mCachedDeviceManager = new CachedBluetoothDeviceManager();
+        mEventManager = new BluetoothEventManager(mLocalAdapter,
+                mCachedDeviceManager);
+        mProfileManager = new LocalBluetoothProfileManager(context,
+                mLocalAdapter, mCachedDeviceManager, mEventManager);
     }
 
-    private final BluetoothProfile.ServiceListener mProfileListener =
-      new BluetoothProfile.ServiceListener() {
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mBluetoothA2dp = (BluetoothA2dp) proxy;
-        }
-        public void onServiceDisconnected(int profile) {
-            mBluetoothA2dp = null;
-        }
-    };
-
-    public BluetoothAdapter getBluetoothAdapter() {
-        return mAdapter;
+    public LocalBluetoothAdapter getBluetoothAdapter() {
+        return mLocalAdapter;
     }
 
     public Context getContext() {
         return mContext;
     }
 
-    public Activity getForegroundActivity() {
-        return mForegroundActivity;
-    }
-
-    public void setForegroundActivity(Activity activity) {
-        if (mErrorDialog != null) {
-            mErrorDialog.dismiss();
-            mErrorDialog = null;
-        }
-        mForegroundActivity = activity;
+    boolean isForegroundActivity() {
+        return mForegroundActivity != null;
     }
 
-    public SharedPreferences getSharedPreferences() {
-        return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
-    }
-
-    public CachedBluetoothDeviceManager getCachedDeviceManager() {
-        return mCachedDeviceManager;
-    }
-
-    List<Callback> getCallbacks() {
-        return mCallbacks;
-    }
-
-    public void registerCallback(Callback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.add(callback);
-        }
-    }
-
-    public void unregisterCallback(Callback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.remove(callback);
-        }
-    }
-
-    public void startScanning(boolean force) {
-        if (mAdapter.isDiscovering()) {
-            /*
-             * Already discovering, but give the callback that information.
-             * Note: we only call the callbacks, not the same path as if the
-             * scanning state had really changed (in that case the device
-             * manager would clear its list of unpaired scanned devices).
-             */
-            dispatchScanningStateChanged(true);
+    synchronized void setForegroundActivity(Context context) {
+        if (context != null) {
+            Log.d(TAG, "setting foreground activity to non-null context");
+            mForegroundActivity = context;
+            mEventManager.resume(context);
         } else {
-            if (!force) {
-                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
-                // unless forced
-                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
-                    return;
-                }
-
-                // If we are playing music, don't scan unless forced.
-                if (mBluetoothA2dp != null) {
-                    List<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedDevices();
-                    if (sinks.size() > 0) {
-                        if (mBluetoothA2dp.isA2dpPlaying(sinks.get(0))) return;
-                    }
-                }
+            if (mForegroundActivity != null) {
+                Log.d(TAG, "setting foreground activity to null");
+                mEventManager.pause(mForegroundActivity);
+                mForegroundActivity = null;
             }
-
-            if (mAdapter.startDiscovery()) {
-                mLastScan = System.currentTimeMillis();
-            }
-        }
-    }
-
-    public void stopScanning() {
-        if (mAdapter.isDiscovering()) {
-            mAdapter.cancelDiscovery();
-        }
-    }
-
-    public int getBluetoothState() {
-
-        if (mState == BluetoothAdapter.ERROR) {
-            syncBluetoothState();
         }
-
-        return mState;
     }
 
-    void setBluetoothStateInt(int state) {
-        mState = state;
-
-        if (state == BluetoothAdapter.STATE_ON) {
-            ParcelUuid[] uuids = mAdapter.getUuids();
-            LocalBluetoothProfileManager.updateLocalProfiles(getInstance(mContext), uuids);
-        }
-
-        if (state == BluetoothAdapter.STATE_ON ||
-            state == BluetoothAdapter.STATE_OFF) {
-            mCachedDeviceManager.onBluetoothStateChanged(state ==
-                    BluetoothAdapter.STATE_ON);
-        }
-    }
-
-    private void syncBluetoothState() {
-        int bluetoothState;
-
-        if (mAdapter != null) {
-            bluetoothState = mAdapter.isEnabled()
-                    ? BluetoothAdapter.STATE_ON
-                    : BluetoothAdapter.STATE_OFF;
-        } else {
-            bluetoothState = BluetoothAdapter.ERROR;
-        }
-
-        setBluetoothStateInt(bluetoothState);
-    }
-
-    public void setBluetoothEnabled(boolean enabled) {
-        boolean wasSetStateSuccessful = enabled
-                ? mAdapter.enable()
-                : mAdapter.disable();
-
-        if (wasSetStateSuccessful) {
-            setBluetoothStateInt(enabled
-                ? BluetoothAdapter.STATE_TURNING_ON
-                : BluetoothAdapter.STATE_TURNING_OFF);
-        } else {
-            if (V) {
-                Log.v(TAG,
-                        "setBluetoothEnabled call, manager didn't return success for enabled: "
-                                + enabled);
-            }
-
-            syncBluetoothState();
-        }
-    }
-
-    /**
-     * @param started True if scanning started, false if scanning finished.
-     */
-    void onScanningStateChanged(boolean started) {
-        // TODO: have it be a callback (once we switch bluetooth state changed to callback)
-        mCachedDeviceManager.onScanningStateChanged(started);
-        dispatchScanningStateChanged(started);
-    }
-
-    private void dispatchScanningStateChanged(boolean started) {
-        synchronized (mCallbacks) {
-            for (Callback callback : mCallbacks) {
-                callback.onScanningStateChanged(started);
-            }
-        }
-    }
-
-    public void showError(BluetoothDevice device, int messageResId) {
-        CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
-        String name = null;
-        if (cachedDevice == null) {
-            if (device != null) name = device.getName();
-
-            if (name == null) {
-                name = mContext.getString(R.string.bluetooth_remote_device);
-            }
-        } else {
-            name = cachedDevice.getName();
-        }
-        String message = mContext.getString(messageResId, name);
-
-        if (mForegroundActivity != null) {
-            // Need an activity context to show a dialog
-            mErrorDialog = new AlertDialog.Builder(mForegroundActivity)
-                .setIcon(android.R.drawable.ic_dialog_alert)
-                .setTitle(R.string.bluetooth_error_title)
-                .setMessage(message)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-        } else {
-            // Fallback on a toast
-            Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
-        }
-    }
-
-    public interface Callback {
-        void onScanningStateChanged(boolean started);
-        void onDeviceAdded(CachedBluetoothDevice cachedDevice);
-        void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
-        void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
-    }
-
-    public boolean shouldShowDialogInForeground(String deviceAddress) {
-        // If Bluetooth Settings is visible
-        if (mForegroundActivity != null) return true;
-
-        long currentTimeMillis = System.currentTimeMillis();
-        SharedPreferences sharedPreferences = getSharedPreferences();
-
-        // If the device was in discoverABLE mode recently
-        long lastDiscoverableEndTime = sharedPreferences.getLong(
-                BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
-        if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
-                > currentTimeMillis) {
-            return true;
-        }
-
-        // If the device was discoverING recently
-        if (mAdapter != null && mAdapter.isDiscovering()) {
-            return true;
-        } else if ((sharedPreferences.getLong(SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, 0) +
-                GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) {
-            return true;
-        }
-
-        // If the device was picked in the device picker recently
-        if (deviceAddress != null) {
-            String lastSelectedDevice = sharedPreferences.getString(
-                    SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, null);
-
-            if (deviceAddress.equals(lastSelectedDevice)) {
-                long lastDeviceSelectedTime = sharedPreferences.getLong(
-                        SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 0);
-                if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
-                        > currentTimeMillis) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    void persistSelectedDeviceInPicker(String deviceAddress) {
-        SharedPreferences.Editor editor = getSharedPreferences().edit();
-        editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE,
-                deviceAddress);
-        editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME,
-                System.currentTimeMillis());
-        editor.apply();
-    }
-
-    public boolean hasDockAutoConnectSetting(String addr) {
-        return getSharedPreferences().contains(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
-    }
-
-    public boolean getDockAutoConnectSetting(String addr) {
-        return getSharedPreferences().getBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr,
-                false);
+    CachedBluetoothDeviceManager getCachedDeviceManager() {
+        return mCachedDeviceManager;
     }
 
-    public void saveDockAutoConnectSetting(String addr, boolean autoConnect) {
-        SharedPreferences.Editor editor = getSharedPreferences().edit();
-        editor.putBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, autoConnect);
-        editor.apply();
+    BluetoothEventManager getEventManager() {
+        return mEventManager;
     }
 
-    public void removeDockAutoConnectSetting(String addr) {
-        SharedPreferences.Editor editor = getSharedPreferences().edit();
-        editor.remove(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
-        editor.apply();
+    LocalBluetoothProfileManager getProfileManager() {
+        return mProfileManager;
     }
 }
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java
new file mode 100644 (file)
index 0000000..7e62b0e
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.app.QueuedWork;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * LocalBluetoothPreferences provides an interface to the preferences
+ * related to Bluetooth.
+ */
+final class LocalBluetoothPreferences {
+//    private static final String TAG = "LocalBluetoothPreferences";
+
+    private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
+
+    // If a device was picked from the device picker or was in discoverable mode
+    // in the last 60 seconds, show the pairing dialogs in foreground instead
+    // of raising notifications
+    private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000;
+
+    private static final String KEY_DISCOVERING_TIMESTAMP = "last_discovering_time";
+
+    private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device";
+
+    private static final String KEY_LAST_SELECTED_DEVICE_TIME = "last_selected_device_time";
+
+    private static final String KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock";
+
+    private static final String KEY_DISCOVERABLE_END_TIMESTAMP = "discoverable_end_timestamp";
+
+    private LocalBluetoothPreferences() {
+    }
+
+    private static SharedPreferences getSharedPreferences(Context context) {
+        return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+    }
+
+    static long getDiscoverableEndTimestamp(Context context) {
+        return getSharedPreferences(context).getLong(
+                KEY_DISCOVERABLE_END_TIMESTAMP, 0);
+    }
+
+    static boolean shouldShowDialogInForeground(Context context,
+            String deviceAddress) {
+        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
+        if (manager == null) {
+            return false;
+        }
+
+        // If Bluetooth Settings is visible
+        if (manager.isForegroundActivity()) {
+            return true;
+        }
+
+        long currentTimeMillis = System.currentTimeMillis();
+        SharedPreferences sharedPreferences = getSharedPreferences(context);
+
+        // If the device was in discoverABLE mode recently
+        long lastDiscoverableEndTime = sharedPreferences.getLong(
+                KEY_DISCOVERABLE_END_TIMESTAMP, 0);
+        if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
+                > currentTimeMillis) {
+            return true;
+        }
+
+        // If the device was discoverING recently
+        LocalBluetoothAdapter adapter = manager.getBluetoothAdapter();
+        if (adapter != null && adapter.isDiscovering()) {
+            return true;
+        } else if ((sharedPreferences.getLong(KEY_DISCOVERING_TIMESTAMP, 0) +
+                GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) {
+            return true;
+        }
+
+        // If the device was picked in the device picker recently
+        if (deviceAddress != null) {
+            String lastSelectedDevice = sharedPreferences.getString(
+                    KEY_LAST_SELECTED_DEVICE, null);
+
+            if (deviceAddress.equals(lastSelectedDevice)) {
+                long lastDeviceSelectedTime = sharedPreferences.getLong(
+                        KEY_LAST_SELECTED_DEVICE_TIME, 0);
+                if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
+                        > currentTimeMillis) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    static void persistSelectedDeviceInPicker(Context context, String deviceAddress) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putString(KEY_LAST_SELECTED_DEVICE,
+                deviceAddress);
+        editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME,
+                System.currentTimeMillis());
+        editor.apply();
+    }
+
+    static void persistDiscoverableEndTimestamp(Context context, long endTimestamp) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putLong(KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp);
+        editor.apply();
+    }
+
+    static void persistDiscoveringTimestamp(final Context context) {
+        // Load the shared preferences and edit it on a background
+        // thread (but serialized!).
+        QueuedWork.singleThreadExecutor().submit(new Runnable() {
+                public void run() {
+                    SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+                    editor.putLong(
+                            KEY_DISCOVERING_TIMESTAMP,
+                        System.currentTimeMillis());
+                    editor.apply();
+                }
+            });
+    }
+
+    static boolean hasDockAutoConnectSetting(Context context, String addr) {
+        return getSharedPreferences(context).contains(KEY_DOCK_AUTO_CONNECT + addr);
+    }
+
+    static boolean getDockAutoConnectSetting(Context context, String addr) {
+        return getSharedPreferences(context).getBoolean(KEY_DOCK_AUTO_CONNECT + addr,
+                false);
+    }
+
+    static void saveDockAutoConnectSetting(Context context, String addr, boolean autoConnect) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.putBoolean(KEY_DOCK_AUTO_CONNECT + addr, autoConnect);
+        editor.apply();
+    }
+
+    static void removeDockAutoConnectSetting(Context context, String addr) {
+        SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+        editor.remove(KEY_DOCK_AUTO_CONNECT + addr);
+        editor.apply();
+    }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfile.java b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java
new file mode 100644 (file)
index 0000000..936231a
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * LocalBluetoothProfile is an interface defining the basic
+ * functionality related to a Bluetooth profile.
+ */
+interface LocalBluetoothProfile {
+
+    /**
+     * Returns true if the user can initiate a connection, false otherwise.
+     */
+    boolean isConnectable();
+
+    /**
+     * Returns true if the user can enable auto connection for this profile.
+     */
+    boolean isAutoConnectable();
+
+    boolean connect(BluetoothDevice device);
+
+    boolean disconnect(BluetoothDevice device);
+
+    int getConnectionStatus(BluetoothDevice device);
+
+    boolean isPreferred(BluetoothDevice device);
+
+    int getPreferred(BluetoothDevice device);
+
+    void setPreferred(BluetoothDevice device, boolean preferred);
+
+    boolean isProfileReady();
+
+    /** Display order for device profile settings. */
+    int getOrdinal();
+
+    /**
+     * Returns the string resource ID for the localized name for this profile.
+     */
+    int getNameResource();
+
+    /**
+     * Returns the string resource ID for the disconnect confirmation text
+     * for this profile.
+     */
+    int getDisconnectResource();
+
+    /**
+     * Returns the string resource ID for the summary text for this profile
+     * for the specified device, e.g. "Use for media audio" or
+     * "Connected to media audio".
+     * @param device the device to query for profile connection status
+     * @return a string resource ID for the profile summary text
+     */
+    int getSummaryResourceForDevice(BluetoothDevice device);
+
+    int getDrawableResource(BluetoothClass btClass);
+}
index 164d2da..0bb6f11 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.R;
-
 import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
-import android.os.Handler;
+import android.content.Context;
+import android.content.Intent;
 import android.os.ParcelUuid;
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 
 /**
- * LocalBluetoothProfileManager is an abstract class defining the basic
- * functionality related to a profile.
+ * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
+ * objects for the available Bluetooth profiles.
  */
-abstract class LocalBluetoothProfileManager {
+final class LocalBluetoothProfileManager {
     private static final String TAG = "LocalBluetoothProfileManager";
 
-    /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.HSP,
-        BluetoothUuid.Handsfree,
-    };
-
-    /* package */ static final ParcelUuid[] A2DP_SINK_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.AudioSink,
-        BluetoothUuid.AdvAudioDist,
-    };
-
-    /* package */ static final ParcelUuid[] A2DP_SRC_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.AudioSource
-    };
-
-    /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.ObexObjectPush
-    };
-
-    /* package */ static final ParcelUuid[] HID_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.Hid
-    };
-
-    /* package */ static final ParcelUuid[] PANU_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.PANU
-    };
-
-    /* package */ static final ParcelUuid[] NAP_PROFILE_UUIDS = new ParcelUuid[] {
-        BluetoothUuid.NAP
-    };
+    /** Singleton instance. */
+    private static LocalBluetoothProfileManager sInstance;
 
     /**
      * An interface for notifying BluetoothHeadset IPC clients when they have
      * been connected to the BluetoothHeadset service.
+     * Only used by {@link DockService}.
      */
     public interface ServiceListener {
         /**
@@ -85,7 +55,7 @@ abstract class LocalBluetoothProfileManager {
          * this callback before making IPC calls on the BluetoothHeadset
          * service.
          */
-        public void onServiceConnected();
+        void onServiceConnected();
 
         /**
          * Called to notify the client that this proxy object has been
@@ -94,743 +64,246 @@ abstract class LocalBluetoothProfileManager {
          * This callback will currently only occur if the application hosting
          * the BluetoothHeadset service, but may be called more often in future.
          */
-        public void onServiceDisconnected();
-    }
-
-    // TODO: close profiles when we're shutting down
-    private static final Map<Profile, LocalBluetoothProfileManager> sProfileMap =
-            new HashMap<Profile, LocalBluetoothProfileManager>();
-
-    protected final LocalBluetoothManager mLocalManager;
-
-    public static void init(LocalBluetoothManager localManager) {
-        synchronized (sProfileMap) {
-            if (sProfileMap.size() == 0) {
-                LocalBluetoothProfileManager profileManager;
-
-                profileManager = new A2dpProfileManager(localManager);
-                sProfileMap.put(Profile.A2DP, profileManager);
-
-                profileManager = new HeadsetProfileManager(localManager);
-                sProfileMap.put(Profile.HEADSET, profileManager);
-
-                profileManager = new OppProfileManager(localManager);
-                sProfileMap.put(Profile.OPP, profileManager);
-
-                profileManager = new HidProfileManager(localManager);
-                sProfileMap.put(Profile.HID, profileManager);
-
-                profileManager = new PanProfileManager(localManager);
-                sProfileMap.put(Profile.PAN, profileManager);
-            }
-        }
+        void onServiceDisconnected();
     }
 
-    // TODO(): Combine the init and updateLocalProfiles codes.
-    // init can get called from various paths, it makes no sense to add and then delete.
-    public static void updateLocalProfiles(LocalBluetoothManager localManager, ParcelUuid[] uuids) {
-        if (!BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) &&
-            !BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
-            sProfileMap.remove(Profile.HEADSET);
-        }
-
-        if (!BluetoothUuid.containsAnyUuid(uuids, A2DP_SRC_PROFILE_UUIDS)) {
-            sProfileMap.remove(Profile.A2DP);
-        }
+    private final Context mContext;
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final BluetoothEventManager mEventManager;
 
-        if (!BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) {
-            sProfileMap.remove(Profile.OPP);
-        }
-
-        // There is no local SDP record for HID and Settings app doesn't control PBAP
-    }
-
-    private static final LinkedList<ServiceListener> mServiceListeners =
-            new LinkedList<ServiceListener>();
-
-    public static void addServiceListener(ServiceListener l) {
-        mServiceListeners.add(l);
-    }
-
-    public static void removeServiceListener(ServiceListener l) {
-        mServiceListeners.remove(l);
-    }
-
-    public static boolean isManagerReady() {
-        // Getting just the headset profile is fine for now. Will need to deal with A2DP
-        // and others if they aren't always in a ready state.
-        LocalBluetoothProfileManager profileManager = sProfileMap.get(Profile.HEADSET);
-        if (profileManager == null) {
-            return sProfileMap.size() > 0;
-        }
-        return profileManager.isProfileReady();
-    }
-
-    public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
-            Profile profile) {
-        // Note: This code assumes that "localManager" is same as the
-        // LocalBluetoothManager that was used to initialize the sProfileMap.
-        // If that every changes, we can't just keep one copy of sProfileMap.
-        synchronized (sProfileMap) {
-            LocalBluetoothProfileManager profileManager = sProfileMap.get(profile);
-            if (profileManager == null) {
-                Log.e(TAG, "profileManager can't be found for " + profile.toString());
-            }
-            return profileManager;
-        }
-    }
+    private A2dpProfile mA2dpProfile;
+    private HeadsetProfile mHeadsetProfile;
+    private final HidProfile mHidProfile;
+    private OppProfile mOppProfile;
+    private final PanProfile mPanProfile;
 
     /**
-     * Temporary method to fill profiles based on a device's class.
-     *
-     * NOTE: This list happens to define the connection order. We should put this logic in a more
-     * well known place when this method is no longer temporary.
-     * @param uuids of the remote device
-     * @param localUuids UUIDs of the local device
-     * @param profiles The list of profiles to fill
+     * Mapping from profile name, e.g. "HEADSET" to profile object.
      */
-    public static void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
-        List<Profile> profiles) {
-        profiles.clear();
+    private final Map<String, LocalBluetoothProfile>
+            mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
 
-        if (uuids == null) {
-            return;
-        }
-
-        if (sProfileMap.containsKey(Profile.HEADSET)) {
-            if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
-                BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
-                (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
-                BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
-                    profiles.add(Profile.HEADSET);
-            }
-        }
+    LocalBluetoothProfileManager(Context context,
+            LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            BluetoothEventManager eventManager) {
+        mContext = context;
 
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mEventManager = eventManager;
+        // pass this reference to adapter and event manager (circular dependency)
+        mLocalAdapter.setProfileManager(this);
+        mEventManager.setProfileManager(this);
 
-        if (BluetoothUuid.containsAnyUuid(uuids, A2DP_SINK_PROFILE_UUIDS) &&
-            sProfileMap.containsKey(Profile.A2DP)) {
-            profiles.add(Profile.A2DP);
-        }
+        ParcelUuid[] uuids = adapter.getUuids();
 
-        if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS) &&
-            sProfileMap.containsKey(Profile.OPP)) {
-            profiles.add(Profile.OPP);
+        // uuids may be null if Bluetooth is turned off
+        if (uuids != null) {
+            updateLocalProfiles(uuids);
         }
 
-        if (BluetoothUuid.containsAnyUuid(uuids, HID_PROFILE_UUIDS) &&
-            sProfileMap.containsKey(Profile.HID)) {
-            profiles.add(Profile.HID);
-        }
+        // Always add HID and PAN profiles
+        mHidProfile = new HidProfile(context, mLocalAdapter);
+        addProfile(mHidProfile, HidProfile.NAME,
+                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
 
-        if (BluetoothUuid.containsAnyUuid(uuids, NAP_PROFILE_UUIDS) &&
-            sProfileMap.containsKey(Profile.PAN)) {
-            profiles.add(Profile.PAN);
-        }
-    }
-
-    protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
-        mLocalManager = localManager;
-    }
-
-    public abstract List<BluetoothDevice> getConnectedDevices();
-
-    public abstract boolean connect(BluetoothDevice device);
-
-    public abstract boolean disconnect(BluetoothDevice device);
-
-    public abstract int getConnectionStatus(BluetoothDevice device);
-
-    public abstract int getSummary(BluetoothDevice device);
-
-    public abstract int convertState(int a2dpState);
-
-    public abstract boolean isPreferred(BluetoothDevice device);
-
-    public abstract int getPreferred(BluetoothDevice device);
-
-    public abstract void setPreferred(BluetoothDevice device, boolean preferred);
-
-    public boolean isConnected(BluetoothDevice device) {
-        return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device));
-    }
-
-    public abstract boolean isProfileReady();
-
-    public abstract int getDrawableResource();
-
-    public static enum Profile {
-        HEADSET(R.string.bluetooth_profile_headset, true, true),
-        A2DP(R.string.bluetooth_profile_a2dp, true, true),
-        OPP(R.string.bluetooth_profile_opp, false, false),
-        HID(R.string.bluetooth_profile_hid, true, true),
-        PAN(R.string.bluetooth_profile_pan, true, false);
-
-        public final int localizedString;
-        private final boolean mConnectable;
-        private final boolean mAutoConnectable;
-
-        private Profile(int localizedString, boolean connectable,
-                boolean autoConnectable) {
-            this.localizedString = localizedString;
-            this.mConnectable = connectable;
-            this.mAutoConnectable = autoConnectable;
-        }
-
-        public boolean isConnectable() {
-            return mConnectable;
-        }
-
-        public boolean isAutoConnectable() {
-            return mAutoConnectable;
-        }
+        mPanProfile = new PanProfile(context);
+        addProfile(mPanProfile, PanProfile.NAME, BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
+        Log.d(TAG, "LocalBluetoothProfileManager construction complete");
     }
 
     /**
-     * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
+     * Initialize or update the local profile objects. If a UUID was previously
+     * present but has been removed, we print a warning but don't remove the
+     * profile object as it might be referenced elsewhere, or the UUID might
+     * come back and we don't want multiple copies of the profile objects.
+     * @param uuids
      */
-    private static class A2dpProfileManager extends LocalBluetoothProfileManager
-          implements BluetoothProfile.ServiceListener {
-        private BluetoothA2dp mService;
-
-        // TODO(): The calls must wait for mService. Its not null just
-        // because it runs in the system server.
-        public A2dpProfileManager(LocalBluetoothManager localManager) {
-            super(localManager);
-            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-            adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.A2DP);
-
-        }
-
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mService = (BluetoothA2dp) proxy;
-        }
-
-        public void onServiceDisconnected(int profile) {
-            mService = null;
-        }
-
-        @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            return mService.getDevicesMatchingConnectionStates(
-                  new int[] {BluetoothProfile.STATE_CONNECTED,
-                             BluetoothProfile.STATE_CONNECTING,
-                             BluetoothProfile.STATE_DISCONNECTING});
-        }
-
-        @Override
-        public boolean connect(BluetoothDevice device) {
-            List<BluetoothDevice> sinks = getConnectedDevices();
-            if (sinks != null) {
-                for (BluetoothDevice sink : sinks) {
-                    mService.disconnect(sink);
-                }
+    void updateLocalProfiles(ParcelUuid[] uuids) {
+        // A2DP
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
+            if (mA2dpProfile == null) {
+                Log.d(TAG, "Adding local A2DP profile");
+                mA2dpProfile = new A2dpProfile(mContext);
+                addProfile(mA2dpProfile, A2dpProfile.NAME,
+                        BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
             }
-            return mService.connect(device);
-        }
-
-        @Override
-        public boolean disconnect(BluetoothDevice device) {
-            // Downgrade priority as user is disconnecting the sink.
-            if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        } else if (mA2dpProfile != null) {
+            Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
+        }
+
+        // Headset / Handsfree
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
+            BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
+            if (mHeadsetProfile == null) {
+                Log.d(TAG, "Adding local HEADSET profile");
+                mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
+                        mDeviceManager, this);
+                addProfile(mHeadsetProfile, HeadsetProfile.NAME,
+                        BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
             }
-            return mService.disconnect(device);
+        } else if (mHeadsetProfile != null) {
+            Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
         }
 
-        @Override
-        public int getConnectionStatus(BluetoothDevice device) {
-            return convertState(mService.getConnectionState(device));
-        }
-
-        @Override
-        public int getSummary(BluetoothDevice device) {
-            int connectionStatus = getConnectionStatus(device);
-
-            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
-                return R.string.bluetooth_a2dp_profile_summary_connected;
-            } else {
-                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+        // OPP
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+            if (mOppProfile == null) {
+                Log.d(TAG, "Adding local OPP profile");
+                mOppProfile = new OppProfile();
+                // Note: no event handler for OPP, only name map.
+                mProfileNameMap.put(OppProfile.NAME, mOppProfile);
             }
+        } else if (mOppProfile != null) {
+            Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
         }
 
-        @Override
-        public boolean isPreferred(BluetoothDevice device) {
-            return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
-        }
-
-        @Override
-        public int getPreferred(BluetoothDevice device) {
-            return mService.getPriority(device);
-        }
-
-        @Override
-        public void setPreferred(BluetoothDevice device, boolean preferred) {
-            if (preferred) {
-                if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
-                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-                }
-            } else {
-                mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
-            }
-        }
-
-        @Override
-        public int convertState(int a2dpState) {
-            switch (a2dpState) {
-            case BluetoothProfile.STATE_CONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
-            case BluetoothProfile.STATE_CONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
-            case BluetoothProfile.STATE_DISCONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
-            case BluetoothProfile.STATE_DISCONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
-            case BluetoothA2dp.STATE_PLAYING:
-                return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
-            default:
-                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
-            }
-        }
-
-        @Override
-        public boolean isProfileReady() {
-            return true;
-        }
-
-        @Override
-        public int getDrawableResource() {
-            return R.drawable.ic_bt_headphones_a2dp;
-        }
+        // There is no local SDP record for HID and Settings app doesn't control PBAP
     }
 
-    /**
-     * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
-     */
-    private static class HeadsetProfileManager extends LocalBluetoothProfileManager
-            implements BluetoothProfile.ServiceListener {
-        private BluetoothHeadset mService;
-        private final Handler mUiHandler = new Handler();
-        private boolean profileReady = false;
-
-        // TODO(): The calls must get queued if mService becomes null.
-        // It can happen  when phone app crashes for some reason.
-        // All callers should have service listeners. Dock Service is the only
-        // one right now.
-        public HeadsetProfileManager(LocalBluetoothManager localManager) {
-            super(localManager);
-            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-            adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.HEADSET);
-        }
-
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mService = (BluetoothHeadset) proxy;
-            profileReady = true;
-            // This could be called on a non-UI thread, funnel to UI thread.
-            mUiHandler.post(new Runnable() {
-                public void run() {
-                    /*
-                     * We just bound to the service, so refresh the UI of the
-                     * headset device.
-                     */
-                    List<BluetoothDevice> deviceList = mService.getConnectedDevices();
-                    if (deviceList.size() == 0) return;
-
-                    mLocalManager.getCachedDeviceManager()
-                            .onProfileStateChanged(deviceList.get(0), Profile.HEADSET,
-                                                   BluetoothProfile.STATE_CONNECTED);
-                }
-            });
-
-            if (mServiceListeners.size() > 0) {
-                Iterator<ServiceListener> it = mServiceListeners.iterator();
-                while(it.hasNext()) {
-                    it.next().onServiceConnected();
-                }
-            }
-        }
-
-        public void onServiceDisconnected(int profile) {
-            mService = null;
-            profileReady = false;
-            if (mServiceListeners.size() > 0) {
-                Iterator<ServiceListener> it = mServiceListeners.iterator();
-                while(it.hasNext()) {
-                    it.next().onServiceDisconnected();
-                }
-            }
-        }
-
-        @Override
-        public boolean isProfileReady() {
-            return profileReady;
-        }
-
-        @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            if (mService != null) {
-                return mService.getConnectedDevices();
-            } else {
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
-
-        @Override
-        public boolean connect(BluetoothDevice device) {
-            List<BluetoothDevice> sinks = getConnectedDevices();
-            if (sinks != null) {
-                for (BluetoothDevice sink : sinks) {
-                    mService.disconnect(sink);
-                }
-            }
-            return mService.connect(device);
-        }
-
-        @Override
-        public boolean disconnect(BluetoothDevice device) {
-            List<BluetoothDevice> deviceList = getConnectedDevices();
-            if (deviceList.size() != 0 && deviceList.get(0).equals(device)) {
-                // Downgrade priority as user is disconnecting the headset.
-                if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-                }
-                return mService.disconnect(device);
-            } else {
-                return false;
-            }
-        }
-
-        @Override
-        public int getConnectionStatus(BluetoothDevice device) {
-            List<BluetoothDevice> deviceList = getConnectedDevices();
-
-            return deviceList.size() > 0 && deviceList.get(0).equals(device)
-                    ? convertState(mService.getConnectionState(device))
-                    : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
-        }
-
-        @Override
-        public int getSummary(BluetoothDevice device) {
-            int connectionStatus = getConnectionStatus(device);
+    private final Collection<ServiceListener> mServiceListeners =
+            new ArrayList<ServiceListener>();
 
-            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
-                return R.string.bluetooth_headset_profile_summary_connected;
-            } else {
-                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
-            }
-        }
-
-        @Override
-        public boolean isPreferred(BluetoothDevice device) {
-            return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
-        }
-
-        @Override
-        public int getPreferred(BluetoothDevice device) {
-            return mService.getPriority(device);
-        }
-
-        @Override
-        public void setPreferred(BluetoothDevice device, boolean preferred) {
-            if (preferred) {
-                if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
-                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-                }
-            } else {
-                mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
-            }
-        }
+    private void addProfile(LocalBluetoothProfile profile,
+            String profileName, String stateChangedAction) {
+        mEventManager.addHandler(stateChangedAction, new StateChangedHandler(profile));
+        mProfileNameMap.put(profileName, profile);
+    }
 
-        @Override
-        public int convertState(int headsetState) {
-            switch (headsetState) {
-            case BluetoothProfile.STATE_CONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
-            case BluetoothProfile.STATE_CONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
-            case BluetoothProfile.STATE_DISCONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
-            default:
-                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
-            }
-        }
+    LocalBluetoothProfile getProfileByName(String name) {
+        return mProfileNameMap.get(name);
+    }
 
-        @Override
-        public int getDrawableResource() {
-            return R.drawable.ic_bt_headset_hfp;
+    // Called from LocalBluetoothAdapter when state changes to ON
+    void setBluetoothStateOn() {
+        ParcelUuid[] uuids = mLocalAdapter.getUuids();
+        if (uuids != null) {
+            updateLocalProfiles(uuids);
         }
+        mEventManager.readPairedDevices();
     }
 
     /**
-     * OppProfileManager
+     * Generic handler for connection state change events for the specified profile.
      */
-    private static class OppProfileManager extends LocalBluetoothProfileManager {
-
-        public OppProfileManager(LocalBluetoothManager localManager) {
-            super(localManager);
-        }
-
-        @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            return null;
-        }
-
-        @Override
-        public boolean connect(BluetoothDevice device) {
-            return false;
-        }
+    private class StateChangedHandler implements BluetoothEventManager.Handler {
+        private final LocalBluetoothProfile mProfile;
 
-        @Override
-        public boolean disconnect(BluetoothDevice device) {
-            return false;
+        StateChangedHandler(LocalBluetoothProfile profile) {
+            mProfile = profile;
         }
 
-        @Override
-        public int getConnectionStatus(BluetoothDevice device) {
-            return -1;
-        }
-
-        @Override
-        public int getSummary(BluetoothDevice device) {
-            int connectionStatus = getConnectionStatus(device);
-
-            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
-                return R.string.bluetooth_opp_profile_summary_connected;
-            } else {
-                return R.string.bluetooth_opp_profile_summary_not_connected;
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "StateChangedHandler found new device: " + device);
+                cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
+                        LocalBluetoothProfileManager.this, device);
             }
-        }
-
-        @Override
-        public boolean isPreferred(BluetoothDevice device) {
-            return false;
-        }
-
-        @Override
-        public int getPreferred(BluetoothDevice device) {
-            return -1;
-        }
-
-        @Override
-        public void setPreferred(BluetoothDevice device, boolean preferred) {
-        }
-
-        @Override
-        public boolean isProfileReady() {
-            return true;
-        }
-
-        @Override
-        public int convertState(int oppState) {
-            switch (oppState) {
-            case 0:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
-            case 1:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
-            case 2:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
-            default:
-                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+            int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+            int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
+            if (newState == BluetoothProfile.STATE_DISCONNECTED &&
+                    oldState == BluetoothProfile.STATE_CONNECTING) {
+                Log.i(TAG, "Failed to connect " + mProfile + " device");
             }
-        }
 
-        @Override
-        public int getDrawableResource() {
-            return 0;   // no icon for OPP
+            cachedDevice.onProfileStateChanged(mProfile, newState);
+            cachedDevice.refresh();
         }
     }
 
-    private static class HidProfileManager extends LocalBluetoothProfileManager
-            implements BluetoothProfile.ServiceListener {
-        private BluetoothInputDevice mService;
-
-        public HidProfileManager(LocalBluetoothManager localManager) {
-            super(localManager);
-            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-            adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.INPUT_DEVICE);
-        }
-
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mService = (BluetoothInputDevice) proxy;
-        }
-
-        public void onServiceDisconnected(int profile) {
-            mService = null;
-        }
-
-        @Override
-        public boolean connect(BluetoothDevice device) {
-            return mService.connect(device);
-        }
-
-        @Override
-        public int convertState(int hidState) {
-            switch (hidState) {
-            case BluetoothProfile.STATE_CONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
-            case BluetoothProfile.STATE_CONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
-            case BluetoothProfile.STATE_DISCONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
-            case BluetoothProfile.STATE_DISCONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
-            default:
-                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
-            }
-        }
-
-        @Override
-        public boolean disconnect(BluetoothDevice device) {
-            return mService.disconnect(device);
-        }
-
-        @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            return mService.getConnectedDevices();
-        }
-
-        @Override
-        public int getConnectionStatus(BluetoothDevice device) {
-            return convertState(mService.getConnectionState(device));
-        }
-
-        @Override
-        public int getPreferred(BluetoothDevice device) {
-            return mService.getPriority(device);
-        }
-
-        @Override
-        public int getSummary(BluetoothDevice device) {
-            final int connectionStatus = getConnectionStatus(device);
-
-            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
-                return R.string.bluetooth_hid_profile_summary_connected;
-            } else {
-                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
-            }
-        }
-
-        @Override
-        public boolean isPreferred(BluetoothDevice device) {
-            return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
-        }
+    // called from DockService
+    void addServiceListener(ServiceListener l) {
+        mServiceListeners.add(l);
+    }
 
-        @Override
-        public boolean isProfileReady() {
-            return true;
-        }
+    // called from DockService
+    void removeServiceListener(ServiceListener l) {
+        mServiceListeners.remove(l);
+    }
 
-        @Override
-        public void setPreferred(BluetoothDevice device, boolean preferred) {
-            if (preferred) {
-                if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
-                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-                }
-            } else {
-                mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
-            }
+    // not synchronized: use only from UI thread! (TODO: verify)
+    void callServiceConnectedListeners() {
+        for (ServiceListener l : mServiceListeners) {
+            l.onServiceConnected();
         }
+    }
 
-        @Override
-        public int getDrawableResource() {
-            return R.drawable.ic_bt_keyboard_hid;
+    // not synchronized: use only from UI thread! (TODO: verify)
+    void callServiceDisconnectedListeners() {
+        for (ServiceListener listener : mServiceListeners) {
+            listener.onServiceDisconnected();
         }
     }
 
-    private static class PanProfileManager extends LocalBluetoothProfileManager
-            implements BluetoothProfile.ServiceListener {
-        private BluetoothPan mService;
-
-        public PanProfileManager(LocalBluetoothManager localManager) {
-            super(localManager);
-            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-            adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.PAN);
+    // This is called by DockService, so check Headset and A2DP.
+    public synchronized boolean isManagerReady() {
+        // Getting just the headset profile is fine for now. Will need to deal with A2DP
+        // and others if they aren't always in a ready state.
+        LocalBluetoothProfile profile = mHeadsetProfile;
+        if (profile != null) {
+            return profile.isProfileReady();
         }
-
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mService = (BluetoothPan) proxy;
+        profile = mA2dpProfile;
+        if (profile != null) {
+            return profile.isProfileReady();
         }
+        return false;
+    }
 
-        public void onServiceDisconnected(int profile) {
-            mService = null;
-        }
+    A2dpProfile getA2dpProfile() {
+        return mA2dpProfile;
+    }
 
-        @Override
-        public boolean connect(BluetoothDevice device) {
-            List<BluetoothDevice> sinks = getConnectedDevices();
-            if (sinks != null) {
-                for (BluetoothDevice sink : sinks) {
-                    mService.disconnect(sink);
-                }
-            }
-            return mService.connect(device);
-        }
+    HeadsetProfile getHeadsetProfile() {
+        return mHeadsetProfile;
+    }
 
-        @Override
-        public int convertState(int panState) {
-            switch (panState) {
-            case BluetoothPan.STATE_CONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
-            case BluetoothPan.STATE_CONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
-            case BluetoothPan.STATE_DISCONNECTED:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
-            case BluetoothPan.STATE_DISCONNECTING:
-                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
-            default:
-                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
-            }
-        }
+    /**
+     * Fill in a list of LocalBluetoothProfile objects that are supported by
+     * the local device and the remote device.
+     *
+     * @param uuids of the remote device
+     * @param localUuids UUIDs of the local device
+     * @param profiles The list of profiles to fill
+     */
+    synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
+        Collection<LocalBluetoothProfile> profiles) {
+        profiles.clear();
 
-        @Override
-        public boolean disconnect(BluetoothDevice device) {
-            return mService.disconnect(device);
+        if (uuids == null) {
+            return;
         }
 
-        @Override
-        public int getSummary(BluetoothDevice device) {
-            final int connectionStatus = getConnectionStatus(device);
-
-            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
-                return R.string.bluetooth_pan_profile_summary_connected;
-            } else {
-                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+        if (mHeadsetProfile != null) {
+            if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
+                BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
+                (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
+                BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
+                    profiles.add(mHeadsetProfile);
             }
         }
 
-        @Override
-        public boolean isProfileReady() {
-            return true;
+        if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
+            mA2dpProfile != null) {
+            profiles.add(mA2dpProfile);
         }
 
-        @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            return mService.getConnectedDevices();
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
+            mOppProfile != null) {
+            profiles.add(mOppProfile);
         }
 
-        @Override
-        public int getConnectionStatus(BluetoothDevice device) {
-            return convertState(mService.getConnectionState(device));
-        }
-
-        @Override
-        public int getPreferred(BluetoothDevice device) {
-            return -1;
-        }
-
-        @Override
-        public boolean isPreferred(BluetoothDevice device) {
-            return true;
-        }
-
-        @Override
-        public void setPreferred(BluetoothDevice device, boolean preferred) {
-            // ignore: isPreferred is always true for PAN
-            return;
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) &&
+            mHidProfile != null) {
+            profiles.add(mHidProfile);
         }
 
-        @Override
-        public int getDrawableResource() {
-            return R.drawable.ic_bt_network_pan;
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
+            mPanProfile != null) {
+            profiles.add(mPanProfile);
         }
     }
 }
diff --git a/src/com/android/settings/bluetooth/OppProfile.java b/src/com/android/settings/bluetooth/OppProfile.java
new file mode 100644 (file)
index 0000000..3f7df38
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+
+/**
+ * OppProfile handles Bluetooth OPP.
+ */
+final class OppProfile implements LocalBluetoothProfile {
+
+    static final String NAME = "OPP";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 2;
+
+    public boolean isConnectable() {
+        return false;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        return false;
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        return false;
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return false;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    }
+
+    public boolean isProfileReady() {
+        return true;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource() {
+        return R.string.bluetooth_profile_opp;
+    }
+
+    public int getDisconnectResource() {
+        return 0; // user must use notification to disconnect OPP transfer.
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return 0;   // OPP profile not displayed in UI
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return 0;   // no icon for OPP
+    }
+}
diff --git a/src/com/android/settings/bluetooth/PanProfile.java b/src/com/android/settings/bluetooth/PanProfile.java
new file mode 100644 (file)
index 0000000..3f456e4
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/**
+ * PanProfile handles Bluetooth PAN profile.
+ */
+final class PanProfile implements LocalBluetoothProfile {
+    private BluetoothPan mService;
+
+    static final String NAME = "PAN";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 4;
+
+    // These callbacks run on the main thread.
+    private final class PanServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothPan) proxy;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mService = null;
+        }
+    }
+
+    PanProfile(Context context) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        adapter.getProfileProxy(context, new PanServiceListener(),
+                BluetoothProfile.PAN);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return true;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return -1;
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        // ignore: isPreferred is always true for PAN
+    }
+
+    public boolean isProfileReady() {
+        return true;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource() {
+        return R.string.bluetooth_profile_pan;
+    }
+
+    public int getDisconnectResource() {
+        return R.string.bluetooth_disconnect_pan_profile;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = mService.getConnectionState(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_pan_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_pan_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_network_pan;
+    }
+}
index 93d05bc..07a7316 100644 (file)
@@ -27,7 +27,6 @@ import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.util.Log;
 
@@ -51,7 +50,7 @@ public class RequestPermissionActivity extends Activity implements
 
     private static final int REQUEST_CODE_START_BT = 1;
 
-    private LocalBluetoothManager mLocalManager;
+    private LocalBluetoothAdapter mLocalAdapter;
 
     private int mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT;
 
@@ -66,18 +65,19 @@ public class RequestPermissionActivity extends Activity implements
 
     // True if requesting BT to be turned on
     // False if requesting BT to be turned on + discoverable mode
-    private boolean mEnableOnly = false;
+    private boolean mEnableOnly;
 
-    private boolean mUserConfirmed = false;
+    private boolean mUserConfirmed;
 
-    private AlertDialog mDialog = null;
+    private AlertDialog mDialog;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent == null)
+            if (intent == null) {
                 return;
+            }
             if (mNeededToEnableBluetooth
                     && BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
@@ -94,12 +94,13 @@ public class RequestPermissionActivity extends Activity implements
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        // Note: initializes mLocalAdapter and returns true on error
         if (parseIntent()) {
             finish();
             return;
         }
 
-        int btState = mLocalManager.getBluetoothState();
+        int btState = mLocalAdapter.getState();
 
         switch (btState) {
             case BluetoothAdapter.STATE_OFF:
@@ -120,28 +121,29 @@ public class RequestPermissionActivity extends Activity implements
                  */
                 registerReceiver(mReceiver,
                         new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-                Intent i = new Intent();
-                i.setClass(this, RequestPermissionHelperActivity.class);
+                Intent intent = new Intent();
+                intent.setClass(this, RequestPermissionHelperActivity.class);
                 if (mEnableOnly) {
-                    i.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON);
+                    intent.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON);
                 } else {
-                    i.setAction(RequestPermissionHelperActivity.
+                    intent.setAction(RequestPermissionHelperActivity.
                             ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE);
-                    i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout);
+                    intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout);
                 }
-                startActivityForResult(i, REQUEST_CODE_START_BT);
+                startActivityForResult(intent, REQUEST_CODE_START_BT);
                 mNeededToEnableBluetooth = true;
                 break;
             case BluetoothAdapter.STATE_ON:
                 if (mEnableOnly) {
                     // Nothing to do. Already enabled.
                     proceedAndFinish();
-                    return;
                 } else {
                     // Ask the user about enabling discovery mode
                     createDialog();
-                    break;
                 }
+                break;
+            default:
+                Log.e(TAG, "Unknown adapter state: " + btState);
         }
     }
 
@@ -176,8 +178,8 @@ public class RequestPermissionActivity extends Activity implements
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode != REQUEST_CODE_START_BT) {
-            Log.e(TAG, "Unexpected onActivityResult " + requestCode + " " + resultCode);
-            setResult(Activity.RESULT_CANCELED);
+            Log.e(TAG, "Unexpected onActivityResult " + requestCode + ' ' + resultCode);
+            setResult(RESULT_CANCELED);
             finish();
             return;
         }
@@ -191,7 +193,7 @@ public class RequestPermissionActivity extends Activity implements
         // BT and discoverable mode.
         mUserConfirmed = true;
 
-        if (mLocalManager.getBluetoothState() == BluetoothAdapter.STATE_ON) {
+        if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
             proceedAndFinish();
         } else {
             // If BT is not up yet, show "Turning on Bluetooth..."
@@ -206,7 +208,7 @@ public class RequestPermissionActivity extends Activity implements
                 break;
 
             case DialogInterface.BUTTON_NEGATIVE:
-                setResult(Activity.RESULT_CANCELED);
+                setResult(RESULT_CANCELED);
                 finish();
                 break;
         }
@@ -217,18 +219,19 @@ public class RequestPermissionActivity extends Activity implements
 
         if (mEnableOnly) {
             // BT enabled. Done
-            returnCode = Activity.RESULT_OK;
-        } else if (mLocalManager.getBluetoothAdapter().setScanMode(
+            returnCode = RESULT_OK;
+        } else if (mLocalAdapter.setScanMode(
                 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) {
             // If already in discoverable mode, this will extend the timeout.
-            persistDiscoverableEndTimestamp(System.currentTimeMillis() + mTimeout * 1000);
+            LocalBluetoothPreferences.persistDiscoverableEndTimestamp(
+                    this, System.currentTimeMillis() + (long) mTimeout * 1000);
             returnCode = mTimeout;
             // Activity.RESULT_FIRST_USER should be 1
-            if (returnCode < Activity.RESULT_FIRST_USER) {
-                returnCode = Activity.RESULT_FIRST_USER;
+            if (returnCode < RESULT_FIRST_USER) {
+                returnCode = RESULT_FIRST_USER;
             }
         } else {
-            returnCode = Activity.RESULT_CANCELED;
+            returnCode = RESULT_CANCELED;
         }
 
         if (mDialog != null) {
@@ -239,6 +242,10 @@ public class RequestPermissionActivity extends Activity implements
         finish();
     }
 
+    /**
+     * Parse the received Intent and initialize mLocalBluetoothAdapter.
+     * @return true if an error occurred; false otherwise
+     */
     private boolean parseIntent() {
         Intent intent = getIntent();
         if (intent != null && intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
@@ -257,16 +264,17 @@ public class RequestPermissionActivity extends Activity implements
             Log.e(TAG, "Error: this activity may be started only with intent "
                     + BluetoothAdapter.ACTION_REQUEST_ENABLE + " or "
                     + BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
-            setResult(Activity.RESULT_CANCELED);
+            setResult(RESULT_CANCELED);
             return true;
         }
 
-        mLocalManager = LocalBluetoothManager.getInstance(this);
-        if (mLocalManager == null) {
-            Log.e(TAG, "Error: there's a problem starting bluetooth");
-            setResult(Activity.RESULT_CANCELED);
+        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
+        if (manager == null) {
+            Log.e(TAG, "Error: there's a problem starting Bluetooth");
+            setResult(RESULT_CANCELED);
             return true;
         }
+        mLocalAdapter = manager.getBluetoothAdapter();
 
         return false;
     }
@@ -274,20 +282,14 @@ public class RequestPermissionActivity extends Activity implements
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        if (mNeededToEnableBluetooth) unregisterReceiver(mReceiver);
-    }
-
-    private void persistDiscoverableEndTimestamp(long endTimestamp) {
-        SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit();
-        editor.putLong(
-                BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP,
-                endTimestamp);
-        editor.apply();
+        if (mNeededToEnableBluetooth) {
+            unregisterReceiver(mReceiver);
+        }
     }
 
     @Override
     public void onBackPressed() {
-        setResult(Activity.RESULT_CANCELED);
+        setResult(RESULT_CANCELED);
         super.onBackPressed();
     }
 }
index 2657d91..9b5946b 100644 (file)
@@ -43,7 +43,7 @@ public class RequestPermissionHelperActivity extends AlertActivity implements
     public static final String ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE =
         "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE";
 
-    private LocalBluetoothManager mLocalManager;
+    private LocalBluetoothAdapter mLocalAdapter;
 
     private int mTimeout;
 
@@ -55,6 +55,7 @@ public class RequestPermissionHelperActivity extends AlertActivity implements
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        // Note: initializes mLocalAdapter and returns true on error
         if (parseIntent()) {
             finish();
             return;
@@ -92,32 +93,33 @@ public class RequestPermissionHelperActivity extends AlertActivity implements
 
     public void onClick(DialogInterface dialog, int which) {
         int returnCode;
+        // FIXME: fix this ugly switch logic!
         switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
+            case BUTTON_POSITIVE:
                 int btState = 0;
 
                 try {
                     // TODO There's a better way.
                     int retryCount = 30;
                     do {
-                        btState = mLocalManager.getBluetoothState();
+                        btState = mLocalAdapter.getBluetoothState();
                         Thread.sleep(100);
                     } while (btState == BluetoothAdapter.STATE_TURNING_OFF && --retryCount > 0);
-                } catch (InterruptedException e) {
+                } catch (InterruptedException ignored) {
                     // don't care
                 }
 
                 if (btState == BluetoothAdapter.STATE_TURNING_ON
                         || btState == BluetoothAdapter.STATE_ON
-                        || mLocalManager.getBluetoothAdapter().enable()) {
+                        || mLocalAdapter.enable()) {
                     returnCode = RequestPermissionActivity.RESULT_BT_STARTING_OR_STARTED;
                 } else {
-                    returnCode = Activity.RESULT_CANCELED;
+                    returnCode = RESULT_CANCELED;
                 }
                 break;
 
-            case DialogInterface.BUTTON_NEGATIVE:
-                returnCode = Activity.RESULT_CANCELED;
+            case BUTTON_NEGATIVE:
+                returnCode = RESULT_CANCELED;
                 break;
             default:
                 return;
@@ -125,6 +127,10 @@ public class RequestPermissionHelperActivity extends AlertActivity implements
         setResult(returnCode);
     }
 
+    /**
+     * Parse the received Intent and initialize mLocalBluetoothAdapter.
+     * @return true if an error occurred; false otherwise
+     */
     private boolean parseIntent() {
         Intent intent = getIntent();
         if (intent != null && intent.getAction().equals(ACTION_INTERNAL_REQUEST_BT_ON)) {
@@ -136,23 +142,24 @@ public class RequestPermissionHelperActivity extends AlertActivity implements
             mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,
                     BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT);
         } else {
-            setResult(Activity.RESULT_CANCELED);
+            setResult(RESULT_CANCELED);
             return true;
         }
 
-        mLocalManager = LocalBluetoothManager.getInstance(this);
-        if (mLocalManager == null) {
-            Log.e(TAG, "Error: there's a problem starting bluetooth");
-            setResult(Activity.RESULT_CANCELED);
+        LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
+        if (manager == null) {
+            Log.e(TAG, "Error: there's a problem starting Bluetooth");
+            setResult(RESULT_CANCELED);
             return true;
         }
+        mLocalAdapter = manager.getBluetoothAdapter();
 
         return false;
     }
 
     @Override
     public void onBackPressed() {
-        setResult(Activity.RESULT_CANCELED);
+        setResult(RESULT_CANCELED);
         super.onBackPressed();
     }
 }
diff --git a/src/com/android/settings/bluetooth/SettingsBtStatus.java b/src/com/android/settings/bluetooth/SettingsBtStatus.java
deleted file mode 100644 (file)
index 2407b53..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-
-import com.android.settings.R;
-
-/**
- * SettingsBtStatus is a helper class that contains constants for various status
- * codes.
- */
-class SettingsBtStatus {
-    private static final String TAG = "SettingsBtStatus";
-
-    // Connection status
-
-    public static final int CONNECTION_STATUS_UNKNOWN = 0;
-    public static final int CONNECTION_STATUS_ACTIVE = 1;
-    /** Use {@link #isConnectionStatusConnected} to check for the connected state */
-    public static final int CONNECTION_STATUS_CONNECTED = 2;
-    public static final int CONNECTION_STATUS_CONNECTING = 3;
-    public static final int CONNECTION_STATUS_DISCONNECTED = 4;
-    public static final int CONNECTION_STATUS_DISCONNECTING = 5;
-
-    public static final int getConnectionStatusSummary(int connectionStatus) {
-        switch (connectionStatus) {
-        case CONNECTION_STATUS_ACTIVE:
-            return R.string.bluetooth_connected;
-        case CONNECTION_STATUS_CONNECTED:
-            return R.string.bluetooth_connected;
-        case CONNECTION_STATUS_CONNECTING:
-            return R.string.bluetooth_connecting;
-        case CONNECTION_STATUS_DISCONNECTED:
-            return R.string.bluetooth_disconnected;
-        case CONNECTION_STATUS_DISCONNECTING:
-            return R.string.bluetooth_disconnecting;
-        case CONNECTION_STATUS_UNKNOWN:
-            return R.string.bluetooth_unknown;
-        default:
-            return 0;
-        }
-    }
-
-    public static final boolean isConnectionStatusConnected(int connectionStatus) {
-        return connectionStatus == CONNECTION_STATUS_ACTIVE
-                || connectionStatus == CONNECTION_STATUS_CONNECTED;
-    }
-
-    public static final boolean isConnectionStatusBusy(int connectionStatus) {
-        return connectionStatus == CONNECTION_STATUS_CONNECTING
-                || connectionStatus == CONNECTION_STATUS_DISCONNECTING;
-    }
-
-    public static final int getPairingStatusSummary(int bondState) {
-        switch (bondState) {
-        case BluetoothDevice.BOND_BONDED:
-            return R.string.bluetooth_paired;
-        case BluetoothDevice.BOND_BONDING:
-            return R.string.bluetooth_pairing;
-        case BluetoothDevice.BOND_NONE:
-            return R.string.bluetooth_not_connected;
-        default:
-            return 0;
-        }
-    }
-}
diff --git a/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java b/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java
new file mode 100644 (file)
index 0000000..bae6e56
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.text.InputFilter;
+import android.text.Spanned;
+
+/**
+ * This filter will constrain edits so that the text length is not
+ * greater than the specified number of bytes using UTF-8 encoding.
+ * <p>The JNI method used by {@link android.server.BluetoothService}
+ * to convert UTF-16 to UTF-8 doesn't support surrogate pairs,
+ * therefore code points outside of the basic multilingual plane
+ * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters,
+ * rather than a single 4-byte UTF-8 encoding. Dalvik implements this
+ * conversion in {@code convertUtf16ToUtf8()} in
+ * {@code dalvik/vm/UtfString.c}.
+ * <p>This JNI method is unlikely to change in the future due to
+ * backwards compatibility requirements. It's also unclear whether
+ * the installed base of Bluetooth devices would correctly handle the
+ * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6.
+ * However, this filter will still work in scenarios where surrogate
+ * pairs are encoded as 4 bytes, with the caveat that the maximum
+ * length will be constrained more conservatively than necessary.
+ */
+class Utf8ByteLengthFilter implements InputFilter {
+    private final int mMaxBytes;
+
+    Utf8ByteLengthFilter(int maxBytes) {
+        mMaxBytes = maxBytes;
+    }
+
+    public CharSequence filter(CharSequence source, int start, int end,
+                               Spanned dest, int dstart, int dend) {
+        int srcByteCount = 0;
+        // count UTF-8 bytes in source substring
+        for (int i = start; i < end; i++) {
+            char c = source.charAt(i);
+            srcByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3);
+        }
+        int destLen = dest.length();
+        int destByteCount = 0;
+        // count UTF-8 bytes in destination excluding replaced section
+        for (int i = 0; i < destLen; i++) {
+            if (i < dstart || i >= dend) {
+                char c = dest.charAt(i);
+                destByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3);
+            }
+        }
+        int keepBytes = mMaxBytes - destByteCount;
+        if (keepBytes <= 0) {
+            return "";
+        } else if (keepBytes >= srcByteCount) {
+            return null; // use original dest string
+        } else {
+            // find end position of largest sequence that fits in keepBytes
+            for (int i = start; i < end; i++) {
+                char c = source.charAt(i);
+                keepBytes -= (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3);
+                if (keepBytes < 0) {
+                    return source.subSequence(start, i);
+                }
+            }
+            // If the entire substring fits, we should have returned null
+            // above, so this line should not be reached. If for some
+            // reason it is, return null to use the original dest string.
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
new file mode 100644 (file)
index 0000000..7d38e17
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.widget.Toast;
+
+import com.android.settings.R;
+
+/**
+ * Utils is a helper class that contains constants for various
+ * Android resource IDs, debug logging flags, and static methods
+ * for creating dialogs.
+ */
+final class Utils {
+    static final boolean V = false; // verbose logging
+    static final boolean D = true;  // regular logging
+
+    private Utils() {
+    }
+
+    public static int getConnectionStateSummary(int connectionState) {
+        switch (connectionState) {
+        case BluetoothProfile.STATE_CONNECTED:
+            return R.string.bluetooth_connected;
+        case BluetoothProfile.STATE_CONNECTING:
+            return R.string.bluetooth_connecting;
+        case BluetoothProfile.STATE_DISCONNECTED:
+            return R.string.bluetooth_disconnected;
+        case BluetoothProfile.STATE_DISCONNECTING:
+            return R.string.bluetooth_disconnecting;
+        default:
+            return 0;
+        }
+    }
+
+    // Create (or recycle existing) and show disconnect dialog.
+    static AlertDialog showDisconnectDialog(Context context,
+            AlertDialog dialog,
+            DialogInterface.OnClickListener disconnectListener,
+            CharSequence title, CharSequence message) {
+        if (dialog == null) {
+            dialog = new AlertDialog.Builder(context)
+                    .setPositiveButton(android.R.string.ok, disconnectListener)
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .create();
+        } else {
+            if (dialog.isShowing()) {
+                dialog.dismiss();
+            }
+            // use disconnectListener for the correct profile(s)
+            CharSequence okText = context.getText(android.R.string.ok);
+            dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                    okText, disconnectListener);
+        }
+        dialog.setTitle(title);
+        dialog.setMessage(message);
+        dialog.show();
+        return dialog;
+    }
+
+    // TODO: wire this up to show connection errors...
+    static void showConnectingError(Context context, String name) {
+        // if (!mIsConnectingErrorPossible) {
+        //     return;
+        // }
+        // mIsConnectingErrorPossible = false;
+
+        showError(context, name, R.string.bluetooth_connecting_error_message);
+    }
+
+    static void showError(Context context, String name, int messageResId) {
+        String message = context.getString(messageResId, name);
+        new AlertDialog.Builder(context)
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setTitle(R.string.bluetooth_error_title)
+                .setMessage(message)
+                .setPositiveButton(android.R.string.ok, null)
+                .show();
+    }
+}
index 8f17e05..ba2b615 100644 (file)
@@ -38,6 +38,7 @@ import android.provider.Settings;
 import android.util.Log;
 import android.widget.RemoteViews;
 import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothAdapter;
 import com.android.settings.bluetooth.LocalBluetoothManager;
 
 /**
@@ -50,7 +51,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
             new ComponentName("com.android.settings",
                     "com.android.settings.widget.SettingsAppWidgetProvider");
 
-    private static LocalBluetoothManager sLocalBluetoothManager = null;
+    private static LocalBluetoothAdapter sLocalBluetoothAdapter = null;
 
     private static final int BUTTON_WIFI = 0;
     private static final int BUTTON_BRIGHTNESS = 1;
@@ -411,18 +412,19 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
 
         @Override
         public int getActualState(Context context) {
-            if (sLocalBluetoothManager == null) {
-                sLocalBluetoothManager = LocalBluetoothManager.getInstance(context);
-                if (sLocalBluetoothManager == null) {
+            if (sLocalBluetoothAdapter == null) {
+                LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
+                if (manager == null) {
                     return STATE_UNKNOWN;  // On emulator?
                 }
+                sLocalBluetoothAdapter = manager.getBluetoothAdapter();
             }
-            return bluetoothStateToFiveState(sLocalBluetoothManager.getBluetoothState());
+            return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState());
         }
 
         @Override
         protected void requestStateChange(Context context, final boolean desiredState) {
-            if (sLocalBluetoothManager == null) {
+            if (sLocalBluetoothAdapter == null) {
                 Log.d(TAG, "No LocalBluetoothManager");
                 return;
             }
@@ -433,7 +435,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
             new AsyncTask<Void, Void, Void>() {
                 @Override
                 protected Void doInBackground(Void... args) {
-                    sLocalBluetoothManager.setBluetoothEnabled(desiredState);
+                    sLocalBluetoothAdapter.setBluetoothEnabled(desiredState);
                     return null;
                 }
             }.execute();
@@ -584,7 +586,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
     public void onUpdate(Context context, AppWidgetManager appWidgetManager,
             int[] appWidgetIds) {
         // Update each requested appWidgetId
-        RemoteViews view = buildUpdate(context, -1);
+        RemoteViews view = buildUpdate(context);
 
         for (int i = 0; i < appWidgetIds.length; i++) {
             appWidgetManager.updateAppWidget(appWidgetIds[i], view);
@@ -613,22 +615,22 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
     /**
      * Load image for given widget and build {@link RemoteViews} for it.
      */
-    static RemoteViews buildUpdate(Context context, int appWidgetId) {
+    static RemoteViews buildUpdate(Context context) {
         RemoteViews views = new RemoteViews(context.getPackageName(),
                 R.layout.widget);
-        views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, appWidgetId,
+        views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context,
                 BUTTON_WIFI));
         views.setOnClickPendingIntent(R.id.btn_brightness,
                 getLaunchPendingIntent(context,
-                        appWidgetId, BUTTON_BRIGHTNESS));
+                        BUTTON_BRIGHTNESS));
         views.setOnClickPendingIntent(R.id.btn_sync,
                 getLaunchPendingIntent(context,
-                        appWidgetId, BUTTON_SYNC));
+                        BUTTON_SYNC));
         views.setOnClickPendingIntent(R.id.btn_gps,
-                getLaunchPendingIntent(context, appWidgetId, BUTTON_GPS));
+                getLaunchPendingIntent(context, BUTTON_GPS));
         views.setOnClickPendingIntent(R.id.btn_bluetooth,
                 getLaunchPendingIntent(context,
-                        appWidgetId, BUTTON_BLUETOOTH));
+                        BUTTON_BLUETOOTH));
 
         updateButtons(views, context);
         return views;
@@ -640,7 +642,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
      * @param context
      */
     public static void updateWidget(Context context) {
-        RemoteViews views = buildUpdate(context, -1);
+        RemoteViews views = buildUpdate(context);
         // Update specific list of appWidgetIds if given, otherwise default to all
         final AppWidgetManager gm = AppWidgetManager.getInstance(context);
         gm.updateAppWidget(THIS_APPWIDGET, views);
@@ -680,10 +682,9 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider {
      * Creates PendingIntent to notify the widget of a button click.
      *
      * @param context
-     * @param appWidgetId
      * @return
      */
-    private static PendingIntent getLaunchPendingIntent(Context context, int appWidgetId,
+    private static PendingIntent getLaunchPendingIntent(Context context,
             int buttonId) {
         Intent launchIntent = new Intent();
         launchIntent.setClass(context, SettingsAppWidgetProvider.class);
index 5b2ac10..ec438cb 100644 (file)
@@ -93,7 +93,7 @@ public class SettingsHookTests extends ActivityInstrumentationTestCase2<Settings
                 result = true;
             }
         }
-        assertTrue("Intent-filer not found", result);
+        assertTrue("Intent-filter not found", result);
     }
 
     /**
@@ -111,7 +111,7 @@ public class SettingsHookTests extends ActivityInstrumentationTestCase2<Settings
                 result = true;
             }
         }
-        assertTrue("Intent-filer not found", result);
+        assertTrue("Intent-filter not found", result);
     }
 
     /**
@@ -119,6 +119,7 @@ public class SettingsHookTests extends ActivityInstrumentationTestCase2<Settings
      * application.
      */
     public void testOperatorPreferenceAvailable() {
+// TODO: fix this test case to work with fragments
 //        PreferenceGroup root = (PreferenceGroup)mSettings.findPreference(KEY_SETTINGS_ROOT);
 //        Preference operatorPreference = root.findPreference(KEY_SETTINGS_OPERATOR);
 //        assertNotNull(operatorPreference);
@@ -129,6 +130,7 @@ public class SettingsHookTests extends ActivityInstrumentationTestCase2<Settings
      * application.
      */
     public void testManufacturerPreferenceAvailable() {
+// TODO: fix this test case to work with fragments
 //        PreferenceGroup root = (PreferenceGroup)mSettings.findPreference(KEY_SETTINGS_ROOT);
 //        Preference manufacturerHook = root.findPreference(KEY_SETTINGS_MANUFACTURER);
 //        assertNotNull(manufacturerHook);
  * limitations under the License.
  */
 
-package com.android.settings.tests;
+package com.android.settings.bluetooth;
 
 import android.test.AndroidTestCase;
 import android.text.InputFilter;
 import android.text.SpannableStringBuilder;
 
-import com.android.settings.bluetooth.BluetoothNamePreference;
+import com.android.settings.bluetooth.Utf8ByteLengthFilter;
 
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetClass;
 import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargets;
 
-@TestTargetClass(BluetoothNamePreference.Utf8ByteLengthFilter.class)
+@TestTargetClass(Utf8ByteLengthFilter.class)
 public class Utf8ByteLengthFilterTest extends AndroidTestCase {
 
     @TestTargets({
@@ -39,7 +39,7 @@ public class Utf8ByteLengthFilterTest extends AndroidTestCase {
         ),
         @TestTargetNew(
             level = TestLevel.COMPLETE,
-            method = "BluetoothNamePreference.Utf8ByteLengthFilter",
+            method = "Utf8ByteLengthFilter",
             args = {int.class}
         )
     })
@@ -48,7 +48,7 @@ public class Utf8ByteLengthFilterTest extends AndroidTestCase {
         CharSequence source;
         SpannableStringBuilder dest;
         // Constructor to create a LengthFilter
-        BluetoothNamePreference.Utf8ByteLengthFilter lengthFilter = new BluetoothNamePreference.Utf8ByteLengthFilter(10);
+        InputFilter lengthFilter = new Utf8ByteLengthFilter(10);
         InputFilter[] filters = {lengthFilter};
 
         // filter() implicitly invoked. If the total length > filter length, the filter will