Fix interaction between BluetoothEventLoop and BluetoothService.

BluetoothEventLoop primarily handles event notifications from Bluez.
It should know innards of Bonding and especially BondState.
BondState class and BluetoothService call into each other.
When BluetoothEvent loop gets the handle to BondState it leads
to deadlocks and ANRs.

Change-Id: I785c57f6246c1288350d26d4acb87d879b27a5f9
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 9b28a9a..e05fe7b 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -52,22 +52,14 @@
     private final BluetoothAdapter mAdapter;
     private final Context mContext;
 
-    private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
-    private static final int EVENT_RESTART_BLUETOOTH = 2;
-    private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3;
-    private static final int EVENT_AGENT_CANCEL = 4;
+    private static final int EVENT_RESTART_BLUETOOTH = 1;
+    private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 2;
+    private static final int EVENT_AGENT_CANCEL = 3;
 
     private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
     private static final int CREATE_DEVICE_SUCCESS = 0;
     private static final int CREATE_DEVICE_FAILED = -1;
 
-    // The time (in millisecs) to delay the pairing attempt after the first
-    // auto pairing attempt fails. We use an exponential delay with
-    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
-    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
-    private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
-    private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
-
     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
 
@@ -76,13 +68,6 @@
         public void handleMessage(Message msg) {
             String address = null;
             switch (msg.what) {
-            case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
-                address = (String)msg.obj;
-                if (address != null) {
-                    mBluetoothService.createBond(address);
-                    return;
-                }
-                break;
             case EVENT_RESTART_BLUETOOTH:
                 mBluetoothService.restart();
                 break;
@@ -95,8 +80,7 @@
             case EVENT_AGENT_CANCEL:
                 // Set the Bond State to BOND_NONE.
                 // We always have only 1 device in BONDING state.
-                String[] devices =
-                    mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING);
+                String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
                 if (devices.length == 0) {
                     break;
                 } else if (devices.length > 1) {
@@ -104,7 +88,7 @@
                     break;
                 }
                 address = devices[0];
-                mBluetoothService.getBondState().setBondState(address,
+                mBluetoothService.setBondState(address,
                         BluetoothDevice.BOND_NONE,
                         BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
                 break;
@@ -115,7 +99,7 @@
     static { classInitNative(); }
     private static native void classInitNative();
 
-    /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
+    /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
             BluetoothService bluetoothService) {
         mBluetoothService = bluetoothService;
         mContext = context;
@@ -209,55 +193,7 @@
 
     private void onCreatePairedDeviceResult(String address, int result) {
         address = address.toUpperCase();
-        if (result == BluetoothDevice.BOND_SUCCESS) {
-            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
-            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
-                mBluetoothService.getBondState().clearPinAttempts(address);
-            }
-        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
-                mBluetoothService.getBondState().getAttempt(address) == 1) {
-            mBluetoothService.getBondState().addAutoPairingFailure(address);
-            pairingAttempt(address, result);
-        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
-                mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
-            pairingAttempt(address, result);
-        } else {
-            mBluetoothService.getBondState().setBondState(address,
-                                                          BluetoothDevice.BOND_NONE, result);
-            if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
-                mBluetoothService.getBondState().clearPinAttempts(address);
-            }
-        }
-    }
-
-    private void pairingAttempt(String address, int result) {
-        // This happens when our initial guess of "0000" as the pass key
-        // fails. Try to create the bond again and display the pin dialog
-        // to the user. Use back-off while posting the delayed
-        // message. The initial value is
-        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
-        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
-        // reached, display an error to the user.
-        int attempt = mBluetoothService.getBondState().getAttempt(address);
-        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
-                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
-            mBluetoothService.getBondState().clearPinAttempts(address);
-            mBluetoothService.getBondState().setBondState(address,
-                    BluetoothDevice.BOND_NONE, result);
-            return;
-        }
-
-        Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
-        message.obj = address;
-        boolean postResult =  mHandler.sendMessageDelayed(message,
-                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
-        if (!postResult) {
-            mBluetoothService.getBondState().clearPinAttempts(address);
-            mBluetoothService.getBondState().setBondState(address,
-                    BluetoothDevice.BOND_NONE, result);
-            return;
-        }
-        mBluetoothService.getBondState().attempt(address);
+        mBluetoothService.onCreatePairedDeviceResult(address, result);
     }
 
     private void onDeviceCreated(String deviceObjectPath) {
@@ -275,8 +211,8 @@
     private void onDeviceRemoved(String deviceObjectPath) {
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         if (address != null) {
-            mBluetoothService.getBondState().setBondState(address.toUpperCase(),
-                    BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
+            mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
+                BluetoothDevice.UNBOND_REASON_REMOVED);
             mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
         }
     }
@@ -407,13 +343,11 @@
                 // If locally initiated pairing, we will
                 // not go to BOND_BONDED state until we have received a
                 // successful return value in onCreatePairedDeviceResult
-                if (null == mBluetoothService.getBondState().getPendingOutgoingBonding()) {
-                    mBluetoothService.getBondState().setBondState(address,
-                            BluetoothDevice.BOND_BONDED);
+                if (null == mBluetoothService.getPendingOutgoingBonding()) {
+                    mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
                 }
             } else {
-                mBluetoothService.getBondState().setBondState(address,
-                        BluetoothDevice.BOND_NONE);
+                mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
                 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
             }
         } else if (name.equals("Trusted")) {
@@ -443,8 +377,8 @@
         // Also set it only when the state is not already Bonded, we can sometimes
         // get an authorization request from the remote end if it doesn't have the link key
         // while we still have it.
-        if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED)
-            mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING);
+        if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
+            mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
         return address;
     }
 
@@ -458,7 +392,7 @@
          * so we may get this request many times. Also if we respond immediately,
          * the other end is unable to handle it. Delay sending the message.
          */
-        if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) {
+        if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
             Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
             message.obj = address;
             mHandler.sendMessageDelayed(message, 1500);
@@ -503,7 +437,7 @@
         if (address == null) return;
 
         String pendingOutgoingAddress =
-                mBluetoothService.getBondState().getPendingOutgoingBonding();
+                mBluetoothService.getPendingOutgoingBonding();
         if (address.equals(pendingOutgoingAddress)) {
             // we initiated the bonding
 
@@ -524,12 +458,7 @@
             case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
             case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
             case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
-                if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
-                    !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
-                    mBluetoothService.getBondState().attempt(address);
-                    mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
-                    return;
-                }
+                if (mBluetoothService.attemptAutoPair(address)) return;
            }
         }
         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index d53ac80..dfe3a25 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -104,6 +104,14 @@
     private static final int MESSAGE_FINISH_DISABLE = 2;
     private static final int MESSAGE_UUID_INTENT = 3;
     private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
+    private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5;
+
+    // The time (in millisecs) to delay the pairing attempt after the first
+    // auto pairing attempt fails. We use an exponential delay with
+    // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
+    // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
+    private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
+    private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
 
     // The timeout used to sent the UUIDs Intent
     // This timeout should be greater than the page timeout
@@ -502,6 +510,13 @@
                     setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
                 }
                 break;
+            case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
+                address = (String)msg.obj;
+                if (address != null) {
+                    createBond(address);
+                    return;
+                }
+                break;
             }
         }
     };
@@ -591,8 +606,68 @@
         Binder.restoreCallingIdentity(origCallerIdentityToken);
     }
 
-    /* package */ BondState getBondState() {
-        return mBondState;
+    /*package*/ synchronized boolean attemptAutoPair(String address) {
+        if (!mBondState.hasAutoPairingFailed(address) &&
+                !mBondState.isAutoPairingBlacklisted(address)) {
+            mBondState.attempt(address);
+            setPin(address, BluetoothDevice.convertPinToBytes("0000"));
+            return true;
+        }
+        return false;
+    }
+
+    /*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) {
+        if (result == BluetoothDevice.BOND_SUCCESS) {
+            setBondState(address, BluetoothDevice.BOND_BONDED);
+            if (mBondState.isAutoPairingAttemptsInProgress(address)) {
+                mBondState.clearPinAttempts(address);
+            }
+        } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
+                mBondState.getAttempt(address) == 1) {
+            mBondState.addAutoPairingFailure(address);
+            pairingAttempt(address, result);
+        } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
+              mBondState.isAutoPairingAttemptsInProgress(address)) {
+            pairingAttempt(address, result);
+        } else {
+            setBondState(address, BluetoothDevice.BOND_NONE, result);
+            if (mBondState.isAutoPairingAttemptsInProgress(address)) {
+                mBondState.clearPinAttempts(address);
+            }
+        }
+    }
+
+    /*package*/ synchronized String getPendingOutgoingBonding() {
+        return mBondState.getPendingOutgoingBonding();
+    }
+
+    private void pairingAttempt(String address, int result) {
+        // This happens when our initial guess of "0000" as the pass key
+        // fails. Try to create the bond again and display the pin dialog
+        // to the user. Use back-off while posting the delayed
+        // message. The initial value is
+        // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
+        // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
+        // reached, display an error to the user.
+        int attempt = mBondState.getAttempt(address);
+        if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
+                    MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
+            mBondState.clearPinAttempts(address);
+            setBondState(address, BluetoothDevice.BOND_NONE, result);
+            return;
+        }
+
+        Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+        message.obj = address;
+        boolean postResult =  mHandler.sendMessageDelayed(message,
+                                        attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+        if (!postResult) {
+            mBondState.clearPinAttempts(address);
+            setBondState(address,
+                    BluetoothDevice.BOND_NONE, result);
+            return;
+        }
+        mBondState.attempt(address);
     }
 
     /** local cache of bonding state.
@@ -1259,6 +1334,10 @@
         return mBondState.listInState(BluetoothDevice.BOND_BONDED);
     }
 
+    /*package*/ synchronized String[] listInState(int state) {
+      return mBondState.listInState(state);
+    }
+
     public synchronized int getBondState(String address) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -1267,6 +1346,15 @@
         return mBondState.getBondState(address.toUpperCase());
     }
 
+    /*package*/ synchronized boolean setBondState(String address, int state) {
+        return setBondState(address, state, 0);
+    }
+
+    /*package*/ synchronized boolean setBondState(String address, int state, int reason) {
+        mBondState.setBondState(address.toUpperCase(), state);
+        return true;
+    }
+
     public synchronized boolean isBluetoothDock(String address) {
         SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
                 mContext.MODE_PRIVATE);