Merge "Move non-ui bt settings code to SettingsLib"
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..6e29d23
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..6110e9e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..6cca225
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..6445f2a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..78c0294
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..2fcc3b0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..70d35bf
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..2d9b75e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..b6ebe34
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..8b67b91
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..1fa0a3d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..175bd78
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..05b27e8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..6e9f8ae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..5f3371f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..539d77f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..3216776
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..4f381ba
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..c67127d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..d3b356b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..2d38129
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..fb76575
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..d8b68eb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..02cc3af
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..7805b7a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..8127774
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..84b8085
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..289d6ac
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..72bc804
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..e31ce2b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..f23b0e7
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..1e12f96
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..8b547d9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..03c5033
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..b9a9923
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..989e1ab
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..de8c389
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..2eb8a92
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f055a2c..870afeb 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -67,4 +67,87 @@
 
     <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
     <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
+
+    <!-- Bluetooth settings.  Message when a device is disconnected -->
+    <string name="bluetooth_disconnected">Disconnected</string>
+    <!-- Bluetooth settings.  Message when disconnecting from a device -->
+    <string name="bluetooth_disconnecting">Disconnecting\u2026</string>
+     <!-- Bluetooth settings.  Message when connecting to a device -->
+    <string name="bluetooth_connecting">Connecting\u2026</string>
+    <!-- Bluetooth settings.  Message when connected to a device. [CHAR LIMIT=40] -->
+    <string name="bluetooth_connected">Connected</string>
+
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the A2DP profile. -->
+    <string name="bluetooth_profile_a2dp">Media audio</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the headset or handsfree profile. -->
+    <string name="bluetooth_profile_headset">Phone audio</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the OPP profile. -->
+    <string name="bluetooth_profile_opp">File transfer</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. -->
+    <string name="bluetooth_profile_hid">Input device</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
+    <string name="bluetooth_profile_pan">Internet access</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
+    <string name="bluetooth_profile_pbap">Contact sharing</string>
+    <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
+    <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+    <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
+    <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the map profile. -->
+    <string name="bluetooth_profile_map">Message Access</string>
+
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference when A2DP is connected. -->
+    <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the headset checkbox preference when headset is connected. -->
+    <string name="bluetooth_headset_profile_summary_connected">Connected to phone audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference when OPP is connected. -->
+    <string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the map checkbox preference when map is connected. -->
+    <string name="bluetooth_map_profile_summary_connected">Connected to map</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference when OPP is not connected. -->
+    <string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string>
+    <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
+    <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string>
+    <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
+    <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string>
+    <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
+    <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string>
+
+    <!-- Bluetooth settings. Connection options screen. The summary
+         for the PAN checkbox preference that describes how checking it
+         will set the PAN profile as preferred. -->
+    <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
+    <!-- Bluetooth settings. Connection options screen.  The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
+    <string name="bluetooth_map_profile_summary_use_for">Use for map</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. -->
+    <string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. -->
+    <string name="bluetooth_headset_profile_summary_use_for">Use for phone audio</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the OPP checkbox preference that describes how checking it will set the OPP profile as preferred. -->
+    <string name="bluetooth_opp_profile_summary_use_for">Use for file transfer</string>
+    <!-- Bluetooth settings. Connection options screen. The summary
+         for the HID checkbox preference that describes how checking it
+         will set the HID profile as preferred. -->
+    <string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
+
+    <!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] -->
+    <string name="bluetooth_pairing_accept">Pair</string>
+    <!-- Button text for accepting an incoming pairing request in all caps. [CHAR LIMIT=20] -->
+    <string name="bluetooth_pairing_accept_all_caps">PAIR</string>
+    <!-- Button text for declining an incoming pairing request. [CHAR LIMIT=20] -->
+    <string name="bluetooth_pairing_decline">Cancel</string>
+
+    <!-- Message in pairing dialogs.  [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string>
+    <!-- Message for the error dialog when BT pairing fails generically. -->
+    <string name="bluetooth_pairing_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g>.</string>
+
+    <!-- Message for the error dialog when BT pairing fails because the PIN /
+    Passkey entered is incorrect. -->
+    <string name="bluetooth_pairing_pin_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g> because of an incorrect PIN or passkey.</string>
+    <!-- Message for the error dialog when BT pairing fails because the other device is down. -->
+    <string name="bluetooth_pairing_device_down_error_message">Can\'t communicate with <xliff:g id="device_name">%1$s</xliff:g>.</string>
+    <!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. -->
+    <string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
new file mode 100755
index 0000000..9608daa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -0,0 +1,214 @@
+/*
+ * 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.settingslib.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 android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class A2dpProfile implements LocalBluetoothProfile {
+    private static final String TAG = "A2dpProfile";
+    private static boolean V = false;
+
+    private BluetoothA2dp mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+
+    static final ParcelUuid[] SINK_UUIDS = {
+        BluetoothUuid.AudioSink,
+        BluetoothUuid.AdvAudioDist,
+    };
+
+    static final String NAME = "A2DP";
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    // 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) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothA2dp) proxy;
+            // We just bound to the service, so refresh the UI for any connected A2DP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    A2dpProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
+                BluetoothProfile.A2DP);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        // 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);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+    boolean isA2dpPlaying() {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (!sinks.isEmpty()) {
+            if (mService.isA2dpPlaying(sinks.get(0))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_a2dp;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(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;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up A2DP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
new file mode 100644
index 0000000..b802f58
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -0,0 +1,30 @@
+/*
+ * 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.settingslib.bluetooth;
+
+
+/**
+ * BluetoothCallback provides a callback interface for the settings
+ * UI to receive events from {@link BluetoothEventManager}.
+ */
+public 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java
new file mode 100644
index 0000000..8dec86a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java
@@ -0,0 +1,169 @@
+/*
+ * 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.settingslib.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}.
+ */
+public final class BluetoothDeviceFilter {
+    private static final String TAG = "BluetoothDeviceFilter";
+
+    /** The filter interface to external classes. */
+    public interface Filter {
+        boolean matches(BluetoothDevice device);
+    }
+
+    /** All filter singleton (referenced directly). */
+    public static final Filter ALL_FILTER = new AllFilter();
+
+    /** Bonded devices only filter (referenced directly). */
+    public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
+
+    /** Unbonded devices only filter (referenced directly). */
+    public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
+
+    /** 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.
+     */
+    public 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;
+        }
+    }
+
+    /** Filter that matches only unbonded devices. */
+    private static final class UnbondedDeviceFilter 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);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
new file mode 100644
index 0000000..acb7e7a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 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.settingslib.bluetooth;
+
+/* Required to handle timeout notification when phone is suspended */
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver {
+    private static final String TAG = "BluetoothDiscoverableTimeoutReceiver";
+
+    private static final String INTENT_DISCOVERABLE_TIMEOUT =
+        "android.bluetooth.intent.DISCOVERABLE_TIMEOUT";
+
+    public static void setDiscoverableAlarm(Context context, long alarmTime) {
+        Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime);
+
+        Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
+        intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
+        PendingIntent pending = PendingIntent.getBroadcast(
+            context, 0, intent, 0);
+        AlarmManager alarmManager =
+              (AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
+
+        if (pending != null) {
+            // Cancel any previous alarms that do the same thing.
+            alarmManager.cancel(pending);
+            Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm");
+        }
+        pending = PendingIntent.getBroadcast(
+            context, 0, intent, 0);
+
+        alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending);
+    }
+
+    public static void cancelDiscoverableAlarm(Context context) {
+        Log.d(TAG, "cancelDiscoverableAlarm(): Enter");
+
+        Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
+        intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
+        PendingIntent pending = PendingIntent.getBroadcast(
+            context, 0, intent, PendingIntent.FLAG_NO_CREATE);
+        if (pending != null) {
+            // Cancel any previous alarms that do the same thing.
+            AlarmManager alarmManager =
+              (AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
+
+            alarmManager.cancel(pending);
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance();
+
+         if(localBluetoothAdapter != null  &&
+            localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
+            Log.d(TAG, "Disable discoverable...");
+
+            localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+         } else {
+            Log.e(TAG, "localBluetoothAdapter is NULL!!");
+        }
+    }
+};
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
new file mode 100755
index 0000000..7c92368
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -0,0 +1,362 @@
+/*
+ * 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.settingslib.bluetooth;
+
+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 com.android.settingslib.R;
+
+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.
+ */
+public final class BluetoothEventManager {
+    private static final String TAG = "BluetoothEventManager";
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private LocalBluetoothProfileManager mProfileManager;
+    private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
+    private final Map<String, Handler> mHandlerMap;
+    private Context mContext;
+
+    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);
+        mAdapterIntentFilter.addAction(action);
+    }
+
+    void addProfileHandler(String action, Handler handler) {
+        mHandlerMap.put(action, handler);
+        mProfileIntentFilter.addAction(action);
+    }
+
+    // Set profile manager after construction due to circular dependency
+    void setProfileManager(LocalBluetoothProfileManager manager) {
+        mProfileManager = manager;
+    }
+
+    BluetoothEventManager(LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager, Context context) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mAdapterIntentFilter = new IntentFilter();
+        mProfileIntentFilter = new IntentFilter();
+        mHandlerMap = new HashMap<String, Handler>();
+        mContext = context;
+
+        // 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());
+
+        mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
+    }
+
+    void registerProfileIntentReceiver() {
+        mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter);
+    }
+
+    /** Register to start receiving callbacks for Bluetooth events. */
+    public void registerCallback(BluetoothCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    /** Unregister to stop receiving callbacks for Bluetooth events. */
+    public void unregisterCallback(BluetoothCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            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);
+                }
+            }
+            // Inform CachedDeviceManager that the adapter state has changed
+            mDeviceManager.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);
+        }
+    }
+
+    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.setNewName(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 (CachedBluetoothDeviceManager.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) {
+                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) {
+                        cachedDevice.setVisible(false);
+                    }
+                }
+            }
+        }
+    }
+    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/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
new file mode 100755
index 0000000..ddcc49f7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -0,0 +1,787 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.ParcelUuid;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.bluetooth.BluetoothAdapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * CachedBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
+    private static final String TAG = "CachedBluetoothDevice";
+    private static final boolean DEBUG = Utils.V;
+
+    private final Context mContext;
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final LocalBluetoothProfileManager mProfileManager;
+    private final BluetoothDevice mDevice;
+    private String mName;
+    private short mRssi;
+    private BluetoothClass mBtClass;
+    private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
+
+    private final List<LocalBluetoothProfile> mProfiles =
+            new ArrayList<LocalBluetoothProfile>();
+
+    // List of profiles that were previously in mProfiles, but have been removed
+    private final List<LocalBluetoothProfile> mRemovedProfiles =
+            new ArrayList<LocalBluetoothProfile>();
+
+    // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
+    private boolean mLocalNapRoleConnected;
+
+    private boolean mVisible;
+
+    private int mPhonebookPermissionChoice;
+
+    private int mMessagePermissionChoice;
+
+    private int mMessageRejectionCount;
+
+    private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
+
+    // Following constants indicate the user's choices of Phone book/message access settings
+    // User hasn't made any choice or settings app has wiped out the memory
+    public final static int ACCESS_UNKNOWN = 0;
+    // User has accepted the connection and let Settings app remember the decision
+    public final static int ACCESS_ALLOWED = 1;
+    // User has rejected the connection and let Settings app remember the decision
+    public final static int ACCESS_REJECTED = 2;
+
+    // How many times user should reject the connection to make the choice persist.
+    private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2;
+
+    private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
+
+    /**
+     * When we connect to multiple profiles, we only want to display a single
+     * error even if they all fail. This tracks that state.
+     */
+    private boolean mIsConnectingErrorPossible;
+
+    /**
+     * Last time a bt profile auto-connect was attempted.
+     * If an ACTION_UUID intent comes in within
+     * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
+     * again with the new UUIDs
+     */
+    private long mConnectAttempted;
+
+    // See mConnectAttempted
+    private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
+
+    /** Auto-connect after pairing only if locally initiated. */
+    private boolean mConnectAfterPairing;
+
+    /**
+     * Describes the current device and profile for logging.
+     *
+     * @param profile Profile to describe
+     * @return Description of the device and profile
+     */
+    private String describe(LocalBluetoothProfile profile) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Address:").append(mDevice);
+        if (profile != null) {
+            sb.append(" Profile:").append(profile);
+        }
+
+        return sb.toString();
+    }
+
+    void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
+        if (Utils.D) {
+            Log.d(TAG, "onProfileStateChanged: profile " + profile +
+                    " newProfileState " + newProfileState);
+        }
+        if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
+        {
+            if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
+            return;
+        }
+        mProfileConnectionState.put(profile, newProfileState);
+        if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
+            if (profile instanceof MapProfile) {
+                profile.setPreferred(mDevice, true);
+            } else if (!mProfiles.contains(profile)) {
+                mRemovedProfiles.remove(profile);
+                mProfiles.add(profile);
+                if (profile instanceof PanProfile &&
+                        ((PanProfile) profile).isLocalRoleNap(mDevice)) {
+                    // Device doesn't support NAP, so remove PanProfile on disconnect
+                    mLocalNapRoleConnected = true;
+                }
+            }
+        } else if (profile instanceof MapProfile &&
+                newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+            profile.setPreferred(mDevice, false);
+        } else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
+                ((PanProfile) profile).isLocalRoleNap(mDevice) &&
+                newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+            Log.d(TAG, "Removing PanProfile from device after NAP disconnect");
+            mProfiles.remove(profile);
+            mRemovedProfiles.add(profile);
+            mLocalNapRoleConnected = false;
+        }
+    }
+
+    CachedBluetoothDevice(Context context,
+                          LocalBluetoothAdapter adapter,
+                          LocalBluetoothProfileManager profileManager,
+                          BluetoothDevice device) {
+        mContext = context;
+        mLocalAdapter = adapter;
+        mProfileManager = profileManager;
+        mDevice = device;
+        mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
+        fillData();
+    }
+
+    public void disconnect() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            disconnect(profile);
+        }
+        // Disconnect  PBAP server in case its connected
+        // This is to ensure all the profiles are disconnected as some CK/Hs do not
+        // disconnect  PBAP connection when HF connection is brought down
+        PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
+        if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
+        {
+            PbapProfile.disconnect(mDevice);
+        }
+    }
+
+    public void disconnect(LocalBluetoothProfile profile) {
+        if (profile.disconnect(mDevice)) {
+            if (Utils.D) {
+                Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
+            }
+        }
+    }
+
+    public void connect(boolean connectAllProfiles) {
+        if (!ensurePaired()) {
+            return;
+        }
+
+        mConnectAttempted = SystemClock.elapsedRealtime();
+        connectWithoutResettingTimer(connectAllProfiles);
+    }
+
+    void onBondingDockConnect() {
+        // Attempt to connect if UUIDs are available. Otherwise,
+        // we will connect when the ACTION_UUID intent arrives.
+        connect(false);
+    }
+
+    private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+        // Try to initialize the profiles if they were not.
+        if (mProfiles.isEmpty()) {
+            // if mProfiles is empty, then do not invoke updateProfiles. This causes a race
+            // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated
+            // from bluetooth stack but ACTION.uuid is not sent yet.
+            // Eventually ACTION.uuid will be received which shall trigger the connection of the
+            // various profiles
+            // If UUIDs are not available yet, connect will be happen
+            // upon arrival of the ACTION_UUID intent.
+            Log.d(TAG, "No profiles. Maybe we will connect later");
+            return;
+        }
+
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+
+        int preferredProfiles = 0;
+        for (LocalBluetoothProfile profile : mProfiles) {
+            if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
+                if (profile.isPreferred(mDevice)) {
+                    ++preferredProfiles;
+                    connectInt(profile);
+                }
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
+
+        if (preferredProfiles == 0) {
+            connectAutoConnectableProfiles();
+        }
+    }
+
+    private void connectAutoConnectableProfiles() {
+        if (!ensurePaired()) {
+            return;
+        }
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+
+        for (LocalBluetoothProfile profile : mProfiles) {
+            if (profile.isAutoConnectable()) {
+                profile.setPreferred(mDevice, true);
+                connectInt(profile);
+            }
+        }
+    }
+
+    /**
+     * Connect this device to the specified profile.
+     *
+     * @param profile the profile to use with the remote device
+     */
+    public void connectProfile(LocalBluetoothProfile profile) {
+        mConnectAttempted = SystemClock.elapsedRealtime();
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+        connectInt(profile);
+        // Refresh the UI based on profile.connect() call
+        refresh();
+    }
+
+    synchronized void connectInt(LocalBluetoothProfile profile) {
+        if (!ensurePaired()) {
+            return;
+        }
+        if (profile.connect(mDevice)) {
+            if (Utils.D) {
+                Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
+            }
+            return;
+        }
+        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+    }
+
+    private boolean ensurePaired() {
+        if (getBondState() == BluetoothDevice.BOND_NONE) {
+            startPairing();
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public boolean startPairing() {
+        // Pairing is unreliable while scanning, so cancel discovery
+        if (mLocalAdapter.isDiscovering()) {
+            mLocalAdapter.cancelDiscovery();
+        }
+
+        if (!mDevice.createBond()) {
+            return false;
+        }
+
+        mConnectAfterPairing = true;  // auto-connect after pairing
+        return true;
+    }
+
+    /**
+     * Return true if user initiated pairing on this device. The message text is
+     * slightly different for local vs. remote initiated pairing dialogs.
+     */
+    boolean isUserInitiatedPairing() {
+        return mConnectAfterPairing;
+    }
+
+    public void unpair() {
+        int state = getBondState();
+
+        if (state == BluetoothDevice.BOND_BONDING) {
+            mDevice.cancelBondProcess();
+        }
+
+        if (state != BluetoothDevice.BOND_NONE) {
+            final BluetoothDevice dev = mDevice;
+            if (dev != null) {
+                final boolean successful = dev.removeBond();
+                if (successful) {
+                    if (Utils.D) {
+                        Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
+                    }
+                } else if (Utils.V) {
+                    Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
+                            describe(null));
+                }
+            }
+        }
+    }
+
+    public int getProfileConnectionState(LocalBluetoothProfile profile) {
+        if (mProfileConnectionState == null ||
+                mProfileConnectionState.get(profile) == null) {
+            // If cache is empty make the binder call to get the state
+            int state = profile.getConnectionStatus(mDevice);
+            mProfileConnectionState.put(profile, state);
+        }
+        return mProfileConnectionState.get(profile);
+    }
+
+    public void clearProfileConnectionState ()
+    {
+        if (Utils.D) {
+            Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
+        }
+        for (LocalBluetoothProfile profile :getProfiles()) {
+            mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
+        }
+    }
+
+    // TODO: do any of these need to run async on a background thread?
+    private void fillData() {
+        fetchName();
+        fetchBtClass();
+        updateProfiles();
+        migratePhonebookPermissionChoice();
+        migrateMessagePermissionChoice();
+        fetchMessageRejectionCount();
+
+        mVisible = false;
+        dispatchAttributesChanged();
+    }
+
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Populate name from BluetoothDevice.ACTION_FOUND intent
+     */
+    void setNewName(String name) {
+        if (mName == null) {
+            mName = name;
+            if (mName == null || TextUtils.isEmpty(mName)) {
+                mName = mDevice.getAddress();
+            }
+            dispatchAttributesChanged();
+        }
+    }
+
+    /**
+     * user changes the device name
+     */
+    public void setName(String name) {
+        if (!mName.equals(name)) {
+            mName = name;
+            mDevice.setAlias(name);
+            dispatchAttributesChanged();
+        }
+    }
+
+    void refreshName() {
+        fetchName();
+        dispatchAttributesChanged();
+    }
+
+    private void fetchName() {
+        mName = mDevice.getAliasName();
+
+        if (TextUtils.isEmpty(mName)) {
+            mName = mDevice.getAddress();
+            if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
+        }
+    }
+
+    void refresh() {
+        dispatchAttributesChanged();
+    }
+
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    public void setVisible(boolean visible) {
+        if (mVisible != visible) {
+            mVisible = visible;
+            dispatchAttributesChanged();
+        }
+    }
+
+    public int getBondState() {
+        return mDevice.getBondState();
+    }
+
+    void setRssi(short rssi) {
+        if (mRssi != rssi) {
+            mRssi = rssi;
+            dispatchAttributesChanged();
+        }
+    }
+
+    /**
+     * Checks whether we are connected to this device (any profile counts).
+     *
+     * @return Whether it is connected.
+     */
+    public boolean isConnected() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            int status = getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTED) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean isConnectedProfile(LocalBluetoothProfile profile) {
+        int status = getProfileConnectionState(profile);
+        return status == BluetoothProfile.STATE_CONNECTED;
+
+    }
+
+    public boolean isBusy() {
+        for (LocalBluetoothProfile profile : mProfiles) {
+            int status = getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTING
+                    || status == BluetoothProfile.STATE_DISCONNECTING) {
+                return true;
+            }
+        }
+        return getBondState() == BluetoothDevice.BOND_BONDING;
+    }
+
+    /**
+     * Fetches a new value for the cached BT class.
+     */
+    private void fetchBtClass() {
+        mBtClass = mDevice.getBluetoothClass();
+    }
+
+    private boolean updateProfiles() {
+        ParcelUuid[] uuids = mDevice.getUuids();
+        if (uuids == null) return false;
+
+        ParcelUuid[] localUuids = mLocalAdapter.getUuids();
+        if (localUuids == null) return false;
+
+        /**
+         * Now we know if the device supports PBAP, update permissions...
+         */
+        processPhonebookAccess();
+
+        mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
+                                       mLocalNapRoleConnected, mDevice);
+
+        if (DEBUG) {
+            Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
+            BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
+
+            if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
+            Log.v(TAG, "UUID:");
+            for (ParcelUuid uuid : uuids) {
+                Log.v(TAG, "  " + uuid);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Refreshes the UI for the BT class, including fetching the latest value
+     * for the class.
+     */
+    void refreshBtClass() {
+        fetchBtClass();
+        dispatchAttributesChanged();
+    }
+
+    /**
+     * Refreshes the UI when framework alerts us of a UUID change.
+     */
+    void onUuidChanged() {
+        updateProfiles();
+
+        if (DEBUG) {
+            Log.e(TAG, "onUuidChanged: Time since last connect"
+                    + (SystemClock.elapsedRealtime() - mConnectAttempted));
+        }
+
+        /*
+         * If a connect was attempted earlier without any UUID, we will do the
+         * connect now.
+         */
+        if (!mProfiles.isEmpty()
+                && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
+                        .elapsedRealtime()) {
+            connectWithoutResettingTimer(false);
+        }
+        dispatchAttributesChanged();
+    }
+
+    void onBondingStateChanged(int bondState) {
+        if (bondState == BluetoothDevice.BOND_NONE) {
+            mProfiles.clear();
+            mConnectAfterPairing = false;  // cancel auto-connect
+            setPhonebookPermissionChoice(ACCESS_UNKNOWN);
+            setMessagePermissionChoice(ACCESS_UNKNOWN);
+            mMessageRejectionCount = 0;
+            saveMessageRejectionCount();
+        }
+
+        refresh();
+
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            if (mDevice.isBluetoothDock()) {
+                onBondingDockConnect();
+            } else if (mConnectAfterPairing) {
+                connect(false);
+            }
+            mConnectAfterPairing = false;
+        }
+    }
+
+    void setBtClass(BluetoothClass btClass) {
+        if (btClass != null && mBtClass != btClass) {
+            mBtClass = btClass;
+            dispatchAttributesChanged();
+        }
+    }
+
+    public BluetoothClass getBtClass() {
+        return mBtClass;
+    }
+
+    public List<LocalBluetoothProfile> getProfiles() {
+        return Collections.unmodifiableList(mProfiles);
+    }
+
+    public List<LocalBluetoothProfile> getConnectableProfiles() {
+        List<LocalBluetoothProfile> connectableProfiles =
+                new ArrayList<LocalBluetoothProfile>();
+        for (LocalBluetoothProfile profile : mProfiles) {
+            if (profile.isConnectable()) {
+                connectableProfiles.add(profile);
+            }
+        }
+        return connectableProfiles;
+    }
+
+    public List<LocalBluetoothProfile> getRemovedProfiles() {
+        return mRemovedProfiles;
+    }
+
+    public void registerCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    public void unregisterCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    private void dispatchAttributesChanged() {
+        synchronized (mCallbacks) {
+            for (Callback callback : mCallbacks) {
+                callback.onDeviceAttributesChanged();
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mDevice.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
+            return false;
+        }
+        return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
+    }
+
+    @Override
+    public int hashCode() {
+        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) {
+        // Connected above not connected
+        int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Paired above not paired
+        comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
+            (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Visible above not visible
+        comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Stronger signal above weaker signal
+        comparison = another.mRssi - mRssi;
+        if (comparison != 0) return comparison;
+
+        // Fallback on name
+        return mName.compareTo(another.mName);
+    }
+
+    public interface Callback {
+        void onDeviceAttributesChanged();
+    }
+
+    public int getPhonebookPermissionChoice() {
+        int permission = mDevice.getPhonebookAccessPermission();
+        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+            return ACCESS_ALLOWED;
+        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+            return ACCESS_REJECTED;
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    public void setPhonebookPermissionChoice(int permissionChoice) {
+        int permission = BluetoothDevice.ACCESS_UNKNOWN;
+        if (permissionChoice == ACCESS_ALLOWED) {
+            permission = BluetoothDevice.ACCESS_ALLOWED;
+        } else if (permissionChoice == ACCESS_REJECTED) {
+            permission = BluetoothDevice.ACCESS_REJECTED;
+        }
+        mDevice.setPhonebookAccessPermission(permission);
+    }
+
+    // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+    // app's shared preferences).
+    private void migratePhonebookPermissionChoice() {
+        SharedPreferences preferences = mContext.getSharedPreferences(
+                "bluetooth_phonebook_permission", Context.MODE_PRIVATE);
+        if (!preferences.contains(mDevice.getAddress())) {
+            return;
+        }
+
+        if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+            int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+            if (oldPermission == ACCESS_ALLOWED) {
+                mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+            } else if (oldPermission == ACCESS_REJECTED) {
+                mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+            }
+        }
+
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.remove(mDevice.getAddress());
+        editor.commit();
+    }
+
+    public int getMessagePermissionChoice() {
+        int permission = mDevice.getMessageAccessPermission();
+        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+            return ACCESS_ALLOWED;
+        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+            return ACCESS_REJECTED;
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    public void setMessagePermissionChoice(int permissionChoice) {
+        int permission = BluetoothDevice.ACCESS_UNKNOWN;
+        if (permissionChoice == ACCESS_ALLOWED) {
+            permission = BluetoothDevice.ACCESS_ALLOWED;
+        } else if (permissionChoice == ACCESS_REJECTED) {
+            permission = BluetoothDevice.ACCESS_REJECTED;
+        }
+        mDevice.setMessageAccessPermission(permission);
+    }
+
+    // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+    // app's shared preferences).
+    private void migrateMessagePermissionChoice() {
+        SharedPreferences preferences = mContext.getSharedPreferences(
+                "bluetooth_message_permission", Context.MODE_PRIVATE);
+        if (!preferences.contains(mDevice.getAddress())) {
+            return;
+        }
+
+        if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+            int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+            if (oldPermission == ACCESS_ALLOWED) {
+                mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+            } else if (oldPermission == ACCESS_REJECTED) {
+                mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+            }
+        }
+
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.remove(mDevice.getAddress());
+        editor.commit();
+    }
+
+    /**
+     * @return Whether this rejection should persist.
+     */
+    public boolean checkAndIncreaseMessageRejectionCount() {
+        if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) {
+            mMessageRejectionCount++;
+            saveMessageRejectionCount();
+        }
+        return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST;
+    }
+
+    private void fetchMessageRejectionCount() {
+        SharedPreferences preference = mContext.getSharedPreferences(
+                MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE);
+        mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0);
+    }
+
+    private void saveMessageRejectionCount() {
+        SharedPreferences.Editor editor = mContext.getSharedPreferences(
+                MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit();
+        if (mMessageRejectionCount == 0) {
+            editor.remove(mDevice.getAddress());
+        } else {
+            editor.putInt(mDevice.getAddress(), mMessageRejectionCount);
+        }
+        editor.commit();
+    }
+
+    private void processPhonebookAccess() {
+        if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return;
+
+        ParcelUuid[] uuids = mDevice.getUuids();
+        if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
+            // The pairing dialog now warns of phone-book access for paired devices.
+            // No separate prompt is displayed after pairing.
+            setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
new file mode 100755
index 0000000..65db95f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -0,0 +1,172 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
+ */
+public final class CachedBluetoothDeviceManager {
+    private static final String TAG = "CachedBluetoothDeviceManager";
+    private static final boolean DEBUG = Utils.D;
+
+    private Context mContext;
+    private final List<CachedBluetoothDevice> mCachedDevices =
+            new ArrayList<CachedBluetoothDevice>();
+
+    CachedBluetoothDeviceManager(Context context) {
+        mContext = context;
+    }
+
+    public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
+        return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
+    }
+
+    public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
+        cachedDevice.setVisible(false);
+        return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
+    }
+
+    public void onDeviceNameUpdated(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.refreshName();
+        }
+    }
+
+    /**
+     * 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
+     */
+    public 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
+     */
+    public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
+            LocalBluetoothProfileManager profileManager,
+            BluetoothDevice device) {
+        CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
+            profileManager, device);
+        synchronized (mCachedDevices) {
+            mCachedDevices.add(newDevice);
+        }
+        return newDevice;
+    }
+
+    /**
+     * Attempts to get the name of a remote device, otherwise returns the address.
+     *
+     * @param device The remote device.
+     * @return The name, or if unavailable, the address.
+     */
+    public String getName(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            return cachedDevice.getName();
+        }
+
+        String name = device.getAliasName();
+        if (name != null) {
+            return name;
+        }
+
+        return device.getAddress();
+    }
+
+    public synchronized void clearNonBondedDevices() {
+        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                mCachedDevices.remove(i);
+            }
+        }
+    }
+
+    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);
+        }
+    }
+
+    public synchronized void onBtClassChanged(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.refreshBtClass();
+        }
+    }
+
+    public synchronized void onUuidChanged(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.onUuidChanged();
+        }
+    }
+
+    public synchronized void onBluetoothStateChanged(int bluetoothState) {
+        // When Bluetooth is turning off, we need to clear the non-bonded devices
+        // Otherwise, they end up showing up on the next BT enable
+        if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
+            for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+                CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+                if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                    cachedDevice.setVisible(false);
+                    mCachedDevices.remove(i);
+                } else {
+                    // For bonded devices, we need to clear the connection status so that
+                    // when BT is enabled next time, device connection status shall be retrieved
+                    // by making a binder call.
+                    cachedDevice.clearProfileConnectionState();
+                }
+            }
+        }
+    }
+    private void log(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
new file mode 100755
index 0000000..5529866
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2012 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+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.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HeadsetProfile handles Bluetooth HFP and Headset profiles.
+ */
+public final class HeadsetProfile implements LocalBluetoothProfile {
+    private static final String TAG = "HeadsetProfile";
+    private static boolean V = true;
+
+    private BluetoothHeadset mService;
+    private boolean mIsProfileReady;
+
+    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) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothHeadset) proxy;
+            // We just bound to the service, so refresh the UI for any connected HFP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "HeadsetProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(HeadsetProfile.this,
+                        BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mProfileManager.callServiceConnectedListeners();
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mProfileManager.callServiceDisconnectedListeners();
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    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) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                Log.d(TAG,"Not disconnecting device = " + sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty()) {
+            for (BluetoothDevice dev : deviceList) {
+                if (dev.equals(device)) {
+                    if (V) Log.d(TAG,"Downgrade priority as user" +
+                                        "is disconnecting the headset");
+                    // 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);
+                }
+            }
+        }
+        return false;
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty()){
+            for (BluetoothDevice dev : deviceList) {
+                if (dev.equals(device)) {
+                    return mService.getConnectionState(device);
+                }
+            }
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_headset;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(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;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HID proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
new file mode 100755
index 0000000..a9e8db5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2012 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.List;
+
+/**
+ * HidProfile handles Bluetooth HID profile.
+ */
+public final class HidProfile implements LocalBluetoothProfile {
+    private static final String TAG = "HidProfile";
+    private static boolean V = true;
+
+    private BluetoothInputDevice mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    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) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothInputDevice) proxy;
+            // We just bound to the service, so refresh the UI for any connected HID devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "HidProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    HidProfile(Context context, LocalBluetoothAdapter adapter,
+        CachedBluetoothDeviceManager deviceManager,
+        LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        adapter.getProfileProxy(context, new InputDeviceServiceListener(),
+                BluetoothProfile.INPUT_DEVICE);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        // TODO: distinguish between keyboard and mouse?
+        return R.string.bluetooth_profile_hid;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(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_lockscreen_ime;
+        }
+        return getHidClassDrawable(btClass);
+    }
+
+    public static int getHidClassDrawable(BluetoothClass btClass) {
+        switch (btClass.getDeviceClass()) {
+            case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
+            case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
+                return R.drawable.ic_lockscreen_ime;
+            case BluetoothClass.Device.PERIPHERAL_POINTING:
+                return R.drawable.ic_bt_pointing_hid;
+            default:
+                return R.drawable.ic_bt_misc_hid;
+        }
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HID proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
new file mode 100644
index 0000000..0c1adec
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -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.settingslib.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
+
+    public void cancelDiscovery() {
+        mAdapter.cancelDiscovery();
+    }
+
+    public boolean enable() {
+        return mAdapter.enable();
+    }
+
+    public boolean disable() {
+        return mAdapter.disable();
+    }
+
+    void getProfileProxy(Context context,
+            BluetoothProfile.ServiceListener listener, int profile) {
+        mAdapter.getProfileProxy(context, listener, profile);
+    }
+
+    public Set<BluetoothDevice> getBondedDevices() {
+        return mAdapter.getBondedDevices();
+    }
+
+    public String getName() {
+        return mAdapter.getName();
+    }
+
+    public int getScanMode() {
+        return mAdapter.getScanMode();
+    }
+
+    public int getState() {
+        return mAdapter.getState();
+    }
+
+    public ParcelUuid[] getUuids() {
+        return mAdapter.getUuids();
+    }
+
+    public boolean isDiscovering() {
+        return mAdapter.isDiscovering();
+    }
+
+    public boolean isEnabled() {
+        return mAdapter.isEnabled();
+    }
+
+    public void setDiscoverableTimeout(int timeout) {
+        mAdapter.setDiscoverableTimeout(timeout);
+    }
+
+    public void setName(String name) {
+        mAdapter.setName(name);
+    }
+
+    public void setScanMode(int mode) {
+        mAdapter.setScanMode(mode);
+    }
+
+    public boolean setScanMode(int mode, int duration) {
+        return mAdapter.setScanMode(mode, duration);
+    }
+
+    public 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();
+            }
+        }
+    }
+
+    public 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();
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000..4adc62e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,122 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * LocalBluetoothManager provides a simplified interface on top of a subset of
+ * 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 final class LocalBluetoothManager {
+    private static final String TAG = "LocalBluetoothManager";
+
+    /** Singleton instance. */
+    private static LocalBluetoothManager sInstance;
+
+    private final Context mContext;
+
+    /** If a BT-related activity is in the foreground, this will be it. */
+    private Context mForegroundActivity;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+
+    private final CachedBluetoothDeviceManager mCachedDeviceManager;
+
+    /** The Bluetooth profile manager. */
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    /** The broadcast receiver event manager. */
+    private final BluetoothEventManager mEventManager;
+
+    public static synchronized LocalBluetoothManager getInstance(Context context,
+            BluetoothManagerCallback onInitCallback) {
+        if (sInstance == null) {
+            LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+            if (adapter == null) {
+                return null;
+            }
+            // This will be around as long as this process is
+            Context appContext = context.getApplicationContext();
+            sInstance = new LocalBluetoothManager(adapter, appContext);
+            if (onInitCallback != null) {
+                onInitCallback.onBluetoothManagerInitialized(appContext, sInstance);
+            }
+        }
+
+        return sInstance;
+    }
+
+    private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
+        mContext = context;
+        mLocalAdapter = adapter;
+
+        mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
+        mEventManager = new BluetoothEventManager(mLocalAdapter,
+                mCachedDeviceManager, context);
+        mProfileManager = new LocalBluetoothProfileManager(context,
+                mLocalAdapter, mCachedDeviceManager, mEventManager);
+    }
+
+    public LocalBluetoothAdapter getBluetoothAdapter() {
+        return mLocalAdapter;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public Context getForegroundActivity() {
+        return mForegroundActivity;
+    }
+
+    public boolean isForegroundActivity() {
+        return mForegroundActivity != null;
+    }
+
+    public synchronized void setForegroundActivity(Context context) {
+        if (context != null) {
+            Log.d(TAG, "setting foreground activity to non-null context");
+            mForegroundActivity = context;
+        } else {
+            if (mForegroundActivity != null) {
+                Log.d(TAG, "setting foreground activity to null");
+                mForegroundActivity = null;
+            }
+        }
+    }
+
+    public CachedBluetoothDeviceManager getCachedDeviceManager() {
+        return mCachedDeviceManager;
+    }
+
+    public BluetoothEventManager getEventManager() {
+        return mEventManager;
+    }
+
+    public LocalBluetoothProfileManager getProfileManager() {
+        return mProfileManager;
+    }
+
+    public interface BluetoothManagerCallback {
+        void onBluetoothManagerInitialized(Context appContext,
+                LocalBluetoothManager bluetoothManager);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
new file mode 100755
index 0000000..abcb989
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
@@ -0,0 +1,71 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * LocalBluetoothProfile is an interface defining the basic
+ * functionality related to a Bluetooth profile.
+ */
+public 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.
+     * @param device the Bluetooth device (to distinguish between PAN roles)
+     */
+    int getNameResource(BluetoothDevice device);
+
+    /**
+     * 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);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
new file mode 100644
index 0000000..b0a7b27
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,383 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+
+/**
+ * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
+ * objects for the available Bluetooth profiles.
+ */
+public final class LocalBluetoothProfileManager {
+    private static final String TAG = "LocalBluetoothProfileManager";
+    private static final boolean DEBUG = Utils.D;
+    /** 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 com.android.settings.bluetooth.DockService.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when this proxy object has been
+         * connected to the BluetoothHeadset service. Clients must wait for
+         * this callback before making IPC calls on the BluetoothHeadset
+         * service.
+         */
+        void onServiceConnected();
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the BluetoothHeadset service. Clients must not
+         * make IPC calls on the BluetoothHeadset service after this callback.
+         * This callback will currently only occur if the application hosting
+         * the BluetoothHeadset service, but may be called more often in future.
+         */
+        void onServiceDisconnected();
+    }
+
+    private final Context mContext;
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final BluetoothEventManager mEventManager;
+
+    private A2dpProfile mA2dpProfile;
+    private HeadsetProfile mHeadsetProfile;
+    private MapProfile mMapProfile;
+    private final HidProfile mHidProfile;
+    private OppProfile mOppProfile;
+    private final PanProfile mPanProfile;
+    private final PbapServerProfile mPbapProfile;
+
+    /**
+     * Mapping from profile name, e.g. "HEADSET" to profile object.
+     */
+    private final Map<String, LocalBluetoothProfile>
+            mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
+
+    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);
+
+        ParcelUuid[] uuids = adapter.getUuids();
+
+        // uuids may be null if Bluetooth is turned off
+        if (uuids != null) {
+            updateLocalProfiles(uuids);
+        }
+
+        // Always add HID and PAN profiles
+        mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
+        addProfile(mHidProfile, HidProfile.NAME,
+                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+
+        mPanProfile = new PanProfile(context);
+        addPanProfile(mPanProfile, PanProfile.NAME,
+                BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
+
+        if(DEBUG) Log.d(TAG, "Adding local MAP profile");
+        mMapProfile = new MapProfile(mContext, mLocalAdapter,
+                mDeviceManager, this);
+        addProfile(mMapProfile, MapProfile.NAME,
+                BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
+
+       //Create PBAP server profile, but do not add it to list of profiles
+       // as we do not need to monitor the profile as part of profile list
+        mPbapProfile = new PbapServerProfile(context);
+
+        if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
+    }
+
+    /**
+     * 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
+     */
+    void updateLocalProfiles(ParcelUuid[] uuids) {
+        // A2DP
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
+            if (mA2dpProfile == null) {
+                if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
+                mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
+                addProfile(mA2dpProfile, A2dpProfile.NAME,
+                        BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } 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) {
+                if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
+                mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
+                        mDeviceManager, this);
+                addProfile(mHeadsetProfile, HeadsetProfile.NAME,
+                        BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } else if (mHeadsetProfile != null) {
+            Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
+        }
+
+        // OPP
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+            if (mOppProfile == null) {
+                if(DEBUG) 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.");
+        }
+        mEventManager.registerProfileIntentReceiver();
+
+        // There is no local SDP record for HID and Settings app doesn't control PBAP
+    }
+
+    private final Collection<ServiceListener> mServiceListeners =
+            new ArrayList<ServiceListener>();
+
+    private void addProfile(LocalBluetoothProfile profile,
+            String profileName, String stateChangedAction) {
+        mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
+        mProfileNameMap.put(profileName, profile);
+    }
+
+    private void addPanProfile(LocalBluetoothProfile profile,
+            String profileName, String stateChangedAction) {
+        mEventManager.addProfileHandler(stateChangedAction,
+                new PanStateChangedHandler(profile));
+        mProfileNameMap.put(profileName, profile);
+    }
+
+    public LocalBluetoothProfile getProfileByName(String name) {
+        return mProfileNameMap.get(name);
+    }
+
+    // Called from LocalBluetoothAdapter when state changes to ON
+    void setBluetoothStateOn() {
+        ParcelUuid[] uuids = mLocalAdapter.getUuids();
+        if (uuids != null) {
+            updateLocalProfiles(uuids);
+        }
+        mEventManager.readPairedDevices();
+    }
+
+    /**
+     * Generic handler for connection state change events for the specified profile.
+     */
+    private class StateChangedHandler implements BluetoothEventManager.Handler {
+        final LocalBluetoothProfile mProfile;
+
+        StateChangedHandler(LocalBluetoothProfile profile) {
+            mProfile = profile;
+        }
+
+        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);
+            }
+            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");
+            }
+
+            cachedDevice.onProfileStateChanged(mProfile, newState);
+            cachedDevice.refresh();
+        }
+    }
+
+    /** State change handler for NAP and PANU profiles. */
+    private class PanStateChangedHandler extends StateChangedHandler {
+
+        PanStateChangedHandler(LocalBluetoothProfile profile) {
+            super(profile);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            PanProfile panProfile = (PanProfile) mProfile;
+            int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
+            panProfile.setLocalRole(device, role);
+            super.onReceive(context, intent, device);
+        }
+    }
+
+    // called from DockService
+    public void addServiceListener(ServiceListener l) {
+        mServiceListeners.add(l);
+    }
+
+    // called from DockService
+    public void removeServiceListener(ServiceListener l) {
+        mServiceListeners.remove(l);
+    }
+
+    // not synchronized: use only from UI thread! (TODO: verify)
+    void callServiceConnectedListeners() {
+        for (ServiceListener l : mServiceListeners) {
+            l.onServiceConnected();
+        }
+    }
+
+    // not synchronized: use only from UI thread! (TODO: verify)
+    void callServiceDisconnectedListeners() {
+        for (ServiceListener listener : mServiceListeners) {
+            listener.onServiceDisconnected();
+        }
+    }
+
+    // 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();
+        }
+        profile = mA2dpProfile;
+        if (profile != null) {
+            return profile.isProfileReady();
+        }
+        return false;
+    }
+
+    public A2dpProfile getA2dpProfile() {
+        return mA2dpProfile;
+    }
+
+    public HeadsetProfile getHeadsetProfile() {
+        return mHeadsetProfile;
+    }
+
+    public PbapServerProfile getPbapProfile(){
+        return mPbapProfile;
+    }
+
+    public MapProfile getMapProfile(){
+        return mMapProfile;
+    }
+
+    /**
+     * 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
+     * @param removedProfiles list of profiles that were removed
+     */
+    synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
+            Collection<LocalBluetoothProfile> profiles,
+            Collection<LocalBluetoothProfile> removedProfiles,
+            boolean isPanNapConnected, BluetoothDevice device) {
+        // Copy previous profile list into removedProfiles
+        removedProfiles.clear();
+        removedProfiles.addAll(profiles);
+        profiles.clear();
+
+        if (uuids == null) {
+            return;
+        }
+
+        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);
+                removedProfiles.remove(mHeadsetProfile);
+            }
+        }
+
+        if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
+            mA2dpProfile != null) {
+            profiles.add(mA2dpProfile);
+            removedProfiles.remove(mA2dpProfile);
+        }
+
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
+            mOppProfile != null) {
+            profiles.add(mOppProfile);
+            removedProfiles.remove(mOppProfile);
+        }
+
+        if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
+             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
+            mHidProfile != null) {
+            profiles.add(mHidProfile);
+            removedProfiles.remove(mHidProfile);
+        }
+
+        if(isPanNapConnected)
+            if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
+        if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
+            mPanProfile != null) || isPanNapConnected) {
+            profiles.add(mPanProfile);
+            removedProfiles.remove(mPanProfile);
+        }
+
+        if ((mMapProfile != null) &&
+            (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
+            profiles.add(mMapProfile);
+            removedProfiles.remove(mMapProfile);
+            mMapProfile.setPreferred(device, true);
+        }
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
new file mode 100644
index 0000000..e6a152f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MapProfile handles Bluetooth MAP profile.
+ */
+public final class MapProfile implements LocalBluetoothProfile {
+    private static final String TAG = "MapProfile";
+    private static boolean V = true;
+
+    private BluetoothMap mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final ParcelUuid[] UUIDS = {
+        BluetoothUuid.MAP,
+        BluetoothUuid.MNS,
+        BluetoothUuid.MAS,
+    };
+
+    static final String NAME = "MAP";
+
+    // Order of this profile in device profiles list
+
+    // These callbacks run on the main thread.
+    private final class MapServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothMap) proxy;
+            // We just bound to the service, so refresh the UI for any connected MAP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "MapProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(MapProfile.this,
+                        BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mProfileManager.callServiceConnectedListeners();
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mProfileManager.callServiceDisconnectedListeners();
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady);
+        return mIsProfileReady;
+    }
+
+    MapProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new MapServiceListener(),
+                BluetoothProfile.MAP);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if(V)Log.d(TAG,"connect() - should not get called");
+        return false; // MAP never connects out
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
+            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) {
+        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+        if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device));
+
+        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+                ? mService.getConnectionState(device)
+                : BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return BluetoothProfile.MAP;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_map;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_map_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_map_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up MAP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
new file mode 100755
index 0000000..31e675c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -0,0 +1,89 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import com.android.settingslib.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(BluetoothDevice device) {
+        return R.string.bluetooth_profile_opp;
+    }
+
+    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/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
new file mode 100755
index 0000000..3af89e6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -0,0 +1,183 @@
+/*
+ * 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.settingslib.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 android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * PanProfile handles Bluetooth PAN profile (NAP and PANU).
+ */
+final class PanProfile implements LocalBluetoothProfile {
+    private static final String TAG = "PanProfile";
+    private static boolean V = true;
+
+    private BluetoothPan mService;
+    private boolean mIsProfileReady;
+
+    // Tethering direction for each device
+    private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap =
+            new HashMap<BluetoothDevice, Integer>();
+
+    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) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothPan) proxy;
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    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) {
+        if (mService == null) return false;
+        List<BluetoothDevice> sinks = mService.getConnectedDevices();
+        if (sinks != null) {
+            for (BluetoothDevice sink : sinks) {
+                mService.disconnect(sink);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        // return current connection status so profile checkbox is set correctly
+        return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return -1;
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        // ignore: isPreferred is always true for PAN
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        if (isLocalRoleNap(device)) {
+            return R.string.bluetooth_profile_pan_nap;
+        } else {
+            return R.string.bluetooth_profile_pan;
+        }
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_pan_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                if (isLocalRoleNap(device)) {
+                    return R.string.bluetooth_pan_nap_profile_summary_connected;
+                } else {
+                    return R.string.bluetooth_pan_user_profile_summary_connected;
+                }
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_network_pan;
+    }
+
+    // Tethering direction determines UI strings.
+    void setLocalRole(BluetoothDevice device, int role) {
+        mDeviceRoleMap.put(device, role);
+    }
+
+    boolean isLocalRoleNap(BluetoothDevice device) {
+        if (mDeviceRoleMap.containsKey(device)) {
+            return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE;
+        } else {
+            return false;
+        }
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up PAN proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
new file mode 100755
index 0000000..a552b24a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -0,0 +1,152 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * PBAPServer Profile
+ */
+public final class PbapServerProfile implements LocalBluetoothProfile {
+    private static final String TAG = "PbapServerProfile";
+    private static boolean V = true;
+
+    private BluetoothPbap mService;
+    private boolean mIsProfileReady;
+
+    static final String NAME = "PBAP Server";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 6;
+
+    // The UUIDs indicate that remote device might access pbap server
+    static final ParcelUuid[] PBAB_CLIENT_UUIDS = {
+        BluetoothUuid.HSP,
+        BluetoothUuid.Handsfree,
+        BluetoothUuid.PBAP_PCE
+    };
+
+    // These callbacks run on the main thread.
+    private final class PbapServiceListener
+            implements BluetoothPbap.ServiceListener {
+
+        public void onServiceConnected(BluetoothPbap proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothPbap) proxy;
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected() {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    PbapServerProfile(Context context) {
+        BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        /*Can't connect from server */
+        return false;
+
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.disconnect();
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        if (mService.isConnected(device))
+            return BluetoothProfile.STATE_CONNECTED;
+        else
+            return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        return false;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        return -1;
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        // ignore: isPreferred is always true for PBAP
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_pbap;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return R.string.bluetooth_profile_pbap_summary;
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                mService.close();
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up PBAP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
new file mode 100644
index 0000000..c919426
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
@@ -0,0 +1,43 @@
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+
+public class Utils {
+    public static final boolean V = false; // verbose logging
+    public static final boolean D = true;  // regular logging
+
+    private static ErrorListener sErrorListener;
+
+    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;
+        }
+    }
+
+    static void showError(Context context, String name, int messageResId) {
+        if (sErrorListener != null) {
+            sErrorListener.onShowError(context, name, messageResId);
+        }
+    }
+
+    public static void setErrorListener(ErrorListener listener) {
+        sErrorListener = listener;
+    }
+
+    public interface ErrorListener {
+        void onShowError(Context context, String name, int messageResId);
+    }
+
+}