Fix a crash when mediaSource is null am: 1b6f499ade
am: 94d146ee06

Change-Id: I8edd1a3906217831886d35fe31b2b16e14a0af72
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 3d4b2f8..1912803 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -56,6 +56,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -67,6 +68,13 @@
  *   Calling this API on a device with no such feature will lead to an exception.
  */
 public final class Car {
+
+    /**
+     * Binder service name of car service registered to service manager.
+     *
+     * @hide
+     */
+    public static final String CAR_SERVICE_BINDER_SERVICE_NAME = "car_service";
     /**
      * Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}.
      *
@@ -602,6 +610,9 @@
     private static final long CAR_SERVICE_BIND_RETRY_INTERVAL_MS = 500;
     private static final long CAR_SERVICE_BIND_MAX_RETRY = 20;
 
+    private static final long CAR_SERVICE_BINDER_POLLING_INTERVAL_MS = 50;
+    private static final long CAR_SERVICE_BINDER_POLLING_MAX_RETRY = 100;
+
     private final Context mContext;
     @GuardedBy("this")
     private ICar mService;
@@ -636,7 +647,9 @@
                 mService = ICar.Stub.asInterface(service);
                 mConnectionState = STATE_CONNECTED;
             }
-            mServiceConnectionListenerClient.onServiceConnected(name, service);
+            if (mServiceConnectionListenerClient != null) {
+                mServiceConnectionListenerClient.onServiceConnected(name, service);
+            }
         }
 
         public void onServiceDisconnected(ComponentName name) {
@@ -647,7 +660,9 @@
             }
             // unbind explicitly and set connectionState to STATE_DISCONNECTED here.
             disconnect();
-            mServiceConnectionListenerClient.onServiceDisconnected(name);
+            if (mServiceConnectionListenerClient != null) {
+                mServiceConnectionListenerClient.onServiceDisconnected(name);
+            }
         }
     };
 
@@ -723,11 +738,54 @@
      */
     @Nullable
     public static Car createCar(Context context, @Nullable Handler handler) {
-        IBinder service = ServiceManager.getService("car_service");
-        if (service == null) {
-            return null;
+        Car car = null;
+        IBinder service = null;
+        boolean started = false;
+        int retryCount = 0;
+        while (true) {
+            service = ServiceManager.getService(CAR_SERVICE_BINDER_SERVICE_NAME);
+            if (car == null) {
+                // service can be still null. The constructor is safe for null service.
+                car = new Car(context, ICar.Stub.asInterface(service), handler);
+            }
+            if (service != null) {
+                if (!started) {  // specialization for most common case.
+                    return car;
+                }
+                break;
+            }
+            if (!started) {
+                car.startCarService();
+                started = true;
+            }
+            retryCount++;
+            if (retryCount > CAR_SERVICE_BINDER_POLLING_MAX_RETRY) {
+                Log.e(CarLibLog.TAG_CAR, "cannot get car_service, waited for car service (ms):"
+                                + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS
+                                * CAR_SERVICE_BINDER_POLLING_MAX_RETRY,
+                        new RuntimeException());
+                return null;
+            }
+            try {
+                Thread.sleep(CAR_SERVICE_BINDER_POLLING_INTERVAL_MS);
+            } catch (InterruptedException e) {
+                Log.e(CarLibLog.TAG_CAR, "interrupted while waiting for car_service",
+                        new RuntimeException());
+                return null;
+            }
         }
-        return new Car(context, ICar.Stub.asInterface(service), handler);
+        // Can be accessed from mServiceConnectionListener in main thread.
+        synchronized (car) {
+            if (car.mService == null) {
+                car.mService = ICar.Stub.asInterface(service);
+                Log.w(CarLibLog.TAG_CAR,
+                        "waited for car_service (ms):"
+                                + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS * retryCount,
+                        new RuntimeException());
+            }
+            car.mConnectionState = STATE_CONNECTED;
+        }
+        return car;
     }
 
     private Car(Context context, ServiceConnection serviceConnectionListener,
@@ -737,6 +795,7 @@
         mMainThreadEventHandler = determineMainThreadEventHandler(mEventHandler);
 
         mService = null;
+        mConnectionState = STATE_DISCONNECTED;
         mOwnsService = true;
         mServiceConnectionListenerClient = serviceConnectionListener;
     }
@@ -746,14 +805,19 @@
      * Car constructor when ICar binder is already available.
      * @hide
      */
-    public Car(Context context, ICar service, @Nullable Handler handler) {
+    public Car(Context context, @Nullable ICar service, @Nullable Handler handler) {
         mContext = context;
         mEventHandler = determineEventHandler(handler);
         mMainThreadEventHandler = determineMainThreadEventHandler(mEventHandler);
 
         mService = service;
-        mOwnsService = false;
-        mConnectionState = STATE_CONNECTED;
+        if (service != null) {
+            mConnectionState = STATE_CONNECTED;
+            mOwnsService = false;
+        } else {
+            mConnectionState = STATE_DISCONNECTED;
+            mOwnsService = true;
+        }
         mServiceConnectionListenerClient = null;
     }
 
@@ -833,6 +897,12 @@
         }
     }
 
+    /** @hide */
+    @VisibleForTesting
+    public ServiceConnection getServiceConnectionListener() {
+        return mServiceConnectionListener;
+    }
+
     /**
      * Get car specific service as in {@link Context#getSystemService(String)}. Returned
      * {@link Object} should be type-casted to the desired service.
diff --git a/car-usb-handler/AndroidManifest.xml b/car-usb-handler/AndroidManifest.xml
index 848548f..caa93bd 100644
--- a/car-usb-handler/AndroidManifest.xml
+++ b/car-usb-handler/AndroidManifest.xml
@@ -26,7 +26,7 @@
                  android:directBootAware="true">
         <activity android:name=".UsbHostManagementActivity"
                   android:theme="@android:style/Theme.DeviceDefault.Dialog"
-                  android:launchMode="singleTop">
+                  android:launchMode="standard">
             <meta-data
                 android:name="distractionOptimized"
                 android:value="true" />
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 104d5d6..7c03328 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -53,9 +53,9 @@
     persist.bluetooth.enablenewavrcp=false \
     ro.carrier=unknown
 
-# Enable headless user 0
 PRODUCT_SYSTEM_DEFAULT_PROPERTIES += \
     ro.fw.mu.headless_system_user=true \
+    config.disable_systemtextclassifier=true
 
 # Overlay for Google network and fused location providers
 $(call inherit-product, device/sample/products/location_overlay.mk)
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 62ff41c..6b5ddac 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
@@ -58,6 +58,9 @@
     <!-- Automotive Bluetooth pairing option -->
     <bool name="enable_pbap_pce_profile">true</bool>
 
+    <!-- Whether the device supports quick settings and its associated APIs -->
+    <bool name="config_quickSettingsSupported">false</bool>
+
     <!-- Flag indicating that the entire notification header can be clicked to expand the
          notification. If false, then the expand icon has to be clicked in order for the expand
          to occur. -->
@@ -88,4 +91,6 @@
     <bool name="config_supportsSystemDecorsOnSecondaryDisplays">false</bool>
 
     <string name="config_dataUsageSummaryComponent">com.android.car.settings/com.android.car.settings.datausage.DataWarningAndLimitActivity</string>
+
+    <bool name="config_automotiveHideNavBarForKeyboard">true</bool>
 </resources>
diff --git a/car_product/overlay/packages/apps/CertInstaller/res/values/config.xml b/car_product/overlay/packages/apps/CertInstaller/res/values/config.xml
new file mode 100644
index 0000000..6f94ffb
--- /dev/null
+++ b/car_product/overlay/packages/apps/CertInstaller/res/values/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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>
+    <string name="config_system_install_component" translatable="false">com.android.car.settings/com.android.car.settings.security.CredentialStorageActivity</string>
+</resources>
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index 817aa5d..bc1d74c 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -19,6 +19,7 @@
 allow carservice_app {
     accessibility_service
     activity_service
+    activity_task_service
     audio_service
     audioserver_service
     autofill_service
@@ -49,6 +50,8 @@
 allow carservice_app system_car_data_file:dir create_dir_perms;
 allow carservice_app system_car_data_file:{ file lnk_file } create_file_perms;
 
+net_domain(carservice_app)
+
 allow carservice_app cgroup:file rw_file_perms;
 
 # For I/O stats tracker
diff --git a/car_product/sepolicy/private/platform_app.te b/car_product/sepolicy/private/platform_app.te
index 6f67594..65dd75d 100644
--- a/car_product/sepolicy/private/platform_app.te
+++ b/car_product/sepolicy/private/platform_app.te
@@ -1,2 +1,5 @@
-allow platform_app broadcastradio_service:service_manager find;
-allow platform_app carservice_service:service_manager find;
+allow platform_app {
+    broadcastradio_service
+    carservice_service
+    update_engine_service
+}:service_manager find;
diff --git a/car_product/sepolicy/public/file.te b/car_product/sepolicy/public/file.te
index 8555d29..11bf839 100644
--- a/car_product/sepolicy/public/file.te
+++ b/car_product/sepolicy/public/file.te
@@ -5,4 +5,4 @@
 type sysfs_fs_lifetime_write, sysfs_type, fs_type;
 
 # /data/system/car
-type system_car_data_file, file_type, data_file_type, core_data_file_type;
\ No newline at end of file
+type system_car_data_file, file_type, data_file_type, core_data_file_type;
diff --git a/car_product/sepolicy/test/hal_vehicle_default.te b/car_product/sepolicy/test/hal_vehicle_default.te
new file mode 100644
index 0000000..8b9f6ee
--- /dev/null
+++ b/car_product/sepolicy/test/hal_vehicle_default.te
@@ -0,0 +1,4 @@
+typeattribute hal_vehicle_default hal_automotive_socket_exemption;
+
+unix_socket_connect(hal_vehicle_default, fwmarkd, netd)
+net_domain(hal_vehicle_default)
diff --git a/service/res/values-da/strings.xml b/service/res/values-da/strings.xml
index 5513137..245b488 100644
--- a/service/res/values-da/strings.xml
+++ b/service/res/values-da/strings.xml
@@ -22,8 +22,8 @@
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Få adgang til bilens kameraer."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"få adgang til oplysninger om bilens energiforbrug"</string>
     <string name="car_permission_desc_energy" msgid="3392963810053235407">"Få adgang til oplysninger om bilens energiforbrug"</string>
-    <string name="car_permission_label_hvac" msgid="1499454192558727843">"få adgang til bilens vvac"</string>
-    <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Få adgang til bilens VVAC-system."</string>
+    <string name="car_permission_label_hvac" msgid="1499454192558727843">"få adgang til bilens ventilation"</string>
+    <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Få adgang til bilens ventilationssystem."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"få adgang til oplysninger om bilens kilometertal"</string>
     <string name="car_permission_desc_mileage" msgid="7179735693278681090">"Få adgang til oplysninger om bilens kilometertal."</string>
     <string name="car_permission_label_speed" msgid="1149027717860529745">"tjekke bilens hastighed"</string>
diff --git a/service/res/values-zh-rTW/strings.xml b/service/res/values-zh-rTW/strings.xml
index 1848811..e1504ca 100644
--- a/service/res/values-zh-rTW/strings.xml
+++ b/service/res/values-zh-rTW/strings.xml
@@ -73,8 +73,8 @@
     <string name="car_permission_desc_diag_read" msgid="1121426363040966178">"讀取車輛的診斷資料。"</string>
     <string name="car_permission_label_diag_clear" msgid="4783070510879698157">"清除診斷資料"</string>
     <string name="car_permission_desc_diag_clear" msgid="7453222114866042786">"清除車輛的診斷資料。"</string>
-    <string name="car_permission_label_vms_publisher" msgid="3049934078926106641">"VMS 發佈者"</string>
-    <string name="car_permission_desc_vms_publisher" msgid="5589489298597386828">"發佈 VMS 訊息"</string>
+    <string name="car_permission_label_vms_publisher" msgid="3049934078926106641">"VMS 發布者"</string>
+    <string name="car_permission_desc_vms_publisher" msgid="5589489298597386828">"發布 VMS 訊息"</string>
     <string name="car_permission_label_vms_subscriber" msgid="5648841182059222299">"VMS 訂閱者"</string>
     <string name="car_permission_desc_vms_subscriber" msgid="7551009457847673620">"訂閱 VMS 訊息"</string>
     <string name="car_permission_label_bind_vms_client" msgid="4889732900973280313">"VMS 用戶端服務"</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 95382da..95383dc 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -68,7 +68,7 @@
           The current implementations expects the following system packages/activities to be
           whitelisted. For general guidelines to design distraction optimized apps, please refer
           to Android Auto Driver Distraction Guidelines. -->
-    <string name="systemActivityWhitelist" translatable="false">com.android.systemui,com.google.android.permissioncontroller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity,android/com.android.internal.app.ResolverActivity,com.android.mtp/com.android.mtp.ReceiverActivity</string>
+    <string name="systemActivityWhitelist" translatable="false">com.android.systemui,com.google.android.permissioncontroller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity,com.android.permissioncontroller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity,android/com.android.internal.app.ResolverActivity,com.android.mtp/com.android.mtp.ReceiverActivity</string>
     <!--  Comma separated list of activities that will be blocked during restricted state.
           Format of each entry is either to specify package name to whitelist the whole package
           or use format of "packagename/activity_classname" for tagging each activities.-->
diff --git a/service/src/com/android/car/CarBluetoothUserService.java b/service/src/com/android/car/CarBluetoothUserService.java
index 60bc561..028e66f 100644
--- a/service/src/com/android/car/CarBluetoothUserService.java
+++ b/service/src/com/android/car/CarBluetoothUserService.java
@@ -51,18 +51,17 @@
     );
 
     // Profile Proxies Objects to pair with above list. Access to these proxy objects will all be
-    // guarded by this classes implicit monitor lock.
+    // guarded by the below mBluetoothProxyLock
     private BluetoothA2dpSink mBluetoothA2dpSink = null;
     private BluetoothHeadsetClient mBluetoothHeadsetClient = null;
     private BluetoothPbapClient mBluetoothPbapClient = null;
     private BluetoothMapClient mBluetoothMapClient = null;
     private BluetoothPan mBluetoothPan = null;
 
-    // Concurrency variables for waitForProxyConnections. Used so we can block with a timeout while
-    // setting up or closing down proxy connections.
-    private final ReentrantLock mBluetoothProxyStatusLock;
+    // Concurrency variables for waitForProxies. Used so we can best effort block with a timeout
+    // while waiting for services to be bound to the proxy objects.
+    private final ReentrantLock mBluetoothProxyLock;
     private final Condition mConditionAllProxiesConnected;
-    private final Condition mConditionAllProxiesDisconnected;
     private SparseBooleanArray mBluetoothProfileStatus;
     private int mConnectedProfiles;
     private static final int PROXY_OPERATION_TIMEOUT_MS = 8000;
@@ -80,99 +79,69 @@
         for (int profile : sProfilesToConnect) {
             mBluetoothProfileStatus.put(profile, false);
         }
-        mBluetoothProxyStatusLock = new ReentrantLock();
-        mConditionAllProxiesConnected = mBluetoothProxyStatusLock.newCondition();
-        mConditionAllProxiesDisconnected = mBluetoothProxyStatusLock.newCondition();
+        mBluetoothProxyLock = new ReentrantLock();
+        mConditionAllProxiesConnected = mBluetoothProxyLock.newCondition();
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        Preconditions.checkNotNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
     }
 
     /**
      * Setup connections to the profile proxy objects that talk to the Bluetooth profile services.
      *
-     * Connection requests are asynchronous in nature and return through the ProfileServiceListener
-     * below. Since callers expect that the proxies are initialized by the time we call this, we
-     * will block (with a timeout) until all proxies are connected.
+     * Proxy references are held by the Bluetooth Framework on our behalf. We will be notified each
+     * time the underlying service connects for each proxy we create. Notifications stop when we
+     * close the proxy. As such, each time this is called we clean up any existing proxies before
+     * creating new ones.
      */
     @Override
     public void setupBluetoothConnectionProxies() {
         logd("Initiate connections to profile proxies");
-        Preconditions.checkNotNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
-        mBluetoothProxyStatusLock.lock();
-        try {
 
-            // Connect all the profiles that are unconnected, keep count so we can wait below
-            for (int profile : sProfilesToConnect) {
-                if (mBluetoothProfileStatus.get(profile, false)) {
-                    logd(Utils.getProfileName(profile) + " is already connected");
-                    continue;
-                }
-                logd("Connecting " + Utils.getProfileName(profile));
-                mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(),
-                        mProfileListener, profile);
-            }
+        // Clear existing proxy objects
+        closeBluetoothConnectionProxies();
 
-            // Wait for all the profiles to connect with a generous timeout just in case
-            while (mConnectedProfiles != sProfilesToConnect.size()) {
-                if (!mConditionAllProxiesConnected.await(
-                        PROXY_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                    Log.e(TAG, "Timeout while waiting for all proxies to connect. Connected only "
-                            + mConnectedProfiles + "/" + sProfilesToConnect.size());
-                    break;
-                }
-            }
-        } catch (InterruptedException e) {
-            Log.w(TAG, "setupBluetoothConnectionProxies: interrupted", e);
-        } finally {
-            mBluetoothProxyStatusLock.unlock();
+        // Create proxy for each supported profile. Objects arrive later in the profile listener.
+        // Operations on the proxies expect them to be connected. Functions below should call
+        // waitForProxies() to best effort wait for them to be up if Bluetooth is enabled.
+        for (int profile : sProfilesToConnect) {
+            logd("Creating proxy for " + Utils.getProfileName(profile));
+            mBluetoothAdapter.getProfileProxy(mService.getApplicationContext(),
+                    mProfileListener, profile);
         }
     }
 
     /**
      * Close connections to the profile proxy objects
-     *
-     * Proxy disconnection requests are asynchronous in nature and return through the
-     * ProfileServiceListener below. This method will block (with a timeout) until all proxies have
-     * disconnected.
      */
     @Override
-    public synchronized void closeBluetoothConnectionProxies() {
-        logd("Tear down profile proxy connections");
-        Preconditions.checkNotNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
-        mBluetoothProxyStatusLock.lock();
+    public void closeBluetoothConnectionProxies() {
+        logd("Clean up profile proxy objects");
+        mBluetoothProxyLock.lock();
         try {
-            if (mBluetoothA2dpSink != null) {
-                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
-            }
-            if (mBluetoothHeadsetClient != null) {
-                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
-                        mBluetoothHeadsetClient);
-            }
-            if (mBluetoothPbapClient != null) {
-                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT,
-                        mBluetoothPbapClient);
-            }
-            if (mBluetoothMapClient != null) {
-                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT,
-                        mBluetoothMapClient);
-            }
-            if (mBluetoothPan != null) {
-                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan);
-            }
+            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
+            mBluetoothA2dpSink = null;
+            mBluetoothProfileStatus.put(BluetoothProfile.A2DP_SINK, false);
 
-            while (mConnectedProfiles != 0) {
-                if (!mConditionAllProxiesDisconnected.await(
-                        PROXY_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                    Log.e(TAG, "Timeout while waiting for all proxies to disconnect. There are "
-                            + mConnectedProfiles + "/" + sProfilesToConnect.size() + "still "
-                            + "connected");
-                    break;
-                }
-            }
+            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
+                    mBluetoothHeadsetClient);
+            mBluetoothHeadsetClient = null;
+            mBluetoothProfileStatus.put(BluetoothProfile.HEADSET_CLIENT, false);
 
-        } catch (InterruptedException e) {
-            Log.w(TAG, "closeBluetoothConnectionProxies: interrupted", e);
+            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient);
+            mBluetoothPbapClient = null;
+            mBluetoothProfileStatus.put(BluetoothProfile.PBAP_CLIENT, false);
+
+            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
+            mBluetoothMapClient = null;
+            mBluetoothProfileStatus.put(BluetoothProfile.MAP_CLIENT, false);
+
+            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan);
+            mBluetoothPan = null;
+            mBluetoothProfileStatus.put(BluetoothProfile.PAN, false);
+
+            mConnectedProfiles = 0;
         } finally {
-            mBluetoothProxyStatusLock.unlock();
+            mBluetoothProxyLock.unlock();
         }
     }
 
@@ -182,11 +151,12 @@
     private BluetoothProfile.ServiceListener mProfileListener =
             new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            logd("OnServiceConnected profile: " + Utils.getProfileName(profile));
+            logd("onServiceConnected profile: " + Utils.getProfileName(profile));
 
             // Grab the profile proxy object and update the status book keeping in one step so the
             // book keeping and proxy objects never disagree
-            synchronized (this) {
+            mBluetoothProxyLock.lock();
+            try {
                 switch (profile) {
                     case BluetoothProfile.A2DP_SINK:
                         mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
@@ -208,62 +178,35 @@
                         break;
                 }
 
-                mBluetoothProxyStatusLock.lock();
-                try {
-                    if (!mBluetoothProfileStatus.get(profile, false)) {
-                        mBluetoothProfileStatus.put(profile, true);
-                        mConnectedProfiles++;
-                        if (mConnectedProfiles == sProfilesToConnect.size()) {
-                            logd("All profiles have connected");
-                            mConditionAllProxiesConnected.signal();
-                        }
+                if (!mBluetoothProfileStatus.get(profile, false)) {
+                    mBluetoothProfileStatus.put(profile, true);
+                    mConnectedProfiles++;
+                    if (mConnectedProfiles == sProfilesToConnect.size()) {
+                        logd("All profiles have connected");
+                        mConditionAllProxiesConnected.signal();
                     }
-                } finally {
-                    mBluetoothProxyStatusLock.unlock();
+                } else {
+                    Log.w(TAG, "Received duplicate service connection event for: "
+                            + Utils.getProfileName(profile));
                 }
+            } finally {
+                mBluetoothProxyLock.unlock();
             }
         }
 
         public void onServiceDisconnected(int profile) {
             logd("onServiceDisconnected profile: " + Utils.getProfileName(profile));
-
-            // Null the profile proxy object and update the status book keeping in one step so the
-            // book keeping and proxy objects never disagree
-            synchronized (this) {
-                switch (profile) {
-                    case BluetoothProfile.A2DP_SINK:
-                        mBluetoothA2dpSink = null;
-                        break;
-                    case BluetoothProfile.HEADSET_CLIENT:
-                        mBluetoothHeadsetClient = null;
-                        break;
-                    case BluetoothProfile.PBAP_CLIENT:
-                        mBluetoothPbapClient = null;
-                        break;
-                    case BluetoothProfile.MAP_CLIENT:
-                        mBluetoothMapClient = null;
-                        break;
-                    case BluetoothProfile.PAN:
-                        mBluetoothPan = null;
-                        break;
-                    default:
-                        logd("Unhandled profile disconnected: " + Utils.getProfileName(profile));
-                        break;
+            mBluetoothProxyLock.lock();
+            try {
+                if (mBluetoothProfileStatus.get(profile, false)) {
+                    mBluetoothProfileStatus.put(profile, false);
+                    mConnectedProfiles--;
+                } else {
+                    Log.w(TAG, "Received duplicate service disconnection event for: "
+                            + Utils.getProfileName(profile));
                 }
-
-                mBluetoothProxyStatusLock.lock();
-                try {
-                    if (mBluetoothProfileStatus.get(profile, false)) {
-                        mBluetoothProfileStatus.put(profile, false);
-                        mConnectedProfiles--;
-                        if (mConnectedProfiles == 0) {
-                            logd("All profiles have disconnected");
-                            mConditionAllProxiesDisconnected.signal();
-                        }
-                    }
-                } finally {
-                    mBluetoothProxyStatusLock.unlock();
-                }
+            } finally {
+                mBluetoothProxyLock.unlock();
             }
         }
     };
@@ -271,25 +214,56 @@
     /**
      * Check if a proxy is available for the given profile to talk to the Profile's bluetooth
      * service.
+     *
      * @param profile - Bluetooth profile to check for
      * @return - true if proxy available, false if not.
      */
     @Override
     public boolean isBluetoothConnectionProxyAvailable(int profile) {
+        if (!mBluetoothAdapter.isEnabled()) return false;
         boolean proxyConnected = false;
-        mBluetoothProxyStatusLock.lock();
+        mBluetoothProxyLock.lock();
         try {
             proxyConnected = mBluetoothProfileStatus.get(profile, false);
         } finally {
-            mBluetoothProxyStatusLock.unlock();
-        }
-        if (!proxyConnected) {
-            setupBluetoothConnectionProxies();
-            return isBluetoothConnectionProxyAvailable(profile);
+            mBluetoothProxyLock.unlock();
         }
         return proxyConnected;
     }
 
+    /**
+     * Wait for the proxy objects to be up for all profiles, with a timeout.
+     *
+     * @param timeout Amount of time in milliseconds to wait for giving up on the wait operation
+     * @return True if the condition was satisfied within the timeout, False otherwise
+     */
+    private boolean waitForProxies(int timeout /* ms */) {
+        logd("waitForProxies()");
+        // If bluetooth isn't on then the operation waiting on proxies was never meant to actually
+        // work regardless if Bluetooth comes on within the timeout period or not. Return false.
+        if (!mBluetoothAdapter.isEnabled()) return false;
+        try {
+            while (mConnectedProfiles != sProfilesToConnect.size()) {
+                if (!mConditionAllProxiesConnected.await(
+                        timeout, TimeUnit.MILLISECONDS)) {
+                    Log.e(TAG, "Timeout while waiting for proxies, Connected: " + mConnectedProfiles
+                            + "/" + sProfilesToConnect.size());
+                    return false;
+                }
+            }
+        } catch (InterruptedException e) {
+            Log.w(TAG, "waitForProxies: interrupted", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Connect a given remote device on a specific Bluetooth profile
+     *
+     * @param profile BluetoothProfile.* based profile ID
+     * @param device The device you wish to connect
+     */
     @Override
     public boolean bluetoothConnectToProfile(int profile, BluetoothDevice device) {
         if (device == null) {
@@ -298,8 +272,10 @@
         }
         logd("Trying to connect to " + device.getName() + " (" + device.getAddress() + ") Profile: "
                 + Utils.getProfileName(profile));
-        synchronized (this) {
-            if (!isBluetoothConnectionProxyAvailable(profile)) {
+        mBluetoothProxyLock.lock();
+        try {
+            if (!isBluetoothConnectionProxyAvailable(profile)
+                    && !waitForProxies(PROXY_OPERATION_TIMEOUT_MS)) {
                 Log.e(TAG, "Cannot connect to Profile. Proxy Unavailable");
                 return false;
             }
@@ -318,10 +294,18 @@
                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
                     break;
             }
+        } finally {
+            mBluetoothProxyLock.unlock();
         }
         return false;
     }
 
+    /**
+     * Disonnect a given remote device from a specific Bluetooth profile
+     *
+     * @param profile BluetoothProfile.* based profile ID
+     * @param device The device you wish to disconnect
+     */
     @Override
     public boolean bluetoothDisconnectFromProfile(int profile, BluetoothDevice device) {
         if (device == null) {
@@ -330,8 +314,10 @@
         }
         logd("Trying to disconnect from " + device.getName() + " (" + device.getAddress()
                 + ") Profile: " + Utils.getProfileName(profile));
-        synchronized (this) {
-            if (!isBluetoothConnectionProxyAvailable(profile)) {
+        mBluetoothProxyLock.lock();
+        try {
+            if (!isBluetoothConnectionProxyAvailable(profile)
+                    && !waitForProxies(PROXY_OPERATION_TIMEOUT_MS)) {
                 Log.e(TAG, "Cannot disconnect from profile. Proxy Unavailable");
                 return false;
             }
@@ -350,12 +336,15 @@
                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
                     break;
             }
+        } finally {
+            mBluetoothProxyLock.unlock();
         }
         return false;
     }
 
     /**
      * Get the priority of the given Bluetooth profile for the given remote device
+     *
      * @param profile - Bluetooth profile
      * @param device - remote Bluetooth device
      */
@@ -367,8 +356,10 @@
             return BluetoothProfile.PRIORITY_UNDEFINED;
         }
         int priority;
-        synchronized (this) {
-            if (!isBluetoothConnectionProxyAvailable(profile)) {
+        mBluetoothProxyLock.lock();
+        try {
+            if (!isBluetoothConnectionProxyAvailable(profile)
+                    && !waitForProxies(PROXY_OPERATION_TIMEOUT_MS)) {
                 Log.e(TAG, "Cannot get " + Utils.getProfileName(profile)
                         + " profile priority. Proxy Unavailable");
                 return BluetoothProfile.PRIORITY_UNDEFINED;
@@ -391,6 +382,8 @@
                     priority = BluetoothProfile.PRIORITY_UNDEFINED;
                     break;
             }
+        } finally {
+            mBluetoothProxyLock.unlock();
         }
         logd(Utils.getProfileName(profile) + " priority for " + device.getName() + " ("
                 + device.getAddress() + ") = " + priority);
@@ -399,6 +392,7 @@
 
     /**
      * Set the priority of the given Bluetooth profile for the given remote device
+     *
      * @param profile - Bluetooth profile
      * @param device - remote Bluetooth device
      * @param priority - priority to set
@@ -412,8 +406,10 @@
         }
         logd("Setting " + Utils.getProfileName(profile) + " priority for " + device.getName() + " ("
                 + device.getAddress() + ") to " + priority);
-        synchronized (this) {
-            if (!isBluetoothConnectionProxyAvailable(profile)) {
+        mBluetoothProxyLock.lock();
+        try {
+            if (!isBluetoothConnectionProxyAvailable(profile)
+                    && !waitForProxies(PROXY_OPERATION_TIMEOUT_MS)) {
                 Log.e(TAG, "Cannot set " + Utils.getProfileName(profile)
                         + " profile priority. Proxy Unavailable");
                 return;
@@ -435,6 +431,8 @@
                     Log.w(TAG, "Unknown Profile: " + Utils.getProfileName(profile));
                     break;
             }
+        } finally {
+            mBluetoothProxyLock.unlock();
         }
     }
 
diff --git a/service/src/com/android/car/CarLocationService.java b/service/src/com/android/car/CarLocationService.java
index 3827c22..2f37e61 100644
--- a/service/src/com/android/car/CarLocationService.java
+++ b/service/src/com/android/car/CarLocationService.java
@@ -216,7 +216,17 @@
                 });
                 break;
             case CarPowerStateListener.SUSPEND_EXIT:
-                deleteCacheFile();
+                if (mCarDrivingStateService != null) {
+                    CarDrivingStateEvent event = mCarDrivingStateService.getCurrentDrivingState();
+                    if (event != null
+                            && event.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
+                        deleteCacheFile();
+                    } else {
+                        logd("Registering to receive driving state.");
+                        mCarDrivingStateService.registerDrivingStateChangeListener(
+                                mICarDrivingStateChangeEventListener);
+                    }
+                }
                 if (future != null) {
                     future.complete(null);
                 }
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index 8b5e9e1..bb49ff2 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -60,6 +60,7 @@
 import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -157,22 +158,23 @@
             mCurrentUxRestrictions.put(port, createUnrestrictedRestrictions());
         }
 
-        // subscribe to driving State
+        // Load the prod config, or if there is a staged one, promote that first only if the
+        // current driving state, as provided by the driving state service, is parked.
+        mCarUxRestrictionsConfigurations = convertToMap(loadConfig());
+
+        // subscribe to driving state changes
         mDrivingStateService.registerDrivingStateChangeListener(
                 mICarDrivingStateChangeEventListener);
         // subscribe to property service for speed
         mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED,
                 PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
 
-        // At this point the driving state is known, which determines whether it's safe
-        // to promote staged new config.
-        mCarUxRestrictionsConfigurations = convertToMap(loadConfig());
-
         initializeUxRestrictions();
     }
 
     @Override
     public List<CarUxRestrictionsConfiguration> getConfigs() {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
         return new ArrayList<>(mCarUxRestrictionsConfigurations.values());
     }
 
@@ -187,7 +189,8 @@
      * <li>hardcoded default config.
      * </ol>
      *
-     * This method attempts to promote staged config file. Doing which depends on driving state.
+     * This method attempts to promote staged config file, which requires getting the current
+     * driving state.
      */
     @VisibleForTesting
     synchronized List<CarUxRestrictionsConfiguration> loadConfig() {
@@ -237,6 +240,10 @@
         return null;
     }
 
+    /**
+     * Promotes the staged config to prod, by replacing the prod file. Only do this if the car is
+     * parked to avoid changing the restrictions during a drive.
+     */
     private void promoteStagedConfig() {
         Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath();
 
@@ -409,6 +416,8 @@
     @Override
     @Nullable
     public List<CarUxRestrictionsConfiguration> getStagedConfigs() {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
+
         File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
         if (stagedConfig.exists()) {
             logd("Attempting to read staged config");
@@ -433,6 +442,8 @@
     @Override
     public synchronized boolean setRestrictionMode(
             @CarUxRestrictionsManager.UxRestrictionMode int mode) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
+
         if (mRestrictionMode == mode) {
             return true;
         }
@@ -450,6 +461,8 @@
     @Override
     @CarUxRestrictionsManager.UxRestrictionMode
     public synchronized int getRestrictionMode() {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
+
         return mRestrictionMode;
     }
 
@@ -699,6 +712,11 @@
      */
     private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
             float speed) {
+        Preconditions.checkNotNull(mCarUxRestrictionsConfigurations,
+                "mCarUxRestrictionsConfigurations must be initialized");
+        Preconditions.checkNotNull(mCurrentUxRestrictions,
+                "mCurrentUxRestrictions must be initialized");
+
         if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
             Log.d(TAG, "Not dispatching UX Restriction due to setting");
             return;
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index aef43b9..5c170d9 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -150,11 +150,12 @@
                 mAppFocusService, mCarInputService);
         mSystemStateControllerService = new SystemStateControllerService(
                 serviceContext, mCarAudioService, this);
-        mVmsBrokerService = new VmsBrokerService(mContext.getPackageManager());
+        mVmsBrokerService = new VmsBrokerService();
         mVmsClientManager = new VmsClientManager(
-                serviceContext, mCarUserService, mUserManagerHelper, mHal.getVmsHal());
+                serviceContext, mVmsBrokerService, mCarUserService, mUserManagerHelper,
+                mHal.getVmsHal());
         mVmsSubscriberService = new VmsSubscriberService(
-                serviceContext, mVmsBrokerService, mHal.getVmsHal());
+                serviceContext, mVmsBrokerService, mVmsClientManager, mHal.getVmsHal());
         mVmsPublisherService = new VmsPublisherService(
                 serviceContext, mVmsBrokerService, mVmsClientManager);
         mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal());
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 8e3beeb..2f8a7c5 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -46,7 +46,7 @@
  * Binds to publishers and configures them to use this service.
  * Notifies publishers of subscription changes.
  */
-public class VmsPublisherService implements CarServiceBase, VmsClientManager.ConnectionListener {
+public class VmsPublisherService implements CarServiceBase {
     private static final boolean DBG = true;
     private static final String TAG = "VmsPublisherService";
 
@@ -65,8 +65,8 @@
             "Total packet failure size for layer %s from %s to %s: %d (bytes)\n";
 
     private final Context mContext;
-    private final VmsClientManager mClientManager;
     private final VmsBrokerService mBrokerService;
+    private final VmsClientManager mClientManager;
     private final Map<String, PublisherProxy> mPublisherProxies = Collections.synchronizedMap(
             new ArrayMap<>());
 
@@ -117,9 +117,9 @@
             VmsBrokerService brokerService,
             VmsClientManager clientManager) {
         mContext = context;
-        mClientManager = clientManager;
         mBrokerService = brokerService;
-        mClientManager.registerConnectionListener(this);
+        mClientManager = clientManager;
+        mClientManager.setPublisherService(this);
     }
 
     @Override
@@ -164,11 +164,15 @@
         }
     }
 
-    @Override
-    public void onClientConnected(String publisherName, IBinder binder) {
+    /**
+     * Called when a client connection is established or re-established.
+     *
+     * @param publisherName    String that uniquely identifies the service and user.
+     * @param publisherClient The client's communication channel.
+     */
+    public void onClientConnected(String publisherName, IVmsPublisherClient publisherClient) {
         if (DBG) Log.d(TAG, "onClientConnected: " + publisherName);
         IBinder publisherToken = new Binder();
-        IVmsPublisherClient publisherClient = IVmsPublisherClient.Stub.asInterface(binder);
 
         PublisherProxy publisherProxy = new PublisherProxy(publisherName, publisherToken,
                 publisherClient);
@@ -186,7 +190,11 @@
         }
     }
 
-    @Override
+    /**
+     * Called when a client connection is terminated.
+     *
+     * @param publisherName String that uniquely identifies the service and user.
+     */
     public void onClientDisconnected(String publisherName) {
         if (DBG) Log.d(TAG, "onClientDisconnected: " + publisherName);
         PublisherProxy proxy = mPublisherProxies.remove(publisherName);
@@ -278,7 +286,7 @@
                 try {
                     listener.onVmsMessageReceived(layer, payload);
                 } catch (RemoteException ex) {
-                    String subscriberName = mBrokerService.getPackageName(listener);
+                    String subscriberName = mClientManager.getPackageName(listener);
                     incrementPacketFailure(layer, mName, subscriberName, payloadLength);
                     Log.e(TAG, String.format("Unable to publish to listener: %s", subscriberName));
                 }
diff --git a/service/src/com/android/car/VmsPublishersInfo.java b/service/src/com/android/car/VmsPublishersInfo.java
index 4a75953..78ec9fb 100644
--- a/service/src/com/android/car/VmsPublishersInfo.java
+++ b/service/src/com/android/car/VmsPublishersInfo.java
@@ -16,13 +16,13 @@
 
 package com.android.car;
 
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
 
 public class VmsPublishersInfo {
     private static final String TAG = "VmsPublishersInfo";
@@ -31,9 +31,9 @@
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private final Map<InfoWrapper, Integer> mPublishersIds = new HashMap<>();
+    private final ArrayMap<InfoWrapper, Integer> mPublishersIds = new ArrayMap<>();
     @GuardedBy("mLock")
-    private final Map<Integer, byte[]> mPublishersInfo = new HashMap<>();
+    private final ArrayList<InfoWrapper> mPublishersInfo = new ArrayList<>();
 
     private static class InfoWrapper {
         private final byte[] mInfo;
@@ -43,7 +43,7 @@
         }
 
         public byte[] getInfo() {
-            return mInfo;
+            return mInfo.clone();
         }
 
         @Override
@@ -62,15 +62,24 @@
     }
 
     /**
-     * Returns the ID associated with the publisher info. When called for the first time for a
-     * publisher info will store the info and assign an ID
+     * Retrieves the publisher ID for the given publisher information. If the publisher information
+     * has not previously been seen, it will be assigned a new publisher ID.
+     *
+     * @param publisherInfo Publisher information to query or register.
+     * @return Publisher ID for the given publisher information.
      */
     public int getIdForInfo(byte[] publisherInfo) {
         Integer publisherId;
         InfoWrapper wrappedPublisherInfo = new InfoWrapper(publisherInfo);
         synchronized (mLock) {
-            maybeAddPublisherInfoLocked(wrappedPublisherInfo);
+            // Check if publisher is already registered
             publisherId = mPublishersIds.get(wrappedPublisherInfo);
+            if (publisherId == null) {
+                // Add the new publisher and assign it the next ID
+                mPublishersInfo.add(wrappedPublisherInfo);
+                publisherId = mPublishersInfo.size();
+                mPublishersIds.put(wrappedPublisherInfo, publisherId);
+            }
         }
         if (DBG) {
             Log.i(TAG, "Publisher ID is: " + publisherId);
@@ -78,22 +87,16 @@
         return publisherId;
     }
 
+    /**
+     * Returns the publisher info associated with the given publisher ID.
+     * @param publisherId Publisher ID to query.
+     * @return Publisher info associated with the ID, or an empty array if publisher ID is unknown.
+     */
     public byte[] getPublisherInfo(int publisherId) {
         synchronized (mLock) {
-            return mPublishersInfo.containsKey(publisherId)
-                    ? mPublishersInfo.get(publisherId).clone()
-                    : EMPTY_RESPONSE;
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void maybeAddPublisherInfoLocked(InfoWrapper wrappedPublisherInfo) {
-        if (!mPublishersIds.containsKey(wrappedPublisherInfo)) {
-            // Assign ID to the info
-            Integer publisherId = mPublishersIds.size();
-
-            mPublishersIds.put(wrappedPublisherInfo, publisherId);
-            mPublishersInfo.put(publisherId, wrappedPublisherInfo.getInfo());
+            return publisherId < 1 || publisherId > mPublishersInfo.size()
+                    ? EMPTY_RESPONSE
+                    : mPublishersInfo.get(publisherId - 1).getInfo();
         }
     }
 }
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
index aaadf4f..f3237b3 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -16,246 +16,82 @@
 
 package com.android.car;
 
-import android.car.Car;
 import android.car.vms.IVmsSubscriberClient;
 import android.car.vms.IVmsSubscriberService;
 import android.car.vms.VmsAvailableLayers;
 import android.car.vms.VmsLayer;
 import android.content.Context;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.car.hal.VmsHalService;
 import com.android.car.vms.VmsBrokerService;
-import com.android.internal.annotations.GuardedBy;
+import com.android.car.vms.VmsClientManager;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
- * + Receives HAL updates by implementing VmsHalService.VmsHalListener.
- * + Offers subscriber/publisher services by implementing IVmsService.Stub.
+ * Offers subscriber services by implementing IVmsSubscriberService.Stub.
  */
 public class VmsSubscriberService extends IVmsSubscriberService.Stub implements CarServiceBase,
         VmsBrokerService.SubscriberListener {
     private static final boolean DBG = true;
-    private static final String PERMISSION = Car.PERMISSION_VMS_SUBSCRIBER;
     private static final String TAG = "VmsSubscriberService";
 
     private final Context mContext;
     private final VmsBrokerService mBrokerService;
-
-    @GuardedBy("mSubscriberServiceLock")
-    private final VmsSubscribersManager mSubscribersManager = new VmsSubscribersManager();
-    private final Object mSubscriberServiceLock = new Object();
+    private final VmsClientManager mClientManager;
 
     /**
-     * Keeps track of subscribers of this service.
+     * Constructor for client manager.
+     *
+     * @param context           Context to use for registering receivers and binding services.
+     * @param brokerService     Service managing the VMS publisher/subscriber state.
+     * @param clientManager     Service for monitoring VMS subscriber clients.
+     * @param hal               Service providing the HAL client interface
      */
-    class VmsSubscribersManager {
-        /**
-         * Allows to modify mSubscriberMap and mListenerDeathRecipientMap as a single unit.
-         */
-        private final Object mListenerManagerLock = new Object();
-        @GuardedBy("mListenerManagerLock")
-        private final Map<IBinder, ListenerDeathRecipient> mListenerDeathRecipientMap =
-                new HashMap<>();
-        @GuardedBy("mListenerManagerLock")
-        private final Map<IBinder, IVmsSubscriberClient> mSubscriberMap = new HashMap<>();
-
-        class ListenerDeathRecipient implements IBinder.DeathRecipient {
-            private IBinder mSubscriberBinder;
-
-            ListenerDeathRecipient(IBinder subscriberBinder) {
-                mSubscriberBinder = subscriberBinder;
-            }
-
-            /**
-             * Listener died. Remove it from this service.
-             */
-            @Override
-            public void binderDied() {
-                if (DBG) {
-                    Log.d(TAG, "binderDied " + mSubscriberBinder);
-                }
-
-                // Get the Listener from the Binder
-                IVmsSubscriberClient subscriber = mSubscriberMap.get(mSubscriberBinder);
-
-                // Remove the subscriber subscriptions.
-                if (subscriber != null) {
-                    Log.d(TAG, "Removing subscriptions for dead subscriber: " + subscriber);
-                    mBrokerService.removeDeadSubscriber(subscriber);
-                } else {
-                    Log.d(TAG, "Handling dead binder with no matching subscriber");
-
-                }
-
-                // Remove binder
-                VmsSubscribersManager.this.removeListener(mSubscriberBinder);
-            }
-
-            void release() {
-                mSubscriberBinder.unlinkToDeath(this, 0);
-            }
-        }
-
-        public void release() {
-            for (ListenerDeathRecipient recipient : mListenerDeathRecipientMap.values()) {
-                recipient.release();
-            }
-            mListenerDeathRecipientMap.clear();
-            mSubscriberMap.clear();
-        }
-
-        /**
-         * Adds the subscriber and a death recipient associated to it.
-         *
-         * @param subscriber to be added.
-         * @throws IllegalArgumentException if the subscriber is null.
-         * @throws IllegalStateException    if it was not possible to link a death recipient to the
-         *                                  subscriber.
-         */
-        public void add(IVmsSubscriberClient subscriber) {
-            ICarImpl.assertVmsSubscriberPermission(mContext);
-            if (subscriber == null) {
-                Log.e(TAG, "Trying to add a null subscriber.");
-                throw new IllegalArgumentException("subscriber cannot be null.");
-            }
-            IBinder subscriberBinder = subscriber.asBinder();
-            synchronized (mListenerManagerLock) {
-                if (mSubscriberMap.containsKey(subscriberBinder)) {
-                    if (DBG) {
-                        Log.d(TAG, "Subscriber already registered: " + subscriber);
-                    }
-                    return;
-                }
-                if (DBG) {
-                    Log.d(TAG, "Registering subscriber: " + subscriber);
-                }
-                ListenerDeathRecipient deathRecipient =
-                        new ListenerDeathRecipient(subscriberBinder);
-                try {
-                    subscriberBinder.linkToDeath(deathRecipient, 0);
-                } catch (RemoteException e) {
-                    throw new IllegalStateException("Client already dead", e);
-                }
-                mListenerDeathRecipientMap.put(subscriberBinder, deathRecipient);
-                mSubscriberMap.put(subscriberBinder, subscriber);
-            }
-        }
-
-        /**
-         * Removes the subscriber and associated death recipient.
-         *
-         * @param subscriber to be removed.
-         * @throws IllegalArgumentException if subscriber is null.
-         */
-        public void remove(IVmsSubscriberClient subscriber) {
-            if (DBG) {
-                Log.d(TAG, "unregisterListener");
-            }
-            ICarImpl.assertPermission(mContext, PERMISSION);
-            if (subscriber == null) {
-                Log.e(TAG, "unregister: subscriber is null.");
-                throw new IllegalArgumentException("Listener is null");
-            }
-            IBinder subscriberBinder = subscriber.asBinder();
-            removeListener(subscriberBinder);
-        }
-
-        // Removes the subscriberBinder from the current state.
-        // The function assumes that binder will exist both in subscriber and death recipients list.
-        private void removeListener(IBinder subscriberBinder) {
-            synchronized (mListenerManagerLock) {
-                boolean found = mSubscriberMap.remove(subscriberBinder) != null;
-                if (found) {
-                    mListenerDeathRecipientMap.get(subscriberBinder).release();
-                    mListenerDeathRecipientMap.remove(subscriberBinder);
-                } else {
-                    Log.e(TAG, "removeListener: subscriber was not previously registered.");
-                }
-            }
-        }
-
-        /**
-         * Returns list of subscribers currently registered.
-         *
-         * @return list of subscribers.
-         */
-        public List<IVmsSubscriberClient> getListeners() {
-            synchronized (mListenerManagerLock) {
-                return new ArrayList<>(mSubscriberMap.values());
-            }
-        }
-    }
-
-    public VmsSubscriberService(Context context, VmsBrokerService brokerService,
-            VmsHalService hal) {
+    VmsSubscriberService(Context context, VmsBrokerService brokerService,
+            VmsClientManager clientManager, VmsHalService hal) {
         mContext = context;
         mBrokerService = brokerService;
-        hal.setVmsSubscriberService(this, mBrokerService::removeDeadSubscriber);
-    }
-
-    // Implements CarServiceBase interface.
-    @Override
-    public void init() {
+        mClientManager = clientManager;
         mBrokerService.addSubscriberListener(this);
+        hal.setVmsSubscriberService(this);
     }
 
     @Override
-    public void release() {
-        mBrokerService.removeSubscriberListener(this);
-        mSubscribersManager.release();
-    }
+    public void init() {}
+
+    @Override
+    public void release() {}
 
     @Override
     public void dump(PrintWriter writer) {
     }
 
-    // Implements IVmsService interface.
     @Override
     public void addVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            // Add the subscriber so it can subscribe.
-            mSubscribersManager.add(subscriber);
-        }
+        mClientManager.addSubscriber(subscriber);
     }
 
     @Override
     public void removeVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            mSubscribersManager.remove(subscriber);
-        }
+        mClientManager.removeSubscriber(subscriber);
     }
 
     @Override
     public void addVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            // Add the subscriber so it can subscribe.
-            mSubscribersManager.add(subscriber);
-
-            // Add the subscription for the layer.
-            mBrokerService.addSubscription(subscriber, layer);
-        }
+        mClientManager.addSubscriber(subscriber);
+        mBrokerService.addSubscription(subscriber, layer);
     }
 
     @Override
     public void removeVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            // Remove the subscription.
-            mBrokerService.removeSubscription(subscriber, layer);
-        }
+        mBrokerService.removeSubscription(subscriber, layer);
     }
 
     @Override
@@ -263,13 +99,8 @@
             VmsLayer layer,
             int publisherId) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            // Add the subscriber so it can subscribe.
-            mSubscribersManager.add(subscriber);
-
-            // Add the subscription for the layer.
-            mBrokerService.addSubscription(subscriber, layer, publisherId);
-        }
+        mClientManager.addSubscriber(subscriber);
+        mBrokerService.addSubscription(subscriber, layer, publisherId);
     }
 
     @Override
@@ -277,76 +108,43 @@
             VmsLayer layer,
             int publisherId) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            // Remove the subscription.
-            mBrokerService.removeSubscription(subscriber, layer, publisherId);
-        }
+        mBrokerService.removeSubscription(subscriber, layer, publisherId);
     }
 
     @Override
     public void addVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            mSubscribersManager.add(subscriber);
-            mBrokerService.addSubscription(subscriber);
-        }
+        mClientManager.addSubscriber(subscriber);
+        mBrokerService.addSubscription(subscriber);
     }
 
     @Override
     public void removeVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            // Remove the subscription.
-            mBrokerService.removeSubscription(subscriber);
-        }
+        mBrokerService.removeSubscription(subscriber);
     }
 
     @Override
     public byte[] getPublisherInfo(int publisherId) {
         ICarImpl.assertVmsSubscriberPermission(mContext);
-        synchronized (mSubscriberServiceLock) {
-            return mBrokerService.getPublisherInfo(publisherId);
-        }
+        return mBrokerService.getPublisherInfo(publisherId);
     }
 
     @Override
     public VmsAvailableLayers getAvailableLayers() {
+        ICarImpl.assertVmsSubscriberPermission(mContext);
         return mBrokerService.getAvailableLayers();
-
-    }
-
-    @Override
-    public void onMessageReceived(VmsLayer layer, int publisherId, byte[] payload) {
-        if (DBG) Log.d(TAG, "Publishing a message for layer: " + layer);
-
-        Set<IVmsSubscriberClient> subscribers =
-                mBrokerService.getSubscribersForLayerFromPublisher(layer, publisherId);
-
-        for (IVmsSubscriberClient subscriber : subscribers) {
-            try {
-                subscriber.onVmsMessageReceived(layer, payload);
-            } catch (RemoteException e) {
-                // If we could not send a record, its likely the connection snapped. Let the binder
-                // death handle the situation.
-                Log.e(TAG, "onVmsMessageReceived calling failed: ", e);
-            }
-        }
     }
 
     @Override
     public void onLayersAvailabilityChange(VmsAvailableLayers availableLayers) {
         if (DBG) Log.d(TAG, "Publishing layers availability change: " + availableLayers);
-
-        Set<IVmsSubscriberClient> subscribers;
-        subscribers = new HashSet<>(mSubscribersManager.getListeners());
-
-        for (IVmsSubscriberClient subscriber : subscribers) {
+        for (IVmsSubscriberClient subscriber : mClientManager.getAllSubscribers()) {
             try {
                 subscriber.onLayersAvailabilityChanged(availableLayers);
             } catch (RemoteException e) {
-                // If we could not send a record, its likely the connection snapped. Let the binder
-                // death handle the situation.
-                Log.e(TAG, "onLayersAvailabilityChanged calling failed: ", e);
+                Log.e(TAG, "onLayersAvailabilityChanged failed: "
+                        + mClientManager.getPackageName(subscriber), e);
             }
         }
     }
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index 1165de5..4be06ff 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -231,17 +231,20 @@
         List<JobInfo> startedJobs = mJobScheduler.getStartedJobs();
         int count = 0;
         List<String> currentPendingJobs = new ArrayList<>();
-        for (JobSnapshot snap : mJobScheduler.getAllJobSnapshots()) {
-            if (startedJobs.contains(snap.getJobInfo())
-                    && snap.getJobInfo().isRequireDeviceIdle()) {
-                currentPendingJobs.add(snap.getJobInfo().toString());
-                count++;
+        final List<JobSnapshot> allJobs = mJobScheduler.getAllJobSnapshots();
+        if (allJobs != null) {
+            for (JobSnapshot snap : allJobs) {
+                if (startedJobs.contains(snap.getJobInfo())
+                        && snap.getJobInfo().isRequireDeviceIdle()) {
+                    currentPendingJobs.add(snap.getJobInfo().toString());
+                    count++;
+                }
             }
-        }
-        if (count > 0) {
-            // We have something pending, so update the list.
-            // (Otherwise, keep the old list.)
-            mPendingJobs = currentPendingJobs;
+            if (count > 0) {
+                // We have something pending, so update the list.
+                // (Otherwise, keep the old list.)
+                mPendingJobs = currentPendingJobs;
+            }
         }
         return count;
     }
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index 3f39f48..fcf717f 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -53,6 +53,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.car.CarLog;
+import com.android.car.vms.VmsClientManager;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -62,7 +63,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -83,13 +83,10 @@
     private final MessageQueue mMessageQueue;
     private volatile boolean mIsSupported = false;
 
+    private VmsClientManager mClientManager;
     private IVmsPublisherService mPublisherService;
-    private Consumer<IBinder> mPublisherOnHalConnected;
-    private Runnable mPublisherOnHalDisconnected;
     private IBinder mPublisherToken;
-
     private IVmsSubscriberService mSubscriberService;
-    private Consumer<IVmsSubscriberClient> mSuscriberOnHalDisconnected;
 
     private int mSubscriptionStateSequence = -1;
     private int mAvailableLayersSequence = -1;
@@ -215,21 +212,17 @@
     }
 
     /**
-     * Gets the {@link IVmsPublisherClient} implementation for the HAL's publisher callback.
+     * Sets a reference to the {@link VmsClientManager} implementation for use by the HAL.
      */
-    public void setPublisherConnectionCallbacks(Consumer<IBinder> onHalConnected,
-            Runnable onHalDisconnected) {
-        mPublisherOnHalConnected = onHalConnected;
-        mPublisherOnHalDisconnected = onHalDisconnected;
+    public void setClientManager(VmsClientManager clientManager) {
+        mClientManager = clientManager;
     }
 
     /**
      * Sets a reference to the {@link IVmsSubscriberService} implementation for use by the HAL.
      */
-    public void setVmsSubscriberService(IVmsSubscriberService service,
-            Consumer<IVmsSubscriberClient> onHalDisconnected) {
+    public void setVmsSubscriberService(IVmsSubscriberService service) {
         mSubscriberService = service;
-        mSuscriberOnHalDisconnected = onHalDisconnected;
     }
 
     @Override
@@ -370,15 +363,10 @@
         }
 
         if (coreId != mCoreId) {
-            if (mPublisherOnHalDisconnected != null) {
-                mPublisherOnHalDisconnected.run();
+            if (mClientManager != null) {
+                mClientManager.onHalDisconnected();
             } else {
-                Log.w(TAG, "Publisher disconnect callback not registered");
-            }
-            if (mSuscriberOnHalDisconnected != null) {
-                mSuscriberOnHalDisconnected.accept(mSubscriberClient);
-            } else {
-                Log.w(TAG, "Subscriber disconnect callback not registered");
+                Log.w(TAG, "Client manager not registered");
             }
 
             // Drop all queued messages and client state
@@ -392,20 +380,13 @@
         }
 
         // Notify client manager of connection
-        if (mPublisherOnHalConnected != null) {
-            mPublisherOnHalConnected.accept(mPublisherClient);
+        if (mClientManager != null) {
+            mClientManager.onHalConnected(mPublisherClient, mSubscriberClient);
         } else {
-            Log.w(TAG, "Publisher connect callback not registered");
+            Log.w(TAG, "Client manager not registered");
         }
 
-        // Notify subscriber service of connection
         if (mSubscriberService != null) {
-            try {
-                mSubscriberService.addVmsSubscriberToNotifications(mSubscriberClient);
-            } catch (RemoteException e) {
-                Log.e(TAG, "While adding subscriber callback", e);
-            }
-
             // Publish layer availability to HAL clients (this triggers HAL client initialization)
             try {
                 mSubscriberClient.onLayersAvailabilityChanged(
diff --git a/service/src/com/android/car/pm/CarAppMetadataReader.java b/service/src/com/android/car/pm/CarAppMetadataReader.java
index 648fded..3363d12 100644
--- a/service/src/com/android/car/pm/CarAppMetadataReader.java
+++ b/service/src/com/android/car/pm/CarAppMetadataReader.java
@@ -56,11 +56,13 @@
         final PackageManager pm = context.getPackageManager();
 
         // Check if any of the activities in the package are DO by checking all the
-        // <activity> elements.
+        // <activity> elements. MATCH_DISABLED_COMPONENTS is included so that we are immediately
+        // prepared to respond to any components that toggle from disabled to enabled.
         PackageInfo pkgInfo =
                 pm.getPackageInfoAsUser(
                         packageName, PackageManager.GET_ACTIVITIES
                                 | PackageManager.GET_META_DATA
+                                | PackageManager.MATCH_DISABLED_COMPONENTS
                                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                         userId);
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index d00c7f1..01d5500 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -435,8 +435,6 @@
             mUxRestrictionsListeners.put(displayId, listener);
             mCarUxRestrictionsService.registerUxRestrictionsChangeListener(listener, displayId);
         }
-        mSystemActivityMonitoringService.registerActivityLaunchListener(
-                mActivityLaunchListener);
         mVendorServiceController.init();
     }
 
@@ -446,6 +444,10 @@
         synchronized (this) {
             mHasParsedPackages = true;
         }
+        // Once the activity launch listener is registered we attempt to block any non-whitelisted
+        // activities that are launched. For this reason, we need to wait until after the whitelist
+        // has been created.
+        mSystemActivityMonitoringService.registerActivityLaunchListener(mActivityLaunchListener);
         blockTopActivitiesIfNecessary();
     }
 
@@ -1032,6 +1034,15 @@
         if (topTask.topActivity == null) {
             return;
         }
+
+        // We are not handling the UI blocking until we know what is allowed and what is not.
+        if (!mHasParsedPackages) {
+            if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
+                Log.i(CarLog.TAG_PACKAGE, "Packages not parsed, so ignoring block for " + topTask);
+            }
+            return;
+        }
+
         boolean allowed = isActivityDistractionOptimized(
                 topTask.topActivity.getPackageName(),
                 topTask.topActivity.getClassName());
diff --git a/service/src/com/android/car/trust/BLEMessageV1Factory.java b/service/src/com/android/car/trust/BLEMessageV1Factory.java
index afb5c8c..4afe877 100644
--- a/service/src/com/android/car/trust/BLEMessageV1Factory.java
+++ b/service/src/com/android/car/trust/BLEMessageV1Factory.java
@@ -41,14 +41,6 @@
      */
     private static final int FIXED_32_SIZE = 4;
 
-    /**
-     * Additional bytes that are needed during the encoding of the {@code payload} field.
-     *
-     * <p>The {@code payload} field is defined as {@code bytes}, and thus, needs 2 extra bytes to
-     * encode: one to encode the field number and another for length delimiting.
-     */
-    private static final int ADDITIONAL_PAYLOAD_ENCODING_SIZE = 2;
-
     // The size needed to encode a boolean proto field
     private static final int BOOLEAN_FIELD_ENCODING_SIZE = 1;
 
@@ -145,13 +137,14 @@
     public static List<BLEMessage> makeBLEMessages(byte[] payload, OperationType operation,
             int maxSize, boolean isPayloadEncrypted) {
         List<BLEMessage> bleMessages = new ArrayList();
-        int maxPayloadSize = maxSize - getProtoHeaderSize(operation, isPayloadEncrypted);
-        int payloadLength = payload.length;
-        if (payloadLength <= maxPayloadSize) {
+        int payloadSize = payload.length;
+        int maxPayloadSize =
+                maxSize - getProtoHeaderSize(operation, payloadSize, isPayloadEncrypted);
+        if (payloadSize <= maxPayloadSize) {
             bleMessages.add(makeBLEMessage(payload, operation, isPayloadEncrypted));
             return bleMessages;
         }
-        int totalPackets = (int) Math.ceil((double) payloadLength / maxPayloadSize);
+        int totalPackets = (int) Math.ceil((double) payloadSize / maxPayloadSize);
         int start = 0;
         int end = maxPayloadSize;
         for (int i = 0; i < totalPackets; i++) {
@@ -164,7 +157,7 @@
                     .setPayload(ByteString.copyFrom(Arrays.copyOfRange(payload, start, end)))
                     .build());
             start = end;
-            end = Math.min(start + maxPayloadSize, payloadLength);
+            end = Math.min(start + maxPayloadSize, payloadSize);
         }
         return bleMessages;
     }
@@ -174,12 +167,18 @@
      * contain a payload.
      */
     @VisibleForTesting
-    static int getProtoHeaderSize(OperationType operation, boolean isPayloadEncrypted) {
-        int isPayloadEncryptedFieldSize =
-                isPayloadEncrypted ? (BOOLEAN_FIELD_ENCODING_SIZE + FIELD_NUMBER_ENCODING_SIZE) : 0;
+    static int getProtoHeaderSize(OperationType operation, int payloadSize,
+            boolean isPayloadEncrypted) {
+        int isPayloadEncryptedFieldSize = isPayloadEncrypted
+                ? BOOLEAN_FIELD_ENCODING_SIZE + FIELD_NUMBER_ENCODING_SIZE
+                : 0;
         int operationSize = getEncodedSize(operation.getNumber()) + FIELD_NUMBER_ENCODING_SIZE;
+
+        // The payload size is a varint.
+        int payloadEncodingSize = FIELD_NUMBER_ENCODING_SIZE + getEncodedSize(payloadSize);
+
         return CONSTANT_HEADER_FIELD_SIZE + operationSize + isPayloadEncryptedFieldSize
-                + ADDITIONAL_PAYLOAD_ENCODING_SIZE;
+                + payloadEncodingSize;
     }
 
     /**
diff --git a/service/src/com/android/car/trust/BLEVersionExchangeResolver.java b/service/src/com/android/car/trust/BLEVersionExchangeResolver.java
index 7b9a025..4465b25 100644
--- a/service/src/com/android/car/trust/BLEVersionExchangeResolver.java
+++ b/service/src/com/android/car/trust/BLEVersionExchangeResolver.java
@@ -59,7 +59,7 @@
                 .setMinSupportedMessagingVersion(MESSAGING_VERSION)
                 .setMaxSupportedMessagingVersion(MESSAGING_VERSION)
                 .setMinSupportedSecurityVersion(SECURITY_VERSION)
-                .setMinSupportedSecurityVersion(SECURITY_VERSION)
+                .setMaxSupportedSecurityVersion(SECURITY_VERSION)
                 .build();
     }
 
diff --git a/service/src/com/android/car/trust/CarBleTrustAgent.java b/service/src/com/android/car/trust/CarBleTrustAgent.java
index b8a186c..d3790ff 100644
--- a/service/src/com/android/car/trust/CarBleTrustAgent.java
+++ b/service/src/com/android/car/trust/CarBleTrustAgent.java
@@ -199,6 +199,9 @@
      * @return if the user has trusted device
      */
     private boolean hasTrustedDevice(int uid) {
+        if (mCarTrustAgentEnrollmentService == null) {
+            return false;
+        }
         List<TrustedDeviceInfo> trustedDeviceInfos = mCarTrustAgentEnrollmentService
                 .getEnrolledDeviceInfosForUser(uid);
         return trustedDeviceInfos != null && trustedDeviceInfos.size() > 0;
diff --git a/service/src/com/android/car/trust/CarTrustAgentBleManager.java b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
index 09c59e6..1e43300 100644
--- a/service/src/com/android/car/trust/CarTrustAgentBleManager.java
+++ b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
@@ -57,7 +57,6 @@
  * the Trusted Device feature.
  */
 class CarTrustAgentBleManager extends BleManager {
-
     private static final String TAG = "CarTrustBLEManager";
 
     /**
@@ -70,6 +69,15 @@
     private static final UUID CLIENT_CHARACTERISTIC_CONFIG =
             UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
 
+    /**
+     * Reserved bytes for an ATT write request payload.
+     *
+     * <p>The attribute protocol uses 3 bytes to encode the command type and attribute ID. These
+     * bytes need to be subtracted from the reported MTU size and the resulting value will
+     * represent the total amount of bytes that can be sent in a write.
+     */
+    private static final int ATT_PAYLOAD_RESERVED_BYTES = 3;
+
     /** @hide */
     @IntDef(prefix = {"TRUSTED_DEVICE_OPERATION_"}, value = {
             TRUSTED_DEVICE_OPERATION_NONE,
@@ -94,7 +102,16 @@
     private String mOriginalBluetoothName;
     private byte[] mUniqueId;
     private String mEnrollmentDeviceName;
-    private int mMtuSize = 20;
+
+    /**
+     * The maximum amount of bytes that can be written over BLE.
+     *
+     * <p>This initial value is 20 because BLE has a default write of 23 bytes. However, 3 bytes
+     * are subtracted due to bytes being reserved for the command type and attribute ID.
+     *
+     * @see #ATT_PAYLOAD_RESERVED_BYTES
+     */
+    private int mMaxWriteSize = 20;
 
     // Enrollment Service and Characteristic UUIDs
     private UUID mEnrollmentServiceUuid;
@@ -169,7 +186,12 @@
 
     @Override
     protected void onMtuSizeChanged(int size) {
-        mMtuSize = size;
+        mMaxWriteSize = size - ATT_PAYLOAD_RESERVED_BYTES;
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "MTU size changed to: " + size
+                    + "; setting max payload size to: " + mMaxWriteSize);
+        }
     }
 
     @Override
@@ -347,7 +369,8 @@
         // Characteristic the connected bluetooth device will write to.
         BluetoothGattCharacteristic clientCharacteristic =
                 new BluetoothGattCharacteristic(mEnrollmentClientWriteUuid,
-                        BluetoothGattCharacteristic.PROPERTY_WRITE,
+                        BluetoothGattCharacteristic.PROPERTY_WRITE
+                                | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
                         BluetoothGattCharacteristic.PERMISSION_WRITE);
 
         // Characteristic that this manager will write to.
@@ -380,7 +403,8 @@
         // Characteristic the connected bluetooth device will write to.
         BluetoothGattCharacteristic clientCharacteristic = new BluetoothGattCharacteristic(
                 mUnlockClientWriteUuid,
-                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PROPERTY_WRITE
+                        | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
                 BluetoothGattCharacteristic.PERMISSION_WRITE);
 
         // Characteristic that this manager will write to.
@@ -524,7 +548,7 @@
         }
 
         List<BLEMessage> bleMessages = BLEMessageV1Factory.makeBLEMessages(message, operation,
-                mMtuSize, isPayloadEncrypted);
+                mMaxWriteSize, isPayloadEncrypted);
 
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "sending " + bleMessages.size() + " messages to device");
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
index 26626ff..565f2f1 100644
--- a/service/src/com/android/car/vms/VmsBrokerService.java
+++ b/service/src/com/android/car/vms/VmsBrokerService.java
@@ -22,24 +22,19 @@
 import android.car.vms.VmsLayersOffering;
 import android.car.vms.VmsOperationRecorder;
 import android.car.vms.VmsSubscriptionState;
-import android.content.pm.PackageManager;
-import android.os.Binder;
 import android.os.IBinder;
-import android.os.Process;
 import android.util.Log;
 
 import com.android.car.VmsLayersAvailability;
 import com.android.car.VmsPublishersInfo;
 import com.android.car.VmsRouting;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.IntSupplier;
 
 /**
  * Broker service facilitating subscription handling and message passing between
@@ -49,26 +44,15 @@
     private static final boolean DBG = true;
     private static final String TAG = "VmsBrokerService";
 
-    @VisibleForTesting
-    static final String HAL_CLIENT = "HalClient";
-
-    @VisibleForTesting
-    static final String UNKNOWN_PACKAGE = "UnknownPackage";
-
     private CopyOnWriteArrayList<PublisherListener> mPublisherListeners =
             new CopyOnWriteArrayList<>();
     private CopyOnWriteArrayList<SubscriberListener> mSubscriberListeners =
             new CopyOnWriteArrayList<>();
-    private PackageManager mPackageManager;
-    private IntSupplier mGetCallingPid;
-    private IntSupplier mGetCallingUid;
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final VmsRouting mRouting = new VmsRouting();
     @GuardedBy("mLock")
-    private final Map<IBinder, String> mBinderPackage = new HashMap<>();
-    @GuardedBy("mLock")
     private final Map<IBinder, Map<Integer, VmsLayersOffering>> mOfferings = new HashMap<>();
     @GuardedBy("mLock")
     private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
@@ -92,15 +76,6 @@
      */
     public interface SubscriberListener {
         /**
-         * Callback triggered when data is published for a given layer.
-         *
-         * @param layer       Layer data is being published for
-         * @param publisherId Publisher of data
-         * @param payload     Layer data
-         */
-        void onMessageReceived(VmsLayer layer, int publisherId, byte[] payload);
-
-        /**
          * Callback triggered when the layers available for subscription changes.
          *
          * @param availableLayers Current layer availability
@@ -109,22 +84,6 @@
     }
 
     /**
-     * Constructs new broker service.
-     */
-    public VmsBrokerService(PackageManager packageManager) {
-        this(packageManager, Binder::getCallingPid, Binder::getCallingUid);
-    }
-
-    @VisibleForTesting
-    VmsBrokerService(PackageManager packageManager, IntSupplier getCallingPid,
-            IntSupplier getCallingUid) {
-        if (DBG) Log.d(TAG, "Started VmsBrokerService!");
-        mPackageManager = packageManager;
-        mGetCallingPid = getCallingPid;
-        mGetCallingUid = getCallingUid;
-    }
-
-    /**
      * Adds a listener for publisher callbacks.
      *
      * @param listener Publisher callback listener
@@ -168,8 +127,6 @@
     public void addSubscription(IVmsSubscriberClient subscriber) {
         synchronized (mLock) {
             mRouting.addSubscription(subscriber);
-            // Add mapping from binder to package name of subscriber.
-            mBinderPackage.computeIfAbsent(subscriber.asBinder(), k -> getCallingPackage());
         }
     }
 
@@ -199,9 +156,6 @@
 
             // Add the listeners subscription to the layer
             mRouting.addSubscription(subscriber, layer);
-
-            // Add mapping from binder to package name of subscriber.
-            mBinderPackage.computeIfAbsent(subscriber.asBinder(), k -> getCallingPackage());
         }
         if (firstSubscriptionForLayer) {
             notifyOfSubscriptionChange();
@@ -249,9 +203,6 @@
 
             // Add the listeners subscription to the layer
             mRouting.addSubscription(subscriber, layer, publisherId);
-
-            // Add mapping from binder to package name of subscriber.
-            mBinderPackage.computeIfAbsent(subscriber.asBinder(), k -> getCallingPackage());
         }
         if (firstSubscriptionForLayer) {
             notifyOfSubscriptionChange();
@@ -296,9 +247,6 @@
         boolean subscriptionStateChanged;
         synchronized (mLock) {
             subscriptionStateChanged = mRouting.removeDeadSubscriber(subscriber);
-
-            // Remove mapping from binder to package name of subscriber.
-            mBinderPackage.remove(subscriber.asBinder());
         }
         if (subscriptionStateChanged) {
             notifyOfSubscriptionChange();
@@ -394,15 +342,6 @@
         }
     }
 
-    /**
-     * Gets the package name for a given IVmsSubscriberClient
-     */
-    public String getPackageName(IVmsSubscriberClient subscriber) {
-        synchronized (mLock) {
-            return mBinderPackage.get(subscriber.asBinder());
-        }
-    }
-
     private void updateLayerAvailability() {
         Set<VmsLayersOffering> allPublisherOfferings = new HashSet<>();
         synchronized (mLock) {
@@ -433,21 +372,4 @@
             listener.onLayersAvailabilityChange(availableLayers);
         }
     }
-
-    // If we're in a binder call, returns back the package name of the caller of the binder call.
-    private String getCallingPackage() {
-        int callingPid = mGetCallingPid.getAsInt();
-        // Since the HAL lives in the same process, if the callingPid is equal to this process's
-        // PID, we know it's the HAL client.
-        if (callingPid == Process.myPid()) {
-            return HAL_CLIENT;
-        }
-        int callingUid = mGetCallingUid.getAsInt();
-        String packageName = mPackageManager.getNameForUid(callingUid);
-        if (packageName == null) {
-            return UNKNOWN_PACKAGE;
-        } else {
-            return packageName;
-        }
-    }
 }
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
index 24f66b9..fcc98f2 100644
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ b/service/src/com/android/car/vms/VmsClientManager.java
@@ -18,6 +18,8 @@
 
 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;
@@ -26,9 +28,11 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -36,15 +40,21 @@
 
 import com.android.car.CarServiceBase;
 import com.android.car.R;
+import com.android.car.VmsPublisherService;
 import com.android.car.hal.VmsHalService;
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.IntSupplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Manages service connections lifecycle for VMS publisher clients.
@@ -55,55 +65,46 @@
 public class VmsClientManager implements CarServiceBase {
     private static final boolean DBG = true;
     private static final String TAG = "VmsClientManager";
-    private static final String HAL_CLIENT_NAME = "VmsHalClient";
-
-    /**
-     * Interface for receiving updates about client connections.
-     */
-    public interface ConnectionListener {
-        /**
-         * Called when a client connection is established or re-established.
-         *
-         * @param clientName    String that uniquely identifies the service and user.
-         * @param clientService The IBinder of the client's communication channel.
-         */
-        void onClientConnected(String clientName, IBinder clientService);
-
-        /**
-         * Called when a client connection is terminated.
-         *
-         * @param clientName String that uniquely identifies the service and user.
-         */
-        void onClientDisconnected(String clientName);
-    }
+    private static final String HAL_CLIENT_NAME = "HalClient";
+    private static final String UNKNOWN_PACKAGE = "UnknownPackage";
 
     private final Context mContext;
+    private final PackageManager mPackageManager;
     private final Handler mHandler;
     private final UserManager mUserManager;
     private final CarUserService mUserService;
     private final CarUserManagerHelper mUserManagerHelper;
     private final int mMillisBeforeRebind;
+    private final IntSupplier mGetCallingUid;
 
-    @GuardedBy("mListeners")
-    private final ArrayList<ConnectionListener> mListeners = new ArrayList<>();
-    @GuardedBy("mSystemClients")
-    private final Map<String, ClientConnection> mSystemClients = new ArrayMap<>();
-    @GuardedBy("mSystemClients")
-    private IBinder mHalClient;
-    @GuardedBy("mSystemClients")
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final VmsBrokerService mBrokerService;
+    @GuardedBy("mLock")
+    private VmsPublisherService mPublisherService;
+
+    @GuardedBy("mLock")
+    private final Map<String, PublisherConnection> mSystemClients = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private IVmsPublisherClient mHalClient;
+    @GuardedBy("mLock")
     private boolean mSystemUserUnlocked;
 
-    @GuardedBy("mCurrentUserClients")
-    private final Map<String, ClientConnection> mCurrentUserClients = new ArrayMap<>();
-    @GuardedBy("mCurrentUserClients")
+    @GuardedBy("mLock")
+    private final Map<String, PublisherConnection> mCurrentUserClients = new ArrayMap<>();
+    @GuardedBy("mLock")
     private int mCurrentUser;
 
+    @GuardedBy("mLock")
+    private final Map<IBinder, SubscriberConnection> mSubscribers = new HashMap<>();
+
     @GuardedBy("mRebindCounts")
     private final Map<String, AtomicLong> mRebindCounts = new ArrayMap<>();
 
     @VisibleForTesting
     final Runnable mSystemUserUnlockedListener = () -> {
-        synchronized (mSystemClients) {
+        synchronized (mLock) {
             mSystemUserUnlocked = true;
         }
         bindToSystemClients();
@@ -114,15 +115,17 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DBG) Log.d(TAG, "Received " + intent);
-            synchronized (mCurrentUserClients) {
+            synchronized (mLock) {
                 int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
                 if (mCurrentUser != currentUserId) {
                     terminate(mCurrentUserClients);
+                    terminate(mSubscribers.values().stream()
+                            .filter(subscriber -> subscriber.mUserId != currentUserId)
+                            .filter(subscriber -> subscriber.mUserId != UserHandle.USER_SYSTEM));
                 }
                 mCurrentUser = currentUserId;
 
-                if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())
-                        || mUserManager.isUserUnlocked(mCurrentUser)) {
+                if (mUserManager.isUserUnlocked(mCurrentUser)) {
                     bindToSystemClients();
                     bindToUserClients();
                 }
@@ -131,23 +134,48 @@
     };
 
     /**
-     * Constructor for client managers.
+     * Constructor for client manager.
      *
      * @param context           Context to use for registering receivers and binding services.
+     * @param brokerService     Service managing the VMS publisher/subscriber state.
      * @param userService       User service for registering system unlock listener.
      * @param userManagerHelper User manager for querying current user state.
      * @param halService        Service providing the HAL client interface
      */
-    public VmsClientManager(Context context, CarUserService userService,
-            CarUserManagerHelper userManagerHelper, VmsHalService halService) {
+    public VmsClientManager(Context context, VmsBrokerService brokerService,
+            CarUserService userService, CarUserManagerHelper userManagerHelper,
+            VmsHalService halService) {
+        this(context, brokerService, userService, userManagerHelper, halService,
+                Binder::getCallingUid);
+    }
+
+    @VisibleForTesting
+    VmsClientManager(Context context, VmsBrokerService brokerService,
+            CarUserService userService, CarUserManagerHelper userManagerHelper,
+            VmsHalService halService, IntSupplier getCallingUid) {
         mContext = context;
+        mPackageManager = context.getPackageManager();
         mHandler = new Handler(Looper.getMainLooper());
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mUserService = userService;
         mUserManagerHelper = userManagerHelper;
+        mCurrentUser = mUserManagerHelper.getCurrentForegroundUserId();
+        mBrokerService = brokerService;
         mMillisBeforeRebind = mContext.getResources().getInteger(
                 com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
-        halService.setPublisherConnectionCallbacks(this::onHalConnected, this::onHalDisconnected);
+        mGetCallingUid = getCallingUid;
+        halService.setClientManager(this);
+    }
+
+    /**
+     * Registers the publisher service for connection callbacks.
+     *
+     * @param publisherService Publisher service to register.
+     */
+    public void setPublisherService(VmsPublisherService publisherService) {
+        synchronized (mLock) {
+            mPublisherService = publisherService;
+        }
     }
 
     @Override
@@ -164,12 +192,13 @@
     @Override
     public void release() {
         mContext.unregisterReceiver(mUserSwitchReceiver);
-        notifyListenersOnClientDisconnected(HAL_CLIENT_NAME);
-        synchronized (mSystemClients) {
+        synchronized (mLock) {
+            if (mHalClient != null) {
+                mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
+            }
             terminate(mSystemClients);
-        }
-        synchronized (mCurrentUserClients) {
             terminate(mCurrentUserClients);
+            terminate(mSubscribers.values().stream());
         }
     }
 
@@ -181,15 +210,19 @@
     @Override
     public void dumpMetrics(PrintWriter writer) {
         writer.println("*" + getClass().getSimpleName() + "*");
-        synchronized (mSystemClients) {
+        synchronized (mLock) {
+            writer.println("mCurrentUser:" + mCurrentUser);
             writer.println("mHalClient: " + (mHalClient != null ? "connected" : "disconnected"));
             writer.println("mSystemClients:");
             dumpConnections(writer, mSystemClients);
-        }
-        synchronized (mCurrentUserClients) {
+
             writer.println("mCurrentUserClients:");
             dumpConnections(writer, mCurrentUserClients);
-            writer.println("mCurrentUser:" + mCurrentUser);
+
+            writer.println("mSubscribers:");
+            for (SubscriberConnection subscriber : mSubscribers.values()) {
+                writer.printf("\t%s\n", subscriber);
+            }
         }
         synchronized (mRebindCounts) {
             writer.println("mRebindCounts:");
@@ -199,47 +232,126 @@
         }
     }
 
-    private void dumpConnections(PrintWriter writer, Map<String, ClientConnection> connectionMap) {
-        for (ClientConnection connection : connectionMap.values()) {
+
+    /**
+     * Adds a subscriber for connection tracking.
+     *
+     * @param subscriberClient Subscriber client to track.
+     */
+    public void addSubscriber(IVmsSubscriberClient subscriberClient) {
+        if (subscriberClient == null) {
+            Log.e(TAG, "Trying to add a null subscriber.");
+            throw new IllegalArgumentException("subscriber cannot be null.");
+        }
+
+        synchronized (mLock) {
+            IBinder subscriberBinder = subscriberClient.asBinder();
+            if (mSubscribers.containsKey(subscriberBinder)) {
+                // Already registered
+                return;
+            }
+
+            int subscriberUserId = UserHandle.getUserId(mGetCallingUid.getAsInt());
+            if (subscriberUserId != mCurrentUser && subscriberUserId != UserHandle.USER_SYSTEM) {
+                throw new SecurityException("Caller must be foreground user or system");
+            }
+
+            SubscriberConnection subscriber = new SubscriberConnection(
+                    subscriberClient, getCallingPackage(), subscriberUserId);
+            if (DBG) Log.d(TAG, "Registering subscriber: " + subscriber);
+            try {
+                subscriberBinder.linkToDeath(subscriber, 0);
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Subscriber already dead: " + subscriber, e);
+            }
+            mSubscribers.put(subscriberBinder, subscriber);
+        }
+    }
+
+    /**
+     * Removes a subscriber for connection tracking and expires its subscriptions.
+     *
+     * @param subscriberClient Subscriber client to remove.
+     */
+    public void removeSubscriber(IVmsSubscriberClient subscriberClient) {
+        synchronized (mLock) {
+            SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
+            if (subscriber != null) {
+                subscriber.terminate();
+            }
+        }
+    }
+
+    /**
+     * Returns all active subscriber clients.
+     */
+    public Collection<IVmsSubscriberClient> getAllSubscribers() {
+        return mSubscribers.values().stream()
+                .map(subscriber -> subscriber.mClient)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Gets the package name for a given subscriber client.
+     */
+    public String getPackageName(IVmsSubscriberClient subscriberClient) {
+        synchronized (mLock) {
+            SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
+            return subscriber != null ? subscriber.mPackageName : UNKNOWN_PACKAGE;
+        }
+    }
+
+    /**
+     * Registers the HAL client connections.
+     *
+     * @param publisherClient
+     * @param subscriberClient
+     */
+    public void onHalConnected(IVmsPublisherClient publisherClient,
+            IVmsSubscriberClient subscriberClient) {
+        synchronized (mLock) {
+            mHalClient = publisherClient;
+            mPublisherService.onClientConnected(HAL_CLIENT_NAME, mHalClient);
+            mSubscribers.put(subscriberClient.asBinder(),
+                    new SubscriberConnection(subscriberClient, HAL_CLIENT_NAME,
+                            UserHandle.USER_SYSTEM));
+        }
+    }
+
+    /**
+     *
+     */
+    public void onHalDisconnected() {
+        synchronized (mLock) {
+            if (mHalClient != null) {
+                mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
+            }
+            mHalClient = null;
+            terminate(mSubscribers.values().stream()
+                    .filter(subscriber -> HAL_CLIENT_NAME.equals(subscriber.mPackageName)));
+        }
+        synchronized (mRebindCounts) {
+            mRebindCounts.computeIfAbsent(HAL_CLIENT_NAME, k -> new AtomicLong()).incrementAndGet();
+        }
+    }
+
+    private void dumpConnections(PrintWriter writer,
+            Map<String, PublisherConnection> connectionMap) {
+        for (PublisherConnection connection : connectionMap.values()) {
             writer.printf("\t%s: %s\n",
                     connection.mName.getPackageName(),
                     connection.mIsBound ? "connected" : "disconnected");
         }
     }
 
-    /**
-     * Registers a new client connection state listener.
-     *
-     * @param listener Listener to register.
-     */
-    public void registerConnectionListener(ConnectionListener listener) {
-        synchronized (mListeners) {
-            if (!mListeners.contains(listener)) {
-                mListeners.add(listener);
-            }
-        }
-        notifyListenerOfConnectedClients(listener);
-    }
-
-    /**
-     * Unregisters a client connection state listener.
-     *
-     * @param listener Listener to remove.
-     */
-    public void unregisterConnectionListener(ConnectionListener listener) {
-        synchronized (mListeners) {
-            mListeners.remove(listener);
-        }
-    }
-
     private void bindToSystemClients() {
         String[] clientNames = mContext.getResources().getStringArray(
                 R.array.vmsPublisherSystemClients);
-        Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)");
-        synchronized (mSystemClients) {
+        synchronized (mLock) {
             if (!mSystemUserUnlocked) {
                 return;
             }
+            Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)");
             for (String clientName : clientNames) {
                 bind(mSystemClients, clientName, UserHandle.SYSTEM);
             }
@@ -247,7 +359,7 @@
     }
 
     private void bindToUserClients() {
-        synchronized (mCurrentUserClients) {
+        synchronized (mLock) {
             // 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.
@@ -266,7 +378,7 @@
         }
     }
 
-    private void bind(Map<String, ClientConnection> connectionMap, String clientName,
+    private void bind(Map<String, PublisherConnection> connectionMap, String clientName,
             UserHandle userHandle) {
         if (connectionMap.containsKey(clientName)) {
             Log.i(TAG, "Already bound: " + clientName);
@@ -294,7 +406,7 @@
             return;
         }
 
-        ClientConnection connection = new ClientConnection(name, userHandle);
+        PublisherConnection connection = new PublisherConnection(name, userHandle);
         if (connection.bind()) {
             Log.i(TAG, "Client bound: " + connection);
             connectionMap.put(clientName, connection);
@@ -303,65 +415,21 @@
         }
     }
 
-    private void terminate(Map<String, ClientConnection> connectionMap) {
-        connectionMap.values().forEach(ClientConnection::terminate);
+    private void terminate(Map<String, PublisherConnection> connectionMap) {
+        connectionMap.values().forEach(PublisherConnection::terminate);
         connectionMap.clear();
     }
 
-    private void notifyListenerOfConnectedClients(ConnectionListener listener) {
-        synchronized (mSystemClients) {
-            if (mHalClient != null) {
-                listener.onClientConnected(HAL_CLIENT_NAME, mHalClient);
-            }
-            mSystemClients.values().forEach(conn -> conn.notifyIfConnected(listener));
-        }
-        synchronized (mCurrentUserClients) {
-            mCurrentUserClients.values().forEach(conn -> conn.notifyIfConnected(listener));
-        }
-    }
-
-    private void notifyListenersOnClientConnected(String clientName, IBinder clientService) {
-        synchronized (mListeners) {
-            for (ConnectionListener listener : mListeners) {
-                listener.onClientConnected(clientName, clientService);
-            }
-        }
-    }
-
-    private void notifyListenersOnClientDisconnected(String clientName) {
-        synchronized (mListeners) {
-            for (ConnectionListener listener : mListeners) {
-                listener.onClientDisconnected(clientName);
-            }
-        }
-    }
-
-    private void onHalConnected(IBinder halClient) {
-        synchronized (mSystemClients) {
-            mHalClient = halClient;
-            notifyListenersOnClientConnected(HAL_CLIENT_NAME, mHalClient);
-        }
-    }
-
-    private void onHalDisconnected() {
-        synchronized (mSystemClients) {
-            mHalClient = null;
-            notifyListenersOnClientDisconnected(HAL_CLIENT_NAME);
-        }
-        synchronized (mRebindCounts) {
-            mRebindCounts.computeIfAbsent(HAL_CLIENT_NAME, k -> new AtomicLong()).incrementAndGet();
-        }
-    }
-
-    class ClientConnection implements ServiceConnection {
+    class PublisherConnection implements ServiceConnection {
         private final ComponentName mName;
         private final UserHandle mUser;
         private final String mFullName;
         private boolean mIsBound = false;
         private boolean mIsTerminated = false;
-        private IBinder mClientService;
+        private boolean mRebindScheduled = false;
+        private IVmsPublisherClient mClientService;
 
-        ClientConnection(ComponentName name, UserHandle user) {
+        PublisherConnection(ComponentName name, UserHandle user) {
             mName = name;
             mUser = user;
             mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier();
@@ -400,50 +468,73 @@
                 Log.e(TAG, "While unbinding " + mFullName, t);
             }
             mIsBound = false;
-            if (mClientService != null) {
-                notifyListenersOnClientDisconnected(mFullName);
-            }
-            mClientService = null;
         }
 
-        synchronized void rebind() {
-            unbind();
+        synchronized void scheduleRebind() {
+            if (mRebindScheduled) {
+                return;
+            }
+
             if (DBG) {
                 Log.d(TAG,
                         String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
             }
-            if (!mIsTerminated) {
-                mHandler.postDelayed(this::bind, mMillisBeforeRebind);
-                synchronized (mRebindCounts) {
-                    mRebindCounts.computeIfAbsent(mName.getPackageName(), k -> new AtomicLong())
-                            .incrementAndGet();
-                }
+            mHandler.postDelayed(this::doRebind, mMillisBeforeRebind);
+            mRebindScheduled = true;
+        }
+
+        synchronized void doRebind() {
+            mRebindScheduled = false;
+            // Do not rebind if the connection has been terminated, or the client service has
+            // reconnected on its own.
+            if (mIsTerminated || mClientService != null) {
+                return;
+            }
+
+            if (DBG) Log.d(TAG, "rebinding: " + mFullName);
+            // Ensure that the client is not bound before attempting to rebind.
+            // If the client is not currently bound, unbind() will have no effect.
+            unbind();
+            bind();
+            synchronized (mRebindCounts) {
+                mRebindCounts.computeIfAbsent(mName.getPackageName(), k -> new AtomicLong())
+                        .incrementAndGet();
             }
         }
 
         synchronized void terminate() {
             if (DBG) Log.d(TAG, "terminating: " + mFullName);
             mIsTerminated = true;
+            notifyOnDisconnect();
             unbind();
         }
 
-        synchronized void notifyIfConnected(ConnectionListener listener) {
+        synchronized void notifyOnDisconnect() {
             if (mClientService != null) {
-                listener.onClientConnected(mFullName, mClientService);
+                mPublisherService.onClientDisconnected(mFullName);
+                mClientService = null;
             }
         }
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
-            mClientService = service;
-            notifyListenersOnClientConnected(mFullName, mClientService);
+            mClientService = IVmsPublisherClient.Stub.asInterface(service);
+            mPublisherService.onClientConnected(mFullName, mClientService);
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName);
-            rebind();
+            notifyOnDisconnect();
+            scheduleRebind();
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            if (DBG) Log.d(TAG, "onBindingDied: " + mFullName);
+            notifyOnDisconnect();
+            scheduleRebind();
         }
 
         @Override
@@ -451,4 +542,57 @@
             return mFullName;
         }
     }
+
+    private void terminate(Stream<SubscriberConnection> subscribers) {
+        // Make a copy of the stream, so that terminate() doesn't cause a concurrent modification
+        subscribers.collect(Collectors.toList()).forEach(SubscriberConnection::terminate);
+    }
+
+    // If we're in a binder call, returns back the package name of the caller of the binder call.
+    private String getCallingPackage() {
+        String packageName = mPackageManager.getNameForUid(mGetCallingUid.getAsInt());
+        if (packageName == null) {
+            return UNKNOWN_PACKAGE;
+        } else {
+            return packageName;
+        }
+    }
+
+    private class SubscriberConnection implements IBinder.DeathRecipient {
+        private final IVmsSubscriberClient mClient;
+        private final String mPackageName;
+        private final int mUserId;
+
+        SubscriberConnection(IVmsSubscriberClient subscriberClient, String packageName,
+                int userId) {
+            mClient = subscriberClient;
+            mPackageName = packageName;
+            mUserId = userId;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DBG) Log.d(TAG, "Subscriber died: " + this);
+            terminate();
+        }
+
+        @Override
+        public String toString() {
+            return mPackageName + " U=" + mUserId;
+        }
+
+        void terminate() {
+            if (DBG) Log.d(TAG, "Terminating subscriber: " + this);
+            synchronized (mLock) {
+                mBrokerService.removeDeadSubscriber(mClient);
+                IBinder subscriberBinder = mClient.asBinder();
+                try {
+                    subscriberBinder.unlinkToDeath(this, 0);
+                } catch (NoSuchElementException e) {
+                    if (DBG) Log.d(TAG, "While unlinking subscriber binder for " + this, e);
+                }
+                mSubscribers.remove(subscriberBinder);
+            }
+        }
+    }
 }
diff --git a/tests/CarDeveloperOptions/res/values-da/strings.xml b/tests/CarDeveloperOptions/res/values-da/strings.xml
index a2c527a..d13df6f 100644
--- a/tests/CarDeveloperOptions/res/values-da/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-da/strings.xml
@@ -3039,7 +3039,7 @@
     <string name="account_dashboard_title" msgid="4734300939532555885">"Konti"</string>
     <string name="account_dashboard_default_summary" msgid="6822549669771936206">"Ingen konti er tilføjet"</string>
     <string name="app_default_dashboard_title" msgid="6575301028225232193">"Standardapps"</string>
-    <string name="system_dashboard_summary" msgid="6582464466735779394">"Sprog, bevægelser, klokkeslæt, bckup"</string>
+    <string name="system_dashboard_summary" msgid="6582464466735779394">"Sprog, bevægelser, klokkeslæt, backup"</string>
     <string name="search_results_title" msgid="4160717656435503940">"Indstillinger"</string>
     <string name="keywords_wifi" msgid="8477688080895466846">"wifi, wi-fi, netværksforbindelse, internet, trådløs, data, wi fi"</string>
     <string name="keywords_wifi_notify_open_networks" msgid="1031260564121854773">"Wi‑Fi-notifikation, wifi-notifikation"</string>
diff --git a/tests/CarDeveloperOptions/res/values-de/strings.xml b/tests/CarDeveloperOptions/res/values-de/strings.xml
index 5759b3d..0e47db7 100644
--- a/tests/CarDeveloperOptions/res/values-de/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-de/strings.xml
@@ -1163,7 +1163,7 @@
     <string name="accessibility_personal_account_title" msgid="7251761883688839354">"Privates Konto – <xliff:g id="MANAGED_BY">%s</xliff:g>"</string>
     <string name="search_settings" msgid="5809250790214921377">"Suche"</string>
     <string name="display_settings" msgid="1045535829232307190">"Display"</string>
-    <string name="accelerometer_title" msgid="2427487734964971453">"Display automatisch drehen"</string>
+    <string name="accelerometer_title" msgid="2427487734964971453">"Bildschirm automatisch drehen"</string>
     <string name="color_mode_title" msgid="8164858320869449142">"Farben"</string>
     <string name="color_mode_option_natural" msgid="1292837781836645320">"Natürlich"</string>
     <string name="color_mode_option_boosted" msgid="453557938434778933">"Verstärkt"</string>
@@ -2078,7 +2078,7 @@
     <string name="accessibility_control_timeout_preference_title" msgid="2771808346038759474">"Zeit zum Reagieren"</string>
     <string name="accessibility_content_timeout_preference_summary" msgid="853829064617918179">"Hier kannst du auswählen, wie lange Nachrichten sichtbar sind, die du lesen musst, die aber nur vorübergehend angezeigt werden.\n\nDiese Einstellung wird nicht von allen Apps unterstützt."</string>
     <string name="accessibility_control_timeout_preference_summary" msgid="8582212299606932160">"Hier kannst du auswählen, wie lange Nachrichten sichtbar sind, die eine Reaktion erfordern, aber nur vorübergehend angezeigt werden.\n\nDiese Einstellung wird nicht von allen Apps unterstützt."</string>
-    <string name="accessibility_long_press_timeout_preference_title" msgid="5029685114164868477">"Reaktionszeit Berühren/Halten"</string>
+    <string name="accessibility_long_press_timeout_preference_title" msgid="5029685114164868477">"\"Berühren und halten\"-Reaktionszeit"</string>
     <string name="accessibility_display_inversion_preference_title" msgid="3852635518618938998">"Farbumkehr"</string>
     <string name="accessibility_display_inversion_preference_subtitle" msgid="69291255322175323">"Kann sich auf die Leistung auswirken"</string>
     <string name="accessibility_autoclick_preference_title" msgid="9164599088410340405">"Verweildauer"</string>
diff --git a/tests/CarDeveloperOptions/res/values-es/strings.xml b/tests/CarDeveloperOptions/res/values-es/strings.xml
index e381ae4..8f0ec45 100644
--- a/tests/CarDeveloperOptions/res/values-es/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-es/strings.xml
@@ -1178,7 +1178,7 @@
     <string name="brightness" msgid="7309120144111305275">"Nivel de brillo"</string>
     <string name="brightness_title" msgid="5660190946911149690">"Brillo"</string>
     <string name="brightness_summary" msgid="8687101964451818730">"Ajustar el brillo de la pantalla"</string>
-    <string name="auto_brightness_title" msgid="908511534369820426">"Brillo automático"</string>
+    <string name="auto_brightness_title" msgid="908511534369820426">"Brillo adaptativo"</string>
     <string name="auto_brightness_summary_on" msgid="121488862610275737">"Activado"</string>
     <string name="auto_brightness_summary_off" msgid="8569141123211510256">"Desactivado"</string>
     <string name="auto_brightness_summary_very_low" msgid="7625647285740629347">"El brillo preferido es muy bajo"</string>
@@ -1196,7 +1196,7 @@
     <string name="auto_brightness_off_summary" msgid="6162650416289359104">"No ajustar en función de la luz ambiental"</string>
     <string name="auto_brightness_very_high_summary" msgid="7202032980509583918">"Aumenta el uso de la batería"</string>
     <string name="auto_brightness_disclaimer" msgid="5416696351199148809">"Optimiza el brillo en función de la luz ambiental. Puedes ajustarlo temporalmente aunque actives esta función."</string>
-    <string name="auto_brightness_description" msgid="8209140379089535411">"El brillo de la pantalla se ajustará automáticamente según el entorno y las actividades que hagas. Puedes mover el control deslizante para que la función de brillo adaptable reconozca tus preferencias."</string>
+    <string name="auto_brightness_description" msgid="8209140379089535411">"El brillo de la pantalla se ajustará automáticamente según el entorno y las actividades que hagas. Puedes mover el control deslizante para que la función de brillo adaptativo reconozca tus preferencias."</string>
     <string name="display_white_balance_title" msgid="5747260735311935143">"Balance de blancos de pantalla"</string>
     <string name="adaptive_sleep_title" msgid="3237620948260957018">"Modo privado"</string>
     <string name="adaptive_sleep_summary_on" msgid="6670369739228487082">"Activado: la pantalla no se apagará si estás mirándola"</string>
diff --git a/tests/CarDeveloperOptions/res/values-gl/strings.xml b/tests/CarDeveloperOptions/res/values-gl/strings.xml
index 83e3f2a..a025faa 100644
--- a/tests/CarDeveloperOptions/res/values-gl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-gl/strings.xml
@@ -2546,8 +2546,8 @@
     <string name="personal_data_section_title" msgid="9161854418510071558">"Datos persoais"</string>
     <string name="backup_data_title" msgid="4461508563849583624">"Realizar copia de seguranza dos meus datos"</string>
     <string name="backup_data_summary" msgid="555459891017933746">"Crea unha copia de seguranza dos datos da aplicación, dos contrasinais das redes wifi e doutras configuracións en servidores de Google"</string>
-    <string name="backup_configure_account_title" msgid="1534734650559070294">"Conta de copia seguranza"</string>
-    <string name="backup_data_management_title" msgid="6299288795610243508">"Xestionar conta de copia de seguranza"</string>
+    <string name="backup_configure_account_title" msgid="1534734650559070294">"Copia de seguranza: conta"</string>
+    <string name="backup_data_management_title" msgid="6299288795610243508">"Xestionar conta para a copia de seguranza"</string>
     <string name="include_app_data_title" msgid="6117211611131913293">"Incluír datos de aplicacións"</string>
     <string name="auto_restore_title" msgid="8367486774010915221">"Restauración automática"</string>
     <string name="auto_restore_summary" msgid="1941047568966428377">"Cando volvas instalar unha aplicación, restaura a configuración e os datos dos que fixeches unha copia de seguranza"</string>
diff --git a/tests/CarDeveloperOptions/res/values-in/strings.xml b/tests/CarDeveloperOptions/res/values-in/strings.xml
index d7e79d6..36f93af 100644
--- a/tests/CarDeveloperOptions/res/values-in/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-in/strings.xml
@@ -531,11 +531,11 @@
     <string name="crypt_keeper_warn_wipe" msgid="700814581500057050">"Peringatan: Perangkat Anda akan dihapus setelah <xliff:g id="COUNT">^1</xliff:g> percobaan gagal lagi dalam pembukaan kunci!"</string>
     <string name="crypt_keeper_enter_password" msgid="726933635335219421">"Ketikkan sandi Anda"</string>
     <string name="crypt_keeper_failed_title" msgid="1906382607060855782">"Enkripsi gagal"</string>
-    <string name="crypt_keeper_failed_summary" product="tablet" msgid="7844833877734529625">"Enkripsi terputus dan tidak dapat diselesaikan. Akibatnya, data pada tablet Anda tidak dapat diakses lagi. \n\n Untuk terus menggunakan tablet, Anda perlu mengembalikannya ke setelan pabrik. Saat menyiapkan tablet setelah mengembalikannya ke setelan pabrik, Anda memiliki kesempatan untuk memulihkan data apa pun yang telah di-backup ke Akun Google."</string>
-    <string name="crypt_keeper_failed_summary" product="default" msgid="2895589681839090312">"Enkripsi terputus dan tidak dapat diselesaikan. Akibatnya, data di ponsel Anda tidak dapat diakses lagi. \n\n Untuk terus menggunakan ponsel, Anda perlu mengembalikannya ke setelan pabrik. Saat menyiapkan ponsel setelah mengembalikannya ke setelan pabrik, Anda memiliki kesempatan untuk memulihkan data apa pun yang telah di-backup ke Akun Google."</string>
+    <string name="crypt_keeper_failed_summary" product="tablet" msgid="7844833877734529625">"Enkripsi terputus dan tidak dapat diselesaikan. Akibatnya, data pada tablet Anda tidak dapat diakses lagi. \n\n Untuk terus menggunakan tablet, Anda perlu mengembalikannya ke setelan pabrik. Saat menyiapkan tablet setelah mengembalikannya ke setelan pabrik, Anda memiliki kesempatan untuk memulihkan data apa pun yang telah dicadangkan ke Akun Google."</string>
+    <string name="crypt_keeper_failed_summary" product="default" msgid="2895589681839090312">"Enkripsi terputus dan tidak dapat diselesaikan. Akibatnya, data di ponsel Anda tidak dapat diakses lagi. \n\n Untuk terus menggunakan ponsel, Anda perlu mengembalikannya ke setelan pabrik. Saat menyiapkan ponsel setelah mengembalikannya ke setelan pabrik, Anda memiliki kesempatan untuk memulihkan data apa pun yang telah dicadangkan ke Akun Google."</string>
     <string name="crypt_keeper_data_corrupt_title" msgid="6561535293845985713">"Dekripsi gagal"</string>
-    <string name="crypt_keeper_data_corrupt_summary" product="tablet" msgid="7018748502706237323">"Sandi yang Anda masukkan benar, namun sayangnya data rusak. \n\nUntuk melanjutkan dengan tablet, Anda perlu mengembalikannya ke setelan pabrik. Saat menyiapkan tablet setelah disetel ulang, akan ada kesempatan untuk memulihkan data apa pun yang telah di-backup ke Akun Google."</string>
-    <string name="crypt_keeper_data_corrupt_summary" product="default" msgid="5798580588985326937">"Sandi yang Anda masukkan benar, namun sayangnya data rusak. \n\nUntuk melanjutkan dengan ponsel, Anda perlu mengembalikannya ke setelan pabrik. Jika Anda menyiapkan ponsel setelah setel ulang, akan ada kesempatan untuk memulihkan data apa pun yang telah di-backup ke Akun Google."</string>
+    <string name="crypt_keeper_data_corrupt_summary" product="tablet" msgid="7018748502706237323">"Sandi yang Anda masukkan benar, namun sayangnya data rusak. \n\nUntuk melanjutkan dengan tablet, Anda perlu mengembalikannya ke setelan pabrik. Saat menyiapkan tablet setelah disetel ulang, akan ada kesempatan untuk memulihkan data apa pun yang telah dicadangkan ke Akun Google."</string>
+    <string name="crypt_keeper_data_corrupt_summary" product="default" msgid="5798580588985326937">"Sandi yang Anda masukkan benar, namun sayangnya data rusak. \n\nUntuk melanjutkan dengan ponsel, Anda perlu mengembalikannya ke setelan pabrik. Jika Anda menyiapkan ponsel setelah setel ulang, akan ada kesempatan untuk memulihkan data apa pun yang telah dicadangkan ke Akun Google."</string>
     <string name="crypt_keeper_switch_input_method" msgid="4744137470890459582">"Beralih metode masukan"</string>
     <string name="suggested_lock_settings_title" msgid="1518155558803371661">"Amankan ponsel Anda"</string>
     <string name="suggested_lock_settings_summary" product="tablet" msgid="1861066918594412519">"Setel kunci layar untuk melindungi tablet"</string>
@@ -1437,7 +1437,7 @@
     <string name="storage_dialog_unmounted" msgid="515810851912430933">"<xliff:g id="NAME_0">^1</xliff:g> ini dikeluarkan dengan aman, tetapi masih ada. \n\nUntuk menggunakan <xliff:g id="NAME_1">^1</xliff:g> ini, Anda harus memasangnya terlebih dahulu."</string>
     <string name="storage_dialog_unmountable" msgid="7082856306456936054">"<xliff:g id="NAME_0">^1</xliff:g> ini rusak. \n\nUntuk menggunakan <xliff:g id="NAME_1">^1</xliff:g> ini, Anda harus menyiapkannya terlebih dahulu."</string>
     <string name="storage_dialog_unsupported" msgid="8274023677580782553">"Perangkat ini tidak mendukung <xliff:g id="NAME_0">^1</xliff:g> ini. \n\nUntuk menggunakan <xliff:g id="NAME_1">^1</xliff:g> ini dengan perangkat ini, Anda harus menyiapkannya terlebih dahulu."</string>
-    <string name="storage_internal_format_details" msgid="2780806013122012384">"Setelah memformat, Anda dapat menggunakan <xliff:g id="NAME_0">^1</xliff:g> ini di perangkat lain. \n\nSemua data di <xliff:g id="NAME_1">^1</xliff:g> ini akan dihapus. Pertimbangkan untuk melakukan backup terlebih dahulu. \n\n"<b>"Backup foto &amp; media lain"</b>" \nPindahkan file media ke penyimpanan alternatif di perangkat ini, atau transfer ke komputer menggunakan kabel USB. \n\n"<b>"Backup aplikasi"</b>" \nSemua aplikasi yang disimpan di <xliff:g id="NAME_6">^1</xliff:g> ini akan di-uninstal dan datanya akan dihapus. Untuk menyimpan aplikasi tersebut, pindahkan ke penyimpanan alternatif di perangkat ini."</string>
+    <string name="storage_internal_format_details" msgid="2780806013122012384">"Setelah memformat, Anda dapat menggunakan <xliff:g id="NAME_0">^1</xliff:g> ini di perangkat lain. \n\nSemua data di <xliff:g id="NAME_1">^1</xliff:g> ini akan dihapus. Pertimbangkan untuk melakukan pencadangan terlebih dahulu. \n\n"<b>"Cadangkan foto &amp; media lain"</b>" \nPindahkan file media ke penyimpanan alternatif di perangkat ini, atau transfer ke komputer menggunakan kabel USB. \n\n"<b>"Cadangkan aplikasi"</b>" \nSemua aplikasi yang disimpan di <xliff:g id="NAME_6">^1</xliff:g> ini akan di-uninstal dan datanya akan dihapus. Untuk menyimpan aplikasi tersebut, pindahkan ke penyimpanan alternatif di perangkat ini."</string>
     <string name="storage_internal_unmount_details" msgid="4667435317528624039"><b>"Saat Anda mengeluarkan <xliff:g id="NAME_0">^1</xliff:g> ini, aplikasi yang tersimpan di situ tidak akan berfungsi lagi, dan file media yang tersimpan di situ baru dapat tersedia jika dicolokkan kembali."</b>" \n\n <xliff:g id="NAME_1">^1</xliff:g> ini diformat untuk berfungsi hanya di perangkat ini dan tidak akan berfungsi di perangkat lain."</string>
     <string name="storage_internal_forget_details" msgid="5655856574682184453">"Untuk menggunakan aplikasi, foto, atau data dalam <xliff:g id="NAME">^1</xliff:g> ini, colokkan kembali. \n\nAtau, Anda dapat memilih untuk melupakan penyimpanan ini jika perangkat tidak tersedia. \n\nJika Anda memilih untuk melupakan, semua data dalam perangkat ini akan hilang selamanya. \n\nNanti Anda dapat memasang ulang aplikasi, tetapi datanya yang disimpan di perangkat ini akan hilang."</string>
     <string name="storage_internal_forget_confirm_title" msgid="331032276130605241">"Lupakan <xliff:g id="NAME">^1</xliff:g>?"</string>
@@ -2542,7 +2542,7 @@
     <string name="privacy_settings_title" msgid="3573891462732375772">"Backup"</string>
     <string name="backup_summary_state_on" msgid="1725597360282574647">"Aktif"</string>
     <string name="backup_summary_state_off" msgid="7138020503288730492">"Nonaktif"</string>
-    <string name="backup_section_title" msgid="8177209731777904656">"Mencadangkan &amp; memulihkan"</string>
+    <string name="backup_section_title" msgid="8177209731777904656">"Pencadangan &amp; pemulihan"</string>
     <string name="personal_data_section_title" msgid="9161854418510071558">"Data pribadi"</string>
     <string name="backup_data_title" msgid="4461508563849583624">"Cadangkan data saya"</string>
     <string name="backup_data_summary" msgid="555459891017933746">"Mencadangkan data aplikasi, sandi Wi-Fi, dan setelan lainnya ke server Google"</string>
diff --git a/tests/CarDeveloperOptions/res/values-kn/strings.xml b/tests/CarDeveloperOptions/res/values-kn/strings.xml
index b9b14d4..a361d01 100644
--- a/tests/CarDeveloperOptions/res/values-kn/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-kn/strings.xml
@@ -505,7 +505,7 @@
     <string name="fingerprint_delete_message" msgid="5895802741486967970">"ನೀವು ಈ ಫಿಂಗರ್‌ ಫ್ರಿಂಟ್ ಅಳಿಸಲು ಬಯಸುವಿರಾ?"</string>
     <string name="fingerprint_last_delete_message" msgid="3346252479778971442">"ನಿಮ್ಮ ಫೋನ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು, ಖರೀದಿಗಳನ್ನು ದೃಢೀಕರಿಸಲು ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ನಿಮ್ಮ ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್‌ಗಳನ್ನು ಬಳಸಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ."</string>
     <string name="fingerprint_last_delete_message_profile_challenge" msgid="5385095150532247025">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು, ಖರೀದಿಗಳನ್ನು ದೃಢೀಕರಿಸಲು ಅಥವಾ ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ನಿಮ್ಮ ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್‌ಗಳನ್ನು ಬಳಸಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ."</string>
-    <string name="fingerprint_last_delete_confirm" msgid="7984595457589664004">"ಹೌದು, ತೆಗೆದುಹಾಕು"</string>
+    <string name="fingerprint_last_delete_confirm" msgid="7984595457589664004">"ಹೌದು, ತೆಗೆದುಹಾಕಿ"</string>
     <string name="crypt_keeper_settings_title" msgid="839783588093862748">"ಎನ್‌ಕ್ರಿಪ್ಷನ್"</string>
     <string name="crypt_keeper_encrypt_title" product="tablet" msgid="2292129135369853167">"ಟ್ಯಾಬ್ಲೆಟ್ ಅನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡು"</string>
     <string name="crypt_keeper_encrypt_title" product="default" msgid="3110852053238357832">"ಫೋನ್ ಎನ್‌ಕ್ರಿಪ್ಟ್"</string>
@@ -629,7 +629,7 @@
     <string name="unlock_disable_frp_warning_content_unknown_fingerprint_profile" msgid="1201259228331105948">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಇಲ್ಲದೆ ಪ್ರೊಫೈಲ್ ರಕ್ಷಣೆ ವೈಶಿಷ್ಟ್ಯಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸುವುದಿಲ್ಲ.<xliff:g id="EMPTY_LINE">
 
 </xliff:g>ಈ ಪ್ರೊಫೈಲ್‌ನಿಂದ ನಿಮ್ಮ ಉಳಿಸಲಾದ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್‌ಗಳನ್ನು ಸಹ ತೆಗೆದುಹಾಕಲಾಗುವುದು ಮತ್ತು ನಿಮ್ಮ ಫೋನ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು, ಖರೀದಿಗಳನ್ನು ದೃಢೀಕರಿಸಲು ಅಥವಾ ಅವುಗಳ ಮೂಲಕ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ."</string>
-    <string name="unlock_disable_frp_warning_ok" msgid="2373890505202766456">"ಹೌದು, ತೆಗೆದುಹಾಕು"</string>
+    <string name="unlock_disable_frp_warning_ok" msgid="2373890505202766456">"ಹೌದು, ತೆಗೆದುಹಾಕಿ"</string>
     <string name="unlock_change_lock_pattern_title" msgid="7622476883851319877">"ಅನ್‌ಲಾಕ್ ನಮೂನೆಯನ್ನು ಬದಲಾಯಿಸಿ"</string>
     <string name="unlock_change_lock_pin_title" msgid="6671224158800812238">"ಅನ್‌ಲಾಕ್ ಪಿನ್‌ ಬದಲಾಯಿಸಿ"</string>
     <string name="unlock_change_lock_password_title" msgid="7886432065775170719">"ಅನ್‌ಲಾಕ್ ಪಾಸ್‌ವರ್ಡ್‌ ಬದಲಾಯಿಸಿ"</string>
@@ -1265,14 +1265,14 @@
     <string name="sim_lock_settings_summary_off" msgid="348656447968142307">"ಆಫ್"</string>
     <string name="sim_lock_settings_summary_on" msgid="3440707542514810045">"ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="sim_lock_settings_title" msgid="877336472752342977">"ಸಿಮ್‌ ಕಾರ್ಡ್‌ ಲಾಕ್"</string>
-    <string name="sim_pin_toggle" msgid="2026507420678167488">"ಸಿಮ್‌ ಕಾರ್ಡ್ ಲಾಕ್ ಮಾಡು"</string>
+    <string name="sim_pin_toggle" msgid="2026507420678167488">"ಸಿಮ್‌ ಕಾರ್ಡ್ ಲಾಕ್ ಮಾಡಿ"</string>
     <string name="sim_lock_on" product="tablet" msgid="3917977767884071323">"ಟ್ಯಾಬ್ಲೆಟ್ ಬಳಸಲು ಪಿನ್‌ ಅಗತ್ಯವಿದೆ"</string>
     <string name="sim_lock_on" product="default" msgid="1363159192182487883">"ಫೋನ್ ಬಳಸಲು ಪಿನ್‌ ಅಗತ್ಯವಿದೆ"</string>
     <string name="sim_lock_off" product="tablet" msgid="8428566346685080195">"ಟ್ಯಾಬ್ಲೆಟ್ ಬಳಸಲು ಪಿನ್‌ ಅಗತ್ಯವಿದೆ"</string>
     <string name="sim_lock_off" product="default" msgid="5873747770983496755">"ಫೋನ್ ಬಳಸಲು ಪಿನ್‌ ಅಗತ್ಯವಿದೆ"</string>
     <string name="sim_pin_change" msgid="5615972926944053213">"ಸಿಮ್‌ ಪಿನ್‌ ಬದಲಾಯಿಸು"</string>
     <string name="sim_enter_pin" msgid="149201344579560481">"ಸಿಮ್‌ ಪಿನ್‌"</string>
-    <string name="sim_enable_sim_lock" msgid="4478794975656337476">"ಸಿಮ್‌ ಕಾರ್ಡ್ ಲಾಕ್ ಮಾಡು"</string>
+    <string name="sim_enable_sim_lock" msgid="4478794975656337476">"ಸಿಮ್‌ ಕಾರ್ಡ್ ಲಾಕ್ ಮಾಡಿ"</string>
     <string name="sim_disable_sim_lock" msgid="394864376519820956">"ಸಿಮ್‌ ಕಾರ್ಡ್‌ ಅನ್‌ಲಾಕ್ ಮಾಡು"</string>
     <string name="sim_enter_old" msgid="8984991229691526849">"ಹಳೆಯ ಸಿಮ್‌ ಪಿನ್‌"</string>
     <string name="sim_enter_new" msgid="1720792957661107585">"ಹೊಸ ಸಿಮ್‌ ಪಿನ್‌"</string>
@@ -3785,7 +3785,7 @@
     <string name="zen_access_revoke_warning_dialog_title" msgid="6850994585577513299">"<xliff:g id="APP">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅಡಚಣೆ ಮಾಡಬೇಡಿಗೆ ಪ್ರವೇಶವನ್ನು ಹಿಂತೆಗೆದುಕೊಳ್ಳುವುದೇ?"</string>
     <string name="zen_access_revoke_warning_dialog_summary" msgid="3487422193181311403">"ಈ ಅಪ್ಲಿಕೇಶನ್ ರಚಿಸಿರುವಂತಹ ಎಲ್ಲ ಅಡಚಣೆ ಮಾಡಬೇಡಿ ನಿಯಮಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗುತ್ತದೆ."</string>
     <string name="ignore_optimizations_on" msgid="4373971641328943551">"ಆಪ್ಟಿಮೈಸ್ ಮಾಡಬೇಡಿ"</string>
-    <string name="ignore_optimizations_off" msgid="4372289432580282870">"ಆಪ್ಟಿಮೈಸ್ ಮಾಡು"</string>
+    <string name="ignore_optimizations_off" msgid="4372289432580282870">"ಆಪ್ಟಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="ignore_optimizations_on_desc" msgid="2904484569799521559">"ನಿಮ್ಮ ಬ್ಯಾಟರಿಯನ್ನು ತ್ವರಿತವಾಗಿ ಬರಿದಾಗಿಸಬಹುದು. ಹಿನ್ನೆಲೆ ಬ್ಯಾಟರಿ ಬಳಸದಂತೆ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಇನ್ನು ಮುಂದೆ ನಿರ್ಬಂಧಿಸಲಾಗುವುದಿಲ್ಲ."</string>
     <string name="ignore_optimizations_off_desc" msgid="5598702251817814289">"ಉತ್ತಮ ಬ್ಯಾಟರಿ ಬಾಳಿಕೆಗೆ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ"</string>
     <string name="ignore_optimizations_title" msgid="7924345545276166305">"ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಕಡೆಗಣಿಸಲು <xliff:g id="APP">%s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
diff --git a/tests/CarDeveloperOptions/res/values-ro/strings.xml b/tests/CarDeveloperOptions/res/values-ro/strings.xml
index 69a00cc..c94f124 100644
--- a/tests/CarDeveloperOptions/res/values-ro/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-ro/strings.xml
@@ -1672,7 +1672,7 @@
     <string name="location_no_recent_accesses" msgid="6289916310397279890">"Nicio aplicație nu a accesat recent locația"</string>
     <string name="location_high_battery_use" msgid="7177199869979522663">"Utilizare intensă a bateriei"</string>
     <string name="location_low_battery_use" msgid="5030448574501435888">"Utilizare redusă a bateriei"</string>
-    <string name="location_scanning_screen_title" msgid="7663329319689413454">"Scanare prin Wi‑Fi și Bluetooth"</string>
+    <string name="location_scanning_screen_title" msgid="7663329319689413454">"Căutare Wi‑Fi și Bluetooth"</string>
     <string name="location_scanning_wifi_always_scanning_title" msgid="6750542206763112172">"Căutare de rețele Wi-Fi"</string>
     <string name="location_scanning_wifi_always_scanning_description" msgid="4956048135941851712">"Permiteți aplicațiilor și serviciilor să caute permanent rețele Wi-Fi, chiar și atunci când setarea Wi-Fi este dezactivată. Această permisiune poate fi folosită, de exemplu, pentru a îmbunătăți funcțiile și serviciile bazate pe locație."</string>
     <string name="location_scanning_bluetooth_always_scanning_title" msgid="196241746742607453">"Căutare Bluetooth"</string>
diff --git a/tests/CarDeveloperOptions/res/values-sl/strings.xml b/tests/CarDeveloperOptions/res/values-sl/strings.xml
index 60983ad..b6c9413 100644
--- a/tests/CarDeveloperOptions/res/values-sl/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-sl/strings.xml
@@ -1853,7 +1853,7 @@
     <string name="data_size_label" msgid="7790201846922671662">"Uporabniški podatki"</string>
     <string name="external_data_size_label" product="nosdcard" msgid="8004991551882573479">"Podatki na pogonu USB"</string>
     <string name="external_data_size_label" product="default" msgid="1097584278225902734">"Kartica SD"</string>
-    <string name="uninstall_text" msgid="4859715815689705801">"Odstrani"</string>
+    <string name="uninstall_text" msgid="4859715815689705801">"Odmesti"</string>
     <string name="uninstall_all_users_text" msgid="2620518509352561416">"Odstrani za vse uporabnike"</string>
     <string name="install_text" msgid="2798092278891807849">"Namesti"</string>
     <string name="disable_text" msgid="5065834603951474397">"Onemogoči"</string>
diff --git a/tests/CarDeveloperOptions/res/values-te/strings.xml b/tests/CarDeveloperOptions/res/values-te/strings.xml
index 9f09c49..dd7b366 100644
--- a/tests/CarDeveloperOptions/res/values-te/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-te/strings.xml
@@ -1303,7 +1303,7 @@
     <string name="system_update_settings_list_item_title" msgid="1907497454722790033">"సిస్టమ్ అప్‌డేట్‌లు"</string>
     <string name="system_update_settings_list_item_summary" msgid="3497456690691907873"></string>
     <string name="firmware_version" msgid="547095584029938749">"Android వెర్షన్"</string>
-    <string name="security_patch" msgid="483709031051932208">"Android భద్రతా అతికింపు స్థాయి"</string>
+    <string name="security_patch" msgid="483709031051932208">"Android భద్రతా ప్యాచ్ స్థాయి"</string>
     <string name="model_info" msgid="1729765474260797594">"మోడల్"</string>
     <string name="model_summary" msgid="8781425868254352168">"మోడల్: %1$s"</string>
     <string name="hardware_info" msgid="174270144950621815">"మోడల్ &amp; హార్డ్‌వేర్"</string>
@@ -2459,7 +2459,7 @@
     <string name="battery_saver_turn_on_automatically_never" msgid="2623381258359775227">"ఎప్పటికీ వద్దు"</string>
     <string name="battery_saver_turn_on_automatically_pct" msgid="434270432432390307">"<xliff:g id="PERCENT">%1$s</xliff:g> బ్యాటరీ ఉన్నప్పుడు"</string>
     <string name="battery_percentage" msgid="7782252476471033843">"బ్యాటరీ పవర్ శాతం"</string>
-    <string name="battery_percentage_description" msgid="9219875229166700610">"స్థితి బార్‌లో బ్యాటరీ పవర్ శాతాన్ని చూపు"</string>
+    <string name="battery_percentage_description" msgid="9219875229166700610">"స్టేటస్ బార్‌లో బ్యాటరీ పవర్ శాతాన్ని చూపుతుంది"</string>
     <string name="process_stats_summary_title" msgid="9189588417488537954">"ప్రాసెస్ గణాంకాలు"</string>
     <string name="process_stats_summary" msgid="8077998499161221885">"అమలవుతున్న ప్రాసెస్‌ల గురించి అసాధారణమైన గణాంకాలు"</string>
     <string name="app_memory_use" msgid="5126237308545653706">"మెమరీ వినియోగం"</string>
@@ -2519,13 +2519,13 @@
     <string name="credentials_install" product="nosdcard" msgid="8509362500537206883">"నిల్వ నుండి ఇన్‌స్టాల్ చేయండి"</string>
     <string name="credentials_install" product="default" msgid="8997183776710118353">"SD కార్డు నుండి ఇన్‌స్టాల్ చేయండి"</string>
     <string name="credentials_install_summary" product="nosdcard" msgid="3426661965567059596">"నిల్వ నుండి స‌ర్టిఫికెట్‌ల‌ను ఇన్‌స్టాల్ చేయండి"</string>
-    <string name="credentials_install_summary" product="default" msgid="4943897416156671633">"SD కార్డు నుండి ప్రమాణపత్రాలను ఇన్‌స్టాల్ చేయండి"</string>
+    <string name="credentials_install_summary" product="default" msgid="4943897416156671633">"SD కార్డు నుండి సర్టిఫికెట్‌లను ఇన్‌స్టాల్ చేయండి"</string>
     <string name="credentials_reset" msgid="355080737664731678">"ఆధారాలను క్లియర్ చేయండి"</string>
     <string name="credentials_reset_summary" msgid="7622528359699428555">"అన్ని స‌ర్టిఫికెట్‌ల‌ను తీసివేయండి"</string>
     <string name="trusted_credentials" msgid="6989242522455395200">"విశ్వసనీయ ఆధారాలు"</string>
-    <string name="trusted_credentials_summary" msgid="7411781319056251582">"విశ్వసనీయ CA స‌ర్టిఫికెట్‌ల‌ను ప్రదర్శించు"</string>
+    <string name="trusted_credentials_summary" msgid="7411781319056251582">"విశ్వసనీయ CA స‌ర్టిఫికెట్‌ల‌ను ప్రదర్శిస్తుంది"</string>
     <string name="user_credentials" msgid="8365731467650306757">"వినియోగదారు ఆధారాలు"</string>
-    <string name="user_credentials_summary" msgid="7350223899317423252">"నిల్వ చేసిన ఆధారాలను వీక్షించండి మరియు సవరించండి"</string>
+    <string name="user_credentials_summary" msgid="7350223899317423252">"నిల్వ ఉన్న ఆధారాలను చూడండి, వాటిని సవరించండి"</string>
     <string name="advanced_security_title" msgid="286883005673855845">"అధునాతనం"</string>
     <string name="credential_storage_type" msgid="2585337320206095255">"నిల్వ రకం"</string>
     <string name="credential_storage_type_hardware" msgid="5054143224259023600">"హార్డ్‌వేర్ మద్దతు"</string>
@@ -3397,7 +3397,7 @@
       <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> వర్గం</item>
     </plurals>
     <string name="no_channels" msgid="8884254729302501652">"ఈ యాప్ ఏ నోటిఫికేషన్‌లను పోస్ట్ చేయలేదు"</string>
-    <string name="app_settings_link" msgid="8465287765715790984">"యాప్‌లో అదనపు సెట్టింగ్‌లు"</string>
+    <string name="app_settings_link" msgid="8465287765715790984">"యాప్‌లోని అదనపు సెట్టింగ్‌లు"</string>
     <string name="app_notification_listing_summary_zero" msgid="4047782719487686699">"అన్ని యాప్‌లలో ఆన్ చేయబడ్డాయి"</string>
     <plurals name="app_notification_listing_summary_others" formatted="false" msgid="1161774065480666519">
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> యాప్‌లలో ఆఫ్ చేయబడింది</item>
@@ -3443,9 +3443,9 @@
     <string name="zen_mode_unknown_app_set_behavior" msgid="5666462954329932302">"ఈ సెట్టింగ్‌లు ప్రస్తుతం మార్చబడవు. ఒక యాప్ అనుకూల ప్రవర్తనతో స్వయంచాలకంగా అంతరాయం కలిగించవద్దుని ఆన్ చేసింది."</string>
     <string name="zen_mode_qs_set_behavior" msgid="788646569296973998">"ఈ సెట్టింగ్‌లు ప్రస్తుతం మార్చబడవు. అనుకూల ప్రవర్తనతో అంతరాయం కలిగించవద్దు మాన్యువల్‌గా ఆన్ చేయబడింది."</string>
     <string name="zen_schedule_rule_type_name" msgid="4516851728113801329">"సమయం"</string>
-    <string name="zen_schedule_rule_enabled_toast" msgid="1742354493045049048">"పేర్కొన్న సమయాల్లో అంతరాయం కలిగించవద్దు ఆన్ అయ్యేలా స్వయంచాలక నిబంధన సెట్ చేయబడింది"</string>
+    <string name="zen_schedule_rule_enabled_toast" msgid="1742354493045049048">"నిర్దేశిత‌ సమయాల్లో \'అంతరాయం కలిగించవద్దు\' ఆన్ అయ్యేలా ఆటోమేటిక్ నిబంధన సెట్ చేయబడింది"</string>
     <string name="zen_event_rule_type_name" msgid="7467729997336583342">"ఈవెంట్"</string>
-    <string name="zen_event_rule_enabled_toast" msgid="7087368268966855976">"పేర్కొన్న సందర్భాల్లో అంతరాయం కలిగించవద్దు ఆన్ అయ్యేలా స్వయంచాలక నిబంధన సెట్ చేయబడింది"</string>
+    <string name="zen_event_rule_enabled_toast" msgid="7087368268966855976">"నిర్దిష్ట‌ సందర్భాల్లో \'అంతరాయం కలిగించవద్దు\' ఆన్ అయ్యేలా ఆటోమేటిక్ నిబంధన సెట్ చేయబడింది"</string>
     <string name="zen_mode_event_rule_calendar" msgid="6088077103908487442">"వీటి సంబంధిత ఈవెంట్‌ల సమయంలో"</string>
     <string name="zen_mode_event_rule_summary_calendar_template" msgid="4027207992040792657">"<xliff:g id="CALENDAR">%1$s</xliff:g> సంబంధిత ఈవెంట్‌ల సమయంలో"</string>
     <string name="zen_mode_event_rule_summary_any_calendar" msgid="7590085295784895885">"ఏదైనా క్యాలెండర్"</string>
@@ -3701,10 +3701,10 @@
       <item quantity="other">యాప్‌ల కారణంగా బ్యాటరీ ఖాళీ అవుతోంది</item>
       <item quantity="one"><xliff:g id="APP">%1$s</xliff:g> కారణంగా బ్యాటరీ ఖాళీ అవుతోంది</item>
     </plurals>
-    <string name="high_power_filter_on" msgid="5294209328473386403">"అనుకూలీకరించనివి"</string>
+    <string name="high_power_filter_on" msgid="5294209328473386403">"ఆప్టిమైజ్ చేయనివి"</string>
     <string name="high_power_on" msgid="3573501822510580334">"అనుకూలీకరించబడలేదు"</string>
     <string name="high_power_off" msgid="5906679734326490426">"బ్యాటరీ వినియోగాన్ని ఆప్టిమైజ్ చేస్తోంది"</string>
-    <string name="high_power_system" msgid="739584574711292753">"బ్యాటరీ అనుకూలీకరణ అందుబాటులో లేదు"</string>
+    <string name="high_power_system" msgid="739584574711292753">"బ్యాటరీ ఆప్టిమైజేషన్ అందుబాటులో లేదు"</string>
     <string name="high_power_desc" msgid="333756885680362741">"బ్యాటరీ అనుకూలీకరణను వర్తింపజేయదు. మీ బ్యాటరీ మరింత శీఘ్రంగా వినియోగించబడవచ్చు."</string>
     <string name="high_power_prompt_title" msgid="2805745781720454052">"ఎల్లప్పుడూ నేపథ్యంలో అమలు కావడానికి అనువర్తనాన్ని అనుమతించాలా?"</string>
     <string name="high_power_prompt_body" msgid="8067395096053552289">"ఎల్లప్పుడూ నేపథ్యంలో అమలు కావడానికి <xliff:g id="APP_NAME">%1$s</xliff:g>ని అనుమతిస్తే బ్యాటరీ జీవితకాలం తగ్గిపోవచ్చు. \n\nమీరు తర్వాత సెట్టింగ్‌లు &gt; యాప్‌లు &amp; నోటిఫికేషన్‌లలోకి వెళ్లి దీనిని మార్చవచ్చు."</string>
@@ -3864,7 +3864,7 @@
     <string name="users_summary" msgid="6693338169439092387">"<xliff:g id="USER_NAME">%1$s</xliff:g>గా సైన్ ఇన్ చేసారు"</string>
     <string name="payment_summary" msgid="1381646849276543242">"<xliff:g id="APP_NAME">%1$s</xliff:g> డిఫాల్ట్‌గా ఉంది"</string>
     <string name="backup_disabled" msgid="6941165814784765643">"బ్యాకప్ నిలిపివేయబడింది"</string>
-    <string name="android_version_summary" msgid="2192751442789395445">"Android <xliff:g id="VERSION">%1$s</xliff:g>కి అప్‌డేట్ చేయబడింది"</string>
+    <string name="android_version_summary" msgid="2192751442789395445">"Android <xliff:g id="VERSION">%1$s</xliff:g>కు అప్‌డేట్ చేయబడింది"</string>
     <string name="android_version_pending_update_summary" msgid="3554543810520655076">"అప్‌డేట్ అందుబాటులో ఉంది"</string>
     <string name="disabled_by_policy_title" msgid="1238318274952958846">"చర్య అనుమతించబడదు"</string>
     <string name="disabled_by_policy_title_adjust_volume" msgid="7094547090629203316">"వాల్యూమ్‌ని మార్చలేరు"</string>
diff --git a/tests/CarDeveloperOptions/res/values-th/strings.xml b/tests/CarDeveloperOptions/res/values-th/strings.xml
index 0455a58..32f93b9 100644
--- a/tests/CarDeveloperOptions/res/values-th/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-th/strings.xml
@@ -3521,7 +3521,7 @@
     <string name="zen_mode_when" msgid="7835420687948416255">"เปิดโดยอัตโนมัติ"</string>
     <string name="zen_mode_when_never" msgid="4506296396609224524">"ไม่ใช้"</string>
     <string name="zen_mode_when_every_night" msgid="3942151668791426194">"ทุกคืน"</string>
-    <string name="zen_mode_when_weeknights" msgid="2412709309122408474">"คืนวันจันทร์-ศุกร์"</string>
+    <string name="zen_mode_when_weeknights" msgid="2412709309122408474">"คืนวันธรรมดา"</string>
     <string name="zen_mode_start_time" msgid="5979122139937561731">"เวลาเริ่มต้น"</string>
     <string name="zen_mode_end_time" msgid="3188578493250909972">"เวลาสิ้นสุด"</string>
     <string name="zen_mode_end_time_next_day_summary_format" msgid="1598612215612648214">"<xliff:g id="FORMATTED_TIME">%s</xliff:g> วันถัดไป"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml b/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml
index d231fe4..7c1e237 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rCN/strings.xml
@@ -2078,7 +2078,7 @@
     <string name="accessibility_control_timeout_preference_title" msgid="2771808346038759474">"操作执行时长"</string>
     <string name="accessibility_content_timeout_preference_summary" msgid="853829064617918179">"请选择您需要阅读的消息的显示时间(只会暂时显示)。\n\n只有部分应用支持这项设置。"</string>
     <string name="accessibility_control_timeout_preference_summary" msgid="8582212299606932160">"请选择提示您执行操作的消息的显示时间(只会暂时显示)。\n\n部分应用可能不支持这项设置。"</string>
-    <string name="accessibility_long_press_timeout_preference_title" msgid="5029685114164868477">"触摸和按住延迟"</string>
+    <string name="accessibility_long_press_timeout_preference_title" msgid="5029685114164868477">"轻触并按住的延迟时间"</string>
     <string name="accessibility_display_inversion_preference_title" msgid="3852635518618938998">"颜色反转"</string>
     <string name="accessibility_display_inversion_preference_subtitle" msgid="69291255322175323">"可能会影响性能"</string>
     <string name="accessibility_autoclick_preference_title" msgid="9164599088410340405">"停留时间"</string>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rTW/arrays.xml b/tests/CarDeveloperOptions/res/values-zh-rTW/arrays.xml
index 12d0899..8b9e3bc 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rTW/arrays.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rTW/arrays.xml
@@ -204,7 +204,7 @@
     <item msgid="8357907018938895462">"啟用 VPN"</item>
     <item msgid="8143812849911310973">"寫入桌布"</item>
     <item msgid="6266277260961066535">"輔助結構"</item>
-    <item msgid="7715498149883482300">"輔助螢幕擷取畫面"</item>
+    <item msgid="7715498149883482300">"輔助螢幕截圖"</item>
     <item msgid="4046679376726313293">"讀取手機狀態"</item>
     <item msgid="6329507266039719587">"新增語音留言"</item>
     <item msgid="7692440726415391408">"使用 SIP"</item>
@@ -232,7 +232,7 @@
     <item msgid="3597797992398484655">"讀取日曆"</item>
     <item msgid="2705975774250907343">"修改日曆"</item>
     <item msgid="4668747371441932697">"定位"</item>
-    <item msgid="1487578921720243646">"發佈通知"</item>
+    <item msgid="1487578921720243646">"發布通知"</item>
     <item msgid="4636080349724146638">"位置"</item>
     <item msgid="673510900286463926">"撥打電話"</item>
     <item msgid="542083422784609790">"讀取簡訊/MMS"</item>
@@ -271,7 +271,7 @@
     <item msgid="5117506254221861929">"啟用 VPN"</item>
     <item msgid="8291198322681891160">"寫入桌布"</item>
     <item msgid="7106921284621230961">"輔助結構"</item>
-    <item msgid="4496533640894624799">"輔助螢幕擷取畫面"</item>
+    <item msgid="4496533640894624799">"輔助螢幕截圖"</item>
     <item msgid="2598847264853993611">"讀取手機狀態"</item>
     <item msgid="9215610846802973353">"新增語音留言"</item>
     <item msgid="9186411956086478261">"使用 SIP"</item>
diff --git a/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml b/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml
index 2179468..0b7e7a0 100644
--- a/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml
+++ b/tests/CarDeveloperOptions/res/values-zh-rTW/strings.xml
@@ -3396,7 +3396,7 @@
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 個類別</item>
       <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 個類別</item>
     </plurals>
-    <string name="no_channels" msgid="8884254729302501652">"這個應用程式未發佈任何通知"</string>
+    <string name="no_channels" msgid="8884254729302501652">"這個應用程式未發布任何通知"</string>
     <string name="app_settings_link" msgid="8465287765715790984">"應用程式中的其他設定"</string>
     <string name="app_notification_listing_summary_zero" msgid="4047782719487686699">"已針對所有應用程式開啟這項設定"</string>
     <plurals name="app_notification_listing_summary_others" formatted="false" msgid="1161774065480666519">
@@ -3753,10 +3753,10 @@
     <string name="background_check_title" msgid="4136736684290307970">"完整背景存取權"</string>
     <string name="assist_access_context_title" msgid="2274614501747710439">"使用畫面中的文字"</string>
     <string name="assist_access_context_summary" msgid="5867997494395842785">"允許小幫手應用程式存取畫面中的文字內容"</string>
-    <string name="assist_access_screenshot_title" msgid="1991014038776117688">"使用螢幕擷取畫面"</string>
+    <string name="assist_access_screenshot_title" msgid="1991014038776117688">"使用螢幕截圖"</string>
     <string name="assist_access_screenshot_summary" msgid="3010943864000489424">"允許小幫手應用程式存取畫面擷圖"</string>
     <string name="assist_flash_title" msgid="8852484250748551092">"閃爍螢幕"</string>
-    <string name="assist_flash_summary" msgid="6697095786317559129">"小幫手應用程式存取螢幕或螢幕擷取畫面中的文字時,螢幕邊緣會閃爍"</string>
+    <string name="assist_flash_summary" msgid="6697095786317559129">"小幫手應用程式存取螢幕或螢幕截圖中的文字時,螢幕邊緣會閃爍"</string>
     <string name="assist_footer" msgid="7030121180457472165">"小幫手應用程式可根據你目前瀏覽的畫面資訊,為你提供協助。部分應用程式同時支援啟動器和語音輸入服務,為你提供更完善的服務。"</string>
     <string name="average_memory_use" msgid="5333366040118953945">"記憶體平均用量"</string>
     <string name="maximum_memory_use" msgid="6509872438499846077">"記憶體最高用量"</string>
diff --git a/tests/GarageModeTestApp/res/layout/offcar_testing.xml b/tests/GarageModeTestApp/res/layout/offcar_testing.xml
index c14aa07..1b12752 100644
--- a/tests/GarageModeTestApp/res/layout/offcar_testing.xml
+++ b/tests/GarageModeTestApp/res/layout/offcar_testing.xml
@@ -90,22 +90,14 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:layout_weight="1">
-                <LinearLayout
+                <CheckBox
+                    style="@style/Checkbox"
+                    android:id="@+id/requireIdlenessCheckbox"
                     android:layout_width="match_parent"
                     android:layout_height="match_parent"
-                    android:layout_weight="1">
-                    <TextView
-                        style="@style/SpinnerLabel"
-                        android:text="@string/spinner_label_network_type"/>
-                    <LinearLayout
-                        style="@style/SectionContainer"
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent">
-                        <Spinner
-                            style="@style/Spinner"
-                            android:id="@+id/networkType"/>
-                    </LinearLayout>
-                </LinearLayout>
+                    android:layout_weight="1"
+                    android:checked="true"
+                    android:text="@string/checkbox_require_idleness"/>
                 <CheckBox
                     style="@style/Checkbox"
                     android:id="@+id/requirePersistedCheckbox"
@@ -121,13 +113,6 @@
                 android:weightSum="2">
                 <CheckBox
                     style="@style/Checkbox"
-                    android:id="@+id/requireIdlenessCheckbox"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:text="@string/checkbox_require_idleness"/>
-                <CheckBox
-                    style="@style/Checkbox"
                     android:id="@+id/requireChargingCheckbox"
                     android:layout_width="match_parent"
                     android:layout_height="match_parent"
@@ -137,6 +122,22 @@
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
+                android:layout_weight="1">
+                <TextView
+                    style="@style/SpinnerLabel"
+                    android:text="@string/spinner_label_network_type"/>
+                <LinearLayout
+                    style="@style/SectionContainer"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent">
+                    <Spinner
+                        style="@style/Spinner"
+                        android:id="@+id/networkType"/>
+                </LinearLayout>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:orientation="horizontal">
                 <TextView
@@ -176,4 +177,4 @@
                 android:focusable="false"/>
         </LinearLayout>
     </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/JobSchedulerWrapper.java b/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/JobSchedulerWrapper.java
index 0a38d7f..5908ed9 100644
--- a/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/JobSchedulerWrapper.java
+++ b/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/JobSchedulerWrapper.java
@@ -48,8 +48,8 @@
     private static final int MAX_SECONDS_PER_JOB = 9 * 60; // 9 minutes
     private static final int JOB_OVERLAP_SECONDS = 10;
 
-    @GuardedBy("sExtendedJobInfoMap")
-    private static final Map<Integer, ExtendedJobInfo> sExtendedJobInfoMap = new HashMap<>();
+    @GuardedBy("mExtendedJobInfoMap")
+    private final Map<Integer, ExtendedJobInfo> mExtendedJobInfoMap = new HashMap<>();
 
     private JobScheduler mJobScheduler;
     private Context mContext;
@@ -57,6 +57,7 @@
     private Handler mHandler;
     private Watchdog mWatchdog;
     private Runnable mRefreshWorker;
+    private boolean mStopWhenFinished = false;
 
     private List<JobInfo> mLastJobsList;
     private List<JobInfo> mNewJobs;
@@ -99,18 +100,36 @@
         LOG.d("Starting JobSchedulerWrapper");
         mHandler = new Handler();
         mRefreshWorker = () -> {
-            refresh();
-            mHandler.postDelayed(mRefreshWorker, 1000);
+            refresh(); // Could nullify mHandler
+            if (mHandler != null) {
+                mHandler.postDelayed(mRefreshWorker, 1000);
+            }
         };
         mHandler.postDelayed(mRefreshWorker, 1000);
     }
 
     public void stop() {
+        boolean canStopNow;
+        synchronized (mExtendedJobInfoMap) {
+            canStopNow = mExtendedJobInfoMap.isEmpty();
+        }
+        if (canStopNow) {
+            stopNow();
+        } else {
+            // There are continuing jobs that we need to schedule
+            // in the future, so don't stop now. We'll stop when
+            // we have scheduled those future jobs.
+            mStopWhenFinished = true;
+        }
+    }
+
+    private void stopNow() {
         LOG.d("Stopping JobSchedulerWrapper");
         mHandler.removeCallbacks(mRefreshWorker);
         mRefreshWorker = null;
         mHandler = null;
         mWatchdog = null;
+        mStopWhenFinished = false;
     }
 
     public void scheduleAJob(
@@ -157,12 +176,10 @@
         editor.putInt(PREFS_NEXT_JOB_ID, jobId + 1);
         editor.commit();
 
-        refresh();
-
         if (extraSeconds > 0) {
             // Remember to schedule another job when this one ends
-            synchronized (sExtendedJobInfoMap) {
-                sExtendedJobInfoMap.put(jobId,
+            synchronized (mExtendedJobInfoMap) {
+                mExtendedJobInfoMap.put(jobId,
                                         new ExtendedJobInfo(extraSeconds,
                                                             networkType,
                                                             isChargingRequired,
@@ -179,6 +196,7 @@
                          isChargingRequired,
                          isIdleRequired);
         }
+        refresh();
     }
 
     private void updateListView() {
@@ -254,8 +272,8 @@
         // extension for any of the newly-completed jobs.
         for (JobInfo completedJobInfo : completedJobsList) {
             ExtendedJobInfo extensionInfo;
-            synchronized (sExtendedJobInfoMap) {
-                extensionInfo = sExtendedJobInfoMap.remove(completedJobInfo.getId());
+            synchronized (mExtendedJobInfoMap) {
+                extensionInfo = mExtendedJobInfoMap.remove(completedJobInfo.getId());
             }
             if (extensionInfo != null) {
                 scheduleAJob(extensionInfo.jobLengthSeconds,
@@ -264,6 +282,17 @@
                              extensionInfo.idleRequired);
             }
         }
+        if (mStopWhenFinished) {
+            boolean canStopNow;
+            synchronized (mExtendedJobInfoMap) {
+                canStopNow = mExtendedJobInfoMap.isEmpty();
+            }
+            if (canStopNow) {
+                // We were asked to stop earlier, but we had more
+                // work to do. That work is now done, so stop now.
+                stopNow();
+            }
+        }
         return completedJobsList;
     }
 
diff --git a/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java b/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
index 58bf088..0ae1e1f 100644
--- a/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
@@ -246,7 +246,11 @@
         mPowerStateHandler.sendPowerState(
                 VehicleApPowerStateReq.SHUTDOWN_PREPARE,
                 VehicleApPowerStateShutdownParam.CAN_SLEEP);
-        assertResponse(VehicleApPowerStateReport.SHUTDOWN_PREPARE, 0, true);
+        // The state machine should go to SHUTDOWN_PREPARE, but may
+        // quickly transition to SHUTDOWN_POSTPONE. Report success
+        // if we got to SHUTDOWN_PREPARE, even if we're not there now.
+        assertResponseTransient(VehicleApPowerStateReport.SHUTDOWN_PREPARE, 0, true);
+
         mMockDisplayInterface.waitForDisplayState(false);
         assertResponse(VehicleApPowerStateReport.DEEP_SLEEP_ENTRY, 0, false);
         mMockDisplayInterface.waitForDisplayState(false);
@@ -255,6 +259,7 @@
         mMockDisplayInterface.waitForDisplayState(false);
     }
 
+    // Check that 'expectedState' was reached and is the current state.
     private void assertResponse(int expectedState, int expectedParam, boolean checkParam)
             throws Exception {
         LinkedList<int[]> setEvents = mPowerStateHandler.waitForStateSetAndGetAll(
@@ -266,6 +271,21 @@
         }
     }
 
+    // Check that 'expectedState' was reached. (But it's OK if it is not still current.)
+    private void assertResponseTransient(int expectedState, int expectedParam, boolean checkParam)
+            throws Exception {
+        LinkedList<int[]> setEvents = mPowerStateHandler.waitForStateSetAndGetAll(
+                DEFAULT_WAIT_TIMEOUT_MS, expectedState);
+        for (int[] aState : setEvents) {
+            if (expectedState != aState[0]) continue;
+            if (checkParam) {
+                assertEquals(expectedParam, aState[1]);
+            }
+            return; // Success
+        }
+        fail("Did not find expected state: " + expectedState);
+    }
+
     private void assertWaitForVhal() throws Exception {
         mPowerStateHandler.waitForSubscription(DEFAULT_WAIT_TIMEOUT_MS);
         LinkedList<int[]> setEvents = mPowerStateHandler.waitForStateSetAndGetAll(
@@ -373,6 +393,7 @@
                     for (int[] state : mSetStates) {
                         if (state[0] == expectedSet) {
                             found = true;
+                            break;
                         }
                     }
                     if (found) {
diff --git a/tests/carservice_test/src/com/android/car/vms/VmsBrokerServiceTest.java b/tests/carservice_test/src/com/android/car/vms/VmsBrokerServiceTest.java
deleted file mode 100644
index 5ce7df5..0000000
--- a/tests/carservice_test/src/com/android/car/vms/VmsBrokerServiceTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.car.vms;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.car.vms.IVmsSubscriberClient;
-import android.car.vms.VmsLayer;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.Process;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Test;
-
-import java.util.function.IntSupplier;
-
-@MediumTest
-public class VmsBrokerServiceTest {
-
-    class MockIntProvider implements IntSupplier {
-        private int[] mInts;
-        private int mIdx;
-
-        MockIntProvider(int... ints) {
-            mInts = ints;
-            mIdx = 0;
-        }
-
-        public int getAsInt() {
-            int ret = mInts[mIdx];
-            mIdx++;
-            return ret;
-        }
-    }
-
-    /**
-     * Test that adding a subscriber to VmsBrokerService also keeps track of the package name for
-     * a given subscriber. Also checks that if we remove a dead subscriber, we no longer track the
-     * package name associated with it.
-     */
-    @Test
-    public void testAddSubscription() {
-        PackageManager packageManager = mock(PackageManager.class);
-        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
-        Binder binder = mock(Binder.class);
-        VmsLayer layer = mock(VmsLayer.class);
-        when(packageManager.getNameForUid(0)).thenReturn("test.package1");
-        when(subscriberClient.asBinder()).thenReturn(binder);
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 200, () -> 0);
-        broker.addSubscription(subscriberClient);
-        assertThat(broker.getPackageName(subscriberClient)).isEqualTo("test.package1");
-        broker.removeDeadSubscriber(subscriberClient);
-        assertThat(broker.getPackageName(subscriberClient)).isNull();
-    }
-
-    @Test
-    public void testAddSubscriptionLayer() {
-        PackageManager packageManager = mock(PackageManager.class);
-        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
-        Binder binder = mock(Binder.class);
-        VmsLayer layer = mock(VmsLayer.class);
-        when(packageManager.getNameForUid(0)).thenReturn("test.package2");
-        when(subscriberClient.asBinder()).thenReturn(binder);
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 200, () -> 0);
-        broker.addSubscription(subscriberClient, layer);
-        assertThat(broker.getPackageName(subscriberClient)).isEqualTo("test.package2");
-        broker.removeDeadSubscriber(subscriberClient);
-        assertThat(broker.getPackageName(subscriberClient)).isNull();
-    }
-
-    @Test
-    public void testAddSubscriptionLayerVersion() {
-        PackageManager packageManager = mock(PackageManager.class);
-        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
-        Binder binder = mock(Binder.class);
-        VmsLayer layer = mock(VmsLayer.class);
-        when(packageManager.getNameForUid(0)).thenReturn("test.package3");
-        when(subscriberClient.asBinder()).thenReturn(binder);
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 200, () -> 0);
-        broker.addSubscription(subscriberClient, layer, 1234);
-        assertThat(broker.getPackageName(subscriberClient)).isEqualTo("test.package3");
-        broker.removeDeadSubscriber(subscriberClient);
-        assertThat(broker.getPackageName(subscriberClient)).isNull();
-    }
-
-    @Test
-    public void testMultipleSubscriptionsSameClientCallsPackageManagerOnce() {
-        PackageManager packageManager = mock(PackageManager.class);
-        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
-        Binder binder = mock(Binder.class);
-        when(subscriberClient.asBinder()).thenReturn(binder);
-        when(packageManager.getNameForUid(0)).thenReturn("test.package3");
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 0, () -> 0);
-        broker.addSubscription(subscriberClient);
-        broker.addSubscription(subscriberClient);
-        // The second argument isn't necessary but is here for clarity.
-        verify(packageManager, times(1)).getNameForUid(0);
-    }
-
-    @Test
-    public void testUnknownPackageName() {
-        PackageManager packageManager = mock(PackageManager.class);
-        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
-        Binder binder = mock(Binder.class);
-        when(subscriberClient.asBinder()).thenReturn(binder);
-        when(packageManager.getNameForUid(0)).thenReturn(null);
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 0, () -> 0);
-        broker.addSubscription(subscriberClient);
-        assertThat(broker.getPackageName(subscriberClient)).isEqualTo(
-                VmsBrokerService.UNKNOWN_PACKAGE);
-    }
-
-    /**
-     * Tests that if the HAL is a subscriber, we record its package name as HalClient.
-     */
-    @Test
-    public void testAddingHalSubscriberSavesPackageName() {
-        PackageManager packageManager = mock(PackageManager.class);
-        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> Process.myPid(),
-                () -> Process.SYSTEM_UID);
-        broker.addSubscription(subscriberClient);
-        assertThat(broker.getPackageName(subscriberClient)).isEqualTo(VmsBrokerService.HAL_CLIENT);
-    }
-
-    @Test
-    public void testMultipleSubscriptionsPackageManager() {
-        PackageManager packageManager = mock(PackageManager.class);
-
-        IVmsSubscriberClient subscriberClient1 = mock(IVmsSubscriberClient.class);
-        Binder binder1 = mock(Binder.class);
-        when(subscriberClient1.asBinder()).thenReturn(binder1);
-
-        IVmsSubscriberClient subscriberClient2 = mock(IVmsSubscriberClient.class);
-        Binder binder2 = mock(Binder.class);
-        when(subscriberClient2.asBinder()).thenReturn(binder2);
-
-        IVmsSubscriberClient subscriberClient3 = mock(IVmsSubscriberClient.class);
-        Binder binder3 = mock(Binder.class);
-        when(subscriberClient3.asBinder()).thenReturn(binder3);
-
-        // Tests a client with a different UID but the same package as subscriberClient1
-        IVmsSubscriberClient subscriberClient4 = mock(IVmsSubscriberClient.class);
-        Binder binder4 = mock(Binder.class);
-        when(subscriberClient4.asBinder()).thenReturn(binder4);
-
-        when(packageManager.getNameForUid(0)).thenReturn("test.package0");
-        when(packageManager.getNameForUid(1)).thenReturn("test.package1");
-        when(packageManager.getNameForUid(2)).thenReturn("test.package2");
-        when(packageManager.getNameForUid(3)).thenReturn("test.package0");
-
-        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 10,
-                new MockIntProvider(0, 1, 2, 3));
-
-        broker.addSubscription(subscriberClient1);
-        broker.addSubscription(subscriberClient2);
-        broker.addSubscription(subscriberClient3);
-        broker.addSubscription(subscriberClient4);
-
-        verify(packageManager).getNameForUid(0);
-        verify(packageManager).getNameForUid(1);
-        verify(packageManager).getNameForUid(2);
-        verify(packageManager).getNameForUid(3);
-
-        assertThat(broker.getPackageName(subscriberClient1)).isEqualTo("test.package0");
-        assertThat(broker.getPackageName(subscriberClient2)).isEqualTo("test.package1");
-        assertThat(broker.getPackageName(subscriberClient3)).isEqualTo("test.package2");
-        assertThat(broker.getPackageName(subscriberClient4)).isEqualTo("test.package0");
-    }
-}
diff --git a/tests/carservice_unit_test/Android.mk b/tests/carservice_unit_test/Android.mk
index 6b715fc..ec984f8 100644
--- a/tests/carservice_unit_test/Android.mk
+++ b/tests/carservice_unit_test/Android.mk
@@ -48,17 +48,19 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     androidx.test.core \
+    androidx.test.ext.junit \
     androidx.test.rules \
     car-frameworks-service \
     car-service-lib-for-test \
     com.android.car.test.utils \
-    junit \
-    mockito-target-inline-minus-junit4 \
     frameworks-base-testutils \
+    mockito-target-extended \
+    testng \
     truth-prebuilt
 
 # mockito-target-inline dependency
 LOCAL_JNI_SHARED_LIBRARIES := \
     libdexmakerjvmtiagent \
+    libstaticjvmtiagent \
 
 include $(BUILD_PACKAGE)
diff --git a/tests/carservice_unit_test/src/android/car/CarTest.java b/tests/carservice_unit_test/src/android/car/CarTest.java
new file mode 100644
index 0000000..91a1ddb
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/CarTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+package android.car;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit test for Car API.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarTest {
+    private static final String TAG = CarTest.class.getSimpleName();
+
+    private MockitoSession mMockingSession;
+
+    @Mock
+    private Context mContext;
+
+    // It is tricky to mock this. So create dummy version instead.
+    private ICar.Stub mService = new ICar.Stub() {
+        @Override
+        public void setCarServiceHelper(android.os.IBinder helper) {
+        }
+
+        @Override
+        public void setUserLockStatus(int userHandle, int unlocked) {
+        }
+
+        @Override
+        public void onSwitchUser(int userHandle) {
+        }
+
+        @Override
+        public android.os.IBinder getCarService(java.lang.String serviceName) {
+            return null;
+        }
+
+        @Override
+        public int getCarConnectionType() {
+            return 0;
+        }
+    };
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(ServiceManager.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        mMockingSession.finishMocking();
+    }
+
+    private void expectService(@Nullable IBinder service) {
+        doReturn(service).when(
+                () -> ServiceManager.getService(Car.CAR_SERVICE_BINDER_SERVICE_NAME));
+    }
+
+    @Test
+    public void testCreateCarSuccessWithCarServiceRunning() {
+        expectService(mService);
+        assertThat(Car.createCar(mContext)).isNotNull();
+    }
+
+    @Test
+    public void testCreateCarReturnNull() {
+        // car service is not running yet and bindService does not bring the service yet.
+        // createCar should timeout and give up.
+        expectService(null);
+        assertThat(Car.createCar(mContext)).isNull();
+    }
+
+    @Test
+    public void testCreateCarOkWhenCarServiceIsStarted() {
+        // Car service is not running yet and binsService call should start it.
+        when(mContext.bindServiceAsUser(anyObject(), anyObject(), anyInt(),
+                anyObject())).thenReturn(true);
+        final int returnNonNullAfterThisCall = 10;
+        doAnswer(new Answer() {
+
+            private int mCallCount = 0;
+
+            @Override
+            public Object answer(InvocationOnMock invocation) {
+                mCallCount++;
+                if (mCallCount > returnNonNullAfterThisCall) {
+                    return mService;
+                } else {
+                    return null;
+                }
+            }
+        }).when(() -> ServiceManager.getService(Car.CAR_SERVICE_BINDER_SERVICE_NAME));
+        Car car = Car.createCar(mContext);
+        assertThat(car).isNotNull();
+        verify(mContext, times(1)).bindServiceAsUser(anyObject(), anyObject(),
+                anyInt(), anyObject());
+
+        // Just call these to guarantee that nothing crashes when service is connected /
+        // disconnected.
+        car.getServiceConnectionListener().onServiceConnected(new ComponentName("", ""), mService);
+        car.getServiceConnectionListener().onServiceDisconnected(new ComponentName("", ""));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
index c9e85e0..5384005 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarLocationServiceTest.java
@@ -415,13 +415,17 @@
 
     /**
      * Test that the {@link CarLocationService} deletes location_cache.json when the system resumes
-     * from suspend-to-ram.
+     * from suspend-to-ram if moving.
      */
     @Test
-    public void testDeletesCacheFileUponSuspendExit() throws Exception {
+    public void testDeletesCacheFileUponSuspendExitWhileMoving() throws Exception {
         mCarLocationService.init();
         when(mMockLocationManagerProxy.isLocationEnabled()).thenReturn(false);
+        when(mMockCarDrivingStateService.getCurrentDrivingState()).thenReturn(
+                new CarDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_MOVING, 0));
+        writeCacheFile("{\"provider\":\"latitude\":16.7666,\"longitude\": \"accuracy\":1.0}");
         CompletableFuture<Void> future = new CompletableFuture<>();
+        assertTrue(getLocationCacheFile().exists());
 
         mCarLocationService.onStateChanged(CarPowerStateListener.SUSPEND_EXIT, future);
 
@@ -431,6 +435,26 @@
     }
 
     /**
+     * Test that the {@link CarLocationService} registers for driving state changes when the system
+     * resumes from suspend-to-ram.
+     */
+    @Test
+    public void testRegistersForDrivingStateChangesUponSuspendExit() throws Exception {
+        mCarLocationService.init();
+        when(mMockLocationManagerProxy.isLocationEnabled()).thenReturn(false);
+        when(mMockCarDrivingStateService.getCurrentDrivingState()).thenReturn(
+                new CarDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_PARKED, 0));
+        CompletableFuture<Void> future = new CompletableFuture<>();
+
+        mCarLocationService.onStateChanged(CarPowerStateListener.SUSPEND_EXIT, future);
+
+        assertTrue(future.isDone());
+        verify(mMockLocationManagerProxy, times(0)).isLocationEnabled();
+        // One of the registrations should happen during init and another during onStateChanged.
+        verify(mMockCarDrivingStateService, times(2)).registerDrivingStateChangeListener(any());
+    }
+
+    /**
      * Test that the {@link CarLocationService} deletes location_cache.json when the car enters a
      * moving driving state.
      */
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
index d65bc12..866bc8a 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
@@ -108,7 +108,7 @@
     @Before
     public void setUp() {
         mPublisherService = new VmsPublisherService(mContext, mBrokerService, mClientManager);
-        verify(mClientManager).registerConnectionListener(mPublisherService);
+        verify(mClientManager).setPublisherService(mPublisherService);
 
         mPublisherClient = new MockPublisherClient();
         mPublisherClient2 = new MockPublisherClient();
@@ -124,8 +124,8 @@
 
     @Test
     public void testOnClientConnected() {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
-        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
+        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2);
         verify(mBrokerService, times(2)).addPublisherListener(mProxyCaptor.capture());
 
         assertNotNull(mPublisherClient.mPublisherService);
@@ -138,8 +138,8 @@
 
     @Test
     public void testOnClientDisconnected() {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
-        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
+        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2);
         verify(mBrokerService, times(2)).addPublisherListener(mProxyCaptor.capture());
 
         reset(mClientManager, mBrokerService);
@@ -152,7 +152,7 @@
 
     @Test
     public void testSetLayersOffering() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
 
         mPublisherClient.mPublisherService.setLayersOffering(mPublisherClient.mToken, OFFERING);
         verify(mBrokerService).setPublisherLayersOffering(mPublisherClient.mToken, OFFERING);
@@ -160,14 +160,14 @@
 
     @Test(expected = SecurityException.class)
     public void testSetLayersOffering_InvalidToken() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
 
         mPublisherClient.mPublisherService.setLayersOffering(new Binder(), OFFERING);
     }
 
     @Test(expected = SecurityException.class)
     public void testSetLayersOffering_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         mPublisherService.onClientDisconnected("SomeClient");
 
         mPublisherClient.mPublisherService.setLayersOffering(mPublisherClient.mToken, OFFERING);
@@ -175,7 +175,7 @@
 
     @Test(expected = SecurityException.class)
     public void testSetLayersOffering_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
                 PackageManager.PERMISSION_DENIED);
 
@@ -184,7 +184,7 @@
 
     @Test
     public void testPublish() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
 
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
                 PAYLOAD);
@@ -194,7 +194,7 @@
 
     @Test
     public void testPublishNullLayerAndNullPayload() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
 
         // We just want to ensure that no exceptions are thrown here.
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, null, PUBLISHER_ID,
@@ -203,7 +203,7 @@
 
     @Test
     public void testPublish_ClientError() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         doThrow(new RemoteException()).when(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
 
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
@@ -214,14 +214,14 @@
 
     @Test(expected = SecurityException.class)
     public void testPublish_InvalidToken() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
 
         mPublisherClient.mPublisherService.publish(new Binder(), LAYER, PUBLISHER_ID, PAYLOAD);
     }
 
     @Test(expected = SecurityException.class)
     public void testPublish_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         mPublisherService.onClientDisconnected("SomeClient");
 
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
@@ -230,7 +230,7 @@
 
     @Test(expected = SecurityException.class)
     public void testPublish_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
                 PackageManager.PERMISSION_DENIED);
 
@@ -240,7 +240,7 @@
 
     @Test
     public void testGetSubscriptions() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         when(mBrokerService.getSubscriptionState()).thenReturn(SUBSCRIPTION_STATE);
 
         assertEquals(SUBSCRIPTION_STATE, mPublisherClient.mPublisherService.getSubscriptions());
@@ -248,7 +248,7 @@
 
     @Test(expected = SecurityException.class)
     public void testGetSubscriptions_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         mPublisherService.onClientDisconnected("SomeClient");
 
         mPublisherClient.mPublisherService.getSubscriptions();
@@ -256,7 +256,7 @@
 
     @Test(expected = SecurityException.class)
     public void testGetSubscriptions_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
                 PackageManager.PERMISSION_DENIED);
 
@@ -265,7 +265,7 @@
 
     @Test
     public void testGetPublisherId() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         when(mBrokerService.getPublisherId(PAYLOAD)).thenReturn(PUBLISHER_ID);
 
         assertEquals(PUBLISHER_ID, mPublisherClient.mPublisherService.getPublisherId(PAYLOAD));
@@ -273,7 +273,7 @@
 
     @Test(expected = SecurityException.class)
     public void testGetPublisherId_Disconnected() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         mPublisherService.onClientDisconnected("SomeClient");
 
         mPublisherClient.mPublisherService.getPublisherId(PAYLOAD);
@@ -281,7 +281,7 @@
 
     @Test(expected = SecurityException.class)
     public void testGetPublisherId_PermissionDenied() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_VMS_PUBLISHER)).thenReturn(
                 PackageManager.PERMISSION_DENIED);
 
@@ -290,8 +290,8 @@
 
     @Test
     public void testOnSubscriptionChange() {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
-        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
+        mPublisherService.onClientConnected("SomeOtherClient", mPublisherClient2);
         verify(mBrokerService, times(2)).addPublisherListener(mProxyCaptor.capture());
 
         mProxyCaptor.getAllValues().get(0).onSubscriptionChange(SUBSCRIPTION_STATE);
@@ -302,7 +302,7 @@
 
     @Test
     public void testDump_getPacketCount() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         PrintWriter printWriter = new PrintWriter(outputStream);
 
@@ -322,7 +322,7 @@
 
     @Test
     public void testDump_getPacketCounts() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         PrintWriter printWriter = new PrintWriter(outputStream);
 
@@ -379,7 +379,7 @@
 
     @Test
     public void testDumpNoListeners_getPacketFailureCount() throws Exception {
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         PrintWriter printWriter = new PrintWriter(outputStream);
 
@@ -410,8 +410,8 @@
         when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
                 .thenReturn(new HashSet<>());
 
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
-        mPublisherService.onClientConnected("SomeClient2", mPublisherClient2.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
+        mPublisherService.onClientConnected("SomeClient2", mPublisherClient2);
 
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         PrintWriter printWriter = new PrintWriter(outputStream);
@@ -453,9 +453,9 @@
                 LAYER3, PAYLOAD);
         when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
                 .thenReturn(new HashSet<>(Arrays.asList(mThrowingSubscriberClient)));
-        when(mBrokerService.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
+        when(mClientManager.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
 
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
 
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         PrintWriter printWriter = new PrintWriter(outputStream);
@@ -499,11 +499,11 @@
                 .thenReturn(new HashSet<>(
                         Arrays.asList(mThrowingSubscriberClient, mThrowingSubscriberClient2)));
 
-        when(mBrokerService.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
-        when(mBrokerService.getPackageName(mThrowingSubscriberClient2)).thenReturn("Thrower2");
+        when(mClientManager.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
+        when(mClientManager.getPackageName(mThrowingSubscriberClient2)).thenReturn("Thrower2");
 
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
-        mPublisherService.onClientConnected("SomeClient2", mPublisherClient2.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
+        mPublisherService.onClientConnected("SomeClient2", mPublisherClient2);
 
         // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
@@ -590,9 +590,9 @@
                 .thenReturn(new HashSet<>(
                         Arrays.asList(mThrowingSubscriberClient)));
 
-        when(mBrokerService.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
+        when(mClientManager.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
 
-        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient);
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
                 PAYLOAD);
         mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java
index 2c7b9f1..1938a19 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java
@@ -23,9 +23,9 @@
 import org.junit.Test;
 
 public class VmsPublishersInfoTest {
-    public static final byte[] MOCK_INFO_0 = new byte[]{2, 3, 5, 7, 11, 13, 17};
-    public static final byte[] SAME_MOCK_INFO_0 = new byte[]{2, 3, 5, 7, 11, 13, 17};
-    public static final byte[] MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17, 19};
+    public static final byte[] MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17};
+    public static final byte[] SAME_MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17};
+    public static final byte[] MOCK_INFO_2 = new byte[]{2, 3, 5, 7, 11, 13, 17, 19};
 
     private VmsPublishersInfo mVmsPublishersInfo;
 
@@ -36,9 +36,9 @@
 
     @Test
     public void testSingleInfo() throws Exception {
-        int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0);
-        assertEquals(0, id);
-        assertArrayEquals(MOCK_INFO_0, mVmsPublishersInfo.getPublisherInfo(id));
+        int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1);
+        assertEquals(1, id);
+        assertArrayEquals(MOCK_INFO_1, mVmsPublishersInfo.getPublisherInfo(id));
     }
 
     @Test
@@ -48,20 +48,20 @@
 
     @Test
     public void testTwoInfos() throws Exception {
-        int id0 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0);
         int id1 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1);
-        assertEquals(0, id0);
+        int id2 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_2);
         assertEquals(1, id1);
-        assertArrayEquals(MOCK_INFO_0, mVmsPublishersInfo.getPublisherInfo(id0));
+        assertEquals(2, id2);
         assertArrayEquals(MOCK_INFO_1, mVmsPublishersInfo.getPublisherInfo(id1));
+        assertArrayEquals(MOCK_INFO_2, mVmsPublishersInfo.getPublisherInfo(id2));
     }
 
     @Test
     public void testSingleInfoInsertedTwice() throws Exception {
-        int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0);
-        assertEquals(0, id);
+        int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1);
+        assertEquals(1, id);
 
-        int sameId = mVmsPublishersInfo.getIdForInfo(SAME_MOCK_INFO_0);
+        int sameId = mVmsPublishersInfo.getIdForInfo(SAME_MOCK_INFO_1);
         assertEquals(sameId, id);
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsSubscriberServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsSubscriberServiceTest.java
new file mode 100644
index 0000000..0208515
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/VmsSubscriberServiceTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+package com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsLayer;
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.car.hal.VmsHalService;
+import com.android.car.vms.VmsBrokerService;
+import com.android.car.vms.VmsClientManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@SmallTest
+public class VmsSubscriberServiceTest {
+    private static final VmsLayer LAYER = new VmsLayer(1, 2, 3);
+    private static final int PUBLISHER_ID = 54321;
+    private static final byte[] PUBLISHER_INFO = new byte[]{1, 2, 3, 4};
+    private static final VmsAvailableLayers AVAILABLE_LAYERS =
+            new VmsAvailableLayers(Collections.emptySet(), 0);
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Mock
+    private Context mContext;
+    @Mock
+    private VmsBrokerService mBrokerService;
+    @Mock
+    private VmsClientManager mClientManager;
+    @Mock
+    private VmsHalService mHal;
+
+    @Mock
+    private IVmsSubscriberClient mSubscriberClient;
+    @Mock
+    private IVmsSubscriberClient mSubscriberClient2;
+
+    private VmsSubscriberService mSubscriberService;
+
+    @Before
+    public void setUp() {
+        mSubscriberService = new VmsSubscriberService(mContext, mBrokerService, mClientManager,
+                mHal);
+        verify(mBrokerService).addSubscriberListener(eq(mSubscriberService));
+        verify(mHal).setVmsSubscriberService(eq(mSubscriberService));
+    }
+
+    @After
+    public void tearDown() {
+        verifyNoMoreInteractions(mBrokerService, mClientManager);
+    }
+
+    @Test
+    public void testAddVmsSubscriberToNotifications() {
+        mSubscriberService.addVmsSubscriberToNotifications(mSubscriberClient);
+        verify(mClientManager).addSubscriber(mSubscriberClient);
+    }
+
+    @Test
+    public void testRemoveVmsSubscriberToNotifications() {
+        mSubscriberService.removeVmsSubscriberToNotifications(mSubscriberClient);
+        verify(mClientManager).removeSubscriber(mSubscriberClient);
+    }
+
+    @Test
+    public void testAddVmsSubscriber() {
+        mSubscriberService.addVmsSubscriber(mSubscriberClient, LAYER);
+        verify(mClientManager).addSubscriber(mSubscriberClient);
+        verify(mBrokerService).addSubscription(mSubscriberClient, LAYER);
+    }
+
+    @Test
+    public void testRemoveVmsSubscriber() {
+        mSubscriberService.removeVmsSubscriber(mSubscriberClient, LAYER);
+        verify(mBrokerService).removeSubscription(mSubscriberClient, LAYER);
+    }
+
+
+    @Test
+    public void testAddVmsSubscriberToPublisher() {
+        mSubscriberService.addVmsSubscriberToPublisher(mSubscriberClient, LAYER, PUBLISHER_ID);
+        verify(mClientManager).addSubscriber(mSubscriberClient);
+        verify(mBrokerService).addSubscription(mSubscriberClient, LAYER, PUBLISHER_ID);
+    }
+
+    @Test
+    public void testRemoveVmsSubscriberToPublisher() {
+        testAddVmsSubscriberToPublisher();
+
+        mSubscriberService.removeVmsSubscriberToPublisher(mSubscriberClient, LAYER, PUBLISHER_ID);
+        verify(mBrokerService).removeSubscription(mSubscriberClient, LAYER, PUBLISHER_ID);
+    }
+
+    @Test
+    public void testAddVmsSubscriberPassive() {
+        mSubscriberService.addVmsSubscriberPassive(mSubscriberClient);
+        verify(mClientManager).addSubscriber(mSubscriberClient);
+        verify(mBrokerService).addSubscription(mSubscriberClient);
+    }
+
+    @Test
+    public void testRemoveVmsSubscriberPassive() {
+        mSubscriberService.removeVmsSubscriberPassive(mSubscriberClient);
+        verify(mBrokerService).removeSubscription(mSubscriberClient);
+    }
+
+    @Test
+    public void testGetPublisherInfo() {
+        when(mBrokerService.getPublisherInfo(PUBLISHER_ID)).thenReturn(PUBLISHER_INFO);
+        assertThat(mSubscriberService.getPublisherInfo(PUBLISHER_ID)).isSameAs(PUBLISHER_INFO);
+        verify(mBrokerService).getPublisherInfo(PUBLISHER_ID);
+    }
+
+    @Test
+    public void testGetAvailableLayers() {
+        when(mBrokerService.getAvailableLayers()).thenReturn(AVAILABLE_LAYERS);
+        assertThat(mSubscriberService.getAvailableLayers()).isSameAs(AVAILABLE_LAYERS);
+        verify(mBrokerService).getAvailableLayers();
+    }
+
+    @Test
+    public void testOnLayersAvailabilityChange() throws Exception {
+        when(mClientManager.getAllSubscribers())
+                .thenReturn(Arrays.asList(mSubscriberClient, mSubscriberClient2));
+        mSubscriberService.onLayersAvailabilityChange(AVAILABLE_LAYERS);
+        verify(mClientManager).getAllSubscribers();
+        verify(mSubscriberClient).onLayersAvailabilityChanged(AVAILABLE_LAYERS);
+        verify(mSubscriberClient2).onLayersAvailabilityChanged(AVAILABLE_LAYERS);
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
index f42c52a..2ef469c 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
@@ -16,6 +16,7 @@
 package com.android.car.hal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,6 +38,10 @@
 import android.os.Binder;
 import android.os.IBinder;
 
+import androidx.test.filters.RequiresDevice;
+
+import com.android.car.vms.VmsClientManager;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -53,7 +58,6 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
 
 public class VmsHalServiceTest {
     private static final int LAYER_TYPE = 1;
@@ -71,15 +75,11 @@
     @Mock
     private VehicleHal mVehicleHal;
     @Mock
+    private VmsClientManager mClientManager;
+    @Mock
     private IVmsPublisherService mPublisherService;
     @Mock
     private IVmsSubscriberService mSubscriberService;
-    @Mock
-    private Consumer<IBinder> mPublisherOnHalConnected;
-    @Mock
-    private Runnable mPublisherOnHalDisconnected;
-    @Mock
-    private Consumer<IVmsSubscriberClient> mSubscriberOnHalDisconnected;
 
     private IBinder mToken;
     private VmsHalService mHalService;
@@ -89,9 +89,8 @@
     @Before
     public void setUp() throws Exception {
         mHalService = new VmsHalService(mVehicleHal, () -> (long) CORE_ID);
-        mHalService.setPublisherConnectionCallbacks(
-                mPublisherOnHalConnected, mPublisherOnHalDisconnected);
-        mHalService.setVmsSubscriberService(mSubscriberService, mSubscriberOnHalDisconnected);
+        mHalService.setClientManager(mClientManager);
+        mHalService.setVmsSubscriberService(mSubscriberService);
 
         VehiclePropConfig propConfig = new VehiclePropConfig();
         propConfig.prop = VehicleProperty.VEHICLE_MAP_SERVICE;
@@ -104,7 +103,7 @@
 
         // Verify START_SESSION message was sent
         InOrder initOrder =
-                Mockito.inOrder(mPublisherOnHalConnected, mSubscriberService, mVehicleHal);
+                Mockito.inOrder(mClientManager, mSubscriberService, mVehicleHal);
         initOrder.verify(mVehicleHal).subscribeProperty(mHalService,
                 VehicleProperty.VEHICLE_MAP_SERVICE);
         initOrder.verify(mVehicleHal).set(createHalMessage(
@@ -118,25 +117,25 @@
         // Send START_SESSION response from client
         sendHalMessage(createHalMessage(
                 VmsMessageType.START_SESSION,  // Message type
-                0,                             // Core ID (unknown)
+                CORE_ID,                       // Core ID
                 CLIENT_ID                      // Client ID
         ));
         waitForHandlerCompletion();
 
         // Verify client is marked as connected
-        ArgumentCaptor<IBinder> publisherCaptor = ArgumentCaptor.forClass(IBinder.class);
-        initOrder.verify(mPublisherOnHalConnected).accept(publisherCaptor.capture());
-        mPublisherClient = IVmsPublisherClient.Stub.asInterface(publisherCaptor.getValue());
+        ArgumentCaptor<IVmsPublisherClient> publisherCaptor =
+                ArgumentCaptor.forClass(IVmsPublisherClient.class);
+        ArgumentCaptor<IVmsSubscriberClient> subscriberCaptor =
+                ArgumentCaptor.forClass(IVmsSubscriberClient.class);
+        initOrder.verify(mClientManager, never()).onHalDisconnected();
+        initOrder.verify(mClientManager)
+                .onHalConnected(publisherCaptor.capture(), subscriberCaptor.capture());
+        mPublisherClient = publisherCaptor.getValue();
+        mSubscriberClient = subscriberCaptor.getValue();
 
         mToken = new Binder();
         mPublisherClient.setVmsPublisherService(mToken, mPublisherService);
 
-        ArgumentCaptor<IVmsSubscriberClient> subscriberCaptor = ArgumentCaptor.forClass(
-                IVmsSubscriberClient.class);
-        initOrder.verify(mSubscriberService).addVmsSubscriberToNotifications(
-                subscriberCaptor.capture());
-        mSubscriberClient = subscriberCaptor.getValue();
-
         initOrder.verify(mSubscriberService).getAvailableLayers();
         initOrder.verify(mVehicleHal).set(createHalMessage(
                 VmsMessageType.AVAILABILITY_CHANGE, // Message type
@@ -144,7 +143,7 @@
                 0));                                // # of associated layers
 
         initOrder.verifyNoMoreInteractions();
-        reset(mPublisherOnHalConnected, mSubscriberService, mVehicleHal);
+        reset(mClientManager, mSubscriberService, mVehicleHal);
     }
 
     @Test
@@ -571,13 +570,14 @@
      * </ul>
      */
     @Test
+    @RequiresDevice
     public void testHandleStartSessionEvent() throws Exception {
         when(mSubscriberService.getAvailableLayers()).thenReturn(
                 new VmsAvailableLayers(Collections.emptySet(), 5));
 
         VehiclePropValue request = createHalMessage(
                 VmsMessageType.START_SESSION,  // Message type
-                0,                             // Core ID (unknown)
+                -1,                            // Core ID (unknown)
                 CLIENT_ID                      // Client ID
         );
 
@@ -588,7 +588,8 @@
         );
 
         sendHalMessage(request);
-        InOrder inOrder = Mockito.inOrder(mVehicleHal);
+        InOrder inOrder = Mockito.inOrder(mClientManager, mVehicleHal);
+        inOrder.verify(mClientManager).onHalDisconnected();
         inOrder.verify(mVehicleHal).set(response);
         inOrder.verify(mVehicleHal).set(createHalMessage(
                 VmsMessageType.AVAILABILITY_CHANGE, // Message type
diff --git a/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java b/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java
index b65bce6..a99b1d7 100644
--- a/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/trust/BLEMessagePayloadStreamTest.java
@@ -43,7 +43,7 @@
     private static final byte[] TEST_MESSAGE_PAYLOAD = "testMessage".getBytes();
     private static final int TEST_SINGLE_MESSAGE_SIZE =
             TEST_MESSAGE_PAYLOAD.length + BLEMessageV1Factory.getProtoHeaderSize(
-                    OPERATION_TYPE, IS_MESSAGE_ENCRYPTED);
+                    OPERATION_TYPE, TEST_MESSAGE_PAYLOAD.length, IS_MESSAGE_ENCRYPTED);
 
     private BLEMessagePayloadStream mBLEMessagePayloadStream;
     private List<BLEMessage> mBleMessages;
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 f21413e..b90dac4 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
@@ -17,13 +17,19 @@
 package com.android.car.vms;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 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;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -33,6 +39,12 @@
 
 import android.car.Car;
 import android.car.userlib.CarUserManagerHelper;
+import android.car.vms.IVmsPublisherClient;
+import android.car.vms.IVmsPublisherService;
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.VmsAvailableLayers;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsSubscriptionState;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +61,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.car.VmsPublisherService;
 import com.android.car.hal.VmsHalService;
 import com.android.car.user.CarUserService;
 
@@ -59,15 +72,11 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.function.Consumer;
-
 @SmallTest
 public class VmsClientManagerTest {
-    private static final String HAL_CLIENT_NAME = "VmsHalClient";
     private static final String SYSTEM_CLIENT = "com.google.android.apps.vms.test/.VmsSystemClient";
     private static final ComponentName SYSTEM_CLIENT_COMPONENT =
             ComponentName.unflattenFromString(SYSTEM_CLIENT);
@@ -77,10 +86,17 @@
     private static final String USER_CLIENT = "com.google.android.apps.vms.test/.VmsUserClient";
     private static final ComponentName USER_CLIENT_COMPONENT =
             ComponentName.unflattenFromString(USER_CLIENT);
+    private static final int USER_ID = 10;
     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";
+    private static final String UNKNOWN_PACKAGE = "UnknownPackage";
+
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
@@ -96,20 +112,37 @@
     private CarUserService mUserService;
     @Mock
     private CarUserManagerHelper mUserManagerHelper;
-    private int mUserId;
+
+    @Mock
+    private VmsBrokerService mBrokerService;
 
     @Mock
     private VmsHalService mHal;
-    private Consumer<IBinder> mHalClientConnected;
-    private Runnable mHalClientDisconnected;
 
     @Mock
-    private VmsClientManager.ConnectionListener mConnectionListener;
-    private VmsClientManager mClientManager;
+    private VmsPublisherService mPublisherService;
+
+    @Mock
+    private IVmsSubscriberClient mSubscriberClient1;
+    @Mock
+    private Binder mSubscriberBinder1;
+
+    @Captor
+    private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipient;
+
+    @Mock
+    private IVmsSubscriberClient mSubscriberClient2;
+    @Mock
+    private Binder mSubscriberBinder2;
 
     @Captor
     private ArgumentCaptor<ServiceConnection> mConnectionCaptor;
 
+    private VmsClientManager mClientManager;
+
+    private int mForegroundUserId;
+    private int mCallingAppUid;
+
     @Before
     public void setUp() throws Exception {
         resetContext();
@@ -127,23 +160,22 @@
                 com.android.car.R.array.vmsPublisherUserClients)).thenReturn(
                 new String[]{ USER_CLIENT });
 
-        mUserId = 10;
-        when(mUserManagerHelper.getCurrentForegroundUserId()).thenAnswer((invocation) -> mUserId);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
-        when(mUserManager.isUserUnlocked(any())).thenReturn(false);
+        when(mUserManagerHelper.getCurrentForegroundUserId())
+                .thenAnswer(invocation -> mForegroundUserId);
 
-        mClientManager = new VmsClientManager(mContext, mUserService, mUserManagerHelper, mHal);
-        mClientManager.registerConnectionListener(mConnectionListener);
+        mForegroundUserId = USER_ID;
+        mCallingAppUid = UserHandle.getUid(USER_ID, 0);
 
-        @SuppressWarnings("unchecked")
-        ArgumentCaptor<Consumer<IBinder>> onClientConnectedCaptor =
-                ArgumentCaptor.forClass(Consumer.class);
-        ArgumentCaptor<Runnable> onClientDisconnectedCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-        verify(mHal).setPublisherConnectionCallbacks(
-                onClientConnectedCaptor.capture(), onClientDisconnectedCaptor.capture());
-        mHalClientConnected = onClientConnectedCaptor.getValue();
-        mHalClientDisconnected = onClientDisconnectedCaptor.getValue();
+        mClientManager = new VmsClientManager(mContext, mBrokerService, mUserService,
+                mUserManagerHelper, mHal, () -> mCallingAppUid);
+        verify(mHal).setClientManager(mClientManager);
+        mClientManager.setPublisherService(mPublisherService);
+
+        when(mSubscriberClient1.asBinder()).thenReturn(mSubscriberBinder1);
+        when(mSubscriberClient2.asBinder()).thenReturn(mSubscriberBinder2);
+
+        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
     }
 
     @After
@@ -152,8 +184,7 @@
         verify(mContext, atLeast(0)).getSystemService(eq(Context.USER_SERVICE));
         verify(mContext, atLeast(0)).getResources();
         verify(mContext, atLeast(0)).getPackageManager();
-        verifyNoMoreInteractions(mContext);
-        verifyNoMoreInteractions(mHal);
+        verifyNoMoreInteractions(mContext, mBrokerService, mHal, mPublisherService);
     }
 
     @Test
@@ -182,37 +213,6 @@
     }
 
     @Test
-    public void testRegisterConnectionListener() {
-        VmsClientManager.ConnectionListener listener =
-                Mockito.mock(VmsClientManager.ConnectionListener.class);
-        mClientManager.registerConnectionListener(listener);
-    }
-
-    @Test
-    public void testRegisterConnectionListener_AfterHalClientConnected() {
-        IBinder halClient = bindHalClient();
-
-        VmsClientManager.ConnectionListener listener =
-                Mockito.mock(VmsClientManager.ConnectionListener.class);
-        mClientManager.registerConnectionListener(listener);
-        verify(listener).onClientConnected(HAL_CLIENT_NAME, halClient);
-    }
-
-    @Test
-    public void testRegisterConnectionListener_AfterClientsConnected() {
-        IBinder halClient = bindHalClient();
-        IBinder systemBinder = bindSystemClient();
-        IBinder userBinder = bindUserClient();
-
-        VmsClientManager.ConnectionListener listener =
-                Mockito.mock(VmsClientManager.ConnectionListener.class);
-        mClientManager.registerConnectionListener(listener);
-        verify(listener).onClientConnected(HAL_CLIENT_NAME, halClient);
-        verify(listener).onClientConnected(eq(SYSTEM_CLIENT_NAME), eq(systemBinder));
-        verify(listener).onClientConnected(eq(USER_CLIENT_NAME), eq(userBinder));
-    }
-
-    @Test
     public void testSystemUserUnlocked() {
         notifySystemUserUnlocked();
         notifySystemUserUnlocked();
@@ -266,18 +266,26 @@
 
     @Test
     public void testUserUnlocked() {
-        notifyUserUnlocked();
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         // Multiple events should only trigger a single bind, when successful
         verifyUserBind(1);
     }
 
     @Test
+    public void testUserUnlocked_ForegroundUserNotUnlocked() {
+        notifyUserUnlocked(USER_ID, false);
+
+        // 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());
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
 
         // Process will not be bound
         verifyUserBind(0);
@@ -289,7 +297,7 @@
         serviceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
         when(mPackageManager.getServiceInfo(eq(USER_CLIENT_COMPONENT), anyInt()))
                 .thenReturn(serviceInfo);
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
 
         // Process will not be bound
         verifyUserBind(0);
@@ -299,8 +307,8 @@
     public void testUserUnlocked_BindFailed() {
         when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any()))
                 .thenReturn(false);
-        notifyUserUnlocked();
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         // Failure state will trigger another attempt
         verifyUserBind(2);
@@ -308,10 +316,10 @@
 
     @Test
     public void testUserUnlocked_UserBindFailed() {
-        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.of(mUserId))))
+        when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.of(USER_ID))))
                 .thenReturn(false);
-        notifyUserUnlocked();
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         // Failure state will trigger another attempt
         verifyUserBind(2);
@@ -321,8 +329,8 @@
     public void testUserUnlocked_BindException() {
         when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any()))
                 .thenThrow(new SecurityException());
-        notifyUserUnlocked();
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         // Failure state will trigger another attempt
         verifyUserBind(2);
@@ -338,7 +346,7 @@
 
         when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
                 .thenReturn(true);
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifySystemBind(1);
         verifyUserBind(1);
     }
@@ -353,8 +361,8 @@
 
         when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
                 .thenReturn(false);
-        notifyUserUnlocked();
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         verifySystemBind(2); // Failure state will trigger another attempt
         verifyUserBind(1);
@@ -370,8 +378,8 @@
 
         when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
                 .thenThrow(new SecurityException());
-        notifyUserUnlocked();
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         verifySystemBind(2); // Failure state will trigger another attempt
         verifyUserBind(1);
@@ -379,66 +387,43 @@
 
     @Test
     public void testUserSwitched() {
-        notifyUserSwitched();
+        notifyUserSwitched(USER_ID, true);
+        notifyUserSwitched(USER_ID, true);
 
-        // Clients are not bound on user switch alone
-        verifyUserBind(0);
+        // Multiple events should only trigger a single bind, when successful
+        verifyUserBind(1);
     }
 
     @Test
     public void testUserSwitchedAndUnlocked() {
-        notifyUserSwitched();
-        notifyUserUnlocked();
+        notifyUserSwitched(USER_ID, true);
+        notifyUserUnlocked(USER_ID, true);
 
         // Multiple events should only trigger a single bind, when successful
         verifyUserBind(1);
     }
 
     @Test
-    public void testUserSwitchedAlreadyUnlocked() {
-        when(mUserManager.isUserUnlocked(mUserId)).thenReturn(true);
-        notifyUserSwitched();
+    public void testUserSwitched_ForegroundUserNotUnlocked() {
+        notifyUserSwitched(USER_ID, false);
 
-        // Multiple events should only trigger a single bind, when successful
-        verifyUserBind(1);
-    }
-
-    @Test
-    public void testUserSwitchedToSystemUser() {
-        mUserId = UserHandle.USER_SYSTEM;
-        notifyUserSwitched();
-
-        // User processes will not be bound for system user
+        // Process will not be bound
         verifyUserBind(0);
     }
 
     @Test
-    public void testUnregisterConnectionListener() {
-        mClientManager.unregisterConnectionListener(mConnectionListener);
-        notifySystemUserUnlocked();
-        verifySystemBind(1);
+    public void testUserSwitchedToSystemUser() {
+        notifyUserSwitched(UserHandle.USER_SYSTEM, true);
 
-        ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
-        verifyZeroInteractions(mConnectionListener);
-    }
-
-    @Test
-    public void testHalClientConnected() {
-        IBinder binder = bindHalClient();
-        verify(mConnectionListener).onClientConnected(eq(HAL_CLIENT_NAME), eq(binder));
-    }
-
-    private IBinder bindHalClient() {
-        IBinder binder = new Binder();
-        mHalClientConnected.accept(binder);
-        return binder;
+        // Neither user nor system processes will be bound for system user intent
+        verifySystemBind(0);
+        verifyUserBind(0);
     }
 
     @Test
     public void testOnSystemServiceConnected() {
         IBinder binder = bindSystemClient();
-        verify(mConnectionListener).onClientConnected(eq(SYSTEM_CLIENT_NAME), eq(binder));
+        verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
     }
 
     private IBinder bindSystemClient() {
@@ -446,7 +431,7 @@
         verifySystemBind(1);
         resetContext();
 
-        IBinder binder = new Binder();
+        IBinder binder = createPublisherBinder();
         ServiceConnection connection = mConnectionCaptor.getValue();
         connection.onServiceConnected(null, binder);
         return binder;
@@ -455,137 +440,236 @@
     @Test
     public void testOnUserServiceConnected() {
         IBinder binder = bindUserClient();
-        verify(mConnectionListener).onClientConnected(eq(USER_CLIENT_NAME), eq(binder));
+        verifyOnClientConnected(USER_CLIENT_NAME, binder);
     }
 
     private IBinder bindUserClient() {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         resetContext();
 
-        IBinder binder = new Binder();
+        IBinder binder = createPublisherBinder();
         ServiceConnection connection = mConnectionCaptor.getValue();
         connection.onServiceConnected(null, binder);
         return binder;
     }
 
     @Test
-    public void testOnHalClientDisconnected() throws Exception {
-        bindHalClient();
-        mHalClientDisconnected.run();
-
-        verify(mConnectionListener).onClientDisconnected(eq(HAL_CLIENT_NAME));
-    }
-
-    @Test
     public void testOnSystemServiceDisconnected() throws Exception {
         notifySystemUserUnlocked();
         verifySystemBind(1);
         resetContext();
 
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
-        connection.onServiceDisconnected(null);
+        connection.onServiceConnected(null, createPublisherBinder());
+        reset(mPublisherService);
 
-        verify(mContext).unbindService(connection);
-        verify(mConnectionListener).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
+        connection.onServiceDisconnected(null);
+        verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
 
         Thread.sleep(10);
+        verify(mContext).unbindService(connection);
         verifySystemBind(1);
     }
 
     @Test
-    public void testOnSystemServiceDisconnected_ServiceNotConnected() throws Exception {
+    public void testOnSystemServiceDisconnected_ServiceReboundByAndroid() throws Exception {
         notifySystemUserUnlocked();
         verifySystemBind(1);
         resetContext();
 
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceDisconnected(null);
+        IBinder binder = createPublisherBinder();
+        connection.onServiceConnected(null, binder);
+        verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
+        reset(mPublisherService);
 
-        verify(mContext).unbindService(connection);
-        verifyZeroInteractions(mConnectionListener);
+        connection.onServiceDisconnected(null);
+        verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
+
+        binder = createPublisherBinder();
+        connection.onServiceConnected(null, binder);
+        verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
+        // No more interactions (verified by tearDown)
+    }
+
+
+    @Test
+    public void testOnSystemServiceBindingDied() throws Exception {
+        notifySystemUserUnlocked();
+        verifySystemBind(1);
+        resetContext();
+
+        ServiceConnection connection = mConnectionCaptor.getValue();
+        connection.onServiceConnected(null, createPublisherBinder());
+        reset(mPublisherService);
+
+        connection.onBindingDied(null);
+        verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
 
         Thread.sleep(10);
+        verify(mContext).unbindService(connection);
+        verifySystemBind(1);
+    }
+
+    @Test
+    public void testOnSystemServiceBindingDied_ServiceNotConnected() throws Exception {
+        notifySystemUserUnlocked();
+        verifySystemBind(1);
+        resetContext();
+
+        ServiceConnection connection = mConnectionCaptor.getValue();
+        connection.onBindingDied(null);
+
+        verifyZeroInteractions(mPublisherService);
+
+        Thread.sleep(10);
+        verify(mContext).unbindService(connection);
         verifySystemBind(1);
     }
 
     @Test
     public void testOnUserServiceDisconnected() throws Exception {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         resetContext();
 
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
-        connection.onServiceDisconnected(null);
+        connection.onServiceConnected(null, createPublisherBinder());
+        reset(mPublisherService);
 
-        verify(mContext).unbindService(connection);
-        verify(mConnectionListener).onClientDisconnected(eq(USER_CLIENT_NAME));
+        connection.onServiceDisconnected(null);
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
 
         Thread.sleep(10);
+        verify(mContext).unbindService(connection);
         verifyUserBind(1);
     }
 
     @Test
-    public void testOnUserServiceDisconnected_ServiceNotConnected() throws Exception {
-        notifyUserUnlocked();
+    public void testOnUserServiceDisconnected_ServiceReboundByAndroid() throws Exception {
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         resetContext();
 
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceDisconnected(null);
+        IBinder binder = createPublisherBinder();
+        connection.onServiceConnected(null, binder);
+        verifyOnClientConnected(USER_CLIENT_NAME, binder);
+        reset(mPublisherService);
 
-        verify(mContext).unbindService(connection);
-        verifyZeroInteractions(mConnectionListener);
+        connection.onServiceDisconnected(null);
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+
+        binder = createPublisherBinder();
+        connection.onServiceConnected(null, binder);
+        verifyOnClientConnected(USER_CLIENT_NAME, binder);
+        // No more interactions (verified by tearDown)
+    }
+
+    @Test
+    public void testOnUserServiceBindingDied() throws Exception {
+        notifyUserUnlocked(USER_ID, true);
+        verifyUserBind(1);
+        resetContext();
+
+        ServiceConnection connection = mConnectionCaptor.getValue();
+        connection.onServiceConnected(null, createPublisherBinder());
+        reset(mPublisherService);
+
+        connection.onBindingDied(null);
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
 
         Thread.sleep(10);
+        verify(mContext).unbindService(connection);
+        verifyUserBind(1);
+    }
+
+    @Test
+    public void testOnUserServiceBindingDied_ServiceNotConnected() throws Exception {
+        notifyUserUnlocked(USER_ID, true);
+        verifyUserBind(1);
+        resetContext();
+
+        ServiceConnection connection = mConnectionCaptor.getValue();
+        connection.onBindingDied(null);
+
+        verifyZeroInteractions(mPublisherService);
+
+        Thread.sleep(10);
+        verify(mContext).unbindService(connection);
         verifyUserBind(1);
     }
 
     @Test
     public void testOnUserSwitched_UserChange() {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
+        connection.onServiceConnected(null, createPublisherBinder());
         resetContext();
-        reset(mConnectionListener);
+        reset(mPublisherService);
 
-        mUserId = 11;
-        notifyUserSwitched();
+        notifyUserSwitched(USER_ID_U11, true);
 
         verify(mContext).unbindService(connection);
-        verify(mConnectionListener).onClientDisconnected(eq(USER_CLIENT_NAME));
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+        verifyUserBind(1);
+    }
+
+    @Test
+    public void testOnUserSwitched_UserChange_ForegroundUserNotUnlocked() {
+        notifyUserUnlocked(USER_ID, true);
+        verifyUserBind(1);
+        ServiceConnection connection = mConnectionCaptor.getValue();
+        connection.onServiceConnected(null, createPublisherBinder());
+        resetContext();
+        reset(mPublisherService);
+
+        notifyUserSwitched(USER_ID_U11, false);
+
+        verify(mContext).unbindService(connection);
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
         verifyUserBind(0);
     }
 
     @Test
     public void testOnUserSwitched_UserChange_ToSystemUser() {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
+        connection.onServiceConnected(null, createPublisherBinder());
         resetContext();
-        reset(mConnectionListener);
+        reset(mPublisherService);
 
-        mUserId = UserHandle.USER_SYSTEM;
-        notifyUserSwitched();
+        notifyUserSwitched(UserHandle.USER_SYSTEM, true);
 
         verify(mContext).unbindService(connection);
-        verify(mConnectionListener).onClientDisconnected(eq(USER_CLIENT_NAME));
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
         verifyUserBind(0);
     }
 
     @Test
     public void testOnUserSwitched_UserChange_ServiceNotConnected() {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
-        mUserId = 11;
-        notifyUserSwitched();
+        notifyUserSwitched(USER_ID_U11, true);
+
+        verify(mContext).unbindService(connection);
+        verifyUserBind(1);
+    }
+
+    @Test
+    public void testOnUserSwitched_UserChange_ServiceNotConnected_ForegroundUserNotUnlocked() {
+        notifyUserUnlocked(USER_ID, true);
+        verifyUserBind(1);
+        ServiceConnection connection = mConnectionCaptor.getValue();
+        resetContext();
+
+        notifyUserSwitched(USER_ID_U11, false);
 
         verify(mContext).unbindService(connection);
         verifyUserBind(0);
@@ -593,18 +677,17 @@
 
     @Test
     public void testOnUserUnlocked_UserChange() {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
+        connection.onServiceConnected(null, createPublisherBinder());
         resetContext();
-        reset(mConnectionListener);
+        reset(mPublisherService);
 
-        mUserId = 11;
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID_U11, true);
 
         verify(mContext).unbindService(connection);
-        verify(mConnectionListener).onClientDisconnected(eq(USER_CLIENT_NAME));
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
         verifyUserBind(1);
     }
 
@@ -612,36 +695,238 @@
     public void testOnUserUnlocked_UserChange_ToSystemUser() {
         notifySystemUserUnlocked();
         verifySystemBind(1);
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         ServiceConnection connection = mConnectionCaptor.getValue();
-        connection.onServiceConnected(null, new Binder());
+        connection.onServiceConnected(null, createPublisherBinder());
         resetContext();
-        reset(mConnectionListener);
+        reset(mPublisherService);
 
-        mUserId = UserHandle.USER_SYSTEM;
-        notifyUserUnlocked();
+        notifyUserUnlocked(UserHandle.USER_SYSTEM, true);
 
         verify(mContext).unbindService(connection);
-        verify(mConnectionListener).onClientDisconnected(eq(USER_CLIENT_NAME));
+        verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
         // User processes will not be bound for system user
         verifyUserBind(0);
     }
 
     @Test
     public void testOnUserUnlocked_UserChange_ServiceNotConnected() {
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID, true);
         verifyUserBind(1);
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
-        mUserId = 11;
-        notifyUserUnlocked();
+        notifyUserUnlocked(USER_ID_U11, true);
 
         verify(mContext).unbindService(connection);
         verifyUserBind(1);
     }
 
+    @Test
+    public void testAddSubscriber() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+    @Test
+    public void testAddSubscriber_SystemUser() {
+        mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
+        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
+
+        mClientManager.addSubscriber(mSubscriberClient1);
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+    @Test
+    public void testAddSubscriber_NotForegroundUser() {
+        mCallingAppUid = UserHandle.getUid(USER_ID_U11, 0);
+
+        try {
+            mClientManager.addSubscriber(mSubscriberClient1);
+            fail("Expected client to be rejected");
+        } catch (SecurityException expected) {
+            // expected
+        }
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+    }
+
+    @Test
+    public void testAddSubscriber_MultipleCalls() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+        mClientManager.addSubscriber(mSubscriberClient1);
+        verify(mPackageManager, atMost(1)).getNameForUid(anyInt());
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+    @Test
+    public void testAddSubscriber_MultipleClients_SamePackage() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+        mClientManager.addSubscriber(mSubscriberClient2);
+        verify(mPackageManager, atMost(2)).getNameForUid(anyInt());
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+    @Test
+    public void testAddSubscriber_MultipleClients_ForegroundAndSystemUsers_SamePackage() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+
+        mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
+        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
+        mClientManager.addSubscriber(mSubscriberClient2);
+
+        verify(mPackageManager, atMost(2)).getNameForUid(anyInt());
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+
+    @Test
+    public void testAddSubscriber_MultipleClients_MultiplePackages() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+
+        mCallingAppUid = UserHandle.getUid(mForegroundUserId, 1);
+        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn("test.package2");
+        mClientManager.addSubscriber(mSubscriberClient2);
+
+        verify(mPackageManager, times(2)).getNameForUid(anyInt());
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals("test.package2", mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+    @Test
+    public void testRemoveSubscriber() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+        mClientManager.removeSubscriber(mSubscriberClient1);
+        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+    }
+
+    @Test
+    public void testRemoveSubscriber_NotRegistered() {
+        mClientManager.removeSubscriber(mSubscriberClient1);
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+    }
+
+    @Test
+    public void testRemoveSubscriber_OnDeath() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+
+        verify(mSubscriberBinder1).linkToDeath(mDeathRecipient.capture(), eq(0));
+        mDeathRecipient.getValue().binderDied();
+
+        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+    }
+
+    @Test
+    public void testOnUserSwitch_RemoveSubscriber() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+
+        mForegroundUserId = USER_ID_U11;
+        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+
+        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertTrue(mClientManager.getAllSubscribers().isEmpty());
+    }
+
+    @Test
+    public void testOnUserSwitch_RemoveSubscriber_AddNewSubscriber() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+
+        mForegroundUserId = USER_ID_U11;
+        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
+
+        mCallingAppUid = UserHandle.getUid(USER_ID_U11, 0);
+        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
+        mClientManager.addSubscriber(mSubscriberClient2);
+
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+        assertFalse(mClientManager.getAllSubscribers().contains(mSubscriberClient1));
+        assertTrue(mClientManager.getAllSubscribers().contains(mSubscriberClient2));
+    }
+
+    @Test
+    public void testOnUserSwitch_RemoveSubscriber_RetainSystemClient() {
+        mClientManager.addSubscriber(mSubscriberClient1);
+
+        mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
+        when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
+
+        mClientManager.addSubscriber(mSubscriberClient2);
+
+        mForegroundUserId = USER_ID_U11;
+        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+
+        verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
+        verify(mBrokerService, never()).removeDeadSubscriber(mSubscriberClient2);
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+        assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+    }
+
+    @Test
+    public void testOnUserSwitch_RemoveSubscriber_RetainHalClient() {
+        IVmsPublisherClient publisherClient = createPublisherClient();
+        IVmsSubscriberClient subscriberClient = createSubscriberClient();
+        mClientManager.onHalConnected(publisherClient, subscriberClient);
+        reset(mPublisherService);
+
+        mForegroundUserId = USER_ID_U11;
+        mClientManager.mUserSwitchReceiver.onReceive(mContext, new Intent());
+
+        verify(mBrokerService, never()).removeDeadSubscriber(subscriberClient);
+        assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
+    }
+
+    @Test
+    public void testHalClientConnected() {
+        IVmsPublisherClient publisherClient = createPublisherClient();
+        IVmsSubscriberClient subscriberClient = createSubscriberClient();
+        mClientManager.onHalConnected(publisherClient, subscriberClient);
+        verify(mPublisherService).onClientConnected(eq(HAL_CLIENT_NAME), same(publisherClient));
+        assertTrue(mClientManager.getAllSubscribers().contains(subscriberClient));
+        assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
+    }
+
+    @Test
+    public void testHalClientConnected_AfterAddSubscriber() {
+        IVmsPublisherClient publisherClient = createPublisherClient();
+        IVmsSubscriberClient subscriberClient = createSubscriberClient();
+        mClientManager.addSubscriber(subscriberClient);
+
+        mClientManager.onHalConnected(publisherClient, subscriberClient);
+        verify(mPublisherService).onClientConnected(eq(HAL_CLIENT_NAME), same(publisherClient));
+        assertTrue(mClientManager.getAllSubscribers().contains(subscriberClient));
+        assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
+    }
+
+    @Test
+    public void testOnHalClientDisconnected() {
+        IVmsPublisherClient publisherClient = createPublisherClient();
+        IVmsSubscriberClient subscriberClient = createSubscriberClient();
+        mClientManager.onHalConnected(publisherClient, subscriberClient);
+        reset(mPublisherService);
+
+        mClientManager.onHalDisconnected();
+        verify(mPublisherService).onClientDisconnected(eq(HAL_CLIENT_NAME));
+        verify(mBrokerService).removeDeadSubscriber(eq(subscriberClient));
+        assertFalse(mClientManager.getAllSubscribers().contains(subscriberClient));
+        assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(subscriberClient));
+    }
+
+    @Test
+    public void testOnHalClientDisconnected_NotConnected() {
+        mClientManager.onHalDisconnected();
+        verify(mPublisherService, never()).onClientDisconnected(eq(HAL_CLIENT_NAME));
+        assertTrue(mClientManager.getAllSubscribers().isEmpty());
+    }
+
     private void resetContext() {
         reset(mContext);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
@@ -653,14 +938,24 @@
         mClientManager.mSystemUserUnlockedListener.run();
     }
 
-    private void notifyUserSwitched() {
-        mClientManager.mUserSwitchReceiver.onReceive(mContext,
-                new Intent(Intent.ACTION_USER_SWITCHED));
+    private void notifyUserSwitched(int foregroundUserId, boolean isForegroundUserUnlocked) {
+        notifyUserAction(foregroundUserId, isForegroundUserUnlocked, Intent.ACTION_USER_SWITCHED);
     }
 
-    private void notifyUserUnlocked() {
-        mClientManager.mUserSwitchReceiver.onReceive(mContext,
-                new Intent(Intent.ACTION_USER_UNLOCKED));
+    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));
     }
 
     private void verifySystemBind(int times) {
@@ -668,7 +963,7 @@
     }
 
     private void verifyUserBind(int times) {
-        verifyBind(times, USER_CLIENT_COMPONENT, UserHandle.of(mUserId));
+        verifyBind(times, USER_CLIENT_COMPONENT, UserHandle.of(mForegroundUserId));
     }
 
     private void verifyBind(int times, ComponentName componentName, UserHandle user) {
@@ -679,4 +974,43 @@
                 mConnectionCaptor.capture(),
                 eq(Context.BIND_AUTO_CREATE), any(Handler.class), eq(user));
     }
+
+    private void verifyOnClientConnected(String publisherName, IBinder binder) {
+        ArgumentCaptor<IVmsPublisherClient> clientCaptor =
+                ArgumentCaptor.forClass(IVmsPublisherClient.class);
+        verify(mPublisherService).onClientConnected(eq(publisherName), clientCaptor.capture());
+        assertSame(binder, clientCaptor.getValue().asBinder());
+    }
+
+    private IBinder createPublisherBinder() {
+        return createPublisherClient().asBinder();
+    }
+
+    private IVmsPublisherClient createPublisherClient() {
+        return new IVmsPublisherClient.Stub() {
+            @Override
+            public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
+                throw new RuntimeException("Unexpected call");
+            }
+
+            @Override
+            public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
+                throw new RuntimeException("Unexpected call");
+            }
+        };
+    }
+
+    private IVmsSubscriberClient createSubscriberClient() {
+        return new IVmsSubscriberClient.Stub() {
+            @Override
+            public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
+                throw new RuntimeException("Unexpected call");
+            }
+
+            @Override
+            public void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers) {
+                throw new RuntimeException("Unexpected call");
+            }
+        };
+    }
 }