Refactor android.server.BluetoothService classes.

Several cleanups to BluetoothService:
- Move BluetoothService.BondState inner class to top level.
- Extract adapter and remote device properties cache management
  logic into two new classes: BluetoothAdapterProperties and
  BluetoothDeviceProperties.
- Add getter methods for other classes in the package to access
  the new properties cache objects for multi-part operations.
- Inline log() method in BluetoothService and selected the
  appropriate Log method (Log.d, Log.w, Log.e) for each message.
- Refactor dump() method into smaller sized pieces.
- Clean up logic of updateCountersAndCheckForConnectionStateChange()
  method for better readability.
- Change sendConnectionStateChange() to return instead of sending
  an intent if the current or previous state values are invalid.
  Previously the code sent an intent with -1 for the invalid state.
- Added Javadoc comments to document the methods that are called from
  native Bluez code.
- Fixed some typos and code style issues.

Original Change by: Jake Hamby
Modified by: Jaikumar Ganesh

Change-Id: I76ebac00ecd29566dcb2d1ad3e7a486b7530ce24
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 116a068..9855709 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -82,7 +82,6 @@
     public static final int TRANSITION_TO_STABLE = 102;
     public static final int CONNECT_OTHER_PROFILES = 103;
 
-    private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
     private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
 
     private BondedDevice mBondedDevice = new BondedDevice();
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index b5e85a0..132c346 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -16,7 +16,7 @@
 
 /**
  * TODO: Move this to services.jar
- * and make the contructor package private again.
+ * and make the constructor package private again.
  * @hide
  */
 
@@ -57,8 +57,6 @@
 
     private static final String PROPERTY_STATE = "State";
 
-    private static int mSinkCount;
-
     private final Context mContext;
     private final IntentFilter mIntentFilter;
     private HashMap<BluetoothDevice, Integer> mAudioDevices;
@@ -128,7 +126,6 @@
         }
     };
 
-
     private boolean isPhoneDocked(BluetoothDevice device) {
         // This works only because these broadcast intents are "sticky"
         Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
@@ -184,7 +181,7 @@
         }
     }
 
-    private int convertBluezSinkStringtoState(String value) {
+    private int convertBluezSinkStringToState(String value) {
         if (value.equalsIgnoreCase("disconnected"))
             return BluetoothA2dp.STATE_DISCONNECTED;
         if (value.equalsIgnoreCase("connecting"))
@@ -204,7 +201,7 @@
         return false;
     }
 
-    private synchronized boolean addAudioSink (BluetoothDevice device) {
+    private synchronized boolean addAudioSink(BluetoothDevice device) {
         String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
         String propValues[] = (String []) getSinkPropertiesNative(path);
         if (propValues == null) {
@@ -215,7 +212,7 @@
         // Properties are name-value pairs
         for (int i = 0; i < propValues.length; i+=2) {
             if (propValues[i].equals(PROPERTY_STATE)) {
-                state = new Integer(convertBluezSinkStringtoState(propValues[i+1]));
+                state = new Integer(convertBluezSinkStringToState(propValues[i+1]));
                 break;
             }
         }
@@ -226,7 +223,6 @@
 
     private synchronized void onBluetoothEnable() {
         String devices = mBluetoothService.getProperty("Devices");
-        mSinkCount = 0;
         if (devices != null) {
             String [] paths = devices.split(",");
             for (String path: paths) {
@@ -274,18 +270,18 @@
     private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
         if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
                 getPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
-                return false;
-            }
+            return false;
+        }
 
-            if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
-                return false;
-            }
+        if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
+            return false;
+        }
 
-            String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
-            if (path == null) {
-                return false;
-            }
-            return true;
+        String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
+        if (path == null) {
+            return false;
+        }
+        return true;
     }
 
     public synchronized boolean isA2dpPlaying(BluetoothDevice device) {
@@ -467,7 +463,15 @@
                 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
     }
 
-    private synchronized void onSinkPropertyChanged(String path, String []propValues) {
+    /**
+     * Called by native code on a PropertyChanged signal from
+     * org.bluez.AudioSink.
+     *
+     * @param path the object path for the changed device
+     * @param propValues a string array containing the key and one or more
+     *  values.
+     */
+    private synchronized void onSinkPropertyChanged(String path, String[] propValues) {
         if (!mBluetoothService.isEnabled()) {
             return;
         }
@@ -482,7 +486,7 @@
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
 
         if (name.equals(PROPERTY_STATE)) {
-            int state = convertBluezSinkStringtoState(propValues[1]);
+            int state = convertBluezSinkStringToState(propValues[1]);
             log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
 
             if (mAudioDevices.get(device) == null) {
@@ -508,12 +512,6 @@
 
     private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
         if (state != prevState) {
-            if (state == BluetoothA2dp.STATE_DISCONNECTED ||
-                    state == BluetoothA2dp.STATE_DISCONNECTING) {
-                mSinkCount--;
-            } else if (state == BluetoothA2dp.STATE_CONNECTED) {
-                mSinkCount ++;
-            }
             mAudioDevices.put(device, state);
 
             checkSinkSuspendState(state);
@@ -577,6 +575,13 @@
         return result;
     }
 
+    /**
+     * Called by native code for the async response to a Connect
+     * method call to org.bluez.AudioSink.
+     *
+     * @param deviceObjectPath the object path for the connecting device
+     * @param result true on success; false on error
+     */
     private void onConnectSinkResult(String deviceObjectPath, boolean result) {
         // If the call was a success, ignore we will update the state
         // when we a Sink Property Change
diff --git a/core/java/android/server/BluetoothAdapterProperties.java b/core/java/android/server/BluetoothAdapterProperties.java
new file mode 100644
index 0000000..ae8104b
--- /dev/null
+++ b/core/java/android/server/BluetoothAdapterProperties.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2010 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 android.server;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class BluetoothAdapterProperties {
+
+    private static final String TAG = "BluetoothAdapterProperties";
+
+    private final Map<String, String> mPropertiesMap;
+    private final Context mContext;
+    private final BluetoothService mService;
+
+    BluetoothAdapterProperties(Context context, BluetoothService service) {
+        mPropertiesMap = new HashMap<String, String>();
+        mContext = context;
+        mService = service;
+    }
+
+    synchronized String getProperty(String name) {
+        if (mPropertiesMap.isEmpty()) {
+            getAllProperties();
+        }
+        return mPropertiesMap.get(name);
+    }
+
+    String getObjectPath() {
+        return getProperty("ObjectPath");
+    }
+
+    synchronized void clear() {
+        mPropertiesMap.clear();
+    }
+
+    synchronized boolean isEmpty() {
+        return mPropertiesMap.isEmpty();
+    }
+
+    synchronized void setProperty(String name, String value) {
+        mPropertiesMap.put(name, value);
+    }
+
+    synchronized void getAllProperties() {
+        mContext.enforceCallingOrSelfPermission(
+                BluetoothService.BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        mPropertiesMap.clear();
+
+        String properties[] = (String[]) mService
+                .getAdapterPropertiesNative();
+        // The String Array consists of key-value pairs.
+        if (properties == null) {
+            Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
+            return;
+        }
+
+        for (int i = 0; i < properties.length; i++) {
+            String name = properties[i];
+            String newValue = null;
+            int len;
+            if (name == null) {
+                Log.e(TAG, "Error:Adapter Property at index " + i + " is null");
+                continue;
+            }
+            if (name.equals("Devices") || name.equals("UUIDs")) {
+                StringBuilder str = new StringBuilder();
+                len = Integer.valueOf(properties[++i]);
+                for (int j = 0; j < len; j++) {
+                    str.append(properties[++i]);
+                    str.append(",");
+                }
+                if (len > 0) {
+                    newValue = str.toString();
+                }
+            } else {
+                newValue = properties[++i];
+            }
+            mPropertiesMap.put(name, newValue);
+        }
+
+        // Add adapter object path property.
+        String adapterPath = mService.getAdapterPathNative();
+        if (adapterPath != null) {
+            mPropertiesMap.put("ObjectPath", adapterPath + "/dev_");
+        }
+    }
+}
diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java
new file mode 100644
index 0000000..2304a70
--- /dev/null
+++ b/core/java/android/server/BluetoothBondState.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2010 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 android.server;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Local cache of bonding state.
+ * We keep our own state to track the intermediate state BONDING, which
+ * bluez does not track.
+ * All addresses must be passed in upper case.
+ */
+class BluetoothBondState {
+    private static final String TAG = "BluetoothBondState";
+    private static final boolean DBG =  true;
+
+    private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
+    private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
+
+    private static final String AUTO_PAIRING_BLACKLIST =
+        "/etc/bluetooth/auto_pairing.conf";
+    private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
+        "/data/misc/bluetooth/dynamic_auto_pairing.conf";
+    private ArrayList<String> mAutoPairingAddressBlacklist;
+    private ArrayList<String> mAutoPairingExactNameBlacklist;
+    private ArrayList<String> mAutoPairingPartialNameBlacklist;
+    private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList;
+    // Addresses added to blacklist dynamically based on usage.
+    private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
+
+    // If this is an outgoing connection, store the address.
+    // There can be only 1 pending outgoing connection at a time,
+    private String mPendingOutgoingBonding;
+
+    private final Context mContext;
+    private final BluetoothService mService;
+    private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;
+
+    BluetoothBondState(Context context, BluetoothService service) {
+        mContext = context;
+        mService = service;
+        mBluetoothInputProfileHandler =
+            BluetoothInputProfileHandler.getInstance(mContext, mService);
+    }
+
+    synchronized void setPendingOutgoingBonding(String address) {
+        mPendingOutgoingBonding = address;
+    }
+
+    public synchronized String getPendingOutgoingBonding() {
+        return mPendingOutgoingBonding;
+    }
+
+    public synchronized void loadBondState() {
+        if (mService.getBluetoothStateInternal() !=
+                BluetoothAdapter.STATE_TURNING_ON) {
+            return;
+        }
+        String val = mService.getAdapterProperties().getProperty("Devices");
+        if (val == null) {
+            return;
+        }
+        String[] bonds = val.split(",");
+        if (bonds == null) {
+            return;
+        }
+        mState.clear();
+        if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices");
+        for (String device : bonds) {
+            mState.put(mService.getAddressFromObjectPath(device).toUpperCase(),
+                    BluetoothDevice.BOND_BONDED);
+        }
+    }
+
+    public synchronized void setBondState(String address, int state) {
+        setBondState(address, state, 0);
+    }
+
+    /** reason is ignored unless state == BOND_NOT_BONDED */
+    public synchronized void setBondState(String address, int state, int reason) {
+        int oldState = getBondState(address);
+        if (oldState == state) {
+            return;
+        }
+
+        // Check if this was an pending outgoing bonding.
+        // If yes, reset the state.
+        if (oldState == BluetoothDevice.BOND_BONDING) {
+            if (address.equals(mPendingOutgoingBonding)) {
+                mPendingOutgoingBonding = null;
+            }
+        }
+
+        if (state == BluetoothDevice.BOND_BONDED) {
+            mService.addProfileState(address);
+        } else if (state == BluetoothDevice.BOND_NONE) {
+            mService.removeProfileState(address);
+        }
+
+        // HID is handled by BluetoothService, other profiles
+        // will be handled by their respective services.
+        mBluetoothInputProfileHandler.setInitialInputDevicePriority(
+            mService.getRemoteDevice(address), state);
+
+        if (DBG) {
+            Log.d(TAG, address + " bond state " + oldState + " -> " + state
+                + " (" + reason + ")");
+        }
+        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address));
+        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
+        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
+        if (state == BluetoothDevice.BOND_NONE) {
+            if (reason <= 0) {
+                Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
+                      "invalid. Overriding reason code with BOND_RESULT_REMOVED");
+                reason = BluetoothDevice.UNBOND_REASON_REMOVED;
+            }
+            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
+            mState.remove(address);
+        } else {
+            mState.put(address, state);
+        }
+
+        mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
+    }
+
+    public boolean isAutoPairingBlacklisted(String address) {
+        if (mAutoPairingAddressBlacklist != null) {
+            for (String blacklistAddress : mAutoPairingAddressBlacklist) {
+                if (address.startsWith(blacklistAddress)) return true;
+            }
+        }
+
+        if (mAutoPairingDynamicAddressBlacklist != null) {
+            for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
+                if (address.equals(blacklistAddress)) return true;
+            }
+        }
+
+        String name = mService.getRemoteName(address);
+        if (name != null) {
+            if (mAutoPairingExactNameBlacklist != null) {
+                for (String blacklistName : mAutoPairingExactNameBlacklist) {
+                    if (name.equals(blacklistName)) return true;
+                }
+            }
+
+            if (mAutoPairingPartialNameBlacklist != null) {
+                for (String blacklistName : mAutoPairingPartialNameBlacklist) {
+                    if (name.startsWith(blacklistName)) return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean isFixedPinZerosAutoPairKeyboard(String address) {
+        // Note: the meaning of blacklist is reversed in this case.
+        // If its in the list, we can go ahead and auto pair since
+        // by default keyboard should have a variable PIN that we don't
+        // auto pair using 0000.
+        if (mAutoPairingFixedPinZerosKeyboardList != null) {
+            for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) {
+                if (address.startsWith(blacklistAddress)) return true;
+            }
+        }
+        return false;
+    }
+
+    public synchronized int getBondState(String address) {
+        Integer state = mState.get(address);
+        if (state == null) {
+            return BluetoothDevice.BOND_NONE;
+        }
+        return state.intValue();
+    }
+
+    /*package*/ synchronized String[] listInState(int state) {
+        ArrayList<String> result = new ArrayList<String>(mState.size());
+        for (Map.Entry<String, Integer> e : mState.entrySet()) {
+            if (e.getValue().intValue() == state) {
+                result.add(e.getKey());
+            }
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    public synchronized void addAutoPairingFailure(String address) {
+        if (mAutoPairingDynamicAddressBlacklist == null) {
+            mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
+        }
+
+        updateAutoPairingData(address);
+        mAutoPairingDynamicAddressBlacklist.add(address);
+    }
+
+    public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
+        return getAttempt(address) != 0;
+    }
+
+    public synchronized void clearPinAttempts(String address) {
+        mPinAttempt.remove(address);
+    }
+
+    public synchronized boolean hasAutoPairingFailed(String address) {
+        if (mAutoPairingDynamicAddressBlacklist == null) return false;
+
+        return mAutoPairingDynamicAddressBlacklist.contains(address);
+    }
+
+    public synchronized int getAttempt(String address) {
+        Integer attempt = mPinAttempt.get(address);
+        if (attempt == null) {
+            return 0;
+        }
+        return attempt.intValue();
+    }
+
+    public synchronized void attempt(String address) {
+        Integer attempt = mPinAttempt.get(address);
+        int newAttempt;
+        if (attempt == null) {
+            newAttempt = 1;
+        } else {
+            newAttempt = attempt.intValue() + 1;
+        }
+        mPinAttempt.put(address, new Integer(newAttempt));
+    }
+
+    private void copyAutoPairingData() {
+        FileInputStream in = null;
+        FileOutputStream out = null;
+        try {
+            File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
+            if (file.exists()) return;
+
+            in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
+            out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
+
+            byte[] buf = new byte[1024];
+            int len;
+            while ((len = in.read(buf)) > 0) {
+                out.write(buf, 0, len);
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException: copyAutoPairingData " + e);
+        } finally {
+             try {
+                 if (in != null) in.close();
+                 if (out != null) out.close();
+             } catch (IOException e) {}
+        }
+    }
+
+    synchronized public void readAutoPairingData() {
+        if (mAutoPairingAddressBlacklist != null) return;
+        copyAutoPairingData();
+        FileInputStream fstream = null;
+        try {
+            fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
+            DataInputStream in = new DataInputStream(fstream);
+            BufferedReader file = new BufferedReader(new InputStreamReader(in));
+            String line;
+            while((line = file.readLine()) != null) {
+                line = line.trim();
+                if (line.length() == 0 || line.startsWith("//")) continue;
+                String[] value = line.split("=");
+                if (value != null && value.length == 2) {
+                    String[] val = value[1].split(",");
+                    if (value[0].equalsIgnoreCase("AddressBlacklist")) {
+                        mAutoPairingAddressBlacklist =
+                            new ArrayList<String>(Arrays.asList(val));
+                    } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
+                        mAutoPairingExactNameBlacklist =
+                            new ArrayList<String>(Arrays.asList(val));
+                    } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
+                        mAutoPairingPartialNameBlacklist =
+                            new ArrayList<String>(Arrays.asList(val));
+                    } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) {
+                        mAutoPairingFixedPinZerosKeyboardList =
+                            new ArrayList<String>(Arrays.asList(val));
+                    } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
+                        mAutoPairingDynamicAddressBlacklist =
+                            new ArrayList<String>(Arrays.asList(val));
+                    } else {
+                        Log.e(TAG, "Error parsing Auto pairing blacklist file");
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException: readAutoPairingData " + e);
+        } finally {
+            if (fstream != null) {
+                try {
+                    fstream.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    // This function adds a bluetooth address to the auto pairing blacklist
+    // file. These addresses are added to DynamicAddressBlacklistSection
+    private void updateAutoPairingData(String address) {
+        BufferedWriter out = null;
+        try {
+            out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
+            StringBuilder str = new StringBuilder();
+            if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
+                str.append("DynamicAddressBlacklist=");
+            }
+            str.append(address);
+            str.append(",");
+            out.write(str.toString());
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException: updateAutoPairingData " + e);
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/server/BluetoothDeviceProperties.java b/core/java/android/server/BluetoothDeviceProperties.java
new file mode 100644
index 0000000..3dc53d7
--- /dev/null
+++ b/core/java/android/server/BluetoothDeviceProperties.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 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 android.server;
+
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+class BluetoothDeviceProperties {
+
+    private static final String TAG = "BluetoothDeviceProperties";
+
+    private final HashMap<String, Map<String, String>> mPropertiesMap;
+    private final BluetoothService mService;
+
+    BluetoothDeviceProperties(BluetoothService service) {
+        mPropertiesMap = new HashMap<String, Map<String, String>>();
+        mService = service;
+    }
+
+    synchronized Map<String, String> addProperties(String address,
+            String[] properties) {
+        /*
+         * We get a DeviceFound signal every time RSSI changes or name changes.
+         * Don't create a new Map object every time.
+         */
+        Map<String, String> propertyValues = mPropertiesMap.get(address);
+        if (propertyValues == null) {
+            propertyValues = new HashMap<String, String>();
+        }
+
+        for (int i = 0; i < properties.length; i++) {
+            String name = properties[i];
+            String newValue = null;
+            int len;
+            if (name == null) {
+                Log.e(TAG, "Error: Remote Device Property at index "
+                        + i + " is null");
+                continue;
+            }
+            if (name.equals("UUIDs") || name.equals("Nodes")) {
+                StringBuilder str = new StringBuilder();
+                len = Integer.valueOf(properties[++i]);
+                for (int j = 0; j < len; j++) {
+                    str.append(properties[++i]);
+                    str.append(",");
+                }
+                if (len > 0) {
+                    newValue = str.toString();
+                }
+            } else {
+                newValue = properties[++i];
+            }
+
+            propertyValues.put(name, newValue);
+        }
+        mPropertiesMap.put(address, propertyValues);
+
+        // We have added a new remote device or updated its properties.
+        // Also update the serviceChannel cache.
+        mService.updateDeviceServiceChannelCache(address);
+        return propertyValues;
+    }
+
+    synchronized void setProperty(String address, String name, String value) {
+        Map <String, String> propVal = mPropertiesMap.get(address);
+        if (propVal != null) {
+            propVal.put(name, value);
+            mPropertiesMap.put(address, propVal);
+        } else {
+            Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
+        }
+    }
+
+    synchronized boolean isInCache(String address) {
+        return (mPropertiesMap.get(address) != null);
+    }
+
+    synchronized boolean isEmpty() {
+        return mPropertiesMap.isEmpty();
+    }
+
+    synchronized Set<String> keySet() {
+        return mPropertiesMap.keySet();
+    }
+
+    synchronized String getProperty(String address, String property) {
+        Map<String, String> properties = mPropertiesMap.get(address);
+        if (properties != null) {
+            return properties.get(property);
+        } else {
+            // Query for remote device properties, again.
+            // We will need to reload the cache when we switch Bluetooth on / off
+            // or if we crash.
+            properties = updateCache(address);
+            if (properties != null) {
+                return properties.get(property);
+            }
+        }
+        Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
+        return null;
+    }
+
+    synchronized Map<String, String> updateCache(String address) {
+        String[] propValues = mService.getRemoteDeviceProperties(address);
+        if (propValues != null) {
+            return addProperties(address, propValues);
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 578580a..e72aaa7 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -34,14 +34,9 @@
 
 import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
 
 
 /**
- * TODO: Move this to
- * java/services/com/android/server/BluetoothEventLoop.java
- * and make the constructor package private again.
- *
  * @hide
  */
 class BluetoothEventLoop {
@@ -114,7 +109,7 @@
             BluetoothService bluetoothService) {
         mBluetoothService = bluetoothService;
         mContext = context;
-        mPasskeyAgentRequestData = new HashMap();
+        mPasskeyAgentRequestData = new HashMap<String, Integer>();
         mAdapter = adapter;
         //WakeLock instantiation in BluetoothEventLoop class
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -180,10 +175,12 @@
     }
 
     private void addDevice(String address, String[] properties) {
-        mBluetoothService.addRemoteDeviceProperties(address, properties);
-        String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI");
-        String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class");
-        String name = mBluetoothService.getRemoteDeviceProperty(address, "Name");
+        BluetoothDeviceProperties deviceProperties =
+                mBluetoothService.getDeviceProperties();
+        deviceProperties.addProperties(address, properties);
+        String rssi = deviceProperties.getProperty(address, "RSSI");
+        String classValue = deviceProperties.getProperty(address, "Class");
+        String name = deviceProperties.getProperty(address, "Name");
         short rssiValue;
         // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
         // If we accept the pairing, we will automatically show it at the top of the list.
@@ -206,6 +203,14 @@
         }
     }
 
+    /**
+     * Called by native code on a DeviceFound signal from org.bluez.Adapter.
+     *
+     * @param address the MAC address of the new device
+     * @param properties an array of property keys and value strings
+     *
+     * @see BluetoothDeviceProperties#addProperties(String, String[])
+     */
     private void onDeviceFound(String address, String[] properties) {
         if (properties == null) {
             Log.e(TAG, "ERROR: Remote device properties are null");
@@ -214,12 +219,24 @@
         addDevice(address, properties);
     }
 
+    /**
+     * Called by native code on a DeviceDisappeared signal from
+     * org.bluez.Adapter.
+     *
+     * @param address the MAC address of the disappeared device
+     */
     private void onDeviceDisappeared(String address) {
         Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
     }
 
+    /**
+     * Called by native code on a DisconnectRequested signal from
+     * org.bluez.Device.
+     *
+     * @param deviceObjectPath the object path for the disconnecting device
+     */
     private void onDeviceDisconnectRequested(String deviceObjectPath) {
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         if (address == null) {
@@ -231,11 +248,23 @@
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
     }
 
+    /**
+     * Called by native code for the async response to a CreatePairedDevice
+     * method call to org.bluez.Adapter.
+     *
+     * @param address the MAC address of the device to pair
+     * @param result success or error result for the pairing operation
+     */
     private void onCreatePairedDeviceResult(String address, int result) {
         address = address.toUpperCase();
         mBluetoothService.onCreatePairedDeviceResult(address, result);
     }
 
+    /**
+     * Called by native code on a DeviceCreated signal from org.bluez.Adapter.
+     *
+     * @param deviceObjectPath the object path for the created device
+     */
     private void onDeviceCreated(String deviceObjectPath) {
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         if (!mBluetoothService.isRemoteDeviceInCache(address)) {
@@ -248,6 +277,11 @@
         return;
     }
 
+    /**
+     * Called by native code on a DeviceRemoved signal from org.bluez.Adapter.
+     *
+     * @param deviceObjectPath the object path for the removed device
+     */
     private void onDeviceRemoved(String deviceObjectPath) {
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         if (address != null) {
@@ -257,31 +291,43 @@
         }
     }
 
+    /**
+     * Called by native code on a PropertyChanged signal from
+     * org.bluez.Adapter. This method is also called from Java at
+     * {@link BluetoothService.EnableThread#run()} to set the "Pairable"
+     * property when Bluetooth is enabled.
+     *
+     * @param propValues a string array containing the key and one or more
+     *  values.
+     */
     /*package*/ void onPropertyChanged(String[] propValues) {
-        if (mBluetoothService.isAdapterPropertiesEmpty()) {
+        BluetoothAdapterProperties adapterProperties =
+                mBluetoothService.getAdapterProperties();
+
+        if (adapterProperties.isEmpty()) {
             // We have got a property change before
             // we filled up our cache.
-            mBluetoothService.getAllProperties();
+            adapterProperties.getAllProperties();
         }
         log("Property Changed: " + propValues[0] + " : " + propValues[1]);
         String name = propValues[0];
         if (name.equals("Name")) {
-            mBluetoothService.setProperty(name, propValues[1]);
+            adapterProperties.setProperty(name, propValues[1]);
             Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
             intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
         } else if (name.equals("Pairable") || name.equals("Discoverable")) {
             String pairable = name.equals("Pairable") ? propValues[1] :
-                mBluetoothService.getPropertyInternal("Pairable");
+                adapterProperties.getProperty("Pairable");
             String discoverable = name.equals("Discoverable") ? propValues[1] :
-                mBluetoothService.getPropertyInternal("Discoverable");
+                adapterProperties.getProperty("Discoverable");
 
             // This shouldn't happen, unless Adapter Properties are null.
             if (pairable == null || discoverable == null)
                 return;
 
-            mBluetoothService.setProperty(name, propValues[1]);
+            adapterProperties.setProperty(name, propValues[1]);
             int mode = BluetoothService.bluezStringToScanMode(
                     pairable.equals("true"),
                     discoverable.equals("true"));
@@ -293,7 +339,7 @@
             }
         } else if (name.equals("Discovering")) {
             Intent intent;
-            mBluetoothService.setProperty(name, propValues[1]);
+            adapterProperties.setProperty(name, propValues[1]);
             if (propValues[1].equals("true")) {
                 mBluetoothService.setIsDiscovering(true);
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
@@ -315,7 +361,7 @@
                 }
                 value = str.toString();
             }
-            mBluetoothService.setProperty(name, value);
+            adapterProperties.setProperty(name, value);
             if (name.equals("UUIDs")) {
                 mBluetoothService.updateBluetoothState(value);
             }
@@ -325,10 +371,18 @@
             if (propValues[1].equals("true"))
                 onRestartRequired();
         } else if (name.equals("DiscoverableTimeout")) {
-            mBluetoothService.setProperty(name, propValues[1]);
+            adapterProperties.setProperty(name, propValues[1]);
         }
     }
 
+    /**
+     * Called by native code on a PropertyChanged signal from
+     * org.bluez.Device.
+     *
+     * @param deviceObjectPath the object path for the changed device
+     * @param propValues a string array containing the key and one or more
+     *  values.
+     */
     private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
         String name = propValues[0];
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
@@ -399,18 +453,26 @@
             }
         } else if (name.equals("Trusted")) {
             if (DBG)
-                log("set trust state succeded, value is  " + propValues[1]);
+                log("set trust state succeeded, value is: " + propValues[1]);
             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
         }
     }
 
+    /**
+     * Called by native code on a PropertyChanged signal from
+     * org.bluez.Input.
+     *
+     * @param path the object path for the changed input device
+     * @param propValues a string array containing the key and one or more
+     *  values.
+     */
     private void onInputDevicePropertyChanged(String path, String[] propValues) {
         String address = mBluetoothService.getAddressFromObjectPath(path);
         if (address == null) {
-            Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device in null");
+            Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device is null");
             return;
         }
-        log(" Input Device : Name of Property is:" + propValues[0]);
+        log("Input Device : Name of Property is: " + propValues[0]);
         boolean state = false;
         if (propValues[1].equals("true")) {
             state = true;
@@ -418,6 +480,14 @@
         mBluetoothService.handleInputDevicePropertyChange(address, state);
     }
 
+    /**
+     * Called by native code on a PropertyChanged signal from
+     * org.bluez.Network.
+     *
+     * @param deviceObjectPath the object path for the changed PAN device
+     * @param propValues a string array containing the key and one or more
+     *  values.
+     */
     private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
         String name = propValues[0];
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
@@ -471,6 +541,13 @@
         return address;
     }
 
+    /**
+     * Called by native code on a RequestPairingConsent method call to
+     * org.bluez.Agent.
+     *
+     * @param objectPath the path of the device to request pairing consent for
+     * @param nativeData a native pointer to the original D-Bus message
+     */
     private void onRequestPairingConsent(String objectPath, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
@@ -494,11 +571,19 @@
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                         BluetoothDevice.PAIRING_VARIANT_CONSENT);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-        // Release wakelock to allow the LCD to go off after the PIN popup notifcation.
+        // Release wakelock to allow the LCD to go off after the PIN popup notification.
         mWakeLock.release();
         return;
     }
 
+    /**
+     * Called by native code on a RequestConfirmation method call to
+     * org.bluez.Agent.
+     *
+     * @param objectPath the path of the device to confirm the passkey for
+     * @param passkey an integer containing the 6-digit passkey to confirm
+     * @param nativeData a native pointer to the original D-Bus message
+     */
     private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
@@ -510,11 +595,18 @@
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                 BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-        // Release wakelock to allow the LCD to go off after the PIN popup notifcation.
+        // Release wakelock to allow the LCD to go off after the PIN popup notification.
         mWakeLock.release();
         return;
     }
 
+    /**
+     * Called by native code on a RequestPasskey method call to
+     * org.bluez.Agent.
+     *
+     * @param objectPath the path of the device requesting a passkey
+     * @param nativeData a native pointer to the original D-Bus message
+     */
     private void onRequestPasskey(String objectPath, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
@@ -525,11 +617,18 @@
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                 BluetoothDevice.PAIRING_VARIANT_PASSKEY);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-        // Release wakelock to allow the LCD to go off after the PIN popup notifcation.
+        // Release wakelock to allow the LCD to go off after the PIN popup notification.
         mWakeLock.release();
         return;
     }
 
+    /**
+     * Called by native code on a RequestPinCode method call to
+     * org.bluez.Agent.
+     *
+     * @param objectPath the path of the device requesting a PIN code
+     * @param nativeData a native pointer to the original D-Bus message
+     */
     private void onRequestPinCode(String objectPath, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
@@ -583,11 +682,19 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-        // Release wakelock to allow the LCD to go off after the PIN popup notifcation.
+        // Release wakelock to allow the LCD to go off after the PIN popup notification.
         mWakeLock.release();
         return;
     }
 
+    /**
+     * Called by native code on a DisplayPasskey method call to
+     * org.bluez.Agent.
+     *
+     * @param objectPath the path of the device to display the passkey for
+     * @param passkey an integer containing the 6-digit passkey
+     * @param nativeData a native pointer to the original D-Bus message
+     */
     private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
@@ -600,7 +707,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                         BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-        //Release wakelock to allow the LCD to go off after the PIN popup notifcation.
+        //Release wakelock to allow the LCD to go off after the PIN popup notification.
         mWakeLock.release();
     }
 
@@ -617,7 +724,14 @@
         mWakeLock.release();
     }
 
-    private void onRequestOobData(String objectPath , int nativeData) {
+    /**
+     * Called by native code on a RequestOobData method call to
+     * org.bluez.Agent.
+     *
+     * @param objectPath the path of the device requesting OOB data
+     * @param nativeData a native pointer to the original D-Bus message
+     */
+    private void onRequestOobData(String objectPath, int nativeData) {
         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
         if (address == null) return;
 
@@ -628,6 +742,13 @@
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
     }
 
+    /**
+     * Called by native code on an Authorize method call to org.bluez.Agent.
+     *
+     * @param objectPath the path of the device requesting to be authorized
+     * @param deviceUuid the UUID of the requesting device
+     * @return true if the authorization is allowed; false if not allowed
+     */
     private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
         if (!mBluetoothService.isEnabled()) return false;
 
@@ -704,6 +825,9 @@
         return false;
     }
 
+    /**
+     * Called by native code on a Cancel method call to org.bluez.Agent.
+     */
     private void onAgentCancel() {
         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
@@ -714,6 +838,13 @@
         return;
     }
 
+    /**
+     * Called by native code for the async response to a DiscoverServices
+     * method call to org.bluez.Adapter.
+     *
+     * @param deviceObjectPath the path for the specified device
+     * @param result true for success; false on error
+     */
     private void onDiscoverServicesResult(String deviceObjectPath, boolean result) {
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         if (address == null) return;
@@ -726,6 +857,14 @@
         mBluetoothService.makeServiceChannelCallbacks(address);
     }
 
+    /**
+     * Called by native code for the async response to a CreateDevice
+     * method call to org.bluez.Adapter.
+     *
+     * @param address the MAC address of the device to create
+     * @param result {@link #CREATE_DEVICE_SUCCESS},
+     *  {@link #CREATE_DEVICE_ALREADY_EXISTS} or {@link #CREATE_DEVICE_FAILED}}
+     */
     private void onCreateDeviceResult(String address, int result) {
         if (DBG) log("Result of onCreateDeviceResult:" + result);
 
@@ -736,7 +875,7 @@
                 mBluetoothService.discoverServicesNative(path, "");
                 break;
             }
-            Log.w(TAG, "Device exists, but we dont have the bluez path, failing");
+            Log.w(TAG, "Device exists, but we don't have the bluez path, failing");
             // fall-through
         case CREATE_DEVICE_FAILED:
             mBluetoothService.sendUuidIntent(address);
@@ -747,6 +886,13 @@
         }
     }
 
+    /**
+     * Called by native code for the async response to a Connect
+     * method call to org.bluez.Input.
+     *
+     * @param path the path of the specified input device
+     * @param result Result code of the operation.
+     */
     private void onInputDeviceConnectionResult(String path, int result) {
         // Success case gets handled by Property Change signal
         if (result != BluetoothInputDevice.INPUT_OPERATION_SUCCESS) {
@@ -776,6 +922,13 @@
         }
     }
 
+    /**
+     * Called by native code for the async response to a Connect
+     * method call to org.bluez.Network.
+     *
+     * @param path the path of the specified PAN device
+     * @param result Result code of the operation.
+     */
     private void onPanDeviceConnectionResult(String path, int result) {
         log ("onPanDeviceConnectionResult " + path + " " + result);
         // Success case gets handled by Property Change signal
@@ -810,12 +963,26 @@
         }
     }
 
+    /**
+     * Called by native code on a DeviceDisconnected signal from
+     * org.bluez.NetworkServer.
+     *
+     * @param address the MAC address of the disconnected device
+     */
     private void onNetworkDeviceDisconnected(String address) {
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED,
                                                       BluetoothPan.LOCAL_NAP_ROLE);
     }
 
+    /**
+     * Called by native code on a DeviceConnected signal from
+     * org.bluez.NetworkServer.
+     *
+     * @param address the MAC address of the connected device
+     * @param iface interface of remote network
+     * @param destUuid unused UUID parameter
+     */
     private void onNetworkDeviceConnected(String address, String iface, int destUuid) {
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED,
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index ebe3ef2..751a8d3 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -57,17 +57,12 @@
 import android.util.Pair;
 
 import java.io.BufferedInputStream;
-import java.io.BufferedReader;
 import java.io.BufferedWriter;
-import java.io.DataInputStream;
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
@@ -95,7 +90,7 @@
     private ParcelUuid[] mAdapterUuids;
 
     private BluetoothAdapter mAdapter;  // constant after init()
-    private final BondState mBondState = new BondState();  // local cache of bondings
+    private final BluetoothBondState mBondState;  // local cache of bondings
     private final IBatteryStats mBatteryStats;
     private final Context mContext;
 
@@ -129,9 +124,8 @@
             BluetoothUuid.HSP,
             BluetoothUuid.ObexObjectPush };
 
-    // TODO(): Optimize all these string handling
-    private final Map<String, String> mAdapterProperties;
-    private final HashMap<String, Map<String, String>> mDeviceProperties;
+    private final BluetoothAdapterProperties mAdapterProperties;
+    private final BluetoothDeviceProperties mDeviceProperties;
 
     private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
     private final ArrayList<String> mUuidIntentTracker;
@@ -203,8 +197,9 @@
         mBluetoothState = BluetoothAdapter.STATE_OFF;
         mIsDiscovering = false;
 
-        mAdapterProperties = new HashMap<String, String>();
-        mDeviceProperties = new HashMap<String, Map<String,String>>();
+        mBondState = new BluetoothBondState(context, this);
+        mAdapterProperties = new BluetoothAdapterProperties(context, this);
+        mDeviceProperties = new BluetoothDeviceProperties(this);
 
         mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
         mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
@@ -242,12 +237,13 @@
                 mDockAddress = dockAddress;
                 return mDockAddress;
             } else {
-                log("CheckBluetoothAddress failed for car dock address:" + dockAddress);
+                Log.e(TAG, "CheckBluetoothAddress failed for car dock address: "
+                        + dockAddress);
             }
         } catch (FileNotFoundException e) {
-            log("FileNotFoundException while trying to read dock address");
+            Log.e(TAG, "FileNotFoundException while trying to read dock address");
         } catch (IOException e) {
-            log("IOException while trying to read dock address");
+            Log.e(TAG, "IOException while trying to read dock address");
         } finally {
             if (file != null) {
                 try {
@@ -274,9 +270,9 @@
             out.write(mDockPin);
             return true;
         } catch (FileNotFoundException e) {
-            log("FileNotFoundException while trying to write dock pairing pin");
+            Log.e(TAG, "FileNotFoundException while trying to write dock pairing pin");
         } catch (IOException e) {
-            log("IOException while while trying to write dock pairing pin");
+            Log.e(TAG, "IOException while while trying to write dock pairing pin");
         } finally {
             if (out != null) {
                 try {
@@ -327,6 +323,9 @@
         return mBluetoothState;
     }
 
+    int getBluetoothStateInternal() {
+        return mBluetoothState;
+    }
 
     /**
      * Bring down bluetooth and disable BT in settings. Returns true on success.
@@ -486,7 +485,7 @@
             return;
         }
 
-        if (DBG) log("Bluetooth state " + mBluetoothState + " -> " + state);
+        if (DBG) Log.d(TAG, "Bluetooth state " + mBluetoothState + " -> " + state);
 
         Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
         intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState);
@@ -537,7 +536,7 @@
                 boolean running = false;
                 while ((retryCount-- > 0) && !running) {
                     mEventLoop.start();
-                    // it may take a momement for the other thread to do its
+                    // it may take a moment for the other thread to do its
                     // thing.  Check periodically for a while.
                     int pollCount = 5;
                     while ((pollCount-- > 0) && !running) {
@@ -551,7 +550,7 @@
                     }
                 }
                 if (!running) {
-                    log("bt EnableThread giving up");
+                    Log.e(TAG, "bt EnableThread giving up");
                     res = false;
                     disableNative();
                 }
@@ -635,6 +634,7 @@
             if (mAdapterUuids != null &&
                     BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) {
                 setBluetoothState(BluetoothAdapter.STATE_ON);
+                autoConnect();
                 String[] propVal = {"Pairable", getProperty("Pairable")};
                 mEventLoop.onPropertyChanged(propVal);
 
@@ -730,316 +730,8 @@
         mBondState.attempt(address);
     }
 
-    /** local cache of bonding state.
-    /* we keep our own state to track the intermediate state BONDING, which
-    /* bluez does not track.
-     * All addresses must be passed in upper case.
-     */
-    public class BondState {
-        private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
-        private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
-
-        private static final String AUTO_PAIRING_BLACKLIST =
-            "/etc/bluetooth/auto_pairing.conf";
-        private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
-            "/data/misc/bluetooth/dynamic_auto_pairing.conf";
-        private ArrayList<String>  mAutoPairingAddressBlacklist;
-        private ArrayList<String> mAutoPairingExactNameBlacklist;
-        private ArrayList<String> mAutoPairingPartialNameBlacklist;
-        private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList;
-        // Addresses added to blacklist dynamically based on usage.
-        private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
-
-
-        // If this is an outgoing connection, store the address.
-        // There can be only 1 pending outgoing connection at a time,
-        private String mPendingOutgoingBonding;
-
-        private synchronized void setPendingOutgoingBonding(String address) {
-            mPendingOutgoingBonding = address;
-        }
-
-        public synchronized String getPendingOutgoingBonding() {
-            return mPendingOutgoingBonding;
-        }
-
-        public synchronized void loadBondState() {
-            if (mBluetoothState != BluetoothAdapter.STATE_TURNING_ON) {
-                return;
-            }
-            String []bonds = null;
-            String val = getPropertyInternal("Devices");
-            if (val != null) {
-                bonds = val.split(",");
-            }
-            if (bonds == null) {
-                return;
-            }
-            mState.clear();
-            if (DBG) log("found " + bonds.length + " bonded devices");
-            for (String device : bonds) {
-                mState.put(getAddressFromObjectPath(device).toUpperCase(),
-                        BluetoothDevice.BOND_BONDED);
-            }
-        }
-
-        public synchronized void setBondState(String address, int state) {
-            setBondState(address, state, 0);
-        }
-
-        /** reason is ignored unless state == BOND_NOT_BONDED */
-        public synchronized void setBondState(String address, int state, int reason) {
-            int oldState = getBondState(address);
-            if (oldState == state) {
-                return;
-            }
-
-            // Check if this was an pending outgoing bonding.
-            // If yes, reset the state.
-            if (oldState == BluetoothDevice.BOND_BONDING) {
-                if (address.equals(mPendingOutgoingBonding)) {
-                    mPendingOutgoingBonding = null;
-                }
-            }
-
-            if (state == BluetoothDevice.BOND_BONDED) {
-                addProfileState(address);
-            } else if (state == BluetoothDevice.BOND_NONE) {
-                removeProfileState(address);
-            }
-
-            // HID is handled by BluetoothService, other profiles
-            // will be handled by their respective services.
-            mBluetoothInputProfileHandler.setInitialInputDevicePriority(
-                    mAdapter.getRemoteDevice(address), state);
-
-            if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
-                         reason + ")");
-            Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
-            intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
-            intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
-            if (state == BluetoothDevice.BOND_NONE) {
-                if (reason <= 0) {
-                    Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
-                          "invalid. Overriding reason code with BOND_RESULT_REMOVED");
-                    reason = BluetoothDevice.UNBOND_REASON_REMOVED;
-                }
-                intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
-                mState.remove(address);
-            } else {
-                mState.put(address, state);
-            }
-
-            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-        }
-
-        public boolean isAutoPairingBlacklisted(String address) {
-            if (mAutoPairingAddressBlacklist != null) {
-                for (String blacklistAddress : mAutoPairingAddressBlacklist) {
-                    if (address.startsWith(blacklistAddress)) return true;
-                }
-            }
-
-            if (mAutoPairingDynamicAddressBlacklist != null) {
-                for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
-                    if (address.equals(blacklistAddress)) return true;
-                }
-            }
-            String name = getRemoteName(address);
-            if (name != null) {
-                if (mAutoPairingExactNameBlacklist != null) {
-                    for (String blacklistName : mAutoPairingExactNameBlacklist) {
-                        if (name.equals(blacklistName)) return true;
-                    }
-                }
-
-                if (mAutoPairingPartialNameBlacklist != null) {
-                    for (String blacklistName : mAutoPairingPartialNameBlacklist) {
-                        if (name.startsWith(blacklistName)) return true;
-                    }
-                }
-            }
-            return false;
-        }
-
-        public boolean isFixedPinZerosAutoPairKeyboard(String address) {
-            // Note: the meaning of blacklist is reversed in this case.
-            // If its in the list, we can go ahead and auto pair since
-            // by default keyboard should have a variable PIN that we don't
-            // auto pair using 0000.
-            if (mAutoPairingFixedPinZerosKeyboardList != null) {
-                for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) {
-                    if (address.startsWith(blacklistAddress)) return true;
-                }
-            }
-            return false;
-        }
-
-        public synchronized int getBondState(String address) {
-            Integer state = mState.get(address);
-            if (state == null) {
-                return BluetoothDevice.BOND_NONE;
-            }
-            return state.intValue();
-        }
-
-        /*package*/ synchronized String[] listInState(int state) {
-            ArrayList<String> result = new ArrayList<String>(mState.size());
-            for (Map.Entry<String, Integer> e : mState.entrySet()) {
-                if (e.getValue().intValue() == state) {
-                    result.add(e.getKey());
-                }
-            }
-            return result.toArray(new String[result.size()]);
-        }
-
-        public synchronized void addAutoPairingFailure(String address) {
-            if (mAutoPairingDynamicAddressBlacklist == null) {
-                mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
-            }
-
-            updateAutoPairingData(address);
-            mAutoPairingDynamicAddressBlacklist.add(address);
-        }
-
-        public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
-            return getAttempt(address) != 0;
-        }
-
-        public synchronized void clearPinAttempts(String address) {
-            mPinAttempt.remove(address);
-        }
-
-        public synchronized boolean hasAutoPairingFailed(String address) {
-            if (mAutoPairingDynamicAddressBlacklist == null) return false;
-
-            return mAutoPairingDynamicAddressBlacklist.contains(address);
-        }
-
-        public synchronized int getAttempt(String address) {
-            Integer attempt = mPinAttempt.get(address);
-            if (attempt == null) {
-                return 0;
-            }
-            return attempt.intValue();
-        }
-
-        public synchronized void attempt(String address) {
-            Integer attempt = mPinAttempt.get(address);
-            int newAttempt;
-            if (attempt == null) {
-                newAttempt = 1;
-            } else {
-                newAttempt = attempt.intValue() + 1;
-            }
-            mPinAttempt.put(address, new Integer(newAttempt));
-        }
-
-        private void copyAutoPairingData() {
-            File file = null;
-            FileInputStream in = null;
-            FileOutputStream out = null;
-            try {
-                file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
-
-                in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
-                out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
-
-                byte[] buf = new byte[1024];
-                int len;
-                while ((len = in.read(buf)) > 0) {
-                    out.write(buf, 0, len);
-                }
-            } catch (FileNotFoundException e) {
-                log("FileNotFoundException: in copyAutoPairingData");
-            } catch (IOException e) {
-                log("IOException: in copyAutoPairingData");
-            } finally {
-                 try {
-                     if (in != null) in.close();
-                     if (out != null) out.close();
-                 } catch (IOException e) {}
-            }
-        }
-
-        public void readAutoPairingData() {
-            if (mAutoPairingAddressBlacklist != null) return;
-            copyAutoPairingData();
-            FileInputStream fstream = null;
-            try {
-                fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
-                DataInputStream in = new DataInputStream(fstream);
-                BufferedReader file = new BufferedReader(new InputStreamReader(in));
-                String line;
-                while((line = file.readLine()) != null) {
-                    line = line.trim();
-                    if (line.length() == 0 || line.startsWith("//")) continue;
-                    String[] value = line.split("=");
-                    if (value != null && value.length == 2) {
-                        String[] val = value[1].split(",");
-                        if (value[0].equalsIgnoreCase("AddressBlacklist")) {
-                            mAutoPairingAddressBlacklist =
-                                new ArrayList<String>(Arrays.asList(val));
-                        } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
-                            mAutoPairingExactNameBlacklist =
-                                new ArrayList<String>(Arrays.asList(val));
-                        } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
-                            mAutoPairingPartialNameBlacklist =
-                                new ArrayList<String>(Arrays.asList(val));
-                        } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) {
-                            mAutoPairingFixedPinZerosKeyboardList =
-                                new ArrayList<String>(Arrays.asList(val));
-                        } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
-                            mAutoPairingDynamicAddressBlacklist =
-                                new ArrayList<String>(Arrays.asList(val));
-                        } else {
-                            Log.e(TAG, "Error parsing Auto pairing blacklist file");
-                        }
-                    }
-                }
-            } catch (FileNotFoundException e) {
-                log("FileNotFoundException: readAutoPairingData" + e.toString());
-            } catch (IOException e) {
-                log("IOException: readAutoPairingData" + e.toString());
-            } finally {
-                if (fstream != null) {
-                    try {
-                        fstream.close();
-                    } catch (IOException e) {
-                        // Ignore
-                    }
-                }
-            }
-        }
-
-        // This function adds a bluetooth address to the auto pairing blacklist
-        // file. These addresses are added to DynamicAddressBlacklistSection
-        private void updateAutoPairingData(String address) {
-            BufferedWriter out = null;
-            try {
-                out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
-                StringBuilder str = new StringBuilder();
-                if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
-                    str.append("DynamicAddressBlacklist=");
-                }
-                str.append(address);
-                str.append(",");
-                out.write(str.toString());
-            } catch (FileNotFoundException e) {
-                log("FileNotFoundException: updateAutoPairingData" + e.toString());
-            } catch (IOException e) {
-                log("IOException: updateAutoPairingData" + e.toString());
-            } finally {
-                if (out != null) {
-                    try {
-                        out.close();
-                    } catch (IOException e) {
-                        // Ignore
-                    }
-                }
-            }
-        }
+    /*package*/ BluetoothDevice getRemoteDevice(String address) {
+        return mAdapter.getRemoteDevice(address);
     }
 
     private static String toBondStateString(int bondState) {
@@ -1055,56 +747,6 @@
         }
     }
 
-    /*package*/ synchronized boolean isAdapterPropertiesEmpty() {
-        return mAdapterProperties.isEmpty();
-    }
-
-    /*package*/synchronized void getAllProperties() {
-
-        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        mAdapterProperties.clear();
-
-        String properties[] = (String [])getAdapterPropertiesNative();
-        // The String Array consists of key-value pairs.
-        if (properties == null) {
-            Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
-            return;
-        }
-
-        for (int i = 0; i < properties.length; i++) {
-            String name = properties[i];
-            String newValue = null;
-            int len;
-            if (name == null) {
-                Log.e(TAG, "Error:Adapter Property at index" + i + "is null");
-                continue;
-            }
-            if (name.equals("Devices") || name.equals("UUIDs")) {
-                StringBuilder str = new StringBuilder();
-                len = Integer.valueOf(properties[++i]);
-                for (int j = 0; j < len; j++) {
-                    str.append(properties[++i]);
-                    str.append(",");
-                }
-                if (len > 0) {
-                    newValue = str.toString();
-                }
-            } else {
-                newValue = properties[++i];
-            }
-            mAdapterProperties.put(name, newValue);
-        }
-
-        // Add adapter object path property.
-        String adapterPath = getAdapterPathNative();
-        if (adapterPath != null)
-            mAdapterProperties.put("ObjectPath", adapterPath + "/dev_");
-    }
-
-    /* package */ synchronized void setProperty(String name, String value) {
-        mAdapterProperties.put(name, value);
-    }
-
     public synchronized boolean setName(String name) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
@@ -1154,8 +796,8 @@
     public synchronized boolean setScanMode(int mode, int duration) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
                                                 "Need WRITE_SECURE_SETTINGS permission");
-        boolean pairable = false;
-        boolean discoverable = false;
+        boolean pairable;
+        boolean discoverable;
 
         switch (mode) {
         case BluetoothAdapter.SCAN_MODE_NONE:
@@ -1183,14 +825,27 @@
 
     /*package*/ synchronized String getProperty(String name) {
         if (!isEnabledInternal()) return null;
-        return getPropertyInternal(name);
+        return mAdapterProperties.getProperty(name);
     }
 
-    /*package*/ synchronized String getPropertyInternal(String name) {
-        if (!mAdapterProperties.isEmpty())
-            return mAdapterProperties.get(name);
-        getAllProperties();
-        return mAdapterProperties.get(name);
+    BluetoothAdapterProperties getAdapterProperties() {
+        return mAdapterProperties;
+    }
+
+    BluetoothDeviceProperties getDeviceProperties() {
+        return mDeviceProperties;
+    }
+
+    boolean isRemoteDeviceInCache(String address) {
+        return mDeviceProperties.isInCache(address);
+    }
+
+    void setRemoteDeviceProperty(String address, String name, String value) {
+        mDeviceProperties.setProperty(address, name, value);
+    }
+
+    void updateRemoteDevicePropertiesCache(String address) {
+        mDeviceProperties.updateCache(address);
     }
 
     public synchronized String getAddress() {
@@ -1239,7 +894,7 @@
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             return null;
         }
-        return getRemoteDeviceProperty(address, "Name");
+        return mDeviceProperties.getProperty(address, "Name");
     }
 
     /**
@@ -1305,7 +960,7 @@
         address = address.toUpperCase();
 
         if (mBondState.getPendingOutgoingBonding() != null) {
-            log("Ignoring createBond(): another device is bonding");
+            Log.d(TAG, "Ignoring createBond(): another device is bonding");
             // a different device is currently bonding, fail
             return false;
         }
@@ -1314,13 +969,13 @@
         // pairing exponential back-off attempts.
         if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
                 mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) {
-            log("Ignoring createBond(): this device is already bonding or bonded");
+            Log.d(TAG, "Ignoring createBond(): this device is already bonding or bonded");
             return false;
         }
 
         if (address.equals(mDockAddress)) {
             if (!writeDockPin()) {
-                log("Error while writing Pin for the dock");
+                Log.e(TAG, "Error while writing Pin for the dock");
                 return false;
             }
         }
@@ -1364,8 +1019,8 @@
         Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer);
 
         if (DBG) {
-            log("Setting out of band data for:" + address + ":" +
-              Arrays.toString(hash) + ":" + Arrays.toString(randomizer));
+            Log.d(TAG, "Setting out of band data for: " + address + ":" +
+                  Arrays.toString(hash) + ":" + Arrays.toString(randomizer));
         }
 
         mDeviceOobData.put(address, value);
@@ -1453,7 +1108,7 @@
 
     public synchronized boolean isBluetoothDock(String address) {
         SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
-                mContext.MODE_PRIVATE);
+                Context.MODE_PRIVATE);
 
         return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
     }
@@ -1465,85 +1120,6 @@
         return (String [])getDevicePropertiesNative(objectPath);
     }
 
-    /*package*/ synchronized String getRemoteDeviceProperty(String address, String property) {
-        Map<String, String> properties = mDeviceProperties.get(address);
-        if (properties != null) {
-            return properties.get(property);
-        } else {
-            // Query for remote device properties, again.
-            // We will need to reload the cache when we switch Bluetooth on / off
-            // or if we crash.
-            if (updateRemoteDevicePropertiesCache(address))
-                return getRemoteDeviceProperty(address, property);
-        }
-        Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
-        return null;
-    }
-
-    /* package */ synchronized boolean updateRemoteDevicePropertiesCache(String address) {
-        String[] propValues = getRemoteDeviceProperties(address);
-        if (propValues != null) {
-            addRemoteDeviceProperties(address, propValues);
-            return true;
-        }
-        return false;
-    }
-
-    /* package */ synchronized void addRemoteDeviceProperties(String address, String[] properties) {
-        /*
-         * We get a DeviceFound signal every time RSSI changes or name changes.
-         * Don't create a new Map object every time */
-        Map<String, String> propertyValues = mDeviceProperties.get(address);
-        if (propertyValues == null) {
-            propertyValues = new HashMap<String, String>();
-        }
-
-        for (int i = 0; i < properties.length; i++) {
-            String name = properties[i];
-            String newValue = null;
-            int len;
-            if (name == null) {
-                Log.e(TAG, "Error: Remote Device Property at index" + i + "is null");
-                continue;
-            }
-            if (name.equals("UUIDs") || name.equals("Nodes")) {
-                StringBuilder str = new StringBuilder();
-                len = Integer.valueOf(properties[++i]);
-                for (int j = 0; j < len; j++) {
-                    str.append(properties[++i]);
-                    str.append(",");
-                }
-                if (len > 0) {
-                    newValue = str.toString();
-                }
-            } else {
-                newValue = properties[++i];
-            }
-
-            propertyValues.put(name, newValue);
-        }
-        mDeviceProperties.put(address, propertyValues);
-
-        // We have added a new remote device or updated its properties.
-        // Also update the serviceChannel cache.
-        updateDeviceServiceChannelCache(address);
-    }
-
-    /* package */ void removeRemoteDeviceProperties(String address) {
-        mDeviceProperties.remove(address);
-    }
-
-    /* package */ synchronized void setRemoteDeviceProperty(String address, String name,
-                                                              String value) {
-        Map <String, String> propVal = mDeviceProperties.get(address);
-        if (propVal != null) {
-            propVal.put(name, value);
-            mDeviceProperties.put(address, propVal);
-        } else {
-            Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
-        }
-    }
-
     /**
      * Sets the remote device trust state.
      *
@@ -1558,8 +1134,8 @@
 
         if (!isEnabledInternal()) return false;
 
-        return setDevicePropertyBooleanNative(getObjectPathFromAddress(address), "Trusted",
-                value ? 1 : 0);
+        return setDevicePropertyBooleanNative(
+                getObjectPathFromAddress(address), "Trusted", value ? 1 : 0);
     }
 
     /**
@@ -1567,7 +1143,7 @@
      * Note: this value may be
      * retrieved from cache if we retrieved the data before *
      *
-     * @return boolean to indicate trust or untrust state
+     * @return boolean to indicate trusted or untrusted state
      */
     public synchronized boolean getTrustState(String address) {
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -1575,11 +1151,11 @@
             return false;
         }
 
-        String val = getRemoteDeviceProperty(address, "Trusted");
+        String val = mDeviceProperties.getProperty(address, "Trusted");
         if (val == null) {
             return false;
         } else {
-            return val.equals("true") ? true : false;
+            return val.equals("true");
         }
     }
 
@@ -1598,7 +1174,7 @@
             mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
             return BluetoothClass.ERROR;
         }
-        String val = getRemoteDeviceProperty(address, "Class");
+        String val = mDeviceProperties.getProperty(address, "Class");
         if (val == null)
             return BluetoothClass.ERROR;
         else {
@@ -1620,8 +1196,8 @@
         return getUuidFromCache(address);
     }
 
-    private ParcelUuid[] getUuidFromCache(String address) {
-        String value = getRemoteDeviceProperty(address, "UUIDs");
+    ParcelUuid[] getUuidFromCache(String address) {
+        String value = mDeviceProperties.getProperty(address, "UUIDs");
         if (value == null) return null;
 
         String[] uuidStrings = null;
@@ -1672,7 +1248,7 @@
         boolean ret;
         // Just do the SDP if the device is already  created and UUIDs are not
         // NULL, else create the device and then do SDP.
-        if (isRemoteDeviceInCache(address) && getRemoteUuids(address) != null) {
+        if (mDeviceProperties.isInCache(address) && getRemoteUuids(address) != null) {
             String path = getObjectPathFromAddress(address);
             if (path == null) return false;
 
@@ -1712,7 +1288,7 @@
         }
         // Check if we are recovering from a crash.
         if (mDeviceProperties.isEmpty()) {
-            if (!updateRemoteDevicePropertiesCache(address))
+            if (mDeviceProperties.updateCache(address) == null)
                 return -1;
         }
 
@@ -1833,13 +1409,13 @@
     }
 
     /*package*/ void updateDeviceServiceChannelCache(String address) {
-        ParcelUuid[] deviceUuids = getRemoteUuids(address);
+        if (DBG) Log.d(TAG, "updateDeviceServiceChannelCache(" + address + ")");
+
         // We are storing the rfcomm channel numbers only for the uuids
         // we are interested in.
-        int channel;
-        if (DBG) log("updateDeviceServiceChannelCache(" + address + ")");
+        ParcelUuid[] deviceUuids = getRemoteUuids(address);
 
-        ArrayList<ParcelUuid> applicationUuids = new ArrayList();
+        ArrayList<ParcelUuid> applicationUuids = new ArrayList<ParcelUuid>();
 
         synchronized (this) {
             for (RemoteService service : mUuidCallbackTracker.keySet()) {
@@ -1849,24 +1425,22 @@
             }
         }
 
-        Map <ParcelUuid, Integer> value = new HashMap<ParcelUuid, Integer>();
+        Map <ParcelUuid, Integer> uuidToChannelMap = new HashMap<ParcelUuid, Integer>();
 
         // Retrieve RFCOMM channel for default uuids
         for (ParcelUuid uuid : RFCOMM_UUIDS) {
             if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
-                channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
-                        uuid.toString(), 0x0004);
-                if (DBG) log("\tuuid(system): " + uuid + " " + channel);
-                value.put(uuid, channel);
+                int channel = getDeviceServiceChannelForUuid(address, uuid);
+                uuidToChannelMap.put(uuid, channel);
+                if (DBG) Log.d(TAG, "\tuuid(system): " + uuid + " " + channel);
             }
         }
         // Retrieve RFCOMM channel for application requested uuids
         for (ParcelUuid uuid : applicationUuids) {
             if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
-                channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
-                        uuid.toString(), 0x0004);
-                if (DBG) log("\tuuid(application): " + uuid + " " + channel);
-                value.put(uuid, channel);
+                int channel = getDeviceServiceChannelForUuid(address, uuid);
+                uuidToChannelMap.put(uuid, channel);
+                if (DBG) Log.d(TAG, "\tuuid(application): " + uuid + " " + channel);
             }
         }
 
@@ -1876,13 +1450,11 @@
                     iter.hasNext();) {
                 RemoteService service = iter.next();
                 if (service.address.equals(address)) {
-                    channel = -1;
-                    if (value.get(service.uuid) != null) {
-                        channel = value.get(service.uuid);
-                    }
-                    if (channel != -1) {
-                        if (DBG) log("Making callback for " + service.uuid + " with result " +
-                                channel);
+                    if (uuidToChannelMap.containsKey(service.uuid)) {
+                        int channel = uuidToChannelMap.get(service.uuid);
+
+                        if (DBG) Log.d(TAG, "Making callback for " + service.uuid +
+                                    " with result " + channel);
                         IBluetoothCallback callback = mUuidCallbackTracker.get(service);
                         if (callback != null) {
                             try {
@@ -1896,10 +1468,16 @@
             }
 
             // Update cache
-            mDeviceServiceChannelCache.put(address, value);
+            mDeviceServiceChannelCache.put(address, uuidToChannelMap);
         }
     }
 
+    private int getDeviceServiceChannelForUuid(String address,
+            ParcelUuid uuid) {
+        return getDeviceServiceChannelNative(getObjectPathFromAddress(address),
+                uuid.toString(), 0x0004);
+    }
+
     /**
      * b is a handle to a Binder instance, so that this service can be notified
      * for Applications that terminate unexpectedly, to clean there service
@@ -1921,7 +1499,7 @@
         int handle = addRfcommServiceRecordNative(serviceName,
                 uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(),
                 (short)channel);
-        if (DBG) log("new handle " + Integer.toHexString(handle));
+        if (DBG) Log.d(TAG, "new handle " + Integer.toHexString(handle));
         if (handle == -1) {
             return -1;
         }
@@ -1944,8 +1522,8 @@
         Integer handleInt = new Integer(handle);
         Integer owner = mServiceRecordToPid.get(handleInt);
         if (owner != null && pid == owner.intValue()) {
-            if (DBG) log("Removing service record " + Integer.toHexString(handle) + " for pid " +
-                    pid);
+            if (DBG) Log.d(TAG, "Removing service record " +
+                Integer.toHexString(handle) + " for pid " + pid);
             mServiceRecordToPid.remove(handleInt);
             removeServiceRecordNative(handle);
         }
@@ -1960,7 +1538,7 @@
         }
         public void binderDied() {
             synchronized (BluetoothService.this) {
-                if (DBG) log("Tracked app " + pid + " died");
+                if (DBG) Log.d(TAG, "Tracked app " + pid + " died");
                 checkAndRemoveRecord(handle, pid);
             }
         }
@@ -2033,10 +1611,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
         intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid);
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-
-        if (mUuidIntentTracker.contains(address))
-            mUuidIntentTracker.remove(address);
-
+        mUuidIntentTracker.remove(address);
     }
 
     /*package*/ synchronized void makeServiceChannelCallbacks(String address) {
@@ -2044,8 +1619,8 @@
                 iter.hasNext();) {
             RemoteService service = iter.next();
             if (service.address.equals(address)) {
-                if (DBG) log("Cleaning up failed UUID channel lookup: " + service.address +
-                        " " + service.uuid);
+                if (DBG) Log.d(TAG, "Cleaning up failed UUID channel lookup: "
+                    + service.address + " " + service.uuid);
                 IBluetoothCallback callback = mUuidCallbackTracker.get(service);
                 if (callback != null) {
                     try {
@@ -2060,18 +1635,9 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        switch(mBluetoothState) {
-        case BluetoothAdapter.STATE_OFF:
-            pw.println("Bluetooth OFF\n");
+        dumpBluetoothState(pw);
+        if (mBluetoothState != BluetoothAdapter.STATE_ON) {
             return;
-        case BluetoothAdapter.STATE_TURNING_ON:
-            pw.println("Bluetooth TURNING ON\n");
-            return;
-        case BluetoothAdapter.STATE_TURNING_OFF:
-            pw.println("Bluetooth TURNING OFF\n");
-            return;
-        case BluetoothAdapter.STATE_ON:
-            pw.println("Bluetooth ON\n");
         }
 
         pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
@@ -2088,58 +1654,15 @@
         mAdapter.getProfileProxy(mContext,
                 mBluetoothProfileServiceListener, BluetoothProfile.PAN);
 
-        pw.println("\n--Known devices--");
-        for (String address : mDeviceProperties.keySet()) {
-            int bondState = mBondState.getBondState(address);
-            pw.printf("%s %10s (%d) %s\n", address,
-                       toBondStateString(bondState),
-                       mBondState.getAttempt(address),
-                       getRemoteName(address));
-
-            Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
-            if (uuidChannels == null) {
-                pw.println("\tuuids = null");
-            } else {
-                for (ParcelUuid uuid : uuidChannels.keySet()) {
-                    Integer channel = uuidChannels.get(uuid);
-                    if (channel == null) {
-                        pw.println("\t" + uuid);
-                    } else {
-                        pw.println("\t" + uuid + " RFCOMM channel = " + channel);
-                    }
-                }
-            }
-            for (RemoteService service : mUuidCallbackTracker.keySet()) {
-                if (service.address.equals(address)) {
-                    pw.println("\tPENDING CALLBACK: " + service.uuid);
-                }
-            }
-        }
-
-        String value = getProperty("Devices");
-        String[] devicesObjectPath = null;
-        if (value != null) {
-            devicesObjectPath = value.split(",");
-        }
-        pw.println("\n--ACL connected devices--");
-        if (devicesObjectPath != null) {
-            for (String device : devicesObjectPath) {
-                pw.println(getAddressFromObjectPath(device));
-            }
-        }
-
-        dumpHeadsetProfile(pw);
+        dumpKnownDevices(pw);
+        dumpAclConnectedDevices(pw);
+        dumpHeadsetService(pw);
         dumpInputDeviceProfile(pw);
         dumpPanProfile(pw);
-
-        pw.println("\n--Application Service Records--");
-        for (Integer handle : mServiceRecordToPid.keySet()) {
-            Integer pid = mServiceRecordToPid.get(handle);
-            pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
-        }
+        dumpApplicationServiceRecords(pw);
     }
 
-    private void dumpHeadsetProfile(PrintWriter pw) {
+    private void dumpHeadsetService(PrintWriter pw) {
         pw.println("\n--Headset Service--");
         if (mBluetoothHeadset != null) {
             List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
@@ -2147,22 +1670,8 @@
                 pw.println("No headsets connected");
             } else {
                 BluetoothDevice device = deviceList.get(0);
-                pw.println("getConnectedDevices[0] = " + device);
-
-                switch (mBluetoothHeadset.getConnectionState(device)) {
-                    case BluetoothHeadset.STATE_CONNECTING:
-                        pw.println("getConnectionState() = STATE_CONNECTING");
-                        break;
-                    case BluetoothHeadset.STATE_CONNECTED:
-                        pw.println("getConnectionState() = STATE_CONNECTED");
-                        break;
-                    case BluetoothHeadset.STATE_DISCONNECTING:
-                        pw.println("getConnectionState() = STATE_DISCONNECTING");
-                        break;
-                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                        pw.println("getConnectionState() = STATE_AUDIO_CONNECTED");
-                        break;
-                }
+                pw.println("\ngetConnectedDevices[0] = " + device);
+                dumpHeadsetConnectionState(pw, device);
                 pw.println("getBatteryUsageHint() = " +
                              mBluetoothHeadset.getBatteryUsageHint(device));
             }
@@ -2248,9 +1757,94 @@
                 pw.println(device);
             }
         }
+    }
+
+    private void dumpHeadsetConnectionState(PrintWriter pw,
+            BluetoothDevice device) {
+        switch (mBluetoothHeadset.getConnectionState(device)) {
+            case BluetoothHeadset.STATE_CONNECTING:
+                pw.println("getConnectionState() = STATE_CONNECTING");
+                break;
+            case BluetoothHeadset.STATE_CONNECTED:
+                pw.println("getConnectionState() = STATE_CONNECTED");
+                break;
+            case BluetoothHeadset.STATE_DISCONNECTING:
+                pw.println("getConnectionState() = STATE_DISCONNECTING");
+                break;
+            case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                pw.println("getConnectionState() = STATE_AUDIO_CONNECTED");
+                break;
+        }
+    }
+
+    private void dumpApplicationServiceRecords(PrintWriter pw) {
+        pw.println("\n--Application Service Records--");
+        for (Integer handle : mServiceRecordToPid.keySet()) {
+            Integer pid = mServiceRecordToPid.get(handle);
+            pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
+        }
         mAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothHeadset);
     }
 
+    private void dumpAclConnectedDevices(PrintWriter pw) {
+        String[] devicesObjectPath = getKnownDevices();
+        pw.println("\n--ACL connected devices--");
+        if (devicesObjectPath != null) {
+            for (String device : devicesObjectPath) {
+                pw.println(getAddressFromObjectPath(device));
+            }
+        }
+    }
+
+    private void dumpKnownDevices(PrintWriter pw) {
+        pw.println("\n--Known devices--");
+        for (String address : mDeviceProperties.keySet()) {
+            int bondState = mBondState.getBondState(address);
+            pw.printf("%s %10s (%d) %s\n", address,
+                       toBondStateString(bondState),
+                       mBondState.getAttempt(address),
+                       getRemoteName(address));
+
+            Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
+            if (uuidChannels == null) {
+                pw.println("\tuuids = null");
+            } else {
+                for (ParcelUuid uuid : uuidChannels.keySet()) {
+                    Integer channel = uuidChannels.get(uuid);
+                    if (channel == null) {
+                        pw.println("\t" + uuid);
+                    } else {
+                        pw.println("\t" + uuid + " RFCOMM channel = " + channel);
+                    }
+                }
+            }
+            for (RemoteService service : mUuidCallbackTracker.keySet()) {
+                if (service.address.equals(address)) {
+                    pw.println("\tPENDING CALLBACK: " + service.uuid);
+                }
+            }
+        }
+    }
+
+    private void dumpBluetoothState(PrintWriter pw) {
+        switch(mBluetoothState) {
+        case BluetoothAdapter.STATE_OFF:
+            pw.println("Bluetooth OFF\n");
+            break;
+        case BluetoothAdapter.STATE_TURNING_ON:
+            pw.println("Bluetooth TURNING ON\n");
+            break;
+        case BluetoothAdapter.STATE_TURNING_OFF:
+            pw.println("Bluetooth TURNING OFF\n");
+            break;
+        case BluetoothAdapter.STATE_ON:
+            pw.println("Bluetooth ON\n");
+            break;
+        default:
+            pw.println("Bluetooth UNKNOWN STATE " + mBluetoothState);
+        }
+    }
+
     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
         new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
@@ -2295,7 +1889,7 @@
     }
 
     /*package*/ String getAddressFromObjectPath(String objectPath) {
-        String adapterObjectPath = getPropertyInternal("ObjectPath");
+        String adapterObjectPath = mAdapterProperties.getObjectPath();
         if (adapterObjectPath == null || objectPath == null) {
             Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
                     "  or deviceObjectPath:" + objectPath + " is null");
@@ -2315,7 +1909,7 @@
     }
 
     /*package*/ String getObjectPathFromAddress(String address) {
-        String path = getPropertyInternal("ObjectPath");
+        String path = mAdapterProperties.getObjectPath();
         if (path == null) {
             Log.e(TAG, "Error: Object Path is null");
             return null;
@@ -2328,7 +1922,7 @@
         String path = getObjectPathFromAddress(address);
         boolean result = setLinkTimeoutNative(path, num_slots);
 
-        if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
+        if (!result) Log.d(TAG, "Set Link Timeout to " + num_slots + " slots failed");
     }
 
     /**** Handlers for PAN  Profile ****/
@@ -2448,10 +2042,6 @@
         mBluetoothInputProfileHandler.handleInputDevicePropertyChange(address, connected);
     }
 
-    /*package*/ boolean isRemoteDeviceInCache(String address) {
-        return (mDeviceProperties.get(address) != null);
-    }
-
     public boolean connectHeadset(String address) {
         if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
 
@@ -2508,7 +2098,7 @@
         return false;
     }
 
-    private BluetoothDeviceProfileState addProfileState(String address) {
+    BluetoothDeviceProfileState addProfileState(String address) {
         BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
         if (state != null) return state;
 
@@ -2518,26 +2108,47 @@
         return state;
     }
 
-    private void removeProfileState(String address) {
+    void removeProfileState(String address) {
         mDeviceProfileState.remove(address);
     }
 
+    synchronized String[] getKnownDevices() {
+        String[] bonds = null;
+        String val = getProperty("Devices");
+        if (val != null) {
+            bonds = val.split(",");
+        }
+        return bonds;
+    }
+
     private void initProfileState() {
-        String []bonds = null;
-        String val = getPropertyInternal("Devices");
+        String[] bonds = null;
+        String val = mAdapterProperties.getProperty("Devices");
         if (val != null) {
             bonds = val.split(",");
         }
         if (bonds == null) {
             return;
         }
-
         for (String path : bonds) {
             String address = getAddressFromObjectPath(path);
             BluetoothDeviceProfileState state = addProfileState(address);
-            Message msg = new Message();
-            msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;
-            state.sendMessage(msg);
+        }
+    }
+
+    private void autoConnect() {
+        String[] bonds = getKnownDevices();
+        if (bonds == null) {
+            return;
+        }
+        for (String path : bonds) {
+            String address = getAddressFromObjectPath(path);
+            BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+            if (state != null) {
+                Message msg = new Message();
+                msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;
+                state.sendMessage(msg);
+            }
         }
     }
 
@@ -2585,78 +2196,91 @@
 
     public synchronized void sendConnectionStateChange(BluetoothDevice device, int state,
                                                         int prevState) {
-        if (updateCountersAndCheckForConnectionStateChange(device, state, prevState)) {
-            state = translateToAdapterConnectionState(state);
-            prevState = translateToAdapterConnectionState(prevState);
+        // Since this is a binder call check if Bluetooth is on still
+        if (mBluetoothState == BluetoothAdapter.STATE_OFF) return;
+
+        if (updateCountersAndCheckForConnectionStateChange(state, prevState)) {
+            if (!validateProfileConnectionState(state) ||
+                    !validateProfileConnectionState(prevState)) {
+                // Previously, an invalid state was broadcast anyway,
+                // with the invalid state converted to -1 in the intent.
+                // Better to log an error and not send an intent with
+                // invalid contents or set mAdapterConnectionState to -1.
+                Log.e(TAG, "Error in sendConnectionStateChange: "
+                        + "prevState " + prevState + " state " + state);
+                return;
+            }
 
             mAdapterConnectionState = state;
 
             Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-            intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, state);
-            intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, prevState);
+            intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                    convertToAdapterState(state));
+            intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE,
+                    convertToAdapterState(prevState));
             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-            log("CONNECTION_STATE_CHANGE: " + device + ": " + prevState + "-> " + state);
+            Log.d(TAG, "CONNECTION_STATE_CHANGE: " + device + ": "
+                    + prevState + " -> " + state);
         }
     }
 
-    private int translateToAdapterConnectionState(int state) {
+    private boolean validateProfileConnectionState(int state) {
+        return (state == BluetoothProfile.STATE_DISCONNECTED ||
+                state == BluetoothProfile.STATE_CONNECTING ||
+                state == BluetoothProfile.STATE_CONNECTED ||
+                state == BluetoothProfile.STATE_DISCONNECTING);
+    }
+
+    private int convertToAdapterState(int state) {
         switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return BluetoothAdapter.STATE_DISCONNECTED;
+            case BluetoothProfile.STATE_DISCONNECTING:
+                return BluetoothAdapter.STATE_DISCONNECTING;
+            case BluetoothProfile.STATE_CONNECTED:
+                return BluetoothAdapter.STATE_CONNECTED;
             case BluetoothProfile.STATE_CONNECTING:
                 return BluetoothAdapter.STATE_CONNECTING;
-            case BluetoothProfile.STATE_CONNECTED:
-              return BluetoothAdapter.STATE_CONNECTED;
-            case BluetoothProfile.STATE_DISCONNECTING:
-              return BluetoothAdapter.STATE_DISCONNECTING;
-            case BluetoothProfile.STATE_DISCONNECTED:
-              return BluetoothAdapter.STATE_DISCONNECTED;
-            default:
-              Log.e(TAG, "Error in getAdapterConnectionState");
-              return -1;
         }
+        Log.e(TAG, "Error in convertToAdapterState");
+        return -1;
     }
 
-    private boolean updateCountersAndCheckForConnectionStateChange(BluetoothDevice device,
-                                                                   int state,
-                                                                   int prevState) {
+    private boolean updateCountersAndCheckForConnectionStateChange(int state, int prevState) {
+        switch (prevState) {
+            case BluetoothProfile.STATE_CONNECTING:
+                mProfilesConnecting--;
+                break;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                mProfilesConnected--;
+                break;
+
+            case BluetoothProfile.STATE_DISCONNECTING:
+                mProfilesDisconnecting--;
+                break;
+        }
+
         switch (state) {
             case BluetoothProfile.STATE_CONNECTING:
                 mProfilesConnecting++;
-                if (prevState == BluetoothAdapter.STATE_DISCONNECTING) mProfilesDisconnecting--;
-                if (mProfilesConnected > 0 || mProfilesConnecting > 1) return false;
+                return (mProfilesConnected == 0 && mProfilesConnecting == 1);
 
-                break;
             case BluetoothProfile.STATE_CONNECTED:
-                if (prevState == BluetoothAdapter.STATE_CONNECTING) mProfilesConnecting--;
-                if (prevState == BluetoothAdapter.STATE_DISCONNECTING) mProfilesDisconnecting--;
-
                 mProfilesConnected++;
+                return (mProfilesConnected == 1);
 
-                if (mProfilesConnected > 1) return false;
-                break;
             case BluetoothProfile.STATE_DISCONNECTING:
                 mProfilesDisconnecting++;
-                mProfilesConnected--;
+                return (mProfilesConnected == 0 && mProfilesDisconnecting == 1);
 
-                if (mProfilesConnected > 0 || mProfilesDisconnecting > 1) return false;
-
-                break;
             case BluetoothProfile.STATE_DISCONNECTED:
-                if (prevState == BluetoothAdapter.STATE_CONNECTING) mProfilesConnecting--;
-                if (prevState == BluetoothAdapter.STATE_DISCONNECTING) mProfilesDisconnecting--;
+                return (mProfilesConnected == 0 && mProfilesConnecting == 0);
 
-                if (prevState == BluetoothAdapter.STATE_CONNECTED) {
-                    mProfilesConnected--;
-                }
-
-                if (mProfilesConnected  > 0 || mProfilesConnecting > 0) return false;
-                break;
+            default:
+                return true;
         }
-        return true;
-    }
-
-    private static void log(String msg) {
-        Log.d(TAG, msg);
     }
 
     private native static void classInitNative();
@@ -2664,13 +2288,13 @@
     private native boolean setupNativeDataNative();
     private native boolean tearDownNativeDataNative();
     private native void cleanupNativeDataNative();
-    private native String getAdapterPathNative();
+    /*package*/ native String getAdapterPathNative();
 
     private native int isEnabledNative();
     private native int enableNative();
     private native int disableNative();
 
-    private native Object[] getAdapterPropertiesNative();
+    /*package*/ native Object[] getAdapterPropertiesNative();
     private native Object[] getDevicePropertiesNative(String objectPath);
     private native boolean setAdapterPropertyStringNative(String key, String value);
     private native boolean setAdapterPropertyIntegerNative(String key, int value);