Merge "Fix car service crash while running CarServiceTest due to race" into qt-qpr1-dev
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index 4bb2790..c0f393a 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -117,7 +117,7 @@
             }
         }
     }
-    private BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
+    private final BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
 
     /**
      * Create a new BluetoothDeviceConnectionPolicy object, responsible for encapsulating the
@@ -153,6 +153,7 @@
         mUserId = userId;
         mContext = Objects.requireNonNull(context);
         mCarBluetoothService = bluetoothService;
+        mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
         mBluetoothAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter());
     }
 
@@ -160,9 +161,8 @@
      * Setup the Bluetooth profile service connections and Vehicle Event listeners.
      * and start the state machine -{@link BluetoothAutoConnectStateMachine}
      */
-    public synchronized void init() {
+    public void init() {
         logd("init()");
-        mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT,
@@ -190,7 +190,7 @@
      * Clean up slate. Close the Bluetooth profile service connections and quit the state machine -
      * {@link BluetoothAutoConnectStateMachine}
      */
-    public synchronized void release() {
+    public void release() {
         logd("release()");
         if (mCarPowerManager != null) {
             mCarPowerManager.clearListener();
@@ -198,7 +198,6 @@
         }
         if (mBluetoothBroadcastReceiver != null) {
             mContext.unregisterReceiver(mBluetoothBroadcastReceiver);
-            mBluetoothBroadcastReceiver = null;
         }
     }
 
@@ -250,7 +249,7 @@
     /**
      * Print the verbose status of the object
      */
-    public synchronized void dump(PrintWriter writer, String indent) {
+    public void dump(PrintWriter writer, String indent) {
         writer.println(indent + TAG + ":");
         writer.println(indent + "\tUserId: " + mUserId);
     }
diff --git a/service/src/com/android/car/BluetoothProfileDeviceManager.java b/service/src/com/android/car/BluetoothProfileDeviceManager.java
index 505a05b..e73d9ea 100644
--- a/service/src/com/android/car/BluetoothProfileDeviceManager.java
+++ b/service/src/com/android/car/BluetoothProfileDeviceManager.java
@@ -46,6 +46,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -114,23 +116,30 @@
                         }, new int[] {}));
     }
 
+    // Fixed per-profile information for the profile this object manages
     private final int mProfileId;
     private final String mSettingsKey;
     private final String mProfileConnectionAction;
     private final ParcelUuid[] mProfileUuids;
     private final int[] mProfileTriggers;
+
+    // Central priority list of devices
+    private final Object mPrioritizedDevicesLock = new Object();
+    @GuardedBy("mPrioritizedDevicesLock")
     private ArrayList<BluetoothDevice> mPrioritizedDevices;
 
-    private BluetoothAdapter mBluetoothAdapter;
-    private BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
-
-    private ICarBluetoothUserService mBluetoothUserProxies;
-
+    // Auto connection process state
     private final Object mAutoConnectLock = new Object();
+    @GuardedBy("mAutoConnectLock")
     private boolean mConnecting = false;
+    @GuardedBy("mAutoConnectLock")
     private int mAutoConnectPriority;
+    @GuardedBy("mAutoConnectLock")
     private ArrayList<BluetoothDevice> mAutoConnectingDevices;
 
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
+    private final ICarBluetoothUserService mBluetoothUserProxies;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
     /**
@@ -313,6 +322,7 @@
         mProfileUuids = bpi.mUuids;
         mProfileTriggers = bpi.mProfileTriggers;
 
+        mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
         mBluetoothAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter());
     }
 
@@ -327,7 +337,7 @@
             mAutoConnectPriority = -1;
             mAutoConnectingDevices = null;
         }
-        mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
+
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(mProfileConnectionAction);
         profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -347,7 +357,6 @@
             if (mContext != null) {
                 mContext.unregisterReceiver(mBluetoothBroadcastReceiver);
             }
-            mBluetoothBroadcastReceiver = null;
         }
         cancelAutoConnecting();
         commit();
@@ -392,7 +401,7 @@
             }
         }
 
-        synchronized (this) {
+        synchronized (mPrioritizedDevicesLock) {
             mPrioritizedDevices = devices;
         }
 
@@ -408,7 +417,7 @@
     private boolean commit() {
         StringBuilder sb = new StringBuilder();
         String delimiter = "";
-        synchronized (this) {
+        synchronized (mPrioritizedDevicesLock) {
             for (BluetoothDevice device : mPrioritizedDevices) {
                 sb.append(delimiter);
                 sb.append(device.getAddress());
@@ -434,7 +443,7 @@
             addDevice(device); // No-op if device is already in the priority list
         }
 
-        synchronized (this) {
+        synchronized (mPrioritizedDevicesLock) {
             ArrayList<BluetoothDevice> devices = getDeviceListSnapshot();
             for (BluetoothDevice device : devices) {
                 if (!bondedDevices.contains(device)) {
@@ -451,7 +460,7 @@
      */
     public ArrayList<BluetoothDevice> getDeviceListSnapshot() {
         ArrayList<BluetoothDevice> devices = new ArrayList<>();
-        synchronized (this) {
+        synchronized (mPrioritizedDevicesLock) {
             devices = (ArrayList) mPrioritizedDevices.clone();
         }
         return devices;
@@ -462,12 +471,14 @@
      *
      * @param device - The device you wish to add
      */
-    public synchronized void addDevice(BluetoothDevice device) {
+    public void addDevice(BluetoothDevice device) {
         if (device == null) return;
-        if (mPrioritizedDevices.contains(device)) return;
-        logd("Add device " + device);
-        mPrioritizedDevices.add(device);
-        commit();
+        synchronized (mPrioritizedDevicesLock) {
+            if (mPrioritizedDevices.contains(device)) return;
+            logd("Add device " + device);
+            mPrioritizedDevices.add(device);
+            commit();
+        }
     }
 
     /**
@@ -475,12 +486,14 @@
      *
      * @param device - The device you wish to remove
      */
-    public synchronized void removeDevice(BluetoothDevice device) {
+    public void removeDevice(BluetoothDevice device) {
         if (device == null) return;
-        if (!mPrioritizedDevices.contains(device)) return;
-        logd("Remove device " + device);
-        mPrioritizedDevices.remove(device);
-        commit();
+        synchronized (mPrioritizedDevicesLock) {
+            if (!mPrioritizedDevices.contains(device)) return;
+            logd("Remove device " + device);
+            mPrioritizedDevices.remove(device);
+            commit();
+        }
     }
 
     /**
@@ -489,10 +502,12 @@
      * @param device - The device you want the priority of
      * @return The priority of the device, or -1 if the device is not in the list
      */
-    public synchronized int getDeviceConnectionPriority(BluetoothDevice device) {
+    public int getDeviceConnectionPriority(BluetoothDevice device) {
         if (device == null) return -1;
         logd("Get connection priority of " + device);
-        return mPrioritizedDevices.indexOf(device);
+        synchronized (mPrioritizedDevicesLock) {
+            return mPrioritizedDevices.indexOf(device);
+        }
     }
 
     /**
@@ -505,16 +520,18 @@
      * @param device - The device you want to set the priority of
      * @param priority - The priority you want to the device to have
      */
-    public synchronized void setDeviceConnectionPriority(BluetoothDevice device, int priority) {
-        if (device == null || priority < 0 || priority > mPrioritizedDevices.size()
-                || getDeviceConnectionPriority(device) == priority) return;
-        if (mPrioritizedDevices.contains(device)) {
-            mPrioritizedDevices.remove(device);
-            if (priority > mPrioritizedDevices.size()) priority = mPrioritizedDevices.size();
+    public void setDeviceConnectionPriority(BluetoothDevice device, int priority) {
+        synchronized (mPrioritizedDevicesLock) {
+            if (device == null || priority < 0 || priority > mPrioritizedDevices.size()
+                    || getDeviceConnectionPriority(device) == priority) return;
+            if (mPrioritizedDevices.contains(device)) {
+                mPrioritizedDevices.remove(device);
+                if (priority > mPrioritizedDevices.size()) priority = mPrioritizedDevices.size();
+            }
+            logd("Set connection priority of " + device + " to " + priority);
+            mPrioritizedDevices.add(priority, device);
+            commit();
         }
-        logd("Set connection priority of " + device + " to " + priority);
-        mPrioritizedDevices.add(priority, device);
-        commit();
     }
 
     /**
diff --git a/service/src/com/android/car/BluetoothProfileInhibitManager.java b/service/src/com/android/car/BluetoothProfileInhibitManager.java
index 4dcc6b7..691592f 100644
--- a/service/src/com/android/car/BluetoothProfileInhibitManager.java
+++ b/service/src/com/android/car/BluetoothProfileInhibitManager.java
@@ -56,14 +56,16 @@
     private final int mUserId;
     private final ICarBluetoothUserService mBluetoothUserProxies;
 
-    @GuardedBy("this")
+    private final Object mProfileInhibitsLock = new Object();
+
+    @GuardedBy("mProfileInhibitsLock")
     private final SetMultimap<BluetoothConnection, InhibitRecord> mProfileInhibits =
             new SetMultimap<>();
 
-    @GuardedBy("this")
+    @GuardedBy("mProfileInhibitsLock")
     private final HashSet<InhibitRecord> mRestoredInhibits = new HashSet<>();
 
-    @GuardedBy("this")
+    @GuardedBy("mProfileInhibitsLock")
     private final HashSet<BluetoothConnection> mAlreadyDisabledProfiles = new HashSet<>();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -190,7 +192,7 @@
         }
 
         public boolean removeSelf() {
-            synchronized (BluetoothProfileInhibitManager.this) {
+            synchronized (mProfileInhibitsLock) {
                 if (mRemoved) {
                     return true;
                 }
@@ -328,9 +330,7 @@
 
         BluetoothConnection params = new BluetoothConnection(profile, device);
         InhibitRecord record;
-        synchronized (this) {
-            record = findInhibitRecord(params, token);
-        }
+        record = findInhibitRecord(params, token);
 
         if (record == null) {
             Log.e(TAG, "Record not found");
@@ -343,64 +343,66 @@
     /**
      * Add a profile inhibit record, disabling the profile if necessary.
      */
-    private synchronized boolean addInhibitRecord(InhibitRecord record) {
-        BluetoothConnection params = record.getParams();
-        if (!isProxyAvailable(params.getProfile())) {
-            return false;
-        }
-
-        Set<InhibitRecord> previousRecords = mProfileInhibits.get(params);
-        if (findInhibitRecord(params, record.getToken()) != null) {
-            Log.e(TAG, "Inhibit request already registered - skipping duplicate");
-            return false;
-        }
-
-        try {
-            record.getToken().linkToDeath(record, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not link to death on inhibit token (already dead?)", e);
-            return false;
-        }
-
-        boolean isNewlyAdded = previousRecords.isEmpty();
-        mProfileInhibits.put(params, record);
-
-        if (isNewlyAdded) {
-            try {
-                int priority =
-                        mBluetoothUserProxies.getProfilePriority(
-                                params.getProfile(),
-                                params.getDevice());
-                if (priority == BluetoothProfile.PRIORITY_OFF) {
-                    // This profile was already disabled (and not as the result of an inhibit).
-                    // Add it to the already-disabled list, and do nothing else.
-                    mAlreadyDisabledProfiles.add(params);
-
-                    logd("Profile " + Utils.getProfileName(params.getProfile())
-                            + " already disabled for device " + params.getDevice()
-                            + " - suppressing re-enable");
-                } else {
-                    mBluetoothUserProxies.setProfilePriority(
-                            params.getProfile(),
-                            params.getDevice(),
-                            BluetoothProfile.PRIORITY_OFF);
-                    mBluetoothUserProxies.bluetoothDisconnectFromProfile(
-                            params.getProfile(),
-                            params.getDevice());
-                    logd("Disabled profile "
-                            + Utils.getProfileName(params.getProfile())
-                            + " for device " + params.getDevice());
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "Could not disable profile", e);
-                record.getToken().unlinkToDeath(record, 0);
-                mProfileInhibits.remove(params, record);
+    private boolean addInhibitRecord(InhibitRecord record) {
+        synchronized (mProfileInhibitsLock) {
+            BluetoothConnection params = record.getParams();
+            if (!isProxyAvailable(params.getProfile())) {
                 return false;
             }
-        }
 
-        commit();
-        return true;
+            Set<InhibitRecord> previousRecords = mProfileInhibits.get(params);
+            if (findInhibitRecord(params, record.getToken()) != null) {
+                Log.e(TAG, "Inhibit request already registered - skipping duplicate");
+                return false;
+            }
+
+            try {
+                record.getToken().linkToDeath(record, 0);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not link to death on inhibit token (already dead?)", e);
+                return false;
+            }
+
+            boolean isNewlyAdded = previousRecords.isEmpty();
+            mProfileInhibits.put(params, record);
+
+            if (isNewlyAdded) {
+                try {
+                    int priority =
+                            mBluetoothUserProxies.getProfilePriority(
+                                    params.getProfile(),
+                                    params.getDevice());
+                    if (priority == BluetoothProfile.PRIORITY_OFF) {
+                        // This profile was already disabled (and not as the result of an inhibit).
+                        // Add it to the already-disabled list, and do nothing else.
+                        mAlreadyDisabledProfiles.add(params);
+
+                        logd("Profile " + Utils.getProfileName(params.getProfile())
+                                + " already disabled for device " + params.getDevice()
+                                + " - suppressing re-enable");
+                    } else {
+                        mBluetoothUserProxies.setProfilePriority(
+                                params.getProfile(),
+                                params.getDevice(),
+                                BluetoothProfile.PRIORITY_OFF);
+                        mBluetoothUserProxies.bluetoothDisconnectFromProfile(
+                                params.getProfile(),
+                                params.getDevice());
+                        logd("Disabled profile "
+                                + Utils.getProfileName(params.getProfile())
+                                + " for device " + params.getDevice());
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not disable profile", e);
+                    record.getToken().unlinkToDeath(record, 0);
+                    mProfileInhibits.remove(params, record);
+                    return false;
+                }
+            }
+
+            commit();
+            return true;
+        }
     }
 
     /**
@@ -411,41 +413,45 @@
      * @return InhibitRecord for the connection parameters and token if exists, null otherwise.
      */
     private InhibitRecord findInhibitRecord(BluetoothConnection params, IBinder token) {
-        return mProfileInhibits.get(params)
-            .stream()
-            .filter(r -> r.getToken() == token)
-            .findAny()
-            .orElse(null);
+        synchronized (mProfileInhibitsLock) {
+            return mProfileInhibits.get(params)
+                .stream()
+                .filter(r -> r.getToken() == token)
+                .findAny()
+                .orElse(null);
+        }
     }
 
     /**
      * Remove a given profile inhibit record, reconnecting if necessary.
      */
-    private synchronized boolean removeInhibitRecord(InhibitRecord record) {
-        BluetoothConnection params = record.getParams();
-        if (!isProxyAvailable(params.getProfile())) {
-            return false;
-        }
-        if (!mProfileInhibits.containsEntry(params, record)) {
-            Log.e(TAG, "Record already removed");
-            // Removing something a second time vacuously succeeds.
-            return true;
-        }
-
-        // Re-enable profile before unlinking and removing the record, in case of error.
-        // The profile should be re-enabled if this record is the only one left for that
-        // device and profile combination.
-        if (mProfileInhibits.get(params).size() == 1) {
-            if (!restoreProfilePriority(params)) {
+    private boolean removeInhibitRecord(InhibitRecord record) {
+        synchronized (mProfileInhibitsLock) {
+            BluetoothConnection params = record.getParams();
+            if (!isProxyAvailable(params.getProfile())) {
                 return false;
             }
+            if (!mProfileInhibits.containsEntry(params, record)) {
+                Log.e(TAG, "Record already removed");
+                // Removing something a second time vacuously succeeds.
+                return true;
+            }
+
+            // Re-enable profile before unlinking and removing the record, in case of error.
+            // The profile should be re-enabled if this record is the only one left for that
+            // device and profile combination.
+            if (mProfileInhibits.get(params).size() == 1) {
+                if (!restoreProfilePriority(params)) {
+                    return false;
+                }
+            }
+
+            record.getToken().unlinkToDeath(record, 0);
+            mProfileInhibits.remove(params, record);
+
+            commit();
+            return true;
         }
-
-        record.getToken().unlinkToDeath(record, 0);
-        mProfileInhibits.remove(params, record);
-
-        commit();
-        return true;
     }
 
     /**
@@ -504,46 +510,51 @@
      * Keep trying to remove all profile inhibits that were restored from settings
      * until all such inhibits have been removed.
      */
-    private synchronized void removeRestoredProfileInhibits() {
-        tryRemoveRestoredProfileInhibits();
+    private void removeRestoredProfileInhibits() {
+        synchronized (mProfileInhibitsLock) {
+            tryRemoveRestoredProfileInhibits();
 
-        if (!mRestoredInhibits.isEmpty()) {
-            logd("Could not remove all restored profile inhibits - "
-                        + "trying again in " + RESTORE_BACKOFF_MILLIS + "ms");
-            mHandler.postDelayed(
-                    this::removeRestoredProfileInhibits,
-                    RESTORED_PROFILE_INHIBIT_TOKEN,
-                    RESTORE_BACKOFF_MILLIS);
+            if (!mRestoredInhibits.isEmpty()) {
+                logd("Could not remove all restored profile inhibits - "
+                            + "trying again in " + RESTORE_BACKOFF_MILLIS + "ms");
+                mHandler.postDelayed(
+                        this::removeRestoredProfileInhibits,
+                        RESTORED_PROFILE_INHIBIT_TOKEN,
+                        RESTORE_BACKOFF_MILLIS);
+            }
         }
     }
 
     /**
      * Release all active inhibit records prior to user switch or shutdown
      */
-    private synchronized void releaseAllInhibitsBeforeUnbind() {
+    private  void releaseAllInhibitsBeforeUnbind() {
         logd("Unbinding CarBluetoothUserService - releasing all profile inhibits");
-        for (BluetoothConnection params : mProfileInhibits.keySet()) {
-            for (InhibitRecord record : mProfileInhibits.get(params)) {
-                record.removeSelf();
+
+        synchronized (mProfileInhibitsLock) {
+            for (BluetoothConnection params : mProfileInhibits.keySet()) {
+                for (InhibitRecord record : mProfileInhibits.get(params)) {
+                    record.removeSelf();
+                }
             }
+
+            // Some inhibits might be hanging around because they couldn't be cleaned up.
+            // Make sure they get persisted...
+            commit();
+
+            // ...then clear them from the map.
+            mProfileInhibits.clear();
+
+            // We don't need to maintain previously-disabled profiles any more - they were already
+            // skipped in saveProfileInhibitsToSettings() above, and they don't need any
+            // further handling when the user resumes.
+            mAlreadyDisabledProfiles.clear();
+
+            // Clean up bookkeeping for restored inhibits. (If any are still around, they'll be
+            // restored again when this user restarts.)
+            mHandler.removeCallbacksAndMessages(RESTORED_PROFILE_INHIBIT_TOKEN);
+            mRestoredInhibits.clear();
         }
-
-        // Some inhibits might be hanging around because they couldn't be cleaned up.
-        // Make sure they get persisted...
-        commit();
-
-        // ...then clear them from the map.
-        mProfileInhibits.clear();
-
-        // We don't need to maintain previously-disabled profiles any more - they were already
-        // skipped in saveProfileInhibitsToSettings() above, and they don't need any
-        // further handling when the user resumes.
-        mAlreadyDisabledProfiles.clear();
-
-        // Clean up bookkeeping for restored inhibits. (If any are still around, they'll be
-        // restored again when this user restarts.)
-        mHandler.removeCallbacksAndMessages(RESTORED_PROFILE_INHIBIT_TOKEN);
-        mRestoredInhibits.clear();
     }
 
     /**
@@ -564,7 +575,7 @@
     /**
      * Print the verbose status of the object
      */
-    public synchronized void dump(PrintWriter writer, String indent) {
+    public void dump(PrintWriter writer, String indent) {
         writer.println(indent + TAG + ":");
 
         // User metadata
@@ -572,7 +583,7 @@
 
         // Current inhibits
         String inhibits;
-        synchronized (this) {
+        synchronized (mProfileInhibitsLock) {
             inhibits = mProfileInhibits.keySet().toString();
         }
         writer.println(indent + "\tInhibited profiles: " + inhibits);
diff --git a/service/src/com/android/car/CarBluetoothService.java b/service/src/com/android/car/CarBluetoothService.java
index 0c992e1..54d15a1 100644
--- a/service/src/com/android/car/CarBluetoothService.java
+++ b/service/src/com/android/car/CarBluetoothService.java
@@ -28,6 +28,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -63,20 +65,31 @@
             BluetoothProfile.PAN
     );
 
+    // Each time PerUserCarService connects we need to get new Bluetooth profile proxies and refresh
+    // all our internal objects to use them. When it disconnects we're to assume our proxies are
+    // invalid. This lock protects all our internal objects.
+    private final Object mPerUserLock = new Object();
+
     // Set of Bluetooth Profile Device Managers, own the priority connection lists, updated on user
     // switch
-    private SparseArray<BluetoothProfileDeviceManager> mProfileDeviceManagers = new SparseArray<>();
+    private final SparseArray<BluetoothProfileDeviceManager> mProfileDeviceManagers =
+            new SparseArray<>();
 
     // Profile-Inhibit Manager that will temporarily inhibit connections on profiles, per user
+    @GuardedBy("mPerUserLock")
     private BluetoothProfileInhibitManager mInhibitManager = null;
 
     // Default Bluetooth device connection policy, per user, enabled with an overlay
     private final boolean mUseDefaultPolicy;
+    @GuardedBy("mPerUserLock")
     private BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy = null;
 
     // Listen for user switch events from the PerUserCarService
+    @GuardedBy("mPerUserLock")
     private int mUserId;
+    @GuardedBy("mPerUserLock")
     private ICarUserService mCarUserService;
+    @GuardedBy("mPerUserLock")
     private ICarBluetoothUserService mCarBluetoothUserService;
     private final PerUserCarServiceHelper mUserServiceHelper;
     private final PerUserCarServiceHelper.ServiceCallback mUserServiceCallback =
@@ -84,8 +97,14 @@
         @Override
         public void onServiceConnected(ICarUserService carUserService) {
             logd("Connected to PerUserCarService");
-            synchronized (this) {
+            synchronized (mPerUserLock) {
+                // Explicitly clear out existing per-user objects since we can't rely on the
+                // onServiceDisconnected and onPreUnbind calls to always be called before this
+                destroyUser();
+
                 mCarUserService = carUserService;
+
+                // Create new objects with our new set of profile proxies
                 initializeUser();
             }
         }
@@ -99,9 +118,7 @@
         @Override
         public void onServiceDisconnected() {
             logd("Disconnected from PerUserCarService");
-            synchronized (this) {
-                mCarUserService = null;
-            }
+            destroyUser();
         }
     };
 
@@ -126,7 +143,7 @@
      * Wait for the user service helper to report a user before initializing a user.
      */
     @Override
-    public synchronized void init() {
+    public void init() {
         logd("init()");
         mUserServiceHelper.registerServiceCallback(mUserServiceCallback);
     }
@@ -137,11 +154,10 @@
      * Clean up the user context once we've detached from the user service helper, if any.
      */
     @Override
-    public synchronized void release() {
+    public void release() {
         logd("release()");
         mUserServiceHelper.unregisterServiceCallback(mUserServiceCallback);
         destroyUser();
-        mCarUserService = null;
     }
 
     /**
@@ -158,32 +174,37 @@
      *
      * Only call this following a known user switch once we've connected to the user service helper.
      */
-    private synchronized void initializeUser() {
+    private void initializeUser() {
         logd("Initializing new user");
-        mUserId = ActivityManager.getCurrentUser();
-        createBluetoothUserService();
-        createBluetoothProfileDeviceManagers();
-        createBluetoothProfileInhibitManager();
+        synchronized (mPerUserLock) {
+            mUserId = ActivityManager.getCurrentUser();
+            createBluetoothUserService();
+            createBluetoothProfileDeviceManagers();
+            createBluetoothProfileInhibitManager();
 
-        // Determine if we need to begin the default policy
-        mBluetoothDeviceConnectionPolicy = null;
-        if (mUseDefaultPolicy) {
-            createBluetoothDeviceConnectionPolicy();
+            // Determine if we need to begin the default policy
+            mBluetoothDeviceConnectionPolicy = null;
+            if (mUseDefaultPolicy) {
+                createBluetoothDeviceConnectionPolicy();
+            }
+            logd("Switched to user " + mUserId);
         }
-        logd("Switched to user " + mUserId);
     }
 
     /**
      * Destroy the current user context, defined by the set of profile proxies, profile device
      * managers, inhibit manager and the policy.
      */
-    private synchronized void destroyUser() {
+    private void destroyUser() {
         logd("Destroying user " + mUserId);
-        destroyBluetoothDeviceConnectionPolicy();
-        destroyBluetoothProfileInhibitManager();
-        destroyBluetoothProfileDeviceManagers();
-        destroyBluetoothUserService();
-        mUserId = -1;
+        synchronized (mPerUserLock) {
+            destroyBluetoothDeviceConnectionPolicy();
+            destroyBluetoothProfileInhibitManager();
+            destroyBluetoothProfileDeviceManagers();
+            destroyBluetoothUserService();
+            mCarUserService = null;
+            mUserId = -1;
+        }
     }
 
     /**
@@ -192,17 +213,17 @@
      * Also sets up the connection proxy objects required to communicate with the Bluetooth
      * Profile Services.
      */
-    private synchronized void createBluetoothUserService() {
-        if (mCarUserService != null) {
-            try {
-                mCarBluetoothUserService = mCarUserService.getBluetoothUserService();
-                mCarBluetoothUserService.setupBluetoothConnectionProxies();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
-                        + e.getMessage());
+    private void createBluetoothUserService() {
+        synchronized (mPerUserLock) {
+            if (mCarUserService != null) {
+                try {
+                    mCarBluetoothUserService = mCarUserService.getBluetoothUserService();
+                    mCarBluetoothUserService.setupBluetoothConnectionProxies();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
+                            + e.getMessage());
+                }
             }
-        } else {
-            logd("PerUserCarService not connected. Cannot get bluetooth user proxy objects");
         }
     }
 
@@ -210,109 +231,130 @@
      * Close out the Per User Car Bluetooth profile proxy connections and destroys the Car Bluetooth
      * User Service object.
      */
-    private synchronized void destroyBluetoothUserService() {
-        if (mCarBluetoothUserService == null) return;
-        try {
-            mCarBluetoothUserService.closeBluetoothConnectionProxies();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
-                    + e.getMessage());
+    private void destroyBluetoothUserService() {
+        synchronized (mPerUserLock) {
+            if (mCarBluetoothUserService == null) return;
+            try {
+                mCarBluetoothUserService.closeBluetoothConnectionProxies();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
+                        + e.getMessage());
+            }
+            mCarBluetoothUserService = null;
         }
-        mCarBluetoothUserService = null;
     }
 
     /**
      * Clears out Profile Device Managers and re-creates them for the current user.
      */
-    private synchronized void createBluetoothProfileDeviceManagers() {
-        mProfileDeviceManagers.clear();
-        if (mUserId == -1) {
-            logd("No foreground user, cannot create profile device managers");
-            return;
-        }
-        for (int profileId : sManagedProfiles) {
-            BluetoothProfileDeviceManager deviceManager = BluetoothProfileDeviceManager.create(
-                    mContext, mUserId, mCarBluetoothUserService, profileId);
-            if (deviceManager == null) {
-                logd("Failed to create profile device manager for "
-                        + Utils.getProfileName(profileId));
-                continue;
+    private void createBluetoothProfileDeviceManagers() {
+        synchronized (mPerUserLock) {
+            if (mUserId == -1) {
+                logd("No foreground user, cannot create profile device managers");
+                return;
             }
-            mProfileDeviceManagers.put(profileId, deviceManager);
-            logd("Created profile device manager for " + Utils.getProfileName(profileId));
-        }
+            for (int profileId : sManagedProfiles) {
+                BluetoothProfileDeviceManager deviceManager = mProfileDeviceManagers.get(profileId);
+                if (deviceManager != null) {
+                    deviceManager.stop();
+                    mProfileDeviceManagers.remove(profileId);
+                    logd("Existing device manager removed for profile "
+                            + Utils.getProfileName(profileId));
+                }
 
-        for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
-            int key = mProfileDeviceManagers.keyAt(i);
-            BluetoothProfileDeviceManager deviceManager =
-                    (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
-            deviceManager.start();
+                deviceManager = BluetoothProfileDeviceManager.create(mContext, mUserId,
+                        mCarBluetoothUserService, profileId);
+                if (deviceManager == null) {
+                    logd("Failed to create profile device manager for "
+                            + Utils.getProfileName(profileId));
+                    continue;
+                }
+                mProfileDeviceManagers.put(profileId, deviceManager);
+                logd("Created profile device manager for " + Utils.getProfileName(profileId));
+            }
+
+            for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
+                int key = mProfileDeviceManagers.keyAt(i);
+                BluetoothProfileDeviceManager deviceManager =
+                        (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
+                deviceManager.start();
+            }
         }
     }
 
     /**
      * Stops and clears the entire set of Profile Device Managers.
      */
-    private synchronized void destroyBluetoothProfileDeviceManagers() {
-        for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
-            int key = mProfileDeviceManagers.keyAt(i);
-            BluetoothProfileDeviceManager deviceManager =
-                    (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
-            deviceManager.stop();
+    private void destroyBluetoothProfileDeviceManagers() {
+        synchronized (mPerUserLock) {
+            for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
+                int key = mProfileDeviceManagers.keyAt(i);
+                BluetoothProfileDeviceManager deviceManager =
+                        (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
+                deviceManager.stop();
+            }
+            mProfileDeviceManagers.clear();
         }
-        mProfileDeviceManagers.clear();
     }
 
     /**
      * Creates an instance of a BluetoothProfileInhibitManager under the current user
      */
-    private synchronized void createBluetoothProfileInhibitManager() {
+    private void createBluetoothProfileInhibitManager() {
         logd("Creating inhibit manager");
-        if (mUserId == -1) {
-            logd("No foreground user, cannot create profile inhibit manager");
-            return;
+        synchronized (mPerUserLock) {
+            if (mUserId == -1) {
+                logd("No foreground user, cannot create profile inhibit manager");
+                return;
+            }
+            mInhibitManager = new BluetoothProfileInhibitManager(mContext, mUserId,
+                    mCarBluetoothUserService);
+            mInhibitManager.start();
         }
-        mInhibitManager = new BluetoothProfileInhibitManager(mContext, mUserId,
-                mCarBluetoothUserService);
-        mInhibitManager.start();
     }
 
     /**
      * Destroys the current instance of a BluetoothProfileInhibitManager, if one exists
      */
-    private synchronized void destroyBluetoothProfileInhibitManager() {
+    private void destroyBluetoothProfileInhibitManager() {
         logd("Destroying inhibit manager");
-        if (mInhibitManager == null) return;
-        mInhibitManager.stop();
-        mInhibitManager = null;
+        synchronized (mPerUserLock) {
+            if (mInhibitManager == null) return;
+            mInhibitManager.stop();
+            mInhibitManager = null;
+        }
     }
 
     /**
      * Creates an instance of a BluetoothDeviceConnectionPolicy under the current user
      */
-    private synchronized void createBluetoothDeviceConnectionPolicy() {
+    private void createBluetoothDeviceConnectionPolicy() {
         logd("Creating device connection policy");
-        if (mUserId == -1) {
-            logd("No foreground user, cannot create device connection policy");
-            return;
+        synchronized (mPerUserLock) {
+            if (mUserId == -1) {
+                logd("No foreground user, cannot create device connection policy");
+                return;
+            }
+            mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext,
+                    mUserId, this);
+            if (mBluetoothDeviceConnectionPolicy == null) {
+                logd("Failed to create default Bluetooth device connection policy.");
+                return;
+            }
+            mBluetoothDeviceConnectionPolicy.init();
         }
-        mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext, mUserId,
-                this);
-        if (mBluetoothDeviceConnectionPolicy == null) {
-            logd("Failed to create default Bluetooth device connection policy.");
-            return;
-        }
-        mBluetoothDeviceConnectionPolicy.init();
     }
 
     /**
      * Destroys the current instance of a BluetoothDeviceConnectionPolicy, if one exists
      */
-    private synchronized void destroyBluetoothDeviceConnectionPolicy() {
+    private void destroyBluetoothDeviceConnectionPolicy() {
         logd("Destroying device connection policy");
-        if (mBluetoothDeviceConnectionPolicy != null) {
-            mBluetoothDeviceConnectionPolicy.release();
-            mBluetoothDeviceConnectionPolicy = null;
+        synchronized (mPerUserLock) {
+            if (mBluetoothDeviceConnectionPolicy != null) {
+                mBluetoothDeviceConnectionPolicy.release();
+                mBluetoothDeviceConnectionPolicy = null;
+            }
         }
     }
 
@@ -322,7 +364,9 @@
      * @return true if the default policy is active, false otherwise
      */
     public boolean isUsingDefaultConnectionPolicy() {
-        return mBluetoothDeviceConnectionPolicy != null;
+        synchronized (mPerUserLock) {
+            return mBluetoothDeviceConnectionPolicy != null;
+        }
     }
 
    /**
@@ -332,7 +376,7 @@
     public void connectDevices() {
         enforceBluetoothAdminPermission();
         logd("Connect devices for each profile");
-        synchronized (this) {
+        synchronized (mPerUserLock) {
             for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
                 int key = mProfileDeviceManagers.keyAt(i);
                 BluetoothProfileDeviceManager deviceManager =
@@ -350,7 +394,7 @@
      */
     public List<BluetoothDevice> getProfileDevicePriorityList(int profile) {
         enforceBluetoothAdminPermission();
-        synchronized (this) {
+        synchronized (mPerUserLock) {
             BluetoothProfileDeviceManager deviceManager =
                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile);
             if (deviceManager != null) {
@@ -369,7 +413,7 @@
      */
     public int getDeviceConnectionPriority(int profile, BluetoothDevice device) {
         enforceBluetoothAdminPermission();
-        synchronized (this) {
+        synchronized (mPerUserLock) {
             BluetoothProfileDeviceManager deviceManager =
                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile);
             if (deviceManager != null) {
@@ -388,7 +432,7 @@
      */
     public void setDeviceConnectionPriority(int profile, BluetoothDevice device, int priority) {
         enforceBluetoothAdminPermission();
-        synchronized (this) {
+        synchronized (mPerUserLock) {
             BluetoothProfileDeviceManager deviceManager =
                     (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(profile);
             if (deviceManager != null) {
@@ -408,11 +452,13 @@
      *                owning the token dies, the request will automatically be released
      * @return True if the profile was successfully inhibited, false if an error occurred.
      */
-    synchronized boolean requestProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
+    boolean requestProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
         logd("Request profile inhibit: profile " + Utils.getProfileName(profile)
                 + ", device " + device.getAddress());
-        if (mInhibitManager == null) return false;
-        return mInhibitManager.requestProfileInhibit(device, profile, token);
+        synchronized (mPerUserLock) {
+            if (mInhibitManager == null) return false;
+            return mInhibitManager.requestProfileInhibit(device, profile, token);
+        }
     }
 
     /**
@@ -425,11 +471,13 @@
      *                {@link #requestBluetoothProfileInhibit}.
      * @return True if the request was released, false if an error occurred.
      */
-    synchronized boolean releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
+    boolean releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
         logd("Release profile inhibit: profile " + Utils.getProfileName(profile)
                 + ", device " + device.getAddress());
-        if (mInhibitManager == null) return false;
-        return mInhibitManager.releaseProfileInhibit(device, profile, token);
+        synchronized (mPerUserLock) {
+            if (mInhibitManager == null) return false;
+            return mInhibitManager.releaseProfileInhibit(device, profile, token);
+        }
     }
 
     /**
@@ -452,29 +500,31 @@
      * Print out the verbose debug status of this object
      */
     @Override
-    public synchronized void dump(PrintWriter writer) {
+    public void dump(PrintWriter writer) {
         writer.println("*" + TAG + "*");
-        writer.println("\tUser ID: " + mUserId);
-        writer.println("\tUser Proxies: " + (mCarBluetoothUserService != null ? "Yes" : "No"));
+        synchronized (mPerUserLock) {
+            writer.println("\tUser ID: " + mUserId);
+            writer.println("\tUser Proxies: " + (mCarBluetoothUserService != null ? "Yes" : "No"));
 
-        // Profile Device Manager statuses
-        for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
-            int key = mProfileDeviceManagers.keyAt(i);
-            BluetoothProfileDeviceManager deviceManager =
-                    (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
-            deviceManager.dump(writer, "\t");
-        }
+            // Profile Device Manager statuses
+            for (int i = 0; i < mProfileDeviceManagers.size(); i++) {
+                int key = mProfileDeviceManagers.keyAt(i);
+                BluetoothProfileDeviceManager deviceManager =
+                        (BluetoothProfileDeviceManager) mProfileDeviceManagers.get(key);
+                deviceManager.dump(writer, "\t");
+            }
 
-        // Profile Inhibits
-        if (mInhibitManager != null) mInhibitManager.dump(writer, "\t");
-        else writer.println("\tBluetoothProfileInhibitManager: null");
+            // Profile Inhibits
+            if (mInhibitManager != null) mInhibitManager.dump(writer, "\t");
+            else writer.println("\tBluetoothProfileInhibitManager: null");
 
-        // Device Connection Policy
-        writer.println("\tUsing default policy? " + (mUseDefaultPolicy ? "Yes" : "No"));
-        if (mBluetoothDeviceConnectionPolicy == null) {
-            writer.println("\tBluetoothDeviceConnectionPolicy: null");
-        } else {
-            mBluetoothDeviceConnectionPolicy.dump(writer, "\t");
+            // Device Connection Policy
+            writer.println("\tUsing default policy? " + (mUseDefaultPolicy ? "Yes" : "No"));
+            if (mBluetoothDeviceConnectionPolicy == null) {
+                writer.println("\tBluetoothDeviceConnectionPolicy: null");
+            } else {
+                mBluetoothDeviceConnectionPolicy.dump(writer, "\t");
+            }
         }
     }