Merge changes If500c1cc,I248385ea,I05e27dba into qt-qpr1-dev

* changes:
  Fix crash in CarPowerManagementService
  Clean up synchronization of CarPowerManagementService
  Disable user switching while resuming from Suspend to RAM
diff --git a/car_product/init/init.bootstat.rc b/car_product/init/init.bootstat.rc
index 5c5e796..4122ea4 100644
--- a/car_product/init/init.bootstat.rc
+++ b/car_product/init/init.bootstat.rc
@@ -4,4 +4,4 @@
 # This is a common source of Android security bugs.
 #
 on property:boot.car_service_created=1
-    exec - root root -- /system/bin/bootstat -r car_service_created
+    exec - system log -- /system/bin/bootstat -r car_service_created
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 6b5ddac..1d5fd14 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -93,4 +93,8 @@
     <string name="config_dataUsageSummaryComponent">com.android.car.settings/com.android.car.settings.datausage.DataWarningAndLimitActivity</string>
 
     <bool name="config_automotiveHideNavBarForKeyboard">true</bool>
+
+    <!-- Turn off Wallpaper service -->
+    <bool name="config_enableWallpaperService">false</bool>
+
 </resources>
diff --git a/service/src/com/android/car/BinderInterfaceContainer.java b/service/src/com/android/car/BinderInterfaceContainer.java
index a03b633..5d57b16 100644
--- a/service/src/com/android/car/BinderInterfaceContainer.java
+++ b/service/src/com/android/car/BinderInterfaceContainer.java
@@ -122,8 +122,10 @@
     public synchronized void clear() {
         Collection<BinderInterface<T>> interfaces = getInterfaces();
         for (BinderInterface<T> bInterface : interfaces) {
-            removeBinder(bInterface.binderInterface);
+            IBinder binder = bInterface.binderInterface.asBinder();
+            binder.unlinkToDeath(bInterface, 0);
         }
+        mBinders.clear();
     }
 
     private void handleBinderDeath(BinderInterface<T> bInterface) {
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");
+            }
         }
     }
 
diff --git a/service/src/com/android/car/CarTestService.java b/service/src/com/android/car/CarTestService.java
index 776fd53..d72d87d 100644
--- a/service/src/com/android/car/CarTestService.java
+++ b/service/src/com/android/car/CarTestService.java
@@ -123,4 +123,4 @@
             releaseToken(mToken);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index c61610e..d98b780 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -68,6 +68,7 @@
     public static final String INTERNAL_INPUT_SERVICE = "internal_input";
     public static final String INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE =
             "system_activity_monitoring";
+    public static final String INTERNAL_VMS_MANAGER = "vms_manager";
 
     private final Context mContext;
     private final VehicleHal mHal;
@@ -165,8 +166,8 @@
         mVmsBrokerService = new VmsBrokerService();
         mVmsClientManager = new VmsClientManager(
                 // CarStatsService needs to be passed to the constructor due to HAL init order
-                serviceContext, mCarStatsService, mCarUserService, mUserManagerHelper,
-                mVmsBrokerService, mHal.getVmsHal());
+                serviceContext, mCarStatsService, mCarUserService, mVmsBrokerService,
+                mHal.getVmsHal());
         mVmsSubscriberService = new VmsSubscriberService(
                 serviceContext, mVmsBrokerService, mVmsClientManager, mHal.getVmsHal());
         mVmsPublisherService = new VmsPublisherService(
@@ -243,7 +244,6 @@
             mAllServices[i].release();
         }
         mHal.release();
-        CarLocalServices.removeAllServices();
     }
 
     void vehicleHalReconnected(IVehicle vehicle) {
@@ -377,6 +377,8 @@
                 return mCarInputService;
             case INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE:
                 return mSystemActivityMonitoringService;
+            case INTERNAL_VMS_MANAGER:
+                return mVmsClientManager;
             default:
                 Log.w(CarLog.TAG_SERVICE, "getCarInternalService for unknown service:" +
                         serviceName);
diff --git a/service/src/com/android/car/systeminterface/TimeInterface.java b/service/src/com/android/car/systeminterface/TimeInterface.java
index dea1153..fd350a5 100644
--- a/service/src/com/android/car/systeminterface/TimeInterface.java
+++ b/service/src/com/android/car/systeminterface/TimeInterface.java
@@ -19,6 +19,9 @@
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 
 import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
@@ -42,16 +45,34 @@
     void cancelAllActions();
 
     class DefaultImpl implements TimeInterface {
-        private final ScheduledExecutorService mExecutor = newSingleThreadScheduledExecutor();
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        private ScheduledExecutorService mExecutor;
 
         @Override
         public void scheduleAction(Runnable r, long delayMs) {
-            mExecutor.scheduleAtFixedRate(r, delayMs, delayMs, TimeUnit.MILLISECONDS);
+            ScheduledExecutorService executor;
+            synchronized (mLock) {
+                executor = mExecutor;
+                if (executor == null) {
+                    executor = newSingleThreadScheduledExecutor();
+                    mExecutor = executor;
+                }
+            }
+            executor.scheduleAtFixedRate(r, delayMs, delayMs, TimeUnit.MILLISECONDS);
         }
 
         @Override
         public void cancelAllActions() {
-            mExecutor.shutdownNow();
+            ScheduledExecutorService executor;
+            synchronized (mLock) {
+                executor = mExecutor;
+                mExecutor = null;
+            }
+            if (executor != null) {
+                executor.shutdownNow();
+            }
         }
     }
 }
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
index 0db0457..346c730 100644
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ b/service/src/com/android/car/vms/VmsClientManager.java
@@ -17,14 +17,11 @@
 package com.android.car.vms;
 
 import android.car.Car;
-import android.car.userlib.CarUserManagerHelper;
 import android.car.vms.IVmsPublisherClient;
 import android.car.vms.IVmsSubscriberClient;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
@@ -52,7 +49,6 @@
 
 import java.io.PrintWriter;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.function.IntSupplier;
@@ -75,7 +71,6 @@
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
     private final CarUserService mUserService;
-    private final CarUserManagerHelper mUserManagerHelper;
     private final CarStatsService mStatsService;
     private final Handler mHandler;
     private final IntSupplier mGetCallingUid;
@@ -101,7 +96,7 @@
     private int mCurrentUser;
 
     @GuardedBy("mLock")
-    private final Map<IBinder, SubscriberConnection> mSubscribers = new HashMap<>();
+    private final Map<IBinder, SubscriberConnection> mSubscribers = new ArrayMap<>();
 
     @VisibleForTesting
     final Runnable mSystemUserUnlockedListener = () -> {
@@ -112,22 +107,25 @@
     };
 
     @VisibleForTesting
-    final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() {
+    public final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DBG) Log.d(TAG, "Received " + intent);
+        public void onSwitchUser(int userId) {
             synchronized (mLock) {
-                int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
-                if (mCurrentUser != currentUserId) {
+                if (mCurrentUser != userId) {
+                    mCurrentUser = userId;
                     terminate(mCurrentUserClients);
                     terminate(mSubscribers.values().stream()
-                            .filter(subscriber -> subscriber.mUserId != currentUserId)
+                            .filter(subscriber -> subscriber.mUserId != mCurrentUser)
                             .filter(subscriber -> subscriber.mUserId != UserHandle.USER_SYSTEM));
                 }
-                mCurrentUser = currentUserId;
+            }
+            bindToUserClients();
+        }
 
-                if (mUserManager.isUserUnlocked(mCurrentUser)) {
-                    bindToSystemClients();
+        @Override
+        public void onUserLockChanged(int userId, boolean unlocked) {
+            synchronized (mLock) {
+                if (mCurrentUser == userId && unlocked) {
                     bindToUserClients();
                 }
             }
@@ -140,29 +138,26 @@
      * @param context           Context to use for registering receivers and binding services.
      * @param statsService      Service for logging client metrics.
      * @param userService       User service for registering system unlock listener.
-     * @param userManagerHelper User manager for querying current user state.
      * @param brokerService     Service managing the VMS publisher/subscriber state.
      * @param halService        Service providing the HAL client interface
      */
     public VmsClientManager(Context context, CarStatsService statsService,
-            CarUserService userService, CarUserManagerHelper userManagerHelper,
-            VmsBrokerService brokerService, VmsHalService halService) {
-        this(context, statsService, userService, userManagerHelper, brokerService, halService,
+            CarUserService userService, VmsBrokerService brokerService,
+            VmsHalService halService) {
+        this(context, statsService, userService, brokerService, halService,
                 new Handler(Looper.getMainLooper()), Binder::getCallingUid);
     }
 
     @VisibleForTesting
     VmsClientManager(Context context, CarStatsService statsService,
-            CarUserService userService, CarUserManagerHelper userManagerHelper,
-            VmsBrokerService brokerService, VmsHalService halService,
-            Handler handler, IntSupplier getCallingUid) {
+            CarUserService userService, VmsBrokerService brokerService,
+            VmsHalService halService, Handler handler, IntSupplier getCallingUid) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mStatsService = statsService;
         mUserService = userService;
-        mUserManagerHelper = userManagerHelper;
-        mCurrentUser = mUserManagerHelper.getCurrentForegroundUserId();
+        mCurrentUser = UserHandle.USER_NULL;
         mBrokerService = brokerService;
         mHandler = handler;
         mGetCallingUid = getCallingUid;
@@ -186,17 +181,12 @@
     @Override
     public void init() {
         mUserService.runOnUser0Unlock(mSystemUserUnlockedListener);
-
-        IntentFilter userSwitchFilter = new IntentFilter();
-        userSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        userSwitchFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mContext.registerReceiverAsUser(mUserSwitchReceiver, UserHandle.ALL, userSwitchFilter, null,
-                null);
+        mUserService.addUserCallback(mUserCallback);
     }
 
     @Override
     public void release() {
-        mContext.unregisterReceiver(mUserSwitchReceiver);
+        mUserService.removeUserCallback(mUserCallback);
         synchronized (mLock) {
             if (mHalClient != null) {
                 mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
@@ -365,15 +355,24 @@
     }
 
     private void bindToUserClients() {
+        bindToSystemClients(); // Bind system clients on user switch, if they are not already bound.
         synchronized (mLock) {
+            if (mCurrentUser == UserHandle.USER_NULL) {
+                Log.e(TAG, "Unknown user in foreground.");
+                return;
+            }
             // To avoid the risk of double-binding, clients running as the system user must only
             // ever be bound in bindToSystemClients().
-            // In a headless multi-user system, the system user will never be in the foreground.
             if (mCurrentUser == UserHandle.USER_SYSTEM) {
                 Log.e(TAG, "System user in foreground. Userspace clients will not be bound.");
                 return;
             }
 
+            if (!mUserManager.isUserUnlocked(mCurrentUser)) {
+                Log.i(TAG, "Waiting for foreground user to be unlocked.");
+                return;
+            }
+
             String[] clientNames = mContext.getResources().getStringArray(
                     R.array.vmsPublisherUserClients);
             Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)");
diff --git a/tests/BugReportApp/res/values-fr-rCA/strings.xml b/tests/BugReportApp/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..904bd00
--- /dev/null
+++ b/tests/BugReportApp/res/values-fr-rCA/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+     Copyright (C) 2019 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2596316479611335185">"Rapport de bogue"</string>
+    <string name="bugreport_info_quit" msgid="5590138890181142473">"Fermer"</string>
+    <string name="bugreport_info_start" msgid="667324824650830832">"Lancer le rapport de bogue"</string>
+    <string name="bugreport_info_status" msgid="7211044508792815451">"État :"</string>
+    <string name="bugreport_dialog_submit" msgid="2789636252713280633">"Envoyer"</string>
+    <string name="bugreport_dialog_cancel" msgid="4741928791364757040">"Annuler"</string>
+    <string name="bugreport_dialog_show_bugreports" msgid="6964385141627170297">"Afficher les rapports de bogue"</string>
+    <string name="bugreport_dialog_close" msgid="289925437277364266">"Fermer"</string>
+    <string name="bugreport_dialog_title" msgid="3315160684205929910">"Parler et décrire le problème"</string>
+    <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"Enregistrement terminé"</string>
+    <string name="bugreport_dialog_in_progress_title" msgid="3782308141532622394">"Un rapport de bogue est déjà en cours de récupération"</string>
+    <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"Un rapport de bogue a été récupéré"</string>
+    <string name="bugreport_move_button_text" msgid="1245698439228323880">"Déplacer vers USB"</string>
+    <string name="bugreport_upload_button_text" msgid="2322576150507025845">"Téléverser sur Google"</string>
+    <string name="toast_permissions_denied" msgid="7054832711916992770">"Veuillez accorder les autorisations"</string>
+    <string name="toast_bug_report_in_progress" msgid="8319601113129121579">"Rapport de bogue déjà en cours de récupération"</string>
+    <string name="toast_timed_out" msgid="4410178570031498895">"Délai dépassé, annulation de l\'opération en cours…"</string>
+    <string name="toast_status_failed" msgid="6365384202315043395">"Échec du rapport de bogue"</string>
+    <string name="toast_status_finished" msgid="7473653838545631945">"Rapport de bogue terminé"</string>
+    <string name="toast_status_pending_upload" msgid="7893505453527958690">"Le rapport de bogue est prêt à être téléversé"</string>
+    <string name="toast_status_screencap_failed" msgid="2187083897594745149">"Échec de la capture d\'écran"</string>
+    <string name="toast_status_dump_state_failed" msgid="8072469535227541761">"Échec de l\'état de la capture"</string>
+    <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"Le rapport de bogue est en cours de création"</string>
+    <string name="notification_bugreport_finished_title" msgid="3970195939909624320">"Récupération du rapport de bogue en cours…"</string>
+    <string name="notification_bugreport_manual_upload_finished_text" msgid="7091975159082077240">"Veuillez le téléverser lorsque le véhicule sera stationné"</string>
+    <string name="notification_bugreport_auto_upload_finished_text" msgid="2598986330417740616">"Le rapport sera automatiquement téléversé"</string>
+    <string name="notification_bugreport_channel_name" msgid="776902295433824255">"Chaîne d\'état du rapport de bogue"</string>
+</resources>
diff --git a/tests/CarDeveloperOptions/res/values-bs/strings.xml b/tests/CarDeveloperOptions/res/values-bs/strings.xml
index d8d8503..083faf0 100644
--- a/tests/CarDeveloperOptions/res/values-bs/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-bs/strings.xml
@@ -2991,7 +2991,7 @@
     <string name="enhanced_4g_lte_mode_title" msgid="1624079276378568594">"VoLTE"</string>
     <string name="enhanced_4g_lte_mode_title_advanced_calling" msgid="5155507161065290507">"Napredno pozivanje"</string>
     <string name="enhanced_4g_lte_mode_title_4g_calling" msgid="1262729135500839141">"Pozivanje putem 4G mreže"</string>
-    <string name="enhanced_4g_lte_mode_summary" msgid="4515503153340557170">"Koristi LTE usluge za poboljšanje glasovne i drugih komunikacija (preporučeno)"</string>
+    <string name="enhanced_4g_lte_mode_summary" msgid="4515503153340557170">"Koristite LTE usluge za poboljšanje glasovne i drugih komunikacija (preporučeno)"</string>
     <string name="enhanced_4g_lte_mode_summary_4g_calling" msgid="1006226172299077404">"Koristi usluge 4G mreže za poboljšanje glasovne i drugih komunikacija (preporučeno)"</string>
     <string name="preferred_network_type_title" msgid="1980819233332592332">"Tip preferirane mreže"</string>
     <string name="preferred_network_type_summary" msgid="8828375904939960006">"LTE (preporučeno)"</string>
@@ -4494,14 +4494,14 @@
     <string name="mobile_network_erase_sim_error_dialog_title" msgid="9026625253242102706">"Nije moguće izbrisati SIM"</string>
     <string name="mobile_network_erase_sim_error_dialog_body" msgid="5955463559366034787">"Nije moguće izbrisati ovaj SIM zbog greške.\n\nPonovo pokrenite uređaj i pokušajte ponovo."</string>
     <string name="preferred_network_mode_title" msgid="8324526359482124770">"Vrsta preferirane mreže"</string>
-    <string name="preferred_network_mode_summary" msgid="388957154320426335">"Promijeni način rada mreže"</string>
+    <string name="preferred_network_mode_summary" msgid="388957154320426335">"Promijenite način rada mreže"</string>
     <string name="preferred_network_mode_dialogtitle" msgid="5448698073828567428">"Vrsta preferirane mreže"</string>
     <string name="carrier_settings_euicc" msgid="7723199738771996732">"Operater"</string>
     <string name="carrier_settings_version" msgid="2657511289029828425">"Verzija postavki"</string>
     <string name="call_category" msgid="3418535202893644015">"Pozivanje"</string>
     <string name="video_calling_settings_title" msgid="8011841542502156112">"Operater video pozivanja"</string>
     <string name="cdma_system_select_title" msgid="5620679296177526467">"Odabir sistema"</string>
-    <string name="cdma_system_select_summary" msgid="6749131988334321244">"Promijeni način rada CDMA rominga"</string>
+    <string name="cdma_system_select_summary" msgid="6749131988334321244">"Promijenite način rada CDMA rominga"</string>
     <string name="cdma_system_select_dialogtitle" msgid="7489000866289285390">"Odabir sistema"</string>
     <string name="network_operator_category" msgid="9136988058829550510">"Mreža"</string>
     <string name="network_select_title" msgid="9176906726897071251">"Mreža"</string>
diff --git a/tests/CarDeveloperOptions/res/values-es/strings.xml b/tests/CarDeveloperOptions/res/values-es/strings.xml
index e71fba8..e4d8025 100644
--- a/tests/CarDeveloperOptions/res/values-es/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-es/strings.xml
@@ -2590,7 +2590,7 @@
     <string name="sync_failed" msgid="3142663443336915051">"Error de sincronización"</string>
     <string name="sync_active" msgid="1112604707180806364">"Sincronización activa"</string>
     <string name="account_sync_settings_title" msgid="3344538161552327748">"Sincronización"</string>
-    <string name="sync_is_failing" msgid="8284618104132302644">"En este momento hay incidencias con la sincronización. Se restablecerá en breve."</string>
+    <string name="sync_is_failing" msgid="8284618104132302644">"En este momento hay problemas con la sincronización. Se restablecerá en breve."</string>
     <string name="add_account_label" msgid="4461298847239641874">"Añadir cuenta"</string>
     <string name="managed_profile_not_available_label" msgid="8784246681719821917">"El perfil de trabajo aún no está disponible"</string>
     <string name="work_mode_label" msgid="6845849194740195757">"Perfil de trabajo"</string>
@@ -4047,7 +4047,7 @@
     <string name="display_cutout_emulation_keywords" msgid="6795671536772871439">"recorte de la pantalla, corte"</string>
     <string name="overlay_option_device_default" msgid="165508753381657697">"Opción predeterminada del dispositivo"</string>
     <string name="overlay_toast_failed_to_apply" msgid="5692251825129250040">"No se ha podido crear la superposición"</string>
-    <string name="special_access" msgid="1453926335914696206">"Acceso especial de apps"</string>
+    <string name="special_access" msgid="1453926335914696206">"Acceso especial de aplicaciones"</string>
     <plurals name="special_access_summary" formatted="false" msgid="5182092345063909346">
       <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> aplicaciones pueden utilizar datos sin restricción</item>
       <item quantity="one">1 aplicación puede utilizar datos sin restricción</item>
diff --git a/tests/CarDeveloperOptions/res/values-fi/strings.xml b/tests/CarDeveloperOptions/res/values-fi/strings.xml
index fc6363f..eadaca7 100644
--- a/tests/CarDeveloperOptions/res/values-fi/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fi/strings.xml
@@ -942,7 +942,7 @@
     <string name="wifi_dpp_scan_open_network_qr_code_with_another_device" msgid="572011882416511818">"Yhdistä <xliff:g id="SSID">%1$s</xliff:g> skannaamalla tämä QR-koodi"</string>
     <string name="wifi_dpp_could_not_detect_valid_qr_code" msgid="27667719861826438">"QR-koodin luku epäonnistui. Keskitä koodi ja yritä uudelleen."</string>
     <string name="wifi_dpp_failure_authentication_or_configuration" msgid="9142051662156233679">"Yritä uudelleen. Jos ongelma ei ratkea, ota yhteyttä laitteen valmistajaan"</string>
-    <string name="wifi_dpp_failure_not_compatible" msgid="4320027179973678283">"Jokin meni pieleen"</string>
+    <string name="wifi_dpp_failure_not_compatible" msgid="4320027179973678283">"Jotain meni pieleen"</string>
     <string name="wifi_dpp_failure_timeout" msgid="5060065168142109420">"Varmista, että laite on liitetty virtalähteeseen, ladattu ja päällä"</string>
     <string name="wifi_dpp_failure_generic" msgid="7840142544736640189">"Varmista, että laite on liitetty virtalähteeseen, ladattu ja päällä. Jos ongelma ei ratkea, ota yhteyttä laitteen valmistajaan"</string>
     <string name="wifi_dpp_failure_not_supported" msgid="111779621766171626">"Tämä laite ei tue <xliff:g id="SSID">%1$s</xliff:g>:n lisäämistä"</string>
diff --git a/tests/CarDeveloperOptions/res/values-fr/strings.xml b/tests/CarDeveloperOptions/res/values-fr/strings.xml
index fe052fd..2d5a459 100644
--- a/tests/CarDeveloperOptions/res/values-fr/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-fr/strings.xml
@@ -3781,7 +3781,7 @@
     <string name="running_frequency" msgid="7545170806968474449">"Fréquence"</string>
     <string name="memory_maximum_usage" msgid="4734981118293469479">"Utilisation maximale"</string>
     <string name="no_data_usage" msgid="903383745620135746">"Aucune donnée utilisée"</string>
-    <string name="zen_access_warning_dialog_title" msgid="7704910289810337055">"Autoriser l\'accès au mode Ne pas déranger pour <xliff:g id="APP">%1$s</xliff:g> ?"</string>
+    <string name="zen_access_warning_dialog_title" msgid="7704910289810337055">"Autoriser \"<xliff:g id="APP">%1$s</xliff:g>\" à accéder au mode Ne pas déranger ?"</string>
     <string name="zen_access_warning_dialog_summary" msgid="2717755746850874577">"L\'application disposera d\'une autorisation d\'activation ou de désactivation du mode Ne pas déranger et de modification des paramètres associés."</string>
     <string name="zen_access_disabled_package_warning" msgid="7086237569177576966">"Doit rester activé, car l\'accès aux notifications est activé"</string>
     <string name="zen_access_revoke_warning_dialog_title" msgid="6850994585577513299">"Révoquer l\'accès au mode Ne pas déranger pour <xliff:g id="APP">%1$s</xliff:g> ?"</string>
diff --git a/tests/CarDeveloperOptions/res/values-it/strings.xml b/tests/CarDeveloperOptions/res/values-it/strings.xml
index 22a0378..a43f6ad 100644
--- a/tests/CarDeveloperOptions/res/values-it/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-it/strings.xml
@@ -3820,7 +3820,7 @@
     <string name="write_settings_description" msgid="2536706293042882500">"Questa autorizzazione consente a un\'app di modificare le impostazioni di sistema."</string>
     <string name="write_settings_on" msgid="7328986337962635118">"Sì"</string>
     <string name="write_settings_off" msgid="5708257434958406202">"No"</string>
-    <string name="external_source_switch_title" msgid="5947220058496373178">"Consenti da questa fonte"</string>
+    <string name="external_source_switch_title" msgid="5947220058496373178">"Consenti da questa origine"</string>
     <string name="camera_gesture_title" msgid="899403310746415135">"Doppia rotazione per fotocamera"</string>
     <string name="camera_gesture_desc" msgid="7751841175916789527">"Apri l\'app Fotocamera ruotando due volte il polso"</string>
     <string name="camera_double_tap_power_gesture_title" msgid="8874747801078147525">"Premi due volte tasto di acc. per fotoc."</string>
diff --git a/tests/CarDeveloperOptions/res/values-nl/strings.xml b/tests/CarDeveloperOptions/res/values-nl/strings.xml
index e009d08..f1c2d86 100644
--- a/tests/CarDeveloperOptions/res/values-nl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-nl/strings.xml
@@ -2658,8 +2658,8 @@
     <string name="data_usage_menu_split_4g" msgid="2264683155484246409">"4G-gebruik apart weergeven"</string>
     <string name="data_usage_menu_show_wifi" msgid="8444311679299008486">"Wifi weergeven"</string>
     <string name="data_usage_menu_hide_wifi" msgid="1808008314306824090">"Wifi verbergen"</string>
-    <string name="data_usage_menu_show_ethernet" msgid="2130574690318410238">"Ethernet-gebruik weergeven"</string>
-    <string name="data_usage_menu_hide_ethernet" msgid="1191233197312414533">"Ethernet-gebruik verbergen"</string>
+    <string name="data_usage_menu_show_ethernet" msgid="2130574690318410238">"Ethernetgebruik weergeven"</string>
+    <string name="data_usage_menu_hide_ethernet" msgid="1191233197312414533">"Ethernetgebruik verbergen"</string>
     <string name="data_usage_menu_metered" msgid="3087525150259956831">"Netwerkbeperkingen"</string>
     <string name="data_usage_menu_auto_sync" msgid="3350154877737572146">"Gegevens automatisch synchroniseren"</string>
     <string name="data_usage_menu_sim_cards" msgid="8508154611676507088">"Simkaarten"</string>
@@ -3931,12 +3931,12 @@
     <string name="cellular_data_usage" msgid="1236562234207782386">"Gebruik van mobiele data"</string>
     <string name="app_cellular_data_usage" msgid="8499761516172121957">"Datagebruik van app"</string>
     <string name="wifi_data_usage" msgid="275569900562265895">"Wifi-datagebruik"</string>
-    <string name="ethernet_data_usage" msgid="747614925362556718">"Ethernet-datagebruik"</string>
+    <string name="ethernet_data_usage" msgid="747614925362556718">"Ethernetdatagebruik"</string>
     <string name="wifi" msgid="1586738489862966138">"Wifi"</string>
     <string name="ethernet" msgid="2365753635113154667">"Ethernet"</string>
     <string name="cell_data_template" msgid="5473177306229738078">"<xliff:g id="AMOUNT">^1</xliff:g> mobiele data"</string>
     <string name="wifi_data_template" msgid="3146090439147042068">"<xliff:g id="AMOUNT">^1</xliff:g> wifi-data"</string>
-    <string name="ethernet_data_template" msgid="6414118030827090119">"<xliff:g id="AMOUNT">^1</xliff:g> Ethernet-data"</string>
+    <string name="ethernet_data_template" msgid="6414118030827090119">"<xliff:g id="AMOUNT">^1</xliff:g> ethernetdata"</string>
     <string name="billing_cycle" msgid="5740717948341713190">"Datawaarschuwing en -limiet"</string>
     <string name="app_usage_cycle" msgid="213483325132959663">"Gebruikscyclus app-data"</string>
     <string name="cell_data_warning" msgid="8902740337286652689">"<xliff:g id="ID_1">^1</xliff:g> datawaarschuwing"</string>
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 5989000..69eba19 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -56,6 +56,7 @@
 import com.android.car.vehiclehal.test.MockedVehicleHal.StaticPropertyHandler;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
+import com.android.car.vms.VmsClientManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -185,6 +186,10 @@
         return (CarPackageManagerService) mCarImpl.getCarService(Car.PACKAGE_SERVICE);
     }
 
+    public VmsClientManager getVmsClientManager() {
+        return (VmsClientManager) mCarImpl.getCarInternalService(ICarImpl.INTERNAL_VMS_MANAGER);
+    }
+
     protected Context getCarServiceContext() {
         return getContext();
     }
diff --git a/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java b/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java
index 450b9e9..ccc2e6c 100644
--- a/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedVmsTestBase.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.app.ActivityManager;
 import android.car.Car;
 import android.car.VehicleAreaType;
 import android.car.vms.VmsAvailableLayers;
@@ -25,7 +26,6 @@
 import android.car.vms.VmsPublisherClientService;
 import android.car.vms.VmsSubscriberManager;
 import android.car.vms.VmsSubscriptionState;
-import android.content.Intent;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
@@ -34,7 +34,6 @@
 import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
 import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
 import android.hardware.automotive.vehicle.V2_0.VmsStartSessionMessageIntegerValuesIndex;
-import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
 
@@ -83,8 +82,7 @@
     @Before
     public void setUpVms() throws Exception {
         // Trigger VmsClientManager to bind to the MockPublisherClient
-        getContext().sendBroadcastAsUser(new Intent(Intent.ACTION_USER_UNLOCKED), UserHandle.ALL);
-
+        getVmsClientManager().mUserCallback.onSwitchUser(ActivityManager.getCurrentUser());
         mVmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
                 Car.VMS_SUBSCRIBER_SERVICE);
         mSubscriberClient = new MockSubscriberClient();
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java
index 0e3adde..0bb9d4f 100644
--- a/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java
+++ b/tests/carservice_test/src/com/android/car/VmsPublisherClientPermissionTest.java
@@ -21,10 +21,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.app.ActivityManager;
 import android.car.vms.VmsPublisherClientService;
 import android.car.vms.VmsSubscriptionState;
-import android.content.Intent;
-import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -98,7 +97,7 @@
 
     @Before
     public void triggerClientBinding() {
-        getContext().sendBroadcastAsUser(new Intent(Intent.ACTION_USER_UNLOCKED), UserHandle.ALL);
+        getVmsClientManager().mUserCallback.onSwitchUser(ActivityManager.getCurrentUser());
     }
 
     @Test
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
index 68769c8..c7d6489 100644
--- a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
@@ -38,7 +37,6 @@
 import static org.mockito.Mockito.when;
 
 import android.car.Car;
-import android.car.userlib.CarUserManagerHelper;
 import android.car.vms.IVmsPublisherClient;
 import android.car.vms.IVmsPublisherService;
 import android.car.vms.IVmsSubscriberClient;
@@ -48,7 +46,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -95,8 +92,6 @@
     private static final String USER_CLIENT_NAME =
             "com.google.android.apps.vms.test/com.google.android.apps.vms.test.VmsUserClient U=10";
     private static final int USER_ID_U11 = 11;
-    private static final String USER_CLIENT_NAME_U11 =
-            "com.google.android.apps.vms.test/com.google.android.apps.vms.test.VmsUserClient U=11";
 
     private static final String TEST_PACKAGE = "test.package1";
     private static final String HAL_CLIENT_NAME = "HalClient";
@@ -123,8 +118,6 @@
     private UserManager mUserManager;
     @Mock
     private CarUserService mUserService;
-    @Mock
-    private CarUserManagerHelper mUserManagerHelper;
 
     @Mock
     private VmsBrokerService mBrokerService;
@@ -207,17 +200,15 @@
                 new String[]{ USER_CLIENT });
 
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
-        when(mUserManagerHelper.getCurrentForegroundUserId())
-                .thenAnswer(invocation -> mForegroundUserId);
-
-        mForegroundUserId = USER_ID;
-        mCallingAppUid = UserHandle.getUid(USER_ID, 0);
 
         mClientManager = new VmsClientManager(mContext, mStatsService, mUserService,
-                mUserManagerHelper, mBrokerService, mHal, mHandler, () -> mCallingAppUid);
+                mBrokerService, mHal, mHandler, () -> mCallingAppUid);
         verify(mHal).setClientManager(mClientManager);
         mClientManager.setPublisherService(mPublisherService);
 
+        notifyUserSwitched(USER_ID, false);
+        mCallingAppUid = UserHandle.getUid(USER_ID, 0);
+
         when(mSubscriberClient1.asBinder()).thenReturn(mSubscriberBinder1);
         when(mSubscriberClient2.asBinder()).thenReturn(mSubscriberBinder2);
 
@@ -225,7 +216,7 @@
     }
 
     @After
-    public void tearDown() throws Exception {
+    public void tearDown() {
         verify(mContext, atLeast(0)).getSystemService(eq(Context.USER_SERVICE));
         verify(mContext, atLeast(0)).getResources();
         verify(mContext, atLeast(0)).getPackageManager();
@@ -239,15 +230,8 @@
 
         // Verify registration of system user unlock listener
         verify(mUserService).runOnUser0Unlock(mClientManager.mSystemUserUnlockedListener);
-
-        // Verify registration of user switch receiver
-        ArgumentCaptor<IntentFilter> userFilterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
-        verify(mContext).registerReceiverAsUser(eq(mClientManager.mUserSwitchReceiver),
-                eq(UserHandle.ALL), userFilterCaptor.capture(), isNull(), isNull());
-        IntentFilter userEventFilter = userFilterCaptor.getValue();
-        assertEquals(2, userEventFilter.countActions());
-        assertTrue(userEventFilter.hasAction(Intent.ACTION_USER_SWITCHED));
-        assertTrue(userEventFilter.hasAction(Intent.ACTION_USER_UNLOCKED));
+        // Verify user callback is added
+        verify(mUserService).addUserCallback(eq(mClientManager.mUserCallback));
     }
 
     @Test
@@ -255,7 +239,7 @@
         mClientManager.release();
 
         // Verify user switch receiver is unregistered
-        verify(mContext).unregisterReceiver(mClientManager.mUserSwitchReceiver);
+        verify(mUserService).removeUserCallback(mClientManager.mUserCallback);
     }
 
     @Test
@@ -328,6 +312,14 @@
     }
 
     @Test
+    public void testUserUnlocked_OtherUserUnlocked() {
+        notifyUserUnlocked(USER_ID_U11, true);
+
+        // Process will not be bound
+        verifyUserBind(0);
+    }
+
+    @Test
     public void testUserUnlocked_ClientNotFound() throws Exception {
         when(mPackageManager.getServiceInfo(eq(USER_CLIENT_COMPONENT), anyInt()))
                 .thenThrow(new PackageManager.NameNotFoundException());
@@ -761,6 +753,7 @@
         resetContext();
         reset(mPublisherService);
 
+        notifyUserSwitched(USER_ID_U11, false);
         notifyUserUnlocked(USER_ID_U11, true);
 
         verify(mContext).unbindService(connection);
@@ -780,6 +773,7 @@
         resetContext();
         reset(mPublisherService);
 
+        notifyUserSwitched(USER_ID_U11, false);
         notifyUserUnlocked(UserHandle.USER_SYSTEM, true);
 
         verify(mContext).unbindService(connection);
@@ -796,6 +790,7 @@
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
+        notifyUserSwitched(USER_ID_U11, false);
         notifyUserUnlocked(USER_ID_U11, true);
 
         verify(mContext).unbindService(connection);
@@ -924,9 +919,7 @@
     public void testOnUserSwitch_RemoveSubscriber() {
         mClientManager.addSubscriber(mSubscriberClient1);
 
-        mForegroundUserId = USER_ID_U11;
-        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
-
+        notifyUserSwitched(USER_ID_U11, false);
         verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
         assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
         assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
@@ -937,8 +930,7 @@
     public void testOnUserSwitch_RemoveSubscriber_AddNewSubscriber() {
         mClientManager.addSubscriber(mSubscriberClient1);
 
-        mForegroundUserId = USER_ID_U11;
-        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+        notifyUserSwitched(USER_ID_U11, false);
         verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
 
         mCallingAppUid = UserHandle.getUid(USER_ID_U11, 0);
@@ -962,8 +954,7 @@
 
         mClientManager.addSubscriber(mSubscriberClient2);
 
-        mForegroundUserId = USER_ID_U11;
-        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+        notifyUserSwitched(USER_ID_U11, false);
 
         verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
         verify(mBrokerService, never()).removeDeadSubscriber(mSubscriberClient2);
@@ -979,8 +970,7 @@
         verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
         reset(mPublisherService);
 
-        mForegroundUserId = USER_ID_U11;
-        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+        notifyUserSwitched(USER_ID_U11, false);
 
         verify(mBrokerService, never()).removeDeadSubscriber(subscriberClient);
         assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
@@ -1045,23 +1035,14 @@
     }
 
     private void notifyUserSwitched(int foregroundUserId, boolean isForegroundUserUnlocked) {
-        notifyUserAction(foregroundUserId, isForegroundUserUnlocked, Intent.ACTION_USER_SWITCHED);
+        when(mUserManager.isUserUnlocked(foregroundUserId)).thenReturn(isForegroundUserUnlocked);
+        mForegroundUserId = foregroundUserId; // Member variable used by verifyUserBind()
+        mClientManager.mUserCallback.onSwitchUser(foregroundUserId);
     }
 
     private void notifyUserUnlocked(int foregroundUserId, boolean isForegroundUserUnlocked) {
-        notifyUserAction(foregroundUserId, isForegroundUserUnlocked, Intent.ACTION_USER_UNLOCKED);
-    }
-
-    // Sets the current foreground user + unlock state and dispatches the specified intent action
-    private void notifyUserAction(int foregroundUserId, boolean isForegroundUserUnlocked,
-            String action) {
-        mForegroundUserId = foregroundUserId; // Member variable used by verifyUserBind()
-        when(mUserManagerHelper.getCurrentForegroundUserId()).thenReturn(foregroundUserId);
-
-        reset(mUserManager);
         when(mUserManager.isUserUnlocked(foregroundUserId)).thenReturn(isForegroundUserUnlocked);
-
-        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent(action));
+        mClientManager.mUserCallback.onUserLockChanged(foregroundUserId, isForegroundUserUnlocked);
     }
 
     private void verifySystemBind(int times) {