Merge "Make call button behave like actual incoming call with proper flags."
diff --git a/FrameworkPackageStubs/Android.mk b/FrameworkPackageStubs/Android.mk
new file mode 100644
index 0000000..c6a68c8
--- /dev/null
+++ b/FrameworkPackageStubs/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CarFrameworkPackageStubs
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_PACKAGE)
diff --git a/FrameworkPackageStubs/AndroidManifest.xml b/FrameworkPackageStubs/AndroidManifest.xml
new file mode 100644
index 0000000..42a2317
--- /dev/null
+++ b/FrameworkPackageStubs/AndroidManifest.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.frameworkpackagestubs"
+          android:versionCode="1"
+          android:versionName="1.0.1">
+
+    <uses-sdk android:minSdkVersion="28"/>
+
+    <application android:label="@string/app_name"
+                 android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
+
+        <!-- Music / media stubs. -->
+        <activity android:name=".Stubs$MediaStub"
+                  android:label="@string/stub_name"
+                  android:excludeFromRecents="true">
+            <intent-filter android:priority="-1">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="file"/>
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+            </intent-filter>
+            <intent-filter android:priority="-1">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http"/>
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+            </intent-filter>
+            <intent-filter android:priority="-1">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="content"/>
+                <data android:mimeType="audio/*"/>
+                <data android:mimeType="application/ogg"/>
+                <data android:mimeType="application/x-ogg"/>
+            </intent-filter>
+            <intent-filter android:priority="-1">
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/audio"/>
+            </intent-filter>
+        </activity>
+
+        <!-- Settings package stubs -->
+        <activity android:name=".Stubs$SettingsStub"
+                  android:label="@string/stub_name"
+                  android:excludeFromRecents="true">
+            <intent-filter android:priority="-1">
+                <action android:name="android.app.action.ADD_DEVICE_ADMIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter android:priority="-1">
+                <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter android:priority="-1">
+                <action android:name="android.settings.USER_DICTIONARY_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+    </application>
+</manifest>
diff --git a/FrameworkPackageStubs/res/values/strings.xml b/FrameworkPackageStubs/res/values/strings.xml
new file mode 100644
index 0000000..1446a89
--- /dev/null
+++ b/FrameworkPackageStubs/res/values/strings.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Package name [CHAR LIMIT=NONE]-->
+    <string name="app_name">Activity Stub</string>
+
+    <!-- Toast message displayed when a stub activity is launched. [CHAR LIMIT=NONE] -->
+    <string name="message_not_supported">No application can handle this action</string>
+
+    <!-- Stub name [CHAR LIMIT=NONE]-->
+    <string name="stub_name">None</string>
+
+</resources>
diff --git a/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java b/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java
new file mode 100644
index 0000000..5ebcabd
--- /dev/null
+++ b/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java
@@ -0,0 +1,75 @@
+/*
+ * 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.frameworkpackagestubs;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Toast;
+
+/**
+ * Contains different stub classes.
+ *
+ * <p>These are broken out so that the intent filters are easier to track and so that
+ * individual ones can create more specific messages if desired.
+ */
+public final class Stubs {
+
+    /**
+     * Base class for stubs.
+     *
+     * <p>Shows a toast and finishes.
+     *
+     * <p>Subclasses can override {@link #getMessage()} to customize the message.
+     */
+    private static class BaseActivity extends Activity {
+
+        private Toast mToast;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            showToast();
+            finish();
+        }
+
+        protected CharSequence getMessage() {
+            return getResources().getString(R.string.message_not_supported);
+        }
+
+        private void showToast() {
+            cancelToast();
+            mToast = Toast.makeText(this, getMessage(), Toast.LENGTH_LONG);
+            mToast.show();
+        }
+
+        private void cancelToast() {
+            if (mToast != null) {
+                mToast.cancel();
+            }
+        }
+    }
+
+    /**
+     * Stub activity for media events.
+     */
+    public static class MediaStub extends BaseActivity { }
+
+    /**
+     * Stub activity for setting events.
+     */
+    public static class SettingsStub extends BaseActivity { }
+}
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index 4e03d8c..2c8858a 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -215,6 +215,9 @@
         },
     },
     compile_dex: true,
+    dist: {
+        targets: ["dist_files"],
+    }
 }
 
 java_library_static {
@@ -231,6 +234,9 @@
         },
     },
     compile_dex: true,
+    dist: {
+        targets: ["dist_files"],
+    }
 }
 
 java_library_static {
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index e81aa91..ac38b8b 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -51,7 +51,7 @@
     field public static final int APP_FOCUS_REQUEST_FAILED = 0; // 0x0
     field public static final int APP_FOCUS_REQUEST_SUCCEEDED = 1; // 0x1
     field public static final int APP_FOCUS_TYPE_NAVIGATION = 1; // 0x1
-    field public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2
+    field @Deprecated public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2
   }
 
   public static interface CarAppFocusManager.OnAppFocusChangedListener {
@@ -298,7 +298,6 @@
     method public int getMaxContentDepth();
     method public int getMaxCumulativeContentItems();
     method public int getMaxRestrictedStringLength();
-    method public long getTimeStamp();
     method public boolean isRequiresDistractionOptimization();
     method public boolean isSameRestrictions(android.car.drivingstate.CarUxRestrictions);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/car-lib/api/system-baseline.txt b/car-lib/api/system-baseline.txt
index 3d4f02d..72d9c6a 100644
--- a/car-lib/api/system-baseline.txt
+++ b/car-lib/api/system-baseline.txt
@@ -53,7 +53,4 @@
     Documentation mentions 'TODO'
 Todo: android.car.cluster.renderer.InstrumentClusterRenderer:
     Documentation mentions 'TODO'
-Todo: android.car.drivingstate.CarDrivingStateEvent#DRIVING_STATE_IDLING:
-    Documentation mentions 'TODO'
-
 
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 4ac83df..0975e90 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -1,6 +1,16 @@
 // Signature format: 2.0
 package android.car {
 
+  public abstract class AoapService extends android.app.Service {
+    ctor public AoapService();
+    method @MainThread public int canSwitchToAoap(@NonNull android.hardware.usb.UsbDevice);
+    method @MainThread public abstract int isDeviceSupported(@NonNull android.hardware.usb.UsbDevice);
+    method public android.os.IBinder onBind(android.content.Intent);
+    field public static final int RESULT_DEVICE_NOT_SUPPORTED = 1; // 0x1
+    field public static final int RESULT_DO_NOT_SWITCH_TO_AOAP = 2; // 0x2
+    field public static final int RESULT_OK = 0; // 0x0
+  }
+
   public final class Car {
     field public static final String CABIN_SERVICE = "cabin";
     field public static final String CAR_DRIVING_STATE_SERVICE = "drivingstate";
@@ -49,8 +59,8 @@
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void registerProjectionListener(@NonNull android.car.CarProjectionManager.CarProjectionListener, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void registerProjectionRunner(@NonNull android.content.Intent);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION_STATUS) public void registerProjectionStatusListener(@NonNull android.car.CarProjectionManager.ProjectionStatusListener);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public boolean releaseBluetoothProfileInhibit(android.bluetooth.BluetoothDevice, int, android.os.IBinder);
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public boolean requestBluetoothProfileInhibit(@NonNull android.bluetooth.BluetoothDevice, int, @NonNull android.os.IBinder);
+    method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public boolean releaseBluetoothProfileInhibit(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public boolean requestBluetoothProfileInhibit(@NonNull android.bluetooth.BluetoothDevice, int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void startProjectionAccessPoint(@NonNull android.car.CarProjectionManager.ProjectionAccessPointCallback);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void stopProjectionAccessPoint();
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_PROJECTION) public void unregisterProjectionListener();
@@ -820,7 +830,7 @@
     method public void onSnapshot(android.car.storagemonitoring.IoStats);
   }
 
-  public class IoStats implements android.os.Parcelable {
+  public final class IoStats implements android.os.Parcelable {
     ctor public IoStats(java.util.List<android.car.storagemonitoring.IoStatsEntry>, long);
     ctor public IoStats(android.os.Parcel);
     ctor public IoStats(org.json.JSONObject) throws org.json.JSONException;
@@ -866,7 +876,7 @@
     field public final long fsyncCalls;
   }
 
-  public class LifetimeWriteInfo implements android.os.Parcelable {
+  public final class LifetimeWriteInfo implements android.os.Parcelable {
     ctor public LifetimeWriteInfo(String, String, long);
     ctor public LifetimeWriteInfo(android.os.Parcel);
     ctor public LifetimeWriteInfo(org.json.JSONObject) throws org.json.JSONException;
@@ -894,7 +904,7 @@
     field public final int uid;
   }
 
-  public class WearEstimate implements android.os.Parcelable {
+  public final class WearEstimate implements android.os.Parcelable {
     ctor public WearEstimate(int, int);
     ctor public WearEstimate(android.os.Parcel);
     ctor public WearEstimate(android.util.JsonReader) throws java.io.IOException;
@@ -907,7 +917,7 @@
     field @IntRange(from=0xffffffff, to=100) public final int typeB;
   }
 
-  public class WearEstimateChange implements android.os.Parcelable {
+  public final class WearEstimateChange implements android.os.Parcelable {
     ctor public WearEstimateChange(android.car.storagemonitoring.WearEstimate, android.car.storagemonitoring.WearEstimate, long, java.time.Instant, boolean);
     ctor public WearEstimateChange(android.os.Parcel);
     method public int describeContents();
@@ -935,7 +945,7 @@
 package android.car.trust {
 
   public final class CarTrustAgentEnrollmentManager {
-    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void enrollmentHandshakeAccepted();
+    method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void enrollmentHandshakeAccepted(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) @NonNull public java.util.List<java.lang.Long> getEnrollmentHandlesForUser(int);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public void initiateEnrollmentHandshake(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.car.Car.PERMISSION_CAR_ENROLL_TRUST) public boolean isEscrowTokenActive(long, int);
@@ -966,10 +976,19 @@
 
 package android.car.vms {
 
-  public final class VmsAvailableLayers implements android.os.Parcelable {
-    ctor public VmsAvailableLayers(java.util.Set<android.car.vms.VmsAssociatedLayer>, int);
+  public final class VmsAssociatedLayer implements android.os.Parcelable {
+    ctor public VmsAssociatedLayer(@NonNull android.car.vms.VmsLayer, @NonNull java.util.Set<java.lang.Integer>);
     method public int describeContents();
-    method public java.util.Set<android.car.vms.VmsAssociatedLayer> getAssociatedLayers();
+    method @NonNull public java.util.Set<java.lang.Integer> getPublisherIds();
+    method @NonNull public android.car.vms.VmsLayer getVmsLayer();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsAssociatedLayer> CREATOR;
+  }
+
+  public final class VmsAvailableLayers implements android.os.Parcelable {
+    ctor public VmsAvailableLayers(@NonNull java.util.Set<android.car.vms.VmsAssociatedLayer>, int);
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.car.vms.VmsAssociatedLayer> getAssociatedLayers();
     method public int getSequence();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.car.vms.VmsAvailableLayers> CREATOR;
@@ -986,17 +1005,17 @@
   }
 
   public final class VmsLayerDependency implements android.os.Parcelable {
-    ctor public VmsLayerDependency(android.car.vms.VmsLayer, java.util.Set<android.car.vms.VmsLayer>);
-    ctor public VmsLayerDependency(android.car.vms.VmsLayer);
+    ctor public VmsLayerDependency(@NonNull android.car.vms.VmsLayer, @NonNull java.util.Set<android.car.vms.VmsLayer>);
+    ctor public VmsLayerDependency(@NonNull android.car.vms.VmsLayer);
     method public int describeContents();
-    method public java.util.Set<android.car.vms.VmsLayer> getDependencies();
-    method public android.car.vms.VmsLayer getLayer();
+    method @NonNull public java.util.Set<android.car.vms.VmsLayer> getDependencies();
+    method @NonNull public android.car.vms.VmsLayer getLayer();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayerDependency> CREATOR;
   }
 
   public final class VmsLayersOffering implements android.os.Parcelable {
-    ctor public VmsLayersOffering(java.util.Set<android.car.vms.VmsLayerDependency>, int);
+    ctor public VmsLayersOffering(@NonNull java.util.Set<android.car.vms.VmsLayerDependency>, int);
     method public int describeContents();
     method public java.util.Set<android.car.vms.VmsLayerDependency> getDependencies();
     method public int getPublisherId();
@@ -1028,30 +1047,40 @@
   public abstract class VmsPublisherClientService extends android.app.Service {
     ctor public VmsPublisherClientService();
     method public final int getPublisherId(byte[]);
-    method @Nullable public final android.car.vms.VmsSubscriptionState getSubscriptions();
+    method public final android.car.vms.VmsSubscriptionState getSubscriptions();
     method public android.os.IBinder onBind(android.content.Intent);
     method protected abstract void onVmsPublisherServiceReady();
-    method public abstract void onVmsSubscriptionChange(android.car.vms.VmsSubscriptionState);
-    method public final void publish(android.car.vms.VmsLayer, int, byte[]);
-    method public final void setLayersOffering(android.car.vms.VmsLayersOffering);
+    method public abstract void onVmsSubscriptionChange(@NonNull android.car.vms.VmsSubscriptionState);
+    method public final void publish(@NonNull android.car.vms.VmsLayer, int, byte[]);
+    method public final void setLayersOffering(@NonNull android.car.vms.VmsLayersOffering);
   }
 
   public final class VmsSubscriberManager {
     method public void clearVmsSubscriberClientCallback();
-    method public android.car.vms.VmsAvailableLayers getAvailableLayers();
-    method public byte[] getPublisherInfo(int);
+    method @NonNull public android.car.vms.VmsAvailableLayers getAvailableLayers();
+    method @NonNull public byte[] getPublisherInfo(int);
     method public void setVmsSubscriberClientCallback(@NonNull java.util.concurrent.Executor, @NonNull android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback);
     method public void startMonitoring();
     method public void stopMonitoring();
-    method public void subscribe(android.car.vms.VmsLayer);
-    method public void subscribe(android.car.vms.VmsLayer, int);
-    method public void unsubscribe(android.car.vms.VmsLayer);
-    method public void unsubscribe(android.car.vms.VmsLayer, int);
+    method public void subscribe(@NonNull android.car.vms.VmsLayer);
+    method public void subscribe(@NonNull android.car.vms.VmsLayer, int);
+    method public void unsubscribe(@NonNull android.car.vms.VmsLayer);
+    method public void unsubscribe(@NonNull android.car.vms.VmsLayer, int);
   }
 
   public static interface VmsSubscriberManager.VmsSubscriberClientCallback {
-    method public void onLayersAvailabilityChanged(android.car.vms.VmsAvailableLayers);
-    method public void onVmsMessageReceived(android.car.vms.VmsLayer, byte[]);
+    method public void onLayersAvailabilityChanged(@NonNull android.car.vms.VmsAvailableLayers);
+    method public void onVmsMessageReceived(@NonNull android.car.vms.VmsLayer, byte[]);
+  }
+
+  public final class VmsSubscriptionState implements android.os.Parcelable {
+    ctor public VmsSubscriptionState(int, @NonNull java.util.Set<android.car.vms.VmsLayer>, @NonNull java.util.Set<android.car.vms.VmsAssociatedLayer>);
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.car.vms.VmsAssociatedLayer> getAssociatedLayers();
+    method @NonNull public java.util.Set<android.car.vms.VmsLayer> getLayers();
+    method public int getSequenceNumber();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsSubscriptionState> CREATOR;
   }
 
 }
diff --git a/car-lib/api/test-baseline.txt b/car-lib/api/test-baseline.txt
index d71a9e5..6cb7b91 100644
--- a/car-lib/api/test-baseline.txt
+++ b/car-lib/api/test-baseline.txt
@@ -1,8 +1,4 @@
 // Baseline format: 1.0
-HiddenTypeParameter: android.car.drivingstate.CarUxRestrictionsManager#getConfig():
-    Method android.car.drivingstate.CarUxRestrictionsManager.getConfig() references hidden type android.car.drivingstate.CarUxRestrictionsConfiguration.
-HiddenTypeParameter: android.car.drivingstate.CarUxRestrictionsManager#getStagedConfig():
-    Method android.car.drivingstate.CarUxRestrictionsManager.getStagedConfig() references hidden type android.car.drivingstate.CarUxRestrictionsConfiguration.
 HiddenTypeParameter: android.car.hardware.CarSensorManager#getPropertyList():
     Method android.car.hardware.CarSensorManager.getPropertyList() references hidden type class android.car.hardware.CarPropertyConfig.
 HiddenTypeParameter: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
@@ -25,16 +21,7 @@
     Typedef references constant which isn't part of the API, skipping in documentation: android.car.hardware.CarSensorManager#SENSOR_TYPE_ENGINE_OIL_LEVEL
 
 
-MissingPermission: android.car.drivingstate.CarUxRestrictionsManager#getConfig():
-    Permission Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION required by method android.car.drivingstate.CarUxRestrictionsManager.getConfig() is hidden or removed
-MissingPermission: android.car.drivingstate.CarUxRestrictionsManager#getStagedConfig():
-    Permission Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION required by method android.car.drivingstate.CarUxRestrictionsManager.getStagedConfig() is hidden or removed
 
-
-ReferencesHidden: android.car.drivingstate.CarUxRestrictionsManager#getConfig():
-    Class android.car.drivingstate.CarUxRestrictionsConfiguration is hidden but was referenced (as return type) from public method android.car.drivingstate.CarUxRestrictionsManager.getConfig()
-ReferencesHidden: android.car.drivingstate.CarUxRestrictionsManager#getStagedConfig():
-    Class android.car.drivingstate.CarUxRestrictionsConfiguration is hidden but was referenced (as return type) from public method android.car.drivingstate.CarUxRestrictionsManager.getStagedConfig()
 ReferencesHidden: android.car.hardware.CarSensorManager#getPropertyList():
     Class android.car.hardware.CarPropertyConfig is hidden but was referenced (as return type parameter) from public method android.car.hardware.CarSensorManager.getPropertyList()
 ReferencesHidden: android.car.navigation.CarNavigationStatusManager#getInstrumentClusterInfo():
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index 6658de9..d6b162e 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -7,15 +7,6 @@
 
 }
 
-package android.car.drivingstate {
-
-  public final class CarUxRestrictionsManager {
-    method @RequiresPermission("android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION") public android.car.drivingstate.CarUxRestrictionsConfiguration getConfig();
-    method @Nullable @RequiresPermission("android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION") public android.car.drivingstate.CarUxRestrictionsConfiguration getStagedConfig();
-  }
-
-}
-
 package android.car.media {
 
   public final class CarAudioManager {
diff --git a/car-lib/src/android/car/AoapService.java b/car-lib/src/android/car/AoapService.java
new file mode 100644
index 0000000..20b7fa7
--- /dev/null
+++ b/car-lib/src/android/car/AoapService.java
@@ -0,0 +1,247 @@
+/*
+ * 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.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+/**
+ * The service that must be implemented by USB AOAP handler system apps. The app must hold the
+ * following permission: {@code android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE}.
+ *
+ * <p>This service gets bound by the framework and the service needs to be protected by
+ * {@code android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE} permission to ensure nobody else can
+ * bind to the service. At most only one client should be bound at a time.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AoapService extends Service {
+    private static final String TAG = AoapService.class.getSimpleName();
+
+    /** Indicates success or confirmation. */
+    public static final int RESULT_OK = 0;
+
+    /**
+     * Indicates that the device is not supported by this service and system shouldn't associate
+     * given device with this service.
+     */
+    public static final int RESULT_DEVICE_NOT_SUPPORTED = 1;
+
+    /**
+     * Indicates that device shouldn't be switch to AOAP mode at this time.
+     */
+    public static final int RESULT_DO_NOT_SWITCH_TO_AOAP = 2;
+
+    /** @hide */
+    @IntDef(value = {
+            RESULT_OK, RESULT_DEVICE_NOT_SUPPORTED, RESULT_DO_NOT_SWITCH_TO_AOAP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Result {}
+
+
+    /**
+     * A message sent from the system USB handler service to AOAP handler service to check if the
+     * device is supported. The message must have {@link #KEY_DEVICE} with {@link UsbDevice} object.
+     *
+     * @hide
+     */
+    public static final int MSG_NEW_DEVICE_ATTACHED = 1;
+
+    /**
+     * A response message for {@link #MSG_NEW_DEVICE_ATTACHED}. Must contain {@link #KEY_RESULT}
+     * with one of the {@code RESULT_*} constant.
+     *
+     * @hide */
+    public static final int MSG_NEW_DEVICE_ATTACHED_RESPONSE = 2;
+
+    /**
+     * A message sent from the system USB handler service to AOAP handler service to check if the
+     * device can be switched to AOAP mode. The message must have {@link #KEY_DEVICE} with
+     * {@link UsbDevice} object.
+     *
+     * @hide
+     */
+    public static final int MSG_CAN_SWITCH_TO_AOAP = 3;
+
+    /**
+     * A response message for {@link #MSG_CAN_SWITCH_TO_AOAP}. Must contain {@link #KEY_RESULT}
+     * with one of the {@code RESULT_*} constant.
+     *
+     * @hide */
+    public static final int MSG_CAN_SWITCH_TO_AOAP_RESPONSE = 4;
+
+    /** @hide */
+    public static final String KEY_DEVICE = "usb-device";
+
+    /** @hide */
+    public static final String KEY_RESULT = "result";
+
+
+    /**
+     * Returns {@code true} if the given USB device is supported by this service.
+     *
+     * <p>The device is not expected to be in AOAP mode when this method is called. The purpose of
+     * this method is just to give the service a chance to tell whether based on the information
+     * provided in {@link UsbDevice} class (e.g. PID/VID) this service supports or doesn't support
+     * given device.
+     *
+     * <p>The method must return one of the following status: {@link #RESULT_OK} or
+     * {@link #RESULT_DEVICE_NOT_SUPPORTED}
+     */
+    @MainThread
+    public abstract @Result int isDeviceSupported(@NonNull UsbDevice device);
+
+    /**
+     * This method will be called at least once per connection session before switching device into
+     * AOAP mode.
+     *
+     * <p>The device is connected, but not in AOAP mode yet. Implementors of this method may ask
+     * the framework to ignore this device for now and do not switch to AOAP. This may make sense if
+     * a connection to the device has been established through other means, and switching the device
+     * to AOAP would break that connection.
+     *
+     * <p>Note: the method may be called only if this device was claimed to be supported in
+     * {@link #isDeviceSupported(UsbDevice)} method, and this app has been chosen to handle the
+     * device.
+     *
+     * <p>The method must return one of the following status: {@link #RESULT_OK},
+     * {@link #RESULT_DEVICE_NOT_SUPPORTED} or {@link #RESULT_DO_NOT_SWITCH_TO_AOAP}
+     */
+    @MainThread
+    public @Result int canSwitchToAoap(@NonNull UsbDevice device) {
+        return RESULT_OK;
+    }
+
+    private Messenger mMessenger;
+    private boolean mBound;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mMessenger = new Messenger(new IncomingHandler(this));
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (mBound) {
+            Log.w(TAG, "Received onBind event when the service was already bound");
+        }
+        mBound = true;
+        return mMessenger.getBinder();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mBound = false;
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.write("Bound: " + mBound);
+    }
+
+    private static class IncomingHandler extends Handler {
+        private final WeakReference<AoapService> mServiceRef;
+
+        IncomingHandler(AoapService service) {
+            super(Looper.getMainLooper());
+            mServiceRef = new WeakReference<>(service);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AoapService service = mServiceRef.get();
+            if (service == null) {
+                return;
+            }
+            Bundle data = msg.getData();
+            if (data == null) {
+                Log.e(TAG, "Ignoring message " + msg.what + " without data");
+                return;
+            }
+
+            Log.i(TAG, "Message received: " + msg.what);
+
+            switch (msg.what) {
+                case MSG_NEW_DEVICE_ATTACHED: {
+                    int res = service.isDeviceSupported(
+                            checkNotNull(data.getParcelable(KEY_DEVICE)));
+                    if (res != RESULT_OK && res != RESULT_DEVICE_NOT_SUPPORTED) {
+                        throw new IllegalArgumentException("Result can not be " + res);
+                    }
+                    sendResponse(msg.replyTo, MSG_NEW_DEVICE_ATTACHED_RESPONSE, res);
+                    break;
+                }
+
+                case MSG_CAN_SWITCH_TO_AOAP: {
+                    int res = service.canSwitchToAoap(
+                            checkNotNull(data.getParcelable(KEY_DEVICE)));
+                    if (res != RESULT_OK && res != RESULT_DEVICE_NOT_SUPPORTED
+                            && res != RESULT_DO_NOT_SWITCH_TO_AOAP) {
+                        throw new IllegalArgumentException("Result can not be " + res);
+                    }
+                    sendResponse(msg.replyTo, MSG_CAN_SWITCH_TO_AOAP_RESPONSE, res);
+                    break;
+                }
+
+                default:
+                    Log.e(TAG, "Unknown message received: " + msg.what);
+                    break;
+            }
+        }
+
+        private void sendResponse(Messenger messenger, int msg, int result) {
+            try {
+                messenger.send(createResponseMessage(msg, result));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send message", e);
+            }
+        }
+
+        private Message createResponseMessage(int msg, int result) {
+            Message response = Message.obtain(null, msg);
+            Bundle data = new Bundle();
+            data.putInt(KEY_RESULT, result);
+            response.setData(data);
+            return response;
+        }
+    }
+}
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index 57dc4c5..ff0c25d 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -78,7 +78,10 @@
     public static final int APP_FOCUS_TYPE_NAVIGATION = 1;
     /**
      * Represents voice command focus.
+     *
+     * @deprecated use {@link android.service.voice.VoiceInteractionService} instead.
      */
+    @Deprecated
     public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2;
     /**
      * Update this after adding a new app type.
@@ -89,7 +92,6 @@
     /** @hide */
     @IntDef({
         APP_FOCUS_TYPE_NAVIGATION,
-        APP_FOCUS_TYPE_VOICE_COMMAND
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AppFocusType {}
diff --git a/car-lib/src/android/car/CarProjectionManager.java b/car-lib/src/android/car/CarProjectionManager.java
index 7706bf7..8b5bc82 100644
--- a/car-lib/src/android/car/CarProjectionManager.java
+++ b/car-lib/src/android/car/CarProjectionManager.java
@@ -297,17 +297,14 @@
      *
      * @param device  The device on which to inhibit a profile.
      * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit.
-     * @param token   A {@link IBinder} to be used as an identity for the request. If the process
-     *                owning the token dies, the request will automatically be released.
      * @return True if the profile was successfully inhibited, false if an error occurred.
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public boolean requestBluetoothProfileInhibit(
-            @NonNull BluetoothDevice device, int profile, @NonNull IBinder token) {
+            @NonNull BluetoothDevice device, int profile) {
         Preconditions.checkNotNull(device, "device cannot be null");
-        Preconditions.checkNotNull(token, "token cannot be null");
         try {
-            return mService.requestBluetoothProfileInhibit(device, profile, token);
+            return mService.requestBluetoothProfileInhibit(device, profile, mToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -319,17 +316,13 @@
      *
      * @param device  The device on which to release the inhibit request.
      * @param profile The profile on which to release the inhibit request.
-     * @param token   The token provided in the original call to
-     *                {@link #requestBluetoothProfileInhibit}.
      * @return True if the request was released, false if an error occurred.
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
-    public boolean releaseBluetoothProfileInhibit(
-            BluetoothDevice device, int profile, IBinder token) {
+    public boolean releaseBluetoothProfileInhibit(@NonNull BluetoothDevice device, int profile) {
         Preconditions.checkNotNull(device, "device cannot be null");
-        Preconditions.checkNotNull(token, "token cannot be null");
         try {
-            return mService.releaseBluetoothProfileInhibit(device, profile, token);
+            return mService.releaseBluetoothProfileInhibit(device, profile, mToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -341,7 +334,7 @@
      *
      * @param status the reported status that will be distributed to the interested listeners
      *
-     * @see #registerProjectionListener(CarProjectionListener, int)
+     * @see #registerProjectionStatusListener(ProjectionStatusListener)
      */
     @RequiresPermission(Car.PERMISSION_CAR_PROJECTION)
     public void updateProjectionStatus(@NonNull ProjectionStatus status) {
diff --git a/car-lib/src/android/car/IUsbAoapSupportCheckService.aidl b/car-lib/src/android/car/IUsbAoapSupportCheckService.aidl
deleted file mode 100644
index 3e77357..0000000
--- a/car-lib/src/android/car/IUsbAoapSupportCheckService.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.hardware.usb.UsbDevice;
-
-/**
- * Binder interface for service to check USB AOAP mode support.
- *
- * @hide
- */
-interface IUsbAoapSupportCheckService {
-    boolean isDeviceSupported(in UsbDevice device) = 0;
-}
diff --git a/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java b/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java
index 77dc08d..bd553ce 100644
--- a/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java
+++ b/car-lib/src/android/car/drivingstate/CarDrivingStateEvent.java
@@ -44,7 +44,6 @@
     public static final int DRIVING_STATE_PARKED = 0;
     /**
      * Car is idling.  Gear is not in Parked mode and Speed of the vehicle is zero.
-     * TODO: (b/72157869) - Should speed that differentiates moving vs idling be configurable?
      */
     public static final int DRIVING_STATE_IDLING = 1;
     /**
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictions.java b/car-lib/src/android/car/drivingstate/CarUxRestrictions.java
index 149f1dc..72cd47c 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictions.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictions.java
@@ -220,6 +220,8 @@
      * Time at which this UX restriction event was deduced based on the car's driving state.
      *
      * @return Elapsed time in nanoseconds since system boot.
+     *
+     * @hide
      */
     public long getTimeStamp() {
         return mTimeStamp;
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
index 06d19df..4ca54a7 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
@@ -15,9 +15,18 @@
  */
 package android.car.drivingstate;
 
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
+import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
+
 import android.annotation.FloatRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
+import android.car.drivingstate.CarUxRestrictionsManager.UxRestrictionMode;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,10 +36,12 @@
 import android.util.JsonWriter;
 import android.util.Log;
 
+import java.io.CharArrayWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
@@ -51,6 +62,14 @@
     private static final String JSON_NAME_IDLING_RESTRICTIONS = "idling_restrictions";
     private static final String JSON_NAME_PARKED_RESTRICTIONS = "parked_restrictions";
     private static final String JSON_NAME_UNKNOWN_RESTRICTIONS = "unknown_restrictions";
+    private static final String JSON_NAME_PASSENGER_MOVING_RESTRICTIONS =
+            "passenger_moving_restrictions";
+    private static final String JSON_NAME_PASSENGER_IDLING_RESTRICTIONS =
+            "passenger_idling_restrictions";
+    private static final String JSON_NAME_PASSENGER_PARKED_RESTRICTIONS =
+            "passenger_parked_restrictions";
+    private static final String JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS =
+            "passenger_unknown_restrictions";
     private static final String JSON_NAME_REQ_OPT = "req_opt";
     private static final String JSON_NAME_RESTRICTIONS = "restrictions";
     private static final String JSON_NAME_SPEED_RANGE = "speed_range";
@@ -60,7 +79,9 @@
     private final int mMaxContentDepth;
     private final int mMaxCumulativeContentItems;
     private final int mMaxStringLength;
-    private final Map<Integer, List<RestrictionsPerSpeedRange>> mUxRestrictions =
+    private final Map<Integer, List<RestrictionsPerSpeedRange>> mPassengerUxRestrictions =
+            new ArrayMap<>(DRIVING_STATES.length);
+    private final Map<Integer, List<RestrictionsPerSpeedRange>> mBaselineUxRestrictions =
             new ArrayMap<>(DRIVING_STATES.length);
 
     private CarUxRestrictionsConfiguration(CarUxRestrictionsConfiguration.Builder builder) {
@@ -69,51 +90,89 @@
         mMaxStringLength = builder.mMaxStringLength;
 
         for (int drivingState : DRIVING_STATES) {
-            List<RestrictionsPerSpeedRange> list = new ArrayList<>();
-            for (RestrictionsPerSpeedRange r : builder.mUxRestrictions.get(drivingState)) {
-                list.add(r);
+            List<RestrictionsPerSpeedRange> baseline = new ArrayList<>();
+            for (RestrictionsPerSpeedRange r : builder.mBaselineUxRestrictions.get(drivingState)) {
+                baseline.add(r);
             }
-            mUxRestrictions.put(drivingState, list);
+            mBaselineUxRestrictions.put(drivingState, baseline);
+
+            List<RestrictionsPerSpeedRange> passenger = new ArrayList<>();
+            for (RestrictionsPerSpeedRange r : builder.mPassengerUxRestrictions.get(drivingState)) {
+                passenger.add(r);
+            }
+            mPassengerUxRestrictions.put(drivingState, passenger);
         }
     }
 
     /**
-     * Returns the restrictions based on current driving state and speed.
+     * Returns the restrictions for
+     * {@link UxRestrictionMode#UX_RESTRICTION_MODE_BASELINE}
+     * based on current driving state.
+     *
+     * @param drivingState Driving state.
+     *                     See values in {@link CarDrivingStateEvent.CarDrivingState}.
+     * @param currentSpeed Current speed in meter per second.
+     */
+    public CarUxRestrictions getUxRestrictions(
+            @CarDrivingState int drivingState, float currentSpeed) {
+        return getUxRestrictions(drivingState, currentSpeed, UX_RESTRICTION_MODE_BASELINE);
+    }
+
+    /**
+     * Returns the restrictions based on current driving state and restriction mode.
+     *
+     * <p>Restriction mode allows a different set of restrictions to be applied in the same driving
+     * state. See values in {@link UxRestrictionMode}.
+     *
+     * @param drivingState Driving state.
+     *                     See values in {@link CarDrivingStateEvent.CarDrivingState}.
+     * @param currentSpeed Current speed in meter per second.
+     * @param mode Current UX Restriction mode.
      */
     public CarUxRestrictions getUxRestrictions(@CarDrivingState int drivingState,
-            float currentSpeed) {
-        List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(drivingState);
-        if (restrictions.isEmpty()) {
+            float currentSpeed, @UxRestrictionMode int mode) {
+        RestrictionsPerSpeedRange restriction = null;
+        if (mode == UX_RESTRICTION_MODE_PASSENGER) {
+            restriction = findUxRestrictionsInList(
+                    currentSpeed, mPassengerUxRestrictions.get(drivingState));
+        }
+        if (restriction == null) {
+            // Mode is baseline, or passenger mode does not specify restrictions for current driving
+            // state.
+            restriction = findUxRestrictionsInList(
+                    currentSpeed, mBaselineUxRestrictions.get(drivingState));
+        }
+
+        if (restriction == null) {
             if (Build.IS_ENG || Build.IS_USERDEBUG) {
                 throw new IllegalStateException("No restrictions for driving state "
                         + getDrivingStateName(drivingState));
             }
             return createDefaultUxRestrictionsEvent();
         }
-
-        RestrictionsPerSpeedRange restriction = null;
-        if (restrictions.size() == 1) {
-            restriction = restrictions.get(0);
-        } else {
-            for (RestrictionsPerSpeedRange r : restrictions) {
-                if (r.mSpeedRange != null && r.mSpeedRange.includes(currentSpeed)) {
-                    restriction = r;
-                    break;
-                }
-            }
-        }
-
-        if (restriction == null) {
-            if (Build.IS_ENG || Build.IS_USERDEBUG) {
-                throw new IllegalStateException(
-                        "No restrictions found for driving state " + drivingState
-                                + " at speed " + currentSpeed);
-            }
-            return createDefaultUxRestrictionsEvent();
-        }
         return createUxRestrictionsEvent(restriction.mReqOpt, restriction.mRestrictions);
     }
 
+    @Nullable
+    private static RestrictionsPerSpeedRange findUxRestrictionsInList(float currentSpeed,
+            List<RestrictionsPerSpeedRange> restrictions) {
+        if (restrictions.isEmpty()) {
+            return null;
+        }
+
+        if (restrictions.size() == 1 && restrictions.get(0).mSpeedRange == null) {
+            // Single restriction with no speed range implies it covers all.
+            return restrictions.get(0);
+        }
+
+        for (RestrictionsPerSpeedRange r : restrictions) {
+            if (r.mSpeedRange != null && r.mSpeedRange.includes(currentSpeed)) {
+                return r;
+            }
+        }
+        return null;
+    }
+
     private CarUxRestrictions createDefaultUxRestrictionsEvent() {
         return createUxRestrictionsEvent(true,
                 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
@@ -162,19 +221,35 @@
 
         writer.name(JSON_NAME_PARKED_RESTRICTIONS);
         writeRestrictionsList(writer,
-                mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_PARKED));
+                mBaselineUxRestrictions.get(DRIVING_STATE_PARKED));
 
         writer.name(JSON_NAME_IDLING_RESTRICTIONS);
         writeRestrictionsList(writer,
-                mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_IDLING));
+                mBaselineUxRestrictions.get(DRIVING_STATE_IDLING));
 
         writer.name(JSON_NAME_MOVING_RESTRICTIONS);
         writeRestrictionsList(writer,
-                mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_MOVING));
+                mBaselineUxRestrictions.get(DRIVING_STATE_MOVING));
 
         writer.name(JSON_NAME_UNKNOWN_RESTRICTIONS);
         writeRestrictionsList(writer,
-                mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN));
+                mBaselineUxRestrictions.get(DRIVING_STATE_UNKNOWN));
+
+        writer.name(JSON_NAME_PASSENGER_PARKED_RESTRICTIONS);
+        writeRestrictionsList(writer,
+                mPassengerUxRestrictions.get(DRIVING_STATE_PARKED));
+
+        writer.name(JSON_NAME_PASSENGER_IDLING_RESTRICTIONS);
+        writeRestrictionsList(writer,
+                mPassengerUxRestrictions.get(DRIVING_STATE_IDLING));
+
+        writer.name(JSON_NAME_PASSENGER_MOVING_RESTRICTIONS);
+        writeRestrictionsList(writer,
+                mPassengerUxRestrictions.get(DRIVING_STATE_MOVING));
+
+        writer.name(JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS);
+        writeRestrictionsList(writer,
+                mPassengerUxRestrictions.get(DRIVING_STATE_UNKNOWN));
 
         writer.endObject();
     }
@@ -203,6 +278,19 @@
         writer.endObject();
     }
 
+    @Override
+    public String toString() {
+        CharArrayWriter charWriter = new CharArrayWriter();
+        JsonWriter writer = new JsonWriter(charWriter);
+        writer.setIndent("\t");
+        try {
+            writeJson(writer);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return charWriter.toString();
+    }
+
     /**
      * Reads Json as UX restriction configuration.
      */
@@ -214,23 +302,51 @@
         reader.beginObject();
         while (reader.hasNext()) {
             String name = reader.nextName();
-            if (name.equals(JSON_NAME_MAX_CONTENT_DEPTH)) {
-                builder.setMaxContentDepth(reader.nextInt());
-            } else if (name.equals(JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS)) {
-                builder.setMaxCumulativeContentItems(reader.nextInt());
-            } else if (name.equals(JSON_NAME_MAX_STRING_LENGTH)) {
-                builder.setMaxStringLength(reader.nextInt());
-            } else if (name.equals(JSON_NAME_PARKED_RESTRICTIONS)) {
-                readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_PARKED, builder);
-            } else if (name.equals(JSON_NAME_IDLING_RESTRICTIONS)) {
-                readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_IDLING, builder);
-            } else if (name.equals(JSON_NAME_MOVING_RESTRICTIONS)) {
-                readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_MOVING, builder);
-            } else if (name.equals(JSON_NAME_UNKNOWN_RESTRICTIONS)) {
-                readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_UNKNOWN, builder);
-            } else {
-                Log.e(TAG, "Unknown name parsing json config: " + name);
-                reader.skipValue();
+            switch (name) {
+                case JSON_NAME_MAX_CONTENT_DEPTH:
+                    builder.setMaxContentDepth(reader.nextInt());
+                    break;
+                case JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS:
+                    builder.setMaxCumulativeContentItems(reader.nextInt());
+                    break;
+                case JSON_NAME_MAX_STRING_LENGTH:
+                    builder.setMaxStringLength(reader.nextInt());
+                    break;
+                case JSON_NAME_PARKED_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_PARKED,
+                            UX_RESTRICTION_MODE_BASELINE, builder);
+                    break;
+                case JSON_NAME_IDLING_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_IDLING,
+                            UX_RESTRICTION_MODE_BASELINE, builder);
+                    break;
+                case JSON_NAME_MOVING_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_MOVING,
+                            UX_RESTRICTION_MODE_BASELINE, builder);
+                    break;
+                case JSON_NAME_UNKNOWN_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_UNKNOWN,
+                            UX_RESTRICTION_MODE_BASELINE, builder);
+                    break;
+                case JSON_NAME_PASSENGER_PARKED_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_PARKED,
+                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                    break;
+                case JSON_NAME_PASSENGER_IDLING_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_IDLING,
+                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                    break;
+                case JSON_NAME_PASSENGER_MOVING_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_MOVING,
+                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                    break;
+                case JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS:
+                    readRestrictionsList(reader, DRIVING_STATE_UNKNOWN,
+                            UX_RESTRICTION_MODE_PASSENGER, builder);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown name parsing json config: " + name);
+                    reader.skipValue();
             }
         }
         reader.endObject();
@@ -238,16 +354,18 @@
     }
 
     private static void readRestrictionsList(JsonReader reader, @CarDrivingState int drivingState,
-            Builder builder) throws IOException {
+            @UxRestrictionMode int mode, Builder builder) throws IOException {
         reader.beginArray();
         while (reader.hasNext()) {
-            readRestrictions(reader, drivingState, builder);
+            DrivingStateRestrictions drivingStateRestrictions = readRestrictions(reader);
+            drivingStateRestrictions.setMode(mode);
+
+            builder.setUxRestrictions(drivingState, drivingStateRestrictions);
         }
         reader.endArray();
     }
 
-    private static void readRestrictions(JsonReader reader, @CarDrivingState int drivingState,
-            Builder builder) throws IOException {
+    private static DrivingStateRestrictions readRestrictions(JsonReader reader) throws IOException {
         reader.beginObject();
         boolean reqOpt = false;
         int restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
@@ -280,7 +398,13 @@
             }
         }
         reader.endObject();
-        builder.setUxRestrictions(drivingState, speedRange, reqOpt, restrictions);
+        DrivingStateRestrictions drivingStateRestrictions = new DrivingStateRestrictions()
+                .setDistractionOptimizationRequired(reqOpt)
+                .setRestrictions(restrictions);
+        if (speedRange != null) {
+            drivingStateRestrictions.setSpeedRange(speedRange);
+        }
+        return drivingStateRestrictions;
     }
 
     @Override
@@ -301,24 +425,27 @@
             return false;
         }
 
-        // Compare UXR by driving state.
-        if (!mUxRestrictions.keySet().equals(other.mUxRestrictions.keySet())) {
+        // Compare UXR for each restriction mode.
+        if (!areRestrictionsEqual(mBaselineUxRestrictions, other.mBaselineUxRestrictions)
+                || !areRestrictionsEqual(
+                        mPassengerUxRestrictions, other.mPassengerUxRestrictions)) {
             return false;
         }
-        for (int drivingState : mUxRestrictions.keySet()) {
-            List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(
-                    drivingState);
-            List<RestrictionsPerSpeedRange> otherRestrictions = other.mUxRestrictions.get(
-                    drivingState);
-            if (restrictions.size() != otherRestrictions.size()) {
+        return true;
+    }
+
+    private boolean areRestrictionsEqual(Map<Integer, List<RestrictionsPerSpeedRange>> r1,
+            Map<Integer, List<RestrictionsPerSpeedRange>> r2) {
+        // Compare UXR by driving state.
+        if (!r1.keySet().equals(r2.keySet())) {
+            return false;
+        }
+        for (int drivingState : r1.keySet()) {
+            List<RestrictionsPerSpeedRange> restrictions = r1.get(drivingState);
+            List<RestrictionsPerSpeedRange> otherRestrictions = r2.get(drivingState);
+            if (!restrictions.equals(otherRestrictions)) {
                 return false;
             }
-            // Assuming the restrictions are sorted.
-            for (int i = 0; i < restrictions.size(); i++) {
-                if (!restrictions.get(i).equals(otherRestrictions.get(i))) {
-                    return false;
-                }
-            }
         }
         return true;
     }
@@ -327,49 +454,61 @@
      * Dump the driving state to UX restrictions mapping.
      */
     public void dump(PrintWriter writer) {
-        for (Integer state : mUxRestrictions.keySet()) {
-            List<RestrictionsPerSpeedRange> list = mUxRestrictions.get(state);
-            writer.println("===========================================");
-            writer.println("Driving State to UXR");
-            if (list != null) {
-                writer.println("State:" + getDrivingStateName(state) + " num restrictions:"
-                        + list.size());
-                for (RestrictionsPerSpeedRange r : list) {
-                    writer.println("Requires DO? " + r.mReqOpt
-                            + "\nRestrictions: 0x" + Integer.toHexString(r.mRestrictions)
-                            + "\nSpeed Range: " + (r.mSpeedRange == null
-                            ? "None"
-                            : r.mSpeedRange.mMinSpeed + " - " + r.mSpeedRange.mMaxSpeed));
-                    writer.println("===========================================");
-                }
-            }
-        }
+        writer.println("===========================================");
+        writer.println("Baseline mode UXR:");
+        writer.println("-------------------------------------------");
+        dumpRestrictions(writer, mBaselineUxRestrictions);
+        writer.println("Passenger mode UXR:");
+        writer.println("-------------------------------------------");
+        dumpRestrictions(writer, mPassengerUxRestrictions);
+
         writer.println("Max String length: " + mMaxStringLength);
         writer.println("Max Cumulative Content Items: " + mMaxCumulativeContentItems);
         writer.println("Max Content depth: " + mMaxContentDepth);
+        writer.println("===========================================");
+    }
+
+    private void dumpRestrictions(
+            PrintWriter writer, Map<Integer, List<RestrictionsPerSpeedRange>> restrictions) {
+        for (Integer state : restrictions.keySet()) {
+            List<RestrictionsPerSpeedRange> list = restrictions.get(state);
+            writer.println("State:" + getDrivingStateName(state)
+                    + " num restrictions:" + list.size());
+            for (RestrictionsPerSpeedRange r : list) {
+                writer.println("Requires DO? " + r.mReqOpt
+                        + "\nRestrictions: 0x" + Integer.toHexString(r.mRestrictions)
+                        + "\nSpeed Range: "
+                        + (r.mSpeedRange == null
+                        ? "None"
+                        : (r.mSpeedRange.mMinSpeed + " - " + r.mSpeedRange.mMaxSpeed)));
+                writer.println("-------------------------------------------");
+            }
+        }
     }
 
     private static String getDrivingStateName(@CarDrivingState int state) {
         switch (state) {
-            case 0:
+            case DRIVING_STATE_PARKED:
                 return "parked";
-            case 1:
+            case DRIVING_STATE_IDLING:
                 return "idling";
-            case 2:
+            case DRIVING_STATE_MOVING:
                 return "moving";
-            default:
+            case DRIVING_STATE_UNKNOWN:
                 return "unknown";
+            default:
+                throw new IllegalArgumentException("Unrecognized state value: " + state);
         }
     }
 
     // Parcelable methods/fields.
 
     // Used by Parcel methods to ensure de/serialization order.
-    private static final int[] DRIVING_STATES = new int[]{
-            CarDrivingStateEvent.DRIVING_STATE_UNKNOWN,
-            CarDrivingStateEvent.DRIVING_STATE_PARKED,
-            CarDrivingStateEvent.DRIVING_STATE_IDLING,
-            CarDrivingStateEvent.DRIVING_STATE_MOVING
+    private static final int[] DRIVING_STATES = new int[] {
+            DRIVING_STATE_UNKNOWN,
+            DRIVING_STATE_PARKED,
+            DRIVING_STATE_IDLING,
+            DRIVING_STATE_MOVING,
     };
 
     public static final Parcelable.Creator<CarUxRestrictionsConfiguration> CREATOR =
@@ -395,7 +534,12 @@
         for (int drivingState : DRIVING_STATES) {
             List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
             in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
-            mUxRestrictions.put(drivingState, restrictions);
+            mBaselineUxRestrictions.put(drivingState, restrictions);
+        }
+        for (int drivingState : DRIVING_STATES) {
+            List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
+            in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
+            mPassengerUxRestrictions.put(drivingState, restrictions);
         }
         mMaxContentDepth = in.readInt();
         mMaxCumulativeContentItems = in.readInt();
@@ -405,7 +549,10 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         for (int drivingState : DRIVING_STATES) {
-            dest.writeTypedList(mUxRestrictions.get(drivingState), 0);
+            dest.writeTypedList(mBaselineUxRestrictions.get(drivingState));
+        }
+        for (int drivingState : DRIVING_STATES) {
+            dest.writeTypedList(mPassengerUxRestrictions.get(drivingState));
         }
         dest.writeInt(mMaxContentDepth);
         dest.writeInt(mMaxCumulativeContentItems);
@@ -423,26 +570,31 @@
         private int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
         private int mMaxStringLength = UX_RESTRICTIONS_UNKNOWN;
 
-        private Map<Integer, List<RestrictionsPerSpeedRange>> mUxRestrictions =
+        public Map<Integer, List<RestrictionsPerSpeedRange>> mPassengerUxRestrictions =
+                new ArrayMap<>(DRIVING_STATES.length);
+        public Map<Integer, List<RestrictionsPerSpeedRange>> mBaselineUxRestrictions =
                 new ArrayMap<>(DRIVING_STATES.length);
 
         public Builder() {
             for (int drivingState : DRIVING_STATES) {
-                mUxRestrictions.put(drivingState, new ArrayList<>());
+                mBaselineUxRestrictions.put(drivingState, new ArrayList<>());
+                mPassengerUxRestrictions.put(drivingState, new ArrayList<>());
             }
         }
 
         /**
-         * Sets ux restrictions for driving state.
+         * Sets UX restrictions for driving state.
          */
         public Builder setUxRestrictions(@CarDrivingState int drivingState,
                 boolean requiresOptimization,
                 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
-            return this.setUxRestrictions(drivingState, null, requiresOptimization,  restrictions);
+            return this.setUxRestrictions(drivingState, new DrivingStateRestrictions()
+                    .setDistractionOptimizationRequired(requiresOptimization)
+                    .setRestrictions(restrictions));
         }
 
         /**
-         * Sets ux restrictions with speed range.
+         * Sets UX restrictions with speed range.
          *
          * @param drivingState Restrictions will be set for this Driving state.
          *                     See constants in {@link CarDrivingStateEvent}.
@@ -453,26 +605,57 @@
          * @param requiresOptimization Whether distraction optimization (DO) is required for this
          *                             driving state.
          * @param restrictions See constants in {@link CarUxRestrictions}.
+         *
+         * @deprecated Use {@link #setUxRestrictions(int, DrivingStateRestrictions)} instead.
          */
+        @Deprecated
         public Builder setUxRestrictions(@CarDrivingState int drivingState,
-                SpeedRange speedRange, boolean requiresOptimization,
+                @NonNull SpeedRange speedRange, boolean requiresOptimization,
                 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
-            if (drivingState != CarDrivingStateEvent.DRIVING_STATE_MOVING) {
-                if (speedRange != null) {
-                    throw new IllegalArgumentException(
-                            "Non-moving driving state cannot specify speed range.");
-                }
-                if (mUxRestrictions.get(drivingState).size() > 0) {
-                    throw new IllegalArgumentException("Non-moving driving state cannot have "
-                            + "more than one set of restrictions.");
-                }
+            return setUxRestrictions(drivingState, new DrivingStateRestrictions()
+                    .setDistractionOptimizationRequired(requiresOptimization)
+                    .setRestrictions(restrictions)
+                    .setSpeedRange(speedRange));
+        }
+
+        /**
+         * Sets UX restriction.
+         *
+         * @param drivingState Restrictions will be set for this Driving state.
+         *                     See constants in {@link CarDrivingStateEvent}.
+         * @param drivingStateRestrictions Restrictions to set.
+         *
+         * @return This builder object for method chaining.
+         */
+        public Builder setUxRestrictions(
+                int drivingState, DrivingStateRestrictions drivingStateRestrictions) {
+            SpeedRange speedRange = drivingStateRestrictions.mSpeedRange;
+
+            if (drivingState != DRIVING_STATE_MOVING && speedRange != null) {
+                throw new IllegalArgumentException(
+                        "Non-moving driving state should not specify speed range.");
             }
 
-            mUxRestrictions.get(drivingState).add(
-                    new RestrictionsPerSpeedRange(requiresOptimization, restrictions, speedRange));
+            List<RestrictionsPerSpeedRange> restrictions;
+            switch (drivingStateRestrictions.mMode) {
+                case UX_RESTRICTION_MODE_BASELINE:
+                    restrictions = mBaselineUxRestrictions.get(drivingState);
+                    break;
+                case UX_RESTRICTION_MODE_PASSENGER:
+                    restrictions = mPassengerUxRestrictions.get(drivingState);
+                    break;
+                default:
+                    String mode = CarUxRestrictionsManager.modeToString(
+                            drivingStateRestrictions.mMode);
+                    throw new IllegalArgumentException("Unrecognized restriction mode " + mode);
+            }
+            restrictions.add(new RestrictionsPerSpeedRange(
+                    drivingStateRestrictions.mMode, drivingStateRestrictions.mReqOpt,
+                    drivingStateRestrictions.mRestrictions, speedRange));
             return this;
         }
 
+
         /**
          * Sets max string length.
          */
@@ -501,9 +684,19 @@
          * @return CarUxRestrictionsConfiguration based on builder configuration.
          */
         public CarUxRestrictionsConfiguration build() {
-            // Create default restriction for unspecified driving state.
+            // Unspecified driving state should be fully restricted to be safe.
+            addDefaultRestrictionsToBaseline();
+
+            validateBaselineModeRestrictions();
+            validatePassengerModeRestrictions();
+
+            return new CarUxRestrictionsConfiguration(this);
+        }
+
+        private void addDefaultRestrictionsToBaseline() {
             for (int drivingState : DRIVING_STATES) {
-                List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(drivingState);
+                List<RestrictionsPerSpeedRange> restrictions =
+                        mBaselineUxRestrictions.get(drivingState);
                 if (restrictions.size() == 0) {
                     Log.i(TAG, "Using default restrictions for driving state: "
                             + getDrivingStateName(drivingState));
@@ -511,61 +704,107 @@
                             true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED));
                 }
             }
+        }
 
-            // Configuration validation.
+        private void validateBaselineModeRestrictions() {
             for (int drivingState : DRIVING_STATES) {
-                List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(drivingState);
-
-                if (drivingState == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
-                    // Sort restrictions based on speed range.
-                    Collections.sort(restrictions,
-                            (r1, r2) -> r1.mSpeedRange.compareTo(r2.mSpeedRange));
-
-                    if (!isAllSpeedRangeCovered(restrictions)) {
-                        throw new IllegalStateException(
-                                "Moving state should cover full speed range.");
-                    }
-                } else {
+                List<RestrictionsPerSpeedRange> restrictions =
+                        mBaselineUxRestrictions.get(drivingState);
+                if (drivingState != DRIVING_STATE_MOVING) {
+                    // Note: For non-moving state, setUxRestrictions() rejects UxRestriction with
+                    // speed range, so we don't check here.
                     if (restrictions.size() != 1) {
-                        throw new IllegalStateException("Non-moving driving state should contain "
-                                + "one set of restriction rules.");
+                        throw new IllegalStateException("Non-moving driving state should "
+                                + "contain one set of restriction rules.");
                     }
                 }
+
+                // If there are multiple restrictions, each one should specify speed range.
+                if (restrictions.size() > 1 && restrictions.stream().anyMatch(
+                        restriction -> restriction.mSpeedRange == null)) {
+                    StringBuilder error = new StringBuilder();
+                    for (RestrictionsPerSpeedRange restriction : restrictions) {
+                        error.append(restriction.toString()).append('\n');
+                    }
+                    throw new IllegalStateException(
+                            "Every restriction in MOVING state should contain driving state.\n"
+                                    + error.toString());
+                }
+
+                // Sort restrictions based on speed range.
+                Collections.sort(restrictions,
+                        Comparator.comparing(RestrictionsPerSpeedRange::getSpeedRange));
+
+                validateRangeOfSpeed(restrictions);
+                validateContinuousSpeedRange(restrictions);
             }
-            return new CarUxRestrictionsConfiguration(this);
+        }
+
+        private void validatePassengerModeRestrictions() {
+            List<RestrictionsPerSpeedRange> passengerMovingRestrictions =
+                    mPassengerUxRestrictions.get(DRIVING_STATE_MOVING);
+            Collections.sort(passengerMovingRestrictions,
+                    Comparator.comparing(RestrictionsPerSpeedRange::getSpeedRange));
+
+            validateContinuousSpeedRange(passengerMovingRestrictions);
         }
 
         /**
-         * restrictions should be sorted based on speed range.
+         * Validates if combined speed ranges of given restrictions.
+         *
+         * <p>Restrictions are considered to contain valid speed ranges if:
+         * <ul>
+         * <li>None contains a speed range - implies full range; or
+         * <li>Combination covers range [0 - MAX_SPEED]
+         * </ul>
+         *
+         * Throws exception on invalidate input.
+         *
+         * @param restrictions Restrictions to be checked. Must be sorted.
          */
-        private boolean isAllSpeedRangeCovered(List<RestrictionsPerSpeedRange> restrictions) {
+        private void validateRangeOfSpeed(List<RestrictionsPerSpeedRange> restrictions) {
             if (restrictions.size() == 1) {
-                if (restrictions.get(0).mSpeedRange == null) {
+                SpeedRange speedRange = restrictions.get(0).mSpeedRange;
+                if (speedRange == null) {
                     // Single restriction with null speed range implies that
                     // it applies to the entire driving state.
-                    return true;
+                    return;
                 }
-                return restrictions.get(0).mSpeedRange.mMinSpeed == 0
-                        && Float.compare(restrictions.get(0).mSpeedRange.mMaxSpeed,
-                        SpeedRange.MAX_SPEED) == 0;
             }
+            if (Float.compare(restrictions.get(0).mSpeedRange.mMinSpeed, 0) != 0) {
+                throw new IllegalStateException(
+                        "Speed range min speed should start at 0.");
+            }
+            float lastMaxSpeed = restrictions.get(restrictions.size() - 1).mSpeedRange.mMaxSpeed;
+            if (Float.compare(lastMaxSpeed, SpeedRange.MAX_SPEED) != 0) {
+                throw new IllegalStateException(
+                        "Max speed of last restriction should be MAX_SPEED.");
+            }
+        }
 
-            if (restrictions.get(0).mSpeedRange.mMinSpeed != 0) {
-                Log.e(TAG, "Speed range min speed should start at 0.");
-                return false;
-            }
+        /**
+         * Validates if combined speed ranges of given restrictions are continuous, meaning they:
+         * <ul>
+         * <li>Do not overlap; and
+         * <li>Do not contain gap
+         * </ul>
+         *
+         * <p>Namely the max speed of current range equals the min speed of next range.
+         *
+         * Throws exception on invalidate input.
+         *
+         * @param restrictions Restrictions to be checked. Must be sorted.
+         */
+        private void validateContinuousSpeedRange(List<RestrictionsPerSpeedRange> restrictions) {
             for (int i = 1; i < restrictions.size(); i++) {
                 RestrictionsPerSpeedRange prev = restrictions.get(i - 1);
                 RestrictionsPerSpeedRange curr = restrictions.get(i);
                 // If current min != prev.max, there's either an overlap or a gap in speed range.
                 if (Float.compare(curr.mSpeedRange.mMinSpeed, prev.mSpeedRange.mMaxSpeed) != 0) {
-                    Log.e(TAG, "Mis-configured speed range. Possibly speed range overlap or gap.");
-                    return false;
+                    throw new IllegalArgumentException(
+                            "Mis-configured speed range. Possibly speed range overlap or gap.");
                 }
             }
-            // The last speed range should have max speed.
-            float lastMaxSpeed = restrictions.get(restrictions.size() - 1).mSpeedRange.mMaxSpeed;
-            return lastMaxSpeed == SpeedRange.MAX_SPEED;
         }
 
         /**
@@ -590,9 +829,6 @@
                 if (minSpeed == MAX_SPEED) {
                     throw new IllegalArgumentException("Min speed cannot be MAX_SPEED.");
                 }
-                if (maxSpeed < 0) {
-                    throw new IllegalArgumentException("Max speed cannot be negative.");
-                }
                 if (minSpeed > maxSpeed) {
                     throw new IllegalArgumentException("Min speed " + minSpeed
                             + " should not be greater than max speed " + maxSpeed);
@@ -608,13 +844,7 @@
              * @return {@code true} if in range; {@code false} otherwise.
              */
             public boolean includes(float speed) {
-                if (speed < mMinSpeed) {
-                    return false;
-                }
-                if (mMaxSpeed == MAX_SPEED) {
-                    return true;
-                }
-                return speed < mMaxSpeed;
+                return mMinSpeed <= speed && speed < mMaxSpeed;
             }
 
             @Override
@@ -640,35 +870,116 @@
 
                 return this.compareTo(other) == 0;
             }
+
+            @Override
+            public String toString() {
+                return new StringBuilder()
+                        .append("[min: ").append(mMinSpeed)
+                        .append("; max: ").append(mMaxSpeed == MAX_SPEED ? "max_speed" : mMaxSpeed)
+                        .append("]")
+                        .toString();
+            }
+        }
+    }
+
+    /**
+     * UX restrictions to be applied to a driving state through {@link
+     * Builder#setUxRestrictions(int, CarUxRestrictionsConfiguration.DrivingStateRestrictions)}.
+     * These UX restrictions can also specified to be only applicable to certain speed range and
+     * restriction mode.
+     *
+     * @see UxRestrictionMode
+     * @see Builder.SpeedRange
+     *
+     * @hide
+     */
+    public static final class DrivingStateRestrictions {
+        private int mMode = UX_RESTRICTION_MODE_BASELINE;
+        private boolean mReqOpt = true;
+        private int mRestrictions = CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
+        @Nullable private Builder.SpeedRange mSpeedRange;
+
+        /**
+         * Sets whether Distraction Optimization (DO) is required. Defaults to {@code true}.
+         */
+        public DrivingStateRestrictions setDistractionOptimizationRequired(
+                boolean distractionOptimizationRequired) {
+            mReqOpt = distractionOptimizationRequired;
+            return this;
+        }
+
+        /**
+         * Sets active restrictions.
+         * Defaults to {@link CarUxRestrictions#UX_RESTRICTIONS_FULLY_RESTRICTED}.
+         */
+        public DrivingStateRestrictions setRestrictions(
+                @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
+            mRestrictions = restrictions;
+            return this;
+        }
+
+        /**
+         * Sets restriction mode to apply to.
+         * Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
+         */
+        public DrivingStateRestrictions setMode(@UxRestrictionMode int mode) {
+            mMode = mode;
+            return this;
+        }
+
+        /**
+         * Sets speed range to apply to. Optional value. Not setting one means the restrictions
+         * apply to full speed range, namely {@code 0} to {@link Builder.SpeedRange#MAX_SPEED}.
+         */
+        public DrivingStateRestrictions setSpeedRange(@NonNull Builder.SpeedRange speedRange) {
+            mSpeedRange = speedRange;
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("Mode: ").append(CarUxRestrictionsManager.modeToString(mMode))
+                    .append(". Requires DO? ").append(mReqOpt)
+                    .append(". Restrictions: ").append(Integer.toBinaryString(mRestrictions))
+                    .append(". SpeedRange: ")
+                    .append(mSpeedRange == null ? "null" : mSpeedRange.toString())
+                    .toString();
         }
     }
 
     /**
      * Container for UX restrictions for a speed range.
      * Speed range is valid only for the {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}.
-     * @hide
      */
-    public static final class RestrictionsPerSpeedRange implements Parcelable {
+    private static final class RestrictionsPerSpeedRange implements Parcelable {
+        @UxRestrictionMode
+        final int mMode;
         final boolean mReqOpt;
         final int mRestrictions;
         @Nullable
         final Builder.SpeedRange mSpeedRange;
 
-        public RestrictionsPerSpeedRange(boolean reqOpt, int restrictions) {
-            this(reqOpt, restrictions, null);
+        RestrictionsPerSpeedRange(boolean reqOpt, int restrictions) {
+            this(UX_RESTRICTION_MODE_BASELINE, reqOpt, restrictions, null);
         }
 
-        public RestrictionsPerSpeedRange(boolean reqOpt, int restrictions,
+        RestrictionsPerSpeedRange(@UxRestrictionMode int mode, boolean reqOpt, int restrictions,
                 @Nullable Builder.SpeedRange speedRange) {
             if (!reqOpt && restrictions != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
                 throw new IllegalArgumentException(
                         "Driving optimization is not required but UX restrictions is required.");
             }
+            mMode = mode;
             mReqOpt = reqOpt;
             mRestrictions = restrictions;
             mSpeedRange = speedRange;
         }
 
+        public Builder.SpeedRange getSpeedRange() {
+            return mSpeedRange;
+        }
+
         @Override
         public boolean equals(Object obj) {
             if (this == obj) {
@@ -678,12 +989,25 @@
                 return false;
             }
             RestrictionsPerSpeedRange other = (RestrictionsPerSpeedRange) obj;
-            return mReqOpt == other.mReqOpt
+            return mMode == other.mMode
+                    && mReqOpt == other.mReqOpt
                     && mRestrictions == other.mRestrictions
                     && ((mSpeedRange == null && other.mSpeedRange == null) || mSpeedRange.equals(
                     other.mSpeedRange));
         }
 
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("[Mode is ").append(CarUxRestrictionsManager.modeToString(mMode))
+                    .append("; Requires DO? ").append(mReqOpt)
+                    .append("; Restrictions: ").append(Integer.toBinaryString(mRestrictions))
+                    .append("; Speed range: ")
+                    .append(mSpeedRange == null ? "null" : mSpeedRange.toString())
+                    .append(']')
+                    .toString();
+        }
+
         // Parcelable methods/fields.
 
         public static final Creator<RestrictionsPerSpeedRange> CREATOR =
@@ -705,6 +1029,7 @@
         }
 
         protected RestrictionsPerSpeedRange(Parcel in) {
+            mMode = in.readInt();
             mReqOpt = in.readBoolean();
             mRestrictions = in.readInt();
             // Whether speed range is specified.
@@ -719,6 +1044,7 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mMode);
             dest.writeBoolean(mReqOpt);
             dest.writeInt(mRestrictions);
             // Whether speed range is specified.
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index 06875dd..4848e99 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -16,10 +16,10 @@
 
 package android.car.drivingstate;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.TestApi;
 import android.car.Car;
 import android.car.CarManagerBase;
 import android.content.Context;
@@ -30,6 +30,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 
 /**
@@ -42,6 +44,37 @@
     private static final boolean VDBG = false;
     private static final int MSG_HANDLE_UX_RESTRICTIONS_CHANGE = 0;
 
+    /**
+     * Baseline restriction mode is the default UX restrictions used for driving state.
+     *
+     * @hide
+     */
+    public static final int UX_RESTRICTION_MODE_BASELINE = 0;
+    /**
+     * Passenger restriction mode uses UX restrictions for {@link #UX_RESTRICTION_MODE_PASSENGER},
+     * set through {@link CarUxRestrictionsConfiguration.Builder.UxRestrictions#setMode(int)}.
+     *
+     * <p>If a new {@link CarUxRestrictions} is available upon mode transition, it'll be immediately
+     * dispatched to listeners.
+     *
+     * <p>If passenger mode restrictions is not configured for current driving state, it will fall
+     * back to {@link #UX_RESTRICTION_MODE_BASELINE}.
+     *
+     * <p>Caller are responsible for determining and executing the criteria for entering and exiting
+     * this mode. Exiting by setting mode to {@link #UX_RESTRICTION_MODE_BASELINE}.
+     *
+     * @hide
+     */
+    public static final int UX_RESTRICTION_MODE_PASSENGER = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "UX_RESTRICTION_MODE_" }, value = {
+            UX_RESTRICTION_MODE_BASELINE,
+            UX_RESTRICTION_MODE_PASSENGER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UxRestrictionMode {}
+
     private final Context mContext;
     private final ICarUxRestrictionsManager mUxRService;
     private final EventCallbackHandler mEventCallbackHandler;
@@ -112,30 +145,6 @@
     }
 
     /**
-     * Set a new {@link CarUxRestrictionsConfiguration} for next trip.
-     * <p>
-     * Saving a new configuration does not affect current configuration. The new configuration will
-     * only be used after UX Restrictions service restarts when the vehicle is parked.
-     * <p>
-     * Requires Permission:
-     * {@link android.car.Manifest.permission#CAR_UX_RESTRICTIONS_CONFIGURATION}.
-     *
-     * @param config UX restrictions configuration to be persisted.
-     * @return {@code true} if input config was successfully saved; {@code false} otherwise.
-     *
-     * @hide
-     */
-    @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
-    public synchronized boolean saveUxRestrictionsConfigurationForNextBoot(
-            CarUxRestrictionsConfiguration config) {
-        try {
-            return mUxRService.saveUxRestrictionsConfigurationForNextBoot(config);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Unregister the registered {@link OnUxRestrictionsChangedListener}
      */
     public synchronized void unregisterListener() {
@@ -168,6 +177,59 @@
     }
 
     /**
+     * Sets restriction mode. Returns {@code true} if the operation succeeds.
+     *
+     * @hide
+     */
+    @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
+    public boolean setRestrictionMode(@UxRestrictionMode int mode) {
+        try {
+            return mUxRService.setRestrictionMode(mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current restriction mode.
+     *
+     * @hide
+     */
+    @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
+    @UxRestrictionMode
+    public int getRestrictionMode() {
+        try {
+            return mUxRService.getRestrictionMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set a new {@link CarUxRestrictionsConfiguration} for next trip.
+     * <p>
+     * Saving a new configuration does not affect current configuration. The new configuration will
+     * only be used after UX Restrictions service restarts when the vehicle is parked.
+     * <p>
+     * Requires Permission:
+     * {@link android.car.Manifest.permission#CAR_UX_RESTRICTIONS_CONFIGURATION}.
+     *
+     * @param config UX restrictions configuration to be persisted.
+     * @return {@code true} if input config was successfully saved; {@code false} otherwise.
+     *
+     * @hide
+     */
+    @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
+    public synchronized boolean saveUxRestrictionsConfigurationForNextBoot(
+            CarUxRestrictionsConfiguration config) {
+        try {
+            return mUxRService.saveUxRestrictionsConfigurationForNextBoot(config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get the current staged configuration, staged config file will only be accessible after
      * the boot up completed or user has been switched.
      * This methods is only for test purpose, please do not use in production.
@@ -175,9 +237,7 @@
      * @return current staged configuration, {@code null} if it's not available
      *
      * @hide
-     *
      */
-    @TestApi
     @Nullable
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
     public synchronized CarUxRestrictionsConfiguration getStagedConfig() {
@@ -194,9 +254,7 @@
      * @return current prod configuration that is in effect.
      *
      * @hide
-     *
      */
-    @TestApi
     @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION)
     public synchronized CarUxRestrictionsConfiguration getConfig() {
         try {
@@ -207,6 +265,20 @@
     }
 
     /**
+     * @hide
+     */
+    public static String modeToString(@UxRestrictionMode int mode) {
+        switch (mode) {
+            case UX_RESTRICTION_MODE_BASELINE:
+                return "baseline";
+            case UX_RESTRICTION_MODE_PASSENGER:
+                return "passenger";
+            default:
+                throw new IllegalArgumentException("Unrecognized restriction mode " + mode);
+        }
+    }
+
+    /**
      * Class that implements the listener interface and gets called back from the
      * {@link com.android.car.CarDrivingStateService} across the binder interface.
      */
diff --git a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
index 8f48c5e..76d5a3b 100644
--- a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
+++ b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
@@ -34,4 +34,6 @@
     boolean saveUxRestrictionsConfigurationForNextBoot(in CarUxRestrictionsConfiguration config) = 3;
     CarUxRestrictionsConfiguration getStagedConfig() = 4;
     CarUxRestrictionsConfiguration getConfig() = 5;
+    boolean setRestrictionMode(int mode) = 6;
+    int getRestrictionMode() = 7;
 }
diff --git a/car-lib/src/android/car/storagemonitoring/IoStats.java b/car-lib/src/android/car/storagemonitoring/IoStats.java
index 3fc03c6..0c2b662 100644
--- a/car-lib/src/android/car/storagemonitoring/IoStats.java
+++ b/car-lib/src/android/car/storagemonitoring/IoStats.java
@@ -35,7 +35,7 @@
  * @hide
  */
 @SystemApi
-public class IoStats implements Parcelable {
+public final class IoStats implements Parcelable {
     public static final Creator<IoStats> CREATOR = new Creator<IoStats>() {
         @Override
         public IoStats createFromParcel(Parcel in) {
diff --git a/car-lib/src/android/car/storagemonitoring/LifetimeWriteInfo.java b/car-lib/src/android/car/storagemonitoring/LifetimeWriteInfo.java
index 6313e46..01dc933 100644
--- a/car-lib/src/android/car/storagemonitoring/LifetimeWriteInfo.java
+++ b/car-lib/src/android/car/storagemonitoring/LifetimeWriteInfo.java
@@ -30,7 +30,7 @@
  * @hide
  */
 @SystemApi
-public class LifetimeWriteInfo implements Parcelable {
+public final class LifetimeWriteInfo implements Parcelable {
     public static final Creator<IoStats> CREATOR = new Creator<IoStats>() {
         @Override
         public IoStats createFromParcel(Parcel in) {
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimate.java b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
index 507e6ed..10ae8a1 100644
--- a/car-lib/src/android/car/storagemonitoring/WearEstimate.java
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
@@ -38,7 +38,7 @@
  * @hide
  */
 @SystemApi
-public class WearEstimate implements Parcelable {
+public final class WearEstimate implements Parcelable {
     public static final int UNKNOWN = -1;
 
     /** @hide */
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
index 5c80a31..2d454b4 100644
--- a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
@@ -33,7 +33,7 @@
  * @hide
  */
 @SystemApi
-public class WearEstimateChange implements Parcelable {
+public final class WearEstimateChange implements Parcelable {
     public static final Parcelable.Creator<WearEstimateChange> CREATOR =
         new Parcelable.Creator<WearEstimateChange>() {
             public WearEstimateChange createFromParcel(Parcel in) {
diff --git a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
index e169a17..8b01d87 100644
--- a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
+++ b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
@@ -147,13 +147,15 @@
     }
 
     /**
-     * Confirms that the enrollment handshake has been accepted by the user.  This should be called
+     * Confirms that the enrollment handshake has been accepted by the user. This should be called
      * after the user has confirmed the verification code displayed on the UI.
+     *
+     * @param device the remote Bluetooth device that will receive the signal.
      */
     @RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
-    public void enrollmentHandshakeAccepted() {
+    public void enrollmentHandshakeAccepted(BluetoothDevice device) {
         try {
-            mEnrollmentService.enrollmentHandshakeAccepted();
+            mEnrollmentService.enrollmentHandshakeAccepted(device);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -200,7 +202,11 @@
      */
     @RequiresPermission(PERMISSION_CAR_ENROLL_TRUST)
     public void removeEscrowToken(long handle, int uid) {
-        //TODO:(b/128857992) need to call method in CarTrustAgentEnrollmentService
+        try {
+            mEnrollmentService.removeEscrowToken(handle, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
index 5606c06..bcdf1c9 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentEnrollment.aidl
@@ -30,10 +30,10 @@
     void startEnrollmentAdvertising();
     void stopEnrollmentAdvertising();
     void initiateEnrollmentHandshake(in BluetoothDevice device);
-    void enrollmentHandshakeAccepted();
+    void enrollmentHandshakeAccepted(in BluetoothDevice device);
     void terminateEnrollmentHandshake();
     boolean isEscrowTokenActive(in long handle, int uid);
-    void revokeTrust(in long handle);
+    void removeEscrowToken(in long handle, int uid);
     long[] getEnrollmentHandlesForUser(in int uid);
     void registerEnrollmentCallback(in ICarTrustAgentEnrollmentCallback callback);
     void unregisterEnrollmentCallback(in ICarTrustAgentEnrollmentCallback callback);
diff --git a/car-lib/src/android/car/vms/VmsAssociatedLayer.java b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
index d3f0e18..e522faa 100644
--- a/car-lib/src/android/car/vms/VmsAssociatedLayer.java
+++ b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
@@ -16,33 +16,48 @@
 
 package android.car.vms;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.*;
 
 /**
- * A VMS Layer with a list of publisher IDs it is associated with.
+ * A Vehicle Map Service layer with a list of publisher IDs it is associated with.
  *
  * @hide
  */
+@SystemApi
 public final class VmsAssociatedLayer implements Parcelable {
-
-    // The VmsLayer.
     private final VmsLayer mLayer;
-
-    // The IDs of the publishers that can publish this VmsLayer.
     private final Set<Integer> mPublisherIds;
 
-    public VmsAssociatedLayer(VmsLayer layer, Set<Integer> publisherIds) {
-        mLayer = layer;
+    /**
+     * Constructs a new layer offering.
+     *
+     * @param layer layer being offered
+     * @param publisherIds IDs of publishers associated with the layer
+     */
+    public VmsAssociatedLayer(@NonNull VmsLayer layer, @NonNull Set<Integer> publisherIds) {
+        mLayer = Preconditions.checkNotNull(layer, "layer cannot be null");
         mPublisherIds = Collections.unmodifiableSet(publisherIds);
     }
 
+    /**
+     * @return layer being offered
+     */
+    @NonNull
     public VmsLayer getVmsLayer() {
         return mLayer;
     }
 
+    /**
+     * @return IDs of publishers associated with the layer
+     */
+    @NonNull
     public Set<Integer> getPublisherIds() {
         return mPublisherIds;
     }
@@ -52,7 +67,6 @@
         return "VmsAssociatedLayer{ VmsLayer: " + mLayer + ", Publishers: " + mPublisherIds + "}";
     }
 
-    // Parcelable related methods.
     public static final Parcelable.Creator<VmsAssociatedLayer> CREATOR =
             new Parcelable.Creator<VmsAssociatedLayer>() {
                 public VmsAssociatedLayer createFromParcel(Parcel in) {
diff --git a/car-lib/src/android/car/vms/VmsAvailableLayers.java b/car-lib/src/android/car/vms/VmsAvailableLayers.java
index b0324f6..3852a7f 100644
--- a/car-lib/src/android/car/vms/VmsAvailableLayers.java
+++ b/car-lib/src/android/car/vms/VmsAvailableLayers.java
@@ -16,21 +16,28 @@
 
 package android.car.vms;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-
-import java.util.Set;
 import java.util.ArrayList;
-import java.util.List;
-import java.util.HashSet;
 import java.util.Collections;
-
+import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
- * VMS Layers that can be subscribed to by VMS clients.
+ * Availability of Vehicle Map Service layers.
+ *
+ * The layer availability is used by subscribers to determine which {@link VmsLayer}s are available
+ * for subscription and which publishers are offering to publish data for those layers. However,
+ * the Vehicle Map Service will allow subscription requests for unavailable layers.
+ *
+ * Sequence numbers are used to indicate the succession of availability states, and increase
+ * monotonically with each change in layer availability. They must be used by clients to ignore
+ * states that are received out-of-order.
  *
  * @hide
  */
@@ -43,16 +50,28 @@
     // The list of AssociatedLayers
     private final Set<VmsAssociatedLayer> mAssociatedLayers;
 
-    public VmsAvailableLayers(Set<VmsAssociatedLayer> associatedLayers, int sequence) {
+    /**
+     * Constructs a new layer availability.
+     *
+     * @param associatedLayers set of layers available for subscription
+     * @param sequence         sequence number of the availability state
+     */
+    public VmsAvailableLayers(@NonNull Set<VmsAssociatedLayer> associatedLayers, int sequence) {
         mSeq = sequence;
-
         mAssociatedLayers = Collections.unmodifiableSet(associatedLayers);
     }
 
+    /**
+     * @return sequence number of the availability state
+     */
     public int getSequence() {
         return mSeq;
     }
 
+    /**
+     * @return set of layers available for subscription
+     */
+    @NonNull
     public Set<VmsAssociatedLayer> getAssociatedLayers() {
         return mAssociatedLayers;
     }
diff --git a/car-lib/src/android/car/vms/VmsLayer.java b/car-lib/src/android/car/vms/VmsLayer.java
index b921230..fb811fa 100644
--- a/car-lib/src/android/car/vms/VmsLayer.java
+++ b/car-lib/src/android/car/vms/VmsLayer.java
@@ -23,62 +23,73 @@
 import java.util.Objects;
 
 /**
- * A VMS Layer which can be subscribed to by VMS clients.
+ * A Vehicle Map Service layer, which can be offered or subscribed to by clients.
+ *
+ * The full layer definition is used when routing packets, with each layer having the following
+ * properties:
+ *
+ * <ul>
+ * <li>Type: Type of data being published.</li>
+ * <li>Subtype: Type of packet being published.</li>
+ * <li>Version: Major version of the packet format. Different versions are not guaranteed to be
+ * compatible.</li>
+ * </ul>
+ *
+ * See the Vehicle Maps Service partner documentation for the set of valid types and subtypes.
  *
  * @hide
  */
 @SystemApi
 public final class VmsLayer implements Parcelable {
-
-    // The layer Type.
     private int mType;
-
-    // The layer Subtype.
     private int mSubtype;
-
-    // The layer version.
     private int mVersion;
 
+    /**
+     * Constructs a new layer definition.
+     *
+     * @param type    type of data published on the layer
+     * @param subtype type of packet published on the layer
+     * @param version major version of layer packet format
+     */
     public VmsLayer(int type, int subtype, int version) {
         mType = type;
         mSubtype = subtype;
         mVersion = version;
     }
 
+    /**
+     * @return type of data published on the layer
+     */
     public int getType() {
         return mType;
     }
 
+    /**
+     * @return type of packet published on the layer
+     */
     public int getSubtype() {
         return mSubtype;
     }
 
+    /**
+     * @return major version of layer packet format
+     */
     public int getVersion() {
         return mVersion;
     }
 
-    /**
-     * Checks the two objects for equality by comparing their IDs and Versions.
-     *
-     * @param o the {@link VmsLayer} to which this one is to be checked for equality
-     * @return true if the underlying objects of the VmsLayer are both considered equal
-     */
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof VmsLayer)) {
             return false;
         }
         VmsLayer p = (VmsLayer) o;
-        return Objects.equals(p.mType, mType) &&
-            Objects.equals(p.mSubtype, mSubtype) &&
-            Objects.equals(p.mVersion, mVersion);
+        return Objects.equals(p.mType, mType)
+                && Objects.equals(p.mSubtype, mSubtype)
+                && Objects.equals(p.mVersion, mVersion);
     }
 
-    /**
-     * Compute a hash code similarly tp {@link android.util.Pair}
-     *
-     * @return a hashcode of the Pair
-     */
     @Override
     public int hashCode() {
         return Objects.hash(mType, mSubtype, mVersion);
@@ -86,11 +97,10 @@
 
     @Override
     public String toString() {
-        return "VmsLayer{ Type: " + mType + ", Sub type: " + mSubtype + ", Version: " + mVersion + "}";
+        return "VmsLayer{ Type: " + mType + ", Sub type: " + mSubtype + ", Version: " + mVersion
+                + "}";
     }
 
-
-    // Parcelable related methods.
     public static final Parcelable.Creator<VmsLayer> CREATOR = new
             Parcelable.Creator<VmsLayer>() {
                 public VmsLayer createFromParcel(Parcel in) {
diff --git a/car-lib/src/android/car/vms/VmsLayerDependency.java b/car-lib/src/android/car/vms/VmsLayerDependency.java
index f5b6007..8cf8722 100644
--- a/car-lib/src/android/car/vms/VmsLayerDependency.java
+++ b/car-lib/src/android/car/vms/VmsLayerDependency.java
@@ -16,12 +16,14 @@
 
 package android.car.vms;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -29,7 +31,10 @@
 import java.util.Set;
 
 /**
- * A dependency for a VMS layer on other VMS layers.
+ * Layer dependencies for single Vehicle Map Service layer.
+ *
+ * Dependencies are treated as <b>hard</b> dependencies, meaning that an offered layer will not be
+ * reported as available until all dependent layers are also available.
  *
  * @hide
  */
@@ -39,28 +44,37 @@
     private final Set<VmsLayer> mDependency;
 
     /**
-     * Construct a dependency for layer on other layers.
+     * Constructs a layer with a dependency on other layers.
+     *
+     * @param layer layer that has dependencies
+     * @param dependencies layers that the given layer depends on
      */
-    public VmsLayerDependency(VmsLayer layer, Set<VmsLayer> dependencies) {
-        mLayer = layer;
+    public VmsLayerDependency(@NonNull VmsLayer layer, @NonNull Set<VmsLayer> dependencies) {
+        mLayer = Preconditions.checkNotNull(layer, "layer cannot be null");
         mDependency = Collections.unmodifiableSet(dependencies);
     }
 
     /**
-     * Constructs a layer without a dependency.
+     * Constructs a layer without dependencies.
+     *
+     * @param layer layer that has no dependencies
      */
-    public VmsLayerDependency(VmsLayer layer) {
-        mLayer = layer;
-        mDependency = Collections.emptySet();
+    public VmsLayerDependency(@NonNull VmsLayer layer) {
+        this(layer, Collections.emptySet());
     }
 
+    /**
+     * @return layer that has zero or more dependencies
+     */
+    @NonNull
     public VmsLayer getLayer() {
         return mLayer;
     }
 
     /**
-     * Returns the dependencies.
+     * @return all layers that the layer depends on
      */
+    @NonNull
     public Set<VmsLayer> getDependencies() {
         return mDependency;
     }
@@ -76,6 +90,7 @@
                 }
             };
 
+    @Override
     public String toString() {
         return "VmsLayerDependency{ Layer: " + mLayer + " Dependency: " + mDependency + "}";
     }
diff --git a/car-lib/src/android/car/vms/VmsLayersOffering.java b/car-lib/src/android/car/vms/VmsLayersOffering.java
index f2f4b73..9a79cdb 100644
--- a/car-lib/src/android/car/vms/VmsLayersOffering.java
+++ b/car-lib/src/android/car/vms/VmsLayersOffering.java
@@ -16,6 +16,7 @@
 
 package android.car.vms;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,7 +29,11 @@
 import java.util.Set;
 
 /**
- * The state of dependencies for a single publisher.
+ * A Vehicle Map Service layer offering for a single publisher.
+ *
+ * Contains all layers the publisher can offer, and the layers that offering depends on.
+ *
+ * A layer will not be advertised to subscribers unless all of its dependencies are met.
  *
  * @hide
  */
@@ -39,18 +44,26 @@
 
     private final int mPublisherId;
 
-    public VmsLayersOffering(Set<VmsLayerDependency> dependencies, int publisherId) {
+    /**
+     *
+     * @param dependencies
+     * @param publisherId
+     */
+    public VmsLayersOffering(@NonNull Set<VmsLayerDependency> dependencies, int publisherId) {
         mDependencies = Collections.unmodifiableSet(dependencies);
         mPublisherId = publisherId;
     }
 
     /**
-     * Returns the dependencies.
+     * @return set of layer and dependencies in the offering
      */
     public Set<VmsLayerDependency> getDependencies() {
         return mDependencies;
     }
 
+    /**
+     * @return ID of the publisher making the offering
+     */
     public int getPublisherId() {
         return mPublisherId;
     }
@@ -77,8 +90,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-
-        out.writeParcelableList(new ArrayList(mDependencies), flags);
+        out.writeParcelableList(new ArrayList<>(mDependencies), flags);
         out.writeInt(mPublisherId);
     }
 
@@ -88,7 +100,8 @@
             return false;
         }
         VmsLayersOffering p = (VmsLayersOffering) o;
-        return Objects.equals(p.mPublisherId, mPublisherId) && p.mDependencies.equals(mDependencies);
+        return Objects.equals(p.mPublisherId, mPublisherId)
+                && p.mDependencies.equals(mDependencies);
     }
 
     @Override
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index bdd2510..b89c558 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -17,7 +17,7 @@
 package android.car.vms;
 
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
@@ -29,27 +29,29 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 
 import java.lang.ref.WeakReference;
 
 /**
- * Services that need VMS publisher services need to inherit from this class and also need to be
- * declared in the array vmsPublisherClients located in
- * packages/services/Car/service/res/values/config.xml (most likely, this file will be in an overlay
- * of the target product.
+ * API implementation of a Vehicle Map Service publisher client.
  *
- * The {@link com.android.car.VmsPublisherService} will start this service. The callback
- * {@link #onVmsPublisherServiceReady()} notifies when VMS publisher services can be used, and the
- * publisher can request a publisher ID in order to start publishing.
+ * All publisher clients must inherit from this class and export it as a service, and the service
+ * be added to either the {@code vmsPublisherSystemClients} or {@code vmsPublisherUserClients}
+ * arrays in the Car service configuration, depending on which user the client will run as.
  *
- * SystemApi candidate.
+ * The {@link com.android.car.VmsPublisherService} will then bind to this service, with the
+ * {@link #onVmsPublisherServiceReady()} callback notifying the client implementation when the
+ * connection is established and publisher operations can be called.
+ *
+ * Publishers must also register a publisher ID by calling {@link #getPublisherId(byte[])}.
  *
  * @hide
  */
 @SystemApi
 public abstract class VmsPublisherClientService extends Service {
     private static final boolean DBG = true;
-    private static final String TAG = "VmsPublisherClient";
+    private static final String TAG = "VmsPublisherClientService";
 
     private final Object mLock = new Object();
 
@@ -83,27 +85,29 @@
     }
 
     /**
-     * Notifies that the publisher services are ready.
+     * Notifies the client that publisher services are ready.
      */
     protected abstract void onVmsPublisherServiceReady();
 
     /**
-     * Publishers need to implement this method to receive notifications of subscription changes.
+     * Notifies the client of changes in layer subscriptions.
      *
-     * @param subscriptionState the state of the subscriptions.
+     * @param subscriptionState state of layer subscriptions
      */
-    public abstract void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState);
+    public abstract void onVmsSubscriptionChange(@NonNull VmsSubscriptionState subscriptionState);
 
     /**
-     * Uses the VmsPublisherService binder to publish messages.
+     * Publishes a data packet to subscribers.
      *
-     * @param layer   the layer to publish to.
-     * @param payload the message to be sent.
-     * @param publisherId the ID that got assigned to the publisher that published the message by
-     *                    VMS core.
-     * @return if the call to the method VmsPublisherService.publish was successful.
+     * Publishers must only publish packets for the layers that they have made offerings for.
+     *
+     * @param layer       layer to publish to
+     * @param publisherId ID of the publisher publishing the message
+     * @param payload     data packet to be sent
+     * @throws IllegalStateException if publisher services are not available
      */
-    public final void publish(VmsLayer layer, int publisherId, byte[] payload) {
+    public final void publish(@NonNull VmsLayer layer, int publisherId, byte[] payload) {
+        Preconditions.checkNotNull(layer, "layer cannot be null");
         if (DBG) {
             Log.d(TAG, "Publishing for layer : " + layer);
         }
@@ -118,12 +122,13 @@
     }
 
     /**
-     * Uses the VmsPublisherService binder to set the layers offering.
+     * Sets the layers offered by a specific publisher.
      *
-     * @param offering the layers that the publisher may publish.
-     * @return if the call to VmsPublisherService.setLayersOffering was successful.
+     * @param offering layers being offered for subscription by the publisher
+     * @throws IllegalStateException if publisher services are not available
      */
-    public final void setLayersOffering(VmsLayersOffering offering) {
+    public final void setLayersOffering(@NonNull VmsLayersOffering offering) {
+        Preconditions.checkNotNull(offering, "offering cannot be null");
         if (DBG) {
             Log.d(TAG, "Setting layers offering : " + offering);
         }
@@ -153,31 +158,38 @@
         return token;
     }
 
+    /**
+     * Acquires a publisher ID for a serialized publisher description.
+     *
+     * Multiple calls to this method with the same information will return the same publisher ID.
+     *
+     * @param publisherInfo serialized publisher description information, in a vendor-specific
+     *                      format
+     * @return a publisher ID for the given publisher description
+     * @throws IllegalStateException if publisher services are not available
+     */
     public final int getPublisherId(byte[] publisherInfo) {
         if (mVmsPublisherService == null) {
             throw new IllegalStateException("VmsPublisherService not set.");
         }
-        Integer publisherId = null;
+        int publisherId;
         try {
             Log.i(TAG, "Getting publisher static ID");
             publisherId = mVmsPublisherService.getPublisherId(publisherInfo);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        if (publisherId == null) {
-            throw new IllegalStateException("VmsPublisherService cannot get a publisher static ID.");
-        } else {
-            VmsOperationRecorder.get().getPublisherId(publisherId);
-        }
+        VmsOperationRecorder.get().getPublisherId(publisherId);
         return publisherId;
     }
 
     /**
-     * Uses the VmsPublisherService binder to get the state of the subscriptions.
+     * Gets the state of layer subscriptions.
      *
-     * @return list of layer/version or null in case of error.
+     * @return state of layer subscriptions
+     * @throws IllegalStateException if publisher services are not available
      */
-    public final @Nullable VmsSubscriptionState getSubscriptions() {
+    public final VmsSubscriptionState getSubscriptions() {
         if (mVmsPublisherService == null) {
             throw new IllegalStateException("VmsPublisherService not set.");
         }
@@ -202,7 +214,7 @@
         private long mSequence = -1;
         private final Object mSequenceLock = new Object();
 
-        public VmsPublisherClientBinder(VmsPublisherClientService vmsPublisherClientService) {
+        VmsPublisherClientBinder(VmsPublisherClientService vmsPublisherClientService) {
             mVmsPublisherClientService = new WeakReference<>(vmsPublisherClientService);
         }
 
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
index 8ca0878..06a06ae 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -31,10 +31,10 @@
 import java.util.concurrent.Executor;
 
 /**
- * API for interfacing with the VmsSubscriberService. It supports a single client callback that can
- * (un)subscribe to different layers. Getting notifactions and managing subscriptions is enabled
- * after setting the client callback with #setVmsSubscriberClientCallback.
- * SystemApi candidate
+ * API implementation for use by Vehicle Map Service subscribers.
+ *
+ * Supports a single client callback that can subscribe and unsubscribe to different data layers.
+ * {@link #setVmsSubscriberClientCallback} must be called before any subscription operations.
  *
  * @hide
  */
@@ -52,22 +52,28 @@
     private Executor mExecutor;
 
     /**
-     * Interface exposed to VMS subscribers: it is a wrapper of IVmsSubscriberClient.
+     * Callback interface for Vehicle Map Service subscribers.
      */
     public interface VmsSubscriberClientCallback {
         /**
-         * Called when the property is updated
+         * Called when a data packet is received.
+         *
+         * @param layer   subscribed layer that packet was received for
+         * @param payload data packet that was received
          */
-        void onVmsMessageReceived(VmsLayer layer, byte[] payload);
+        void onVmsMessageReceived(@NonNull VmsLayer layer, byte[] payload);
 
         /**
-         * Called when layers availability change
+         * Called when set of available data layers changes.
+         *
+         * @param availableLayers set of available data layers
          */
-        void onLayersAvailabilityChanged(VmsAvailableLayers availableLayers);
+        void onLayersAvailabilityChanged(@NonNull VmsAvailableLayers availableLayers);
     }
 
     /**
      * Hidden constructor - can only be used internally.
+     *
      * @hide
      */
     public VmsSubscriberManager(IBinder service) {
@@ -86,7 +92,9 @@
                     return;
                 }
                 Binder.clearCallingIdentity();
-                executor.execute(() -> {dispatchOnReceiveMessage(layer, payload);});
+                executor.execute(() -> {
+                    dispatchOnReceiveMessage(layer, payload);
+                });
             }
 
             @Override
@@ -102,28 +110,30 @@
                     return;
                 }
                 Binder.clearCallingIdentity();
-                executor.execute(() -> {dispatchOnAvailabilityChangeMessage(availableLayers);});
+                executor.execute(() -> {
+                    dispatchOnAvailabilityChangeMessage(availableLayers);
+                });
             }
         };
     }
 
     /**
-     * Sets the callback for the notification of onVmsMessageReceived events.
-     * @param executor {@link Executor} to handle the callbacks
-     * @param clientCallback subscriber callback that will handle onVmsMessageReceived events.
-     * @throws IllegalStateException if the client callback was already set.
+     * Sets the subscriber client's callback, for receiving layer availability and data events.
+     *
+     * @param executor       {@link Executor} to handle the callbacks
+     * @param clientCallback subscriber callback that will handle events
+     * @throws IllegalStateException if the client callback was already set
      */
     public void setVmsSubscriberClientCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull VmsSubscriberClientCallback clientCallback) {
-        Preconditions.checkNotNull(clientCallback);
-        Preconditions.checkNotNull(executor);
         synchronized (mClientCallbackLock) {
             if (mClientCallback != null) {
                 throw new IllegalStateException("Client callback is already configured.");
             }
-            mClientCallback = clientCallback;
-            mExecutor = executor;
+            mClientCallback = Preconditions.checkNotNull(clientCallback,
+                    "clientCallback cannot be null");
+            mExecutor = Preconditions.checkNotNull(executor, "executor cannot be null");
         }
         try {
             mVmsSubscriberService.addVmsSubscriberToNotifications(mSubscriberManagerClient);
@@ -134,7 +144,7 @@
 
 
     /**
-     * Clears the client callback which disables communication with the client.
+     * Clears the subscriber client's callback.
      */
     public void clearVmsSubscriberClientCallback() {
         synchronized (mClientCallbackLock) {
@@ -153,8 +163,12 @@
     }
 
     /**
-     * Returns a serialized publisher information for a publisher ID.
+     * Gets a publisher's self-reported description information.
+     *
+     * @param publisherId publisher ID to retrieve information for
+     * @return serialized publisher information, in a vendor-specific format
      */
+    @NonNull
     public byte[] getPublisherInfo(int publisherId) {
         try {
             return mVmsSubscriberService.getPublisherInfo(publisherId);
@@ -164,8 +178,11 @@
     }
 
     /**
-     * Returns the available layers.
+     * Gets all layers available for subscription.
+     *
+     * @return available layers
      */
+    @NonNull
     public VmsAvailableLayers getAvailableLayers() {
         try {
             return mVmsSubscriberService.getAvailableLayers();
@@ -175,13 +192,13 @@
     }
 
     /**
-     * Subscribes to listen to the layer specified.
+     * Subscribes to data packets for a specific layer.
      *
-     * @param layer the layer to subscribe to.
+     * @param layer layer to subscribe to
      * @throws IllegalStateException if the client callback was not set via
      *                               {@link #setVmsSubscriberClientCallback}.
      */
-    public void subscribe(VmsLayer layer) {
+    public void subscribe(@NonNull VmsLayer layer) {
         verifySubscriptionIsAllowed();
         try {
             mVmsSubscriberService.addVmsSubscriber(mSubscriberManagerClient, layer);
@@ -192,14 +209,14 @@
     }
 
     /**
-     * Subscribes to listen to the layer specified from the publisher specified.
+     * Subscribes to data packets for a specific layer from a specific publisher.
      *
-     * @param layer       the layer to subscribe to.
-     * @param publisherId the publisher of the layer.
+     * @param layer       layer to subscribe to
+     * @param publisherId a publisher of the layer
      * @throws IllegalStateException if the client callback was not set via
      *                               {@link #setVmsSubscriberClientCallback}.
      */
-    public void subscribe(VmsLayer layer, int publisherId) {
+    public void subscribe(@NonNull VmsLayer layer, int publisherId) {
         verifySubscriptionIsAllowed();
         try {
             mVmsSubscriberService.addVmsSubscriberToPublisher(
@@ -210,7 +227,9 @@
         }
     }
 
-    /** Starts monitoring. */
+    /**
+     * Start monitoring all messages for all layers, regardless of subscriptions.
+     */
     public void startMonitoring() {
         verifySubscriptionIsAllowed();
         try {
@@ -222,13 +241,13 @@
     }
 
     /**
-     * Unsubscribes from the layer/version specified.
+     * Unsubscribes from data packets for a specific layer.
      *
-     * @param layer the layer to unsubscribe from.
+     * @param layer layer to unsubscribe from
      * @throws IllegalStateException if the client callback was not set via
      *                               {@link #setVmsSubscriberClientCallback}.
      */
-    public void unsubscribe(VmsLayer layer) {
+    public void unsubscribe(@NonNull VmsLayer layer) {
         verifySubscriptionIsAllowed();
         try {
             mVmsSubscriberService.removeVmsSubscriber(mSubscriberManagerClient, layer);
@@ -239,14 +258,14 @@
     }
 
     /**
-     * Unsubscribes from the layer/version specified.
+     * Unsubscribes from data packets for a specific layer from a specific publisher.
      *
-     * @param layer       the layer to unsubscribe from.
-     * @param publisherId the pubisher of the layer.
+     * @param layer       layer to unsubscribe from
+     * @param publisherId a publisher of the layer
      * @throws IllegalStateException if the client callback was not set via
      *                               {@link #setVmsSubscriberClientCallback}.
      */
-    public void unsubscribe(VmsLayer layer, int publisherId) {
+    public void unsubscribe(@NonNull VmsLayer layer, int publisherId) {
         try {
             mVmsSubscriberService.removeVmsSubscriberToPublisher(
                     mSubscriberManagerClient, layer, publisherId);
@@ -256,6 +275,9 @@
         }
     }
 
+    /**
+     * Stop monitoring. Only receive messages for layers which have been subscribed to."
+     */
     public void stopMonitoring() {
         try {
             mVmsSubscriberService.removeVmsSubscriberPassive(mSubscriberManagerClient);
@@ -310,22 +332,4 @@
     @Override
     public void onCarDisconnected() {
     }
-
-    private static final class VmsDataMessage {
-        private final VmsLayer mLayer;
-        private final byte[] mPayload;
-
-        public VmsDataMessage(VmsLayer layer, byte[] payload) {
-            mLayer = layer;
-            mPayload = payload;
-        }
-
-        public VmsLayer getLayer() {
-            return mLayer;
-        }
-
-        public byte[] getPayload() {
-            return mPayload;
-        }
-    }
 }
diff --git a/car-lib/src/android/car/vms/VmsSubscriptionState.java b/car-lib/src/android/car/vms/VmsSubscriptionState.java
index 7b8ce81..08e72ba 100644
--- a/car-lib/src/android/car/vms/VmsSubscriptionState.java
+++ b/car-lib/src/android/car/vms/VmsSubscriptionState.java
@@ -16,6 +16,8 @@
 
 package android.car.vms;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,39 +29,54 @@
 import java.util.Set;
 
 /**
- * The list of layers with subscribers.
+ * Subscription state of Vehicle Map Service layers.
+ *
+ * The subscription state is used by publishers to determine which layers to publish data for, as
+ * any data published to a layer without subscribers will be dropped by the Vehicle Map Service.
+ *
+ * Sequence numbers are used to indicate the succession of subscription states, and increase
+ * monotonically with each change in subscriptions. They must be used by clients to ignore states
+ * that are received out-of-order.
  *
  * @hide
  */
+@SystemApi
 public final class VmsSubscriptionState implements Parcelable {
     private final int mSequenceNumber;
     private final Set<VmsLayer> mLayers;
     private final Set<VmsAssociatedLayer> mSubscribedLayersFromPublishers;
 
     /**
-     * Construcs a summary of the state of the current subscriptions for publishers to consume
+     * Constructs a summary of the state of the current subscriptions for publishers to consume
      * and adjust which layers that the are publishing.
      */
     public VmsSubscriptionState(int sequenceNumber,
-                                Set<VmsLayer> subscribedLayers,
-                                Set<VmsAssociatedLayer> layersFromPublishers) {
+            @NonNull Set<VmsLayer> subscribedLayers,
+            @NonNull Set<VmsAssociatedLayer> layersFromPublishers) {
         mSequenceNumber = sequenceNumber;
         mLayers = Collections.unmodifiableSet(subscribedLayers);
         mSubscribedLayersFromPublishers = Collections.unmodifiableSet(layersFromPublishers);
     }
 
     /**
-     * Returns the sequence number assigned by the VMS service. Sequence numbers are
-     * monotonically increasing and help clients ignore potential out-of-order states.
+     * @return sequence number of the subscription state
      */
     public int getSequenceNumber() {
         return mSequenceNumber;
     }
 
+    /**
+     * @return set of layers with subscriptions to all publishers
+     */
+    @NonNull
     public Set<VmsLayer> getLayers() {
         return mLayers;
     }
 
+    /**
+     * @return set of layers with subscriptions to a subset of publishers
+     */
+    @NonNull
     public Set<VmsAssociatedLayer> getAssociatedLayers() {
         return mSubscribedLayersFromPublishers;
     }
diff --git a/car-usb-handler/AndroidManifest.xml b/car-usb-handler/AndroidManifest.xml
index 7803318..91730a6 100644
--- a/car-usb-handler/AndroidManifest.xml
+++ b/car-usb-handler/AndroidManifest.xml
@@ -15,12 +15,12 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="android.car.usb.handler" >
+        package="android.car.usb.handler">
     <uses-sdk android:minSdkVersion="25" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"
-                 android:directBootAware="true" >
+                 android:directBootAware="true">
         <activity android:name=".UsbHostManagementActivity"
                   android:theme="@android:style/Theme.Material.Light.Dialog"
                   android:launchMode="singleTop">
@@ -28,9 +28,10 @@
                 android:name="distractionOptimized"
                 android:value="true" />
         </activity>
-        <receiver android:name=".BootUsbScanner" >
+        <receiver android:name=".BootUsbScanner"
+                 android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
             </intent-filter>
         </receiver>
     </application>
diff --git a/car-usb-handler/src/android/car/usb/handler/AoapServiceManager.java b/car-usb-handler/src/android/car/usb/handler/AoapServiceManager.java
new file mode 100644
index 0000000..04bef87
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/AoapServiceManager.java
@@ -0,0 +1,310 @@
+/*
+ * 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.usb.handler;
+
+import static android.car.AoapService.KEY_DEVICE;
+import static android.car.AoapService.KEY_RESULT;
+import static android.car.AoapService.MSG_CAN_SWITCH_TO_AOAP;
+import static android.car.AoapService.MSG_CAN_SWITCH_TO_AOAP_RESPONSE;
+import static android.car.AoapService.MSG_NEW_DEVICE_ATTACHED;
+import static android.car.AoapService.MSG_NEW_DEVICE_ATTACHED_RESPONSE;
+
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.car.AoapService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.hardware.usb.UsbDevice;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/** Manages connections to {@link android.car.AoapService} (AOAP handler apps). */
+public class AoapServiceManager {
+    private static final String TAG = AoapServiceManager.class.getSimpleName();
+
+    private static final int MSG_DISCONNECT = 1;
+    private static final int DISCONNECT_DELAY_MS = 30000;
+
+    private static final int INVOCATION_TIMEOUT_MS = 5000;
+
+
+    private final HashMap<ComponentName, AoapServiceConnection> mConnections = new HashMap<>();
+    private Context mContext;
+    private final Object mLock = new Object();
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+
+    public AoapServiceManager(Context context) {
+        mContext = context;
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        mHandler = new Handler(mHandlerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_DISCONNECT) {
+                    removeConnection((AoapServiceConnection) msg.obj);
+                } else {
+                    Log.e(TAG, "Unexpected message " + msg.what);
+                }
+            }
+        };
+    }
+
+    /**
+     * Calls synchronously with timeout {@link #INVOCATION_TIMEOUT_MS} to the given service to check
+     * if it supports the device.
+     */
+    @WorkerThread
+    public boolean isDeviceSupported(UsbDevice device, ComponentName serviceName) {
+        final AoapServiceConnection connection = getConnectionOrNull(serviceName);
+        if (connection == null) {
+            return false;
+        }
+
+        try {
+            return connection.isDeviceSupported(device)
+                    .get(INVOCATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException | InterruptedException | TimeoutException e) {
+            Log.w(TAG, "Failed to get response isDeviceSupported from " + serviceName, e);
+            return false;
+        }
+    }
+
+    /**
+     * Calls synchronously with timeout {@link #INVOCATION_TIMEOUT_MS} to the given service to check
+     * if the device can be switched to AOAP mode now.
+     */
+    @WorkerThread
+    public boolean canSwitchDeviceToAoap(UsbDevice device, ComponentName serviceName) {
+        final AoapServiceConnection connection = getConnectionOrNull(serviceName);
+        if (connection == null) {
+            return false;
+        }
+
+        try {
+            return connection.canSwitchDeviceToAoap(device)
+                    .get(INVOCATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException | InterruptedException | TimeoutException e) {
+            Log.w(TAG, "Failed to get response of canSwitchDeviceToAoap from " + serviceName, e);
+            return false;
+        }
+    }
+
+    @Nullable
+    private AoapServiceConnection getConnectionOrNull(ComponentName name) {
+        AoapServiceConnection connection;
+        synchronized (mLock) {
+            connection = mConnections.get(name);
+            if (connection != null) {
+                postponeServiceDisconnection(connection);
+                return connection;
+            }
+
+            connection = new AoapServiceConnection(name, this, mHandlerThread.getLooper());
+            boolean bound = mContext.bindService(
+                    createIntent(name), connection, Context.BIND_AUTO_CREATE);
+            if (bound) {
+                mConnections.put(name, connection);
+                postponeServiceDisconnection(connection);
+            } else {
+                Log.w(TAG, "Failed to bind to service " + name);
+                return null;
+            }
+        }
+        return connection;
+    }
+
+    private void postponeServiceDisconnection(AoapServiceConnection connection) {
+        mHandler.removeMessages(MSG_DISCONNECT, connection);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCONNECT, connection),
+                DISCONNECT_DELAY_MS);
+    }
+
+    private static Intent createIntent(ComponentName name) {
+        Intent intent = new Intent();
+        intent.setComponent(name);
+        return intent;
+    }
+
+    private void removeConnection(AoapServiceConnection connection) {
+        Log.i(TAG, "Removing connection to " + connection);
+        synchronized (mLock) {
+            mConnections.remove(connection.mComponentName);
+            if (connection.mBound) {
+                mContext.unbindService(connection);
+                connection.mBound = false;
+            }
+        }
+    }
+
+    private static class AoapServiceConnection implements ServiceConnection {
+        private Messenger mOutgoingMessenger;
+        private boolean mBound;
+        private final CompletableFuture<Void> mConnected = new CompletableFuture<>();
+        private final SparseArray<CompletableFuture<Bundle>> mExpectedResponses =
+                new SparseArray<>();
+        private final ComponentName mComponentName;
+        private final WeakReference<AoapServiceManager> mManagerRef;
+        private final Messenger mIncomingMessenger;
+        private final Object mLock = new Object();
+
+        private AoapServiceConnection(ComponentName name, AoapServiceManager manager,
+                Looper looper) {
+            mComponentName = name;
+            mManagerRef = new WeakReference<>(manager);
+            mIncomingMessenger = new Messenger(new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    onResponse(msg);
+                }
+            });
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (service == null) {
+                Log.e(TAG, "Binder object was not provided on service connection to " + name);
+                return;
+            }
+
+            synchronized (mLock) {
+                mBound = true;
+                mOutgoingMessenger = new Messenger(service);
+            }
+            mConnected.complete(null);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                mOutgoingMessenger = null;
+                mBound = false;
+            }
+
+            final AoapServiceManager mgr = mManagerRef.get();
+            if (mgr != null) {
+                mgr.removeConnection(this);
+            }
+        }
+
+        private void onResponse(Message message) {
+            final CompletableFuture<Bundle> response;
+            synchronized (mLock) {
+                response = mExpectedResponses.removeReturnOld(message.what);
+            }
+            if (response == null) {
+                Log.e(TAG, "Received unexpected response " + message.what + ", expected: "
+                        + mExpectedResponses);
+                return;
+            }
+
+            if (message.getData() == null) {
+                throw new IllegalArgumentException("Received response msg " + message.what
+                        + " without data");
+            }
+            Log.i(TAG, "onResponse msg: " + message.what + ", data: " + message.getData());
+            boolean res = response.complete(message.getData());
+            if (!res) {
+                Log.w(TAG, "Failed to complete future " + response);
+            }
+        }
+
+        CompletableFuture<Boolean> isDeviceSupported(UsbDevice device) {
+            return sendMessageForResult(
+                    MSG_NEW_DEVICE_ATTACHED,
+                    MSG_NEW_DEVICE_ATTACHED_RESPONSE,
+                    createUsbDeviceData(device))
+                    .thenApply(this::isResultOk);
+
+        }
+
+        CompletableFuture<Boolean> canSwitchDeviceToAoap(UsbDevice device) {
+            return sendMessageForResult(
+                    MSG_CAN_SWITCH_TO_AOAP,
+                    MSG_CAN_SWITCH_TO_AOAP_RESPONSE,
+                    createUsbDeviceData(device))
+                    .thenApply(this::isResultOk);
+        }
+
+        private boolean isResultOk(Bundle data) {
+            int result = data.getInt(KEY_RESULT);
+            Log.i(TAG, "Got result: " + data);
+            return AoapService.RESULT_OK == result;
+        }
+
+        private static Bundle createUsbDeviceData(UsbDevice device) {
+            Bundle data = new Bundle(1);
+            data.putParcelable(KEY_DEVICE, device);
+            return data;
+        }
+
+        private CompletableFuture<Bundle> sendMessageForResult(
+                int msgRequest, int msgResponse, Bundle data) {
+            return mConnected.thenCompose(x -> {
+                CompletableFuture<Bundle> responseFuture = new CompletableFuture<>();
+                Messenger messenger;
+                synchronized (mLock) {
+                    mExpectedResponses.put(msgResponse, responseFuture);
+                    messenger = mOutgoingMessenger;
+                }
+                send(messenger, msgRequest, data);
+
+                return responseFuture;
+            });
+        }
+
+        private void send(Messenger messenger, int req, Bundle data) {
+            Message msg = Message.obtain(null, req, null);
+            msg.replyTo = mIncomingMessenger;
+            msg.setData(data);
+            try {
+                messenger.send(msg);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Connection broken with " + mComponentName, e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "AoapServiceConnection{"
+                    + "mBound=" + mBound
+                    + ", mConnected=" + mConnected
+                    + ", mExpectedResponses=" + mExpectedResponses
+                    + ", mComponentName=" + mComponentName
+                    + '}';
+        }
+    }
+}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceFilter.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceFilter.java
new file mode 100644
index 0000000..c698290
--- /dev/null
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceFilter.java
@@ -0,0 +1,297 @@
+/*
+ * 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.usb.handler;
+
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * This class is used to describe a USB device. When used in HashMaps all values must be specified,
+ *  but wildcards can be used for any of the fields in the package meta-data.
+ */
+class UsbDeviceFilter {
+    private static final String TAG = UsbDeviceFilter.class.getSimpleName();
+
+    // USB Vendor ID (or -1 for unspecified)
+    public final int mVendorId;
+    // USB Product ID (or -1 for unspecified)
+    public final int mProductId;
+    // USB device or interface class (or -1 for unspecified)
+    public final int mClass;
+    // USB device subclass (or -1 for unspecified)
+    public final int mSubclass;
+    // USB device protocol (or -1 for unspecified)
+    public final int mProtocol;
+    // USB device manufacturer name string (or null for unspecified)
+    public final String mManufacturerName;
+    // USB device product name string (or null for unspecified)
+    public final String mProductName;
+    // USB device serial number string (or null for unspecified)
+    public final String mSerialNumber;
+
+    // USB device in AOAP mode manufacturer
+    public final String mAoapManufacturer;
+    // USB device in AOAP mode model
+    public final String mAoapModel;
+    // USB device in AOAP mode description string
+    public final String mAoapDescription;
+    // USB device in AOAP mode version
+    public final String mAoapVersion;
+    // USB device in AOAP mode URI
+    public final String mAoapUri;
+    // USB device in AOAP mode serial
+    public final String mAoapSerial;
+    // USB device in AOAP mode verification service
+    public final String mAoapService;
+
+    UsbDeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+                        String manufacturer, String product, String serialnum,
+                        String aoapManufacturer, String aoapModel, String aoapDescription,
+                        String aoapVersion, String aoapUri, String aoapSerial,
+                        String aoapService) {
+        mVendorId = vid;
+        mProductId = pid;
+        mClass = clasz;
+        mSubclass = subclass;
+        mProtocol = protocol;
+        mManufacturerName = manufacturer;
+        mProductName = product;
+        mSerialNumber = serialnum;
+
+        mAoapManufacturer = aoapManufacturer;
+        mAoapModel = aoapModel;
+        mAoapDescription = aoapDescription;
+        mAoapVersion = aoapVersion;
+        mAoapUri = aoapUri;
+        mAoapSerial = aoapSerial;
+        mAoapService = aoapService;
+    }
+
+    public static UsbDeviceFilter read(XmlPullParser parser, boolean aoapData) {
+        int vendorId = -1;
+        int productId = -1;
+        int deviceClass = -1;
+        int deviceSubclass = -1;
+        int deviceProtocol = -1;
+        String manufacturerName = null;
+        String productName = null;
+        String serialNumber = null;
+
+        String aoapManufacturer = null;
+        String aoapModel = null;
+        String aoapDescription = null;
+        String aoapVersion = null;
+        String aoapUri = null;
+        String aoapSerial = null;
+        String aoapService = null;
+
+        int count = parser.getAttributeCount();
+        for (int i = 0; i < count; i++) {
+            String name = parser.getAttributeName(i);
+            String value = parser.getAttributeValue(i);
+            // Attribute values are ints or strings
+            if (!aoapData && "manufacturer-name".equals(name)) {
+                manufacturerName = value;
+            } else if (!aoapData && "product-name".equals(name)) {
+                productName = value;
+            } else if (!aoapData && "serial-number".equals(name)) {
+                serialNumber = value;
+            } else if (aoapData && "manufacturer".equals(name)) {
+                aoapManufacturer = value;
+            } else if (aoapData && "model".equals(name)) {
+                aoapModel = value;
+            } else if (aoapData && "description".equals(name)) {
+                aoapDescription = value;
+            } else if (aoapData && "version".equals(name)) {
+                aoapVersion = value;
+            } else if (aoapData && "uri".equals(name)) {
+                aoapUri = value;
+            } else if (aoapData && "serial".equals(name)) {
+                aoapSerial = value;
+            } else if (aoapData && "service".equals(name)) {
+                aoapService = value;
+            } else if (!aoapData) {
+                int intValue = -1;
+                int radix = 10;
+                if (value != null && value.length() > 2 && value.charAt(0) == '0'
+                        && (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+                    // allow hex values starting with 0x or 0X
+                    radix = 16;
+                    value = value.substring(2);
+                }
+                try {
+                    intValue = Integer.parseInt(value, radix);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "invalid number for field " + name, e);
+                    continue;
+                }
+                if ("vendor-id".equals(name)) {
+                    vendorId = intValue;
+                } else if ("product-id".equals(name)) {
+                    productId = intValue;
+                } else if ("class".equals(name)) {
+                    deviceClass = intValue;
+                } else if ("subclass".equals(name)) {
+                    deviceSubclass = intValue;
+                } else if ("protocol".equals(name)) {
+                    deviceProtocol = intValue;
+                }
+            }
+        }
+        return new UsbDeviceFilter(vendorId, productId,
+                                deviceClass, deviceSubclass, deviceProtocol,
+                                manufacturerName, productName, serialNumber, aoapManufacturer,
+                                aoapModel, aoapDescription, aoapVersion, aoapUri, aoapSerial,
+                                aoapService);
+    }
+
+    private boolean matches(int clasz, int subclass, int protocol) {
+        return ((mClass == -1 || clasz == mClass)
+                && (mSubclass == -1 || subclass == mSubclass)
+                && (mProtocol == -1 || protocol == mProtocol));
+    }
+
+    public boolean matches(UsbDevice device) {
+        if (mVendorId != -1 && device.getVendorId() != mVendorId) {
+            return false;
+        }
+        if (mProductId != -1 && device.getProductId() != mProductId) {
+            return false;
+        }
+        if (mManufacturerName != null && device.getManufacturerName() == null) {
+            return false;
+        }
+        if (mProductName != null && device.getProductName() == null) {
+            return false;
+        }
+        if (mSerialNumber != null && device.getSerialNumber() == null) {
+            return false;
+        }
+        if (mManufacturerName != null && device.getManufacturerName() != null
+                && !mManufacturerName.equals(device.getManufacturerName())) {
+            return false;
+        }
+        if (mProductName != null && device.getProductName() != null
+                && !mProductName.equals(device.getProductName())) {
+            return false;
+        }
+        if (mSerialNumber != null && device.getSerialNumber() != null
+                && !mSerialNumber.equals(device.getSerialNumber())) {
+            return false;
+        }
+
+        // check device class/subclass/protocol
+        if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+                    device.getDeviceProtocol())) {
+            return true;
+        }
+
+        // if device doesn't match, check the interfaces
+        int count = device.getInterfaceCount();
+        for (int i = 0; i < count; i++) {
+            UsbInterface intf = device.getInterface(i);
+            if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+                        intf.getInterfaceProtocol())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        // can't compare if we have wildcard strings
+        if (mVendorId == -1 || mProductId == -1
+                || mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+            return false;
+        }
+        if (obj instanceof UsbDeviceFilter) {
+            UsbDeviceFilter filter = (UsbDeviceFilter) obj;
+
+            if (filter.mVendorId != mVendorId
+                    || filter.mProductId != mProductId
+                    || filter.mClass != mClass
+                    || filter.mSubclass != mSubclass
+                    || filter.mProtocol != mProtocol) {
+                return false;
+            }
+            if ((filter.mManufacturerName != null && mManufacturerName == null)
+                    || (filter.mManufacturerName == null && mManufacturerName != null)
+                    || (filter.mProductName != null && mProductName == null)
+                    || (filter.mProductName == null && mProductName != null)
+                    || (filter.mSerialNumber != null && mSerialNumber == null)
+                    || (filter.mSerialNumber == null && mSerialNumber != null)) {
+                return false;
+            }
+            if  ((filter.mManufacturerName != null && mManufacturerName != null
+                      && !mManufacturerName.equals(filter.mManufacturerName))
+                      || (filter.mProductName != null && mProductName != null
+                      && !mProductName.equals(filter.mProductName))
+                      || (filter.mSerialNumber != null && mSerialNumber != null
+                      && !mSerialNumber.equals(filter.mSerialNumber))) {
+                return false;
+            }
+            return true;
+        }
+        if (obj instanceof UsbDevice) {
+            UsbDevice device = (UsbDevice) obj;
+            if (device.getVendorId() != mVendorId
+                    || device.getProductId() != mProductId
+                    || device.getDeviceClass() != mClass
+                    || device.getDeviceSubclass() != mSubclass
+                    || device.getDeviceProtocol() != mProtocol) {
+                return false;
+            }
+            if ((mManufacturerName != null && device.getManufacturerName() == null)
+                    || (mManufacturerName == null && device.getManufacturerName() != null)
+                    || (mProductName != null && device.getProductName() == null)
+                    || (mProductName == null && device.getProductName() != null)
+                    || (mSerialNumber != null && device.getSerialNumber() == null)
+                    || (mSerialNumber == null && device.getSerialNumber() != null)) {
+                return false;
+            }
+            if ((device.getManufacturerName() != null
+                    && !mManufacturerName.equals(device.getManufacturerName()))
+                    || (device.getProductName() != null
+                    && !mProductName.equals(device.getProductName()))
+                    || (device.getSerialNumber() != null
+                    && !mSerialNumber.equals(device.getSerialNumber()))) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return (((mVendorId << 16) | mProductId)
+                ^ ((mClass << 16) | (mSubclass << 8) | mProtocol));
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
+                + ",mClass=" + mClass + ",mSubclass=" + mSubclass
+                + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
+                + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]";
+    }
+}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
index b5abc58..96f12cf 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
@@ -15,12 +15,13 @@
  */
 package android.car.usb.handler;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.Manifest;
 import android.annotation.Nullable;
-import android.car.IUsbAoapSupportCheckService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -28,16 +29,12 @@
 import android.content.res.XmlResourceParser;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbManager;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.util.XmlUtils;
 
@@ -45,15 +42,17 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Queue;
 
 /** Resolves supported handlers for USB device. */
 public final class UsbDeviceHandlerResolver {
+
     private static final String TAG = UsbDeviceHandlerResolver.class.getSimpleName();
     private static final boolean LOCAL_LOGD = true;
 
+    private static final String AOAP_HANDLE_PERMISSION =
+            "android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE";
+
     /**
      * Callbacks for device resolver.
      */
@@ -71,330 +70,7 @@
     private final Context mContext;
     private final HandlerThread mHandlerThread;
     private final UsbDeviceResolverHandler mHandler;
-
-    private class DeviceContext {
-        public final UsbDevice usbDevice;
-        @Nullable public final UsbDeviceConnection connection;
-        public final UsbDeviceSettings settings;
-        public final List<UsbDeviceSettings> activeDeviceSettings;
-        public final Queue<Pair<ResolveInfo, DeviceFilter>> mActiveDeviceOptions =
-                new LinkedList<>();
-
-        private volatile IUsbAoapSupportCheckService mUsbAoapSupportCheckService;
-        private final ServiceConnection mServiceConnection = new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName className, IBinder service) {
-                Log.i(TAG, "onServiceConnected: " + className);
-                mUsbAoapSupportCheckService = IUsbAoapSupportCheckService.Stub.asInterface(service);
-                mHandler.requestOnServiceConnectionStateChanged(DeviceContext.this);
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName className) {
-                Log.i(TAG, "onServiceDisconnected: " + className);
-                mUsbAoapSupportCheckService = null;
-                mHandler.requestOnServiceConnectionStateChanged(DeviceContext.this);
-            }
-        };
-
-        public DeviceContext(UsbDevice usbDevice, UsbDeviceSettings settings,
-                List<UsbDeviceSettings> activeDeviceSettings) {
-            this.usbDevice = usbDevice;
-            this.settings = settings;
-            this.activeDeviceSettings = activeDeviceSettings;
-            connection = UsbUtil.openConnection(mUsbManager, usbDevice);
-        }
-    }
-
-    // This class is used to describe a USB device.
-    // When used in HashMaps all values must be specified,
-    // but wildcards can be used for any of the fields in
-    // the package meta-data.
-    private static class DeviceFilter {
-        // USB Vendor ID (or -1 for unspecified)
-        public final int mVendorId;
-        // USB Product ID (or -1 for unspecified)
-        public final int mProductId;
-        // USB device or interface class (or -1 for unspecified)
-        public final int mClass;
-        // USB device subclass (or -1 for unspecified)
-        public final int mSubclass;
-        // USB device protocol (or -1 for unspecified)
-        public final int mProtocol;
-        // USB device manufacturer name string (or null for unspecified)
-        public final String mManufacturerName;
-        // USB device product name string (or null for unspecified)
-        public final String mProductName;
-        // USB device serial number string (or null for unspecified)
-        public final String mSerialNumber;
-
-        // USB device in AOAP mode manufacturer
-        public final String mAoapManufacturer;
-        // USB device in AOAP mode model
-        public final String mAoapModel;
-        // USB device in AOAP mode description string
-        public final String mAoapDescription;
-        // USB device in AOAP mode version
-        public final String mAoapVersion;
-        // USB device in AOAP mode URI
-        public final String mAoapUri;
-        // USB device in AOAP mode serial
-        public final String mAoapSerial;
-        // USB device in AOAP mode verification service
-        public final String mAoapService;
-
-        DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
-                            String manufacturer, String product, String serialnum,
-                            String aoapManufacturer, String aoapModel, String aoapDescription,
-                            String aoapVersion, String aoapUri, String aoapSerial,
-                            String aoapService) {
-            mVendorId = vid;
-            mProductId = pid;
-            mClass = clasz;
-            mSubclass = subclass;
-            mProtocol = protocol;
-            mManufacturerName = manufacturer;
-            mProductName = product;
-            mSerialNumber = serialnum;
-
-            mAoapManufacturer = aoapManufacturer;
-            mAoapModel = aoapModel;
-            mAoapDescription = aoapDescription;
-            mAoapVersion = aoapVersion;
-            mAoapUri = aoapUri;
-            mAoapSerial = aoapSerial;
-            mAoapService = aoapService;
-        }
-
-        DeviceFilter(UsbDevice device) {
-            mVendorId = device.getVendorId();
-            mProductId = device.getProductId();
-            mClass = device.getDeviceClass();
-            mSubclass = device.getDeviceSubclass();
-            mProtocol = device.getDeviceProtocol();
-            mManufacturerName = device.getManufacturerName();
-            mProductName = device.getProductName();
-            mSerialNumber = device.getSerialNumber();
-            mAoapManufacturer = null;
-            mAoapModel = null;
-            mAoapDescription = null;
-            mAoapVersion = null;
-            mAoapUri = null;
-            mAoapSerial = null;
-            mAoapService = null;
-        }
-
-        public static DeviceFilter read(XmlPullParser parser, boolean aoapData) {
-            int vendorId = -1;
-            int productId = -1;
-            int deviceClass = -1;
-            int deviceSubclass = -1;
-            int deviceProtocol = -1;
-            String manufacturerName = null;
-            String productName = null;
-            String serialNumber = null;
-
-            String aoapManufacturer = null;
-            String aoapModel = null;
-            String aoapDescription = null;
-            String aoapVersion = null;
-            String aoapUri = null;
-            String aoapSerial = null;
-            String aoapService = null;
-
-            int count = parser.getAttributeCount();
-            for (int i = 0; i < count; i++) {
-                String name = parser.getAttributeName(i);
-                String value = parser.getAttributeValue(i);
-                // Attribute values are ints or strings
-                if (!aoapData && "manufacturer-name".equals(name)) {
-                    manufacturerName = value;
-                } else if (!aoapData && "product-name".equals(name)) {
-                    productName = value;
-                } else if (!aoapData && "serial-number".equals(name)) {
-                    serialNumber = value;
-                } else if (aoapData && "manufacturer".equals(name)) {
-                    aoapManufacturer = value;
-                } else if (aoapData && "model".equals(name)) {
-                    aoapModel = value;
-                } else if (aoapData && "description".equals(name)) {
-                    aoapDescription = value;
-                } else if (aoapData && "version".equals(name)) {
-                    aoapVersion = value;
-                } else if (aoapData && "uri".equals(name)) {
-                    aoapUri = value;
-                } else if (aoapData && "serial".equals(name)) {
-                    aoapSerial = value;
-                } else if (aoapData && "service".equals(name)) {
-                    aoapService = value;
-                } else if (!aoapData) {
-                    int intValue = -1;
-                    int radix = 10;
-                    if (value != null && value.length() > 2 && value.charAt(0) == '0'
-                            && (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
-                        // allow hex values starting with 0x or 0X
-                        radix = 16;
-                        value = value.substring(2);
-                    }
-                    try {
-                        intValue = Integer.parseInt(value, radix);
-                    } catch (NumberFormatException e) {
-                        Log.e(TAG, "invalid number for field " + name, e);
-                        continue;
-                    }
-                    if ("vendor-id".equals(name)) {
-                        vendorId = intValue;
-                    } else if ("product-id".equals(name)) {
-                        productId = intValue;
-                    } else if ("class".equals(name)) {
-                        deviceClass = intValue;
-                    } else if ("subclass".equals(name)) {
-                        deviceSubclass = intValue;
-                    } else if ("protocol".equals(name)) {
-                        deviceProtocol = intValue;
-                    }
-                }
-            }
-            return new DeviceFilter(vendorId, productId,
-                                    deviceClass, deviceSubclass, deviceProtocol,
-                                    manufacturerName, productName, serialNumber, aoapManufacturer,
-                                    aoapModel, aoapDescription, aoapVersion, aoapUri, aoapSerial,
-                                    aoapService);
-        }
-
-        private boolean matches(int clasz, int subclass, int protocol) {
-            return ((mClass == -1 || clasz == mClass)
-                    && (mSubclass == -1 || subclass == mSubclass)
-                    && (mProtocol == -1 || protocol == mProtocol));
-        }
-
-        public boolean matches(UsbDevice device) {
-            if (mVendorId != -1 && device.getVendorId() != mVendorId) {
-                return false;
-            }
-            if (mProductId != -1 && device.getProductId() != mProductId) {
-                return false;
-            }
-            if (mManufacturerName != null && device.getManufacturerName() == null) {
-                return false;
-            }
-            if (mProductName != null && device.getProductName() == null) {
-                return false;
-            }
-            if (mSerialNumber != null && device.getSerialNumber() == null) {
-                return false;
-            }
-            if (mManufacturerName != null && device.getManufacturerName() != null
-                    && !mManufacturerName.equals(device.getManufacturerName())) {
-                return false;
-            }
-            if (mProductName != null && device.getProductName() != null
-                    && !mProductName.equals(device.getProductName())) {
-                return false;
-            }
-            if (mSerialNumber != null && device.getSerialNumber() != null
-                    && !mSerialNumber.equals(device.getSerialNumber())) {
-                return false;
-            }
-
-            // check device class/subclass/protocol
-            if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
-                        device.getDeviceProtocol())) {
-                return true;
-            }
-
-            // if device doesn't match, check the interfaces
-            int count = device.getInterfaceCount();
-            for (int i = 0; i < count; i++) {
-                UsbInterface intf = device.getInterface(i);
-                if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
-                            intf.getInterfaceProtocol())) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            // can't compare if we have wildcard strings
-            if (mVendorId == -1 || mProductId == -1
-                    || mClass == -1 || mSubclass == -1 || mProtocol == -1) {
-                return false;
-            }
-            if (obj instanceof DeviceFilter) {
-                DeviceFilter filter = (DeviceFilter) obj;
-
-                if (filter.mVendorId != mVendorId
-                        || filter.mProductId != mProductId
-                        || filter.mClass != mClass
-                        || filter.mSubclass != mSubclass
-                        || filter.mProtocol != mProtocol) {
-                    return false;
-                }
-                if ((filter.mManufacturerName != null && mManufacturerName == null)
-                        || (filter.mManufacturerName == null && mManufacturerName != null)
-                        || (filter.mProductName != null && mProductName == null)
-                        || (filter.mProductName == null && mProductName != null)
-                        || (filter.mSerialNumber != null && mSerialNumber == null)
-                        || (filter.mSerialNumber == null && mSerialNumber != null)) {
-                    return false;
-                }
-                if  ((filter.mManufacturerName != null && mManufacturerName != null
-                          && !mManufacturerName.equals(filter.mManufacturerName))
-                          || (filter.mProductName != null && mProductName != null
-                          && !mProductName.equals(filter.mProductName))
-                          || (filter.mSerialNumber != null && mSerialNumber != null
-                          && !mSerialNumber.equals(filter.mSerialNumber))) {
-                    return false;
-                }
-                return true;
-            }
-            if (obj instanceof UsbDevice) {
-                UsbDevice device = (UsbDevice) obj;
-                if (device.getVendorId() != mVendorId
-                        || device.getProductId() != mProductId
-                        || device.getDeviceClass() != mClass
-                        || device.getDeviceSubclass() != mSubclass
-                        || device.getDeviceProtocol() != mProtocol) {
-                    return false;
-                }
-                if ((mManufacturerName != null && device.getManufacturerName() == null)
-                        || (mManufacturerName == null && device.getManufacturerName() != null)
-                        || (mProductName != null && device.getProductName() == null)
-                        || (mProductName == null && device.getProductName() != null)
-                        || (mSerialNumber != null && device.getSerialNumber() == null)
-                        || (mSerialNumber == null && device.getSerialNumber() != null)) {
-                    return false;
-                }
-                if ((device.getManufacturerName() != null
-                        && !mManufacturerName.equals(device.getManufacturerName()))
-                        || (device.getProductName() != null
-                        && !mProductName.equals(device.getProductName()))
-                        || (device.getSerialNumber() != null
-                        && !mSerialNumber.equals(device.getSerialNumber()))) {
-                    return false;
-                }
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return (((mVendorId << 16) | mProductId)
-                    ^ ((mClass << 16) | (mSubclass << 8) | mProtocol));
-        }
-
-        @Override
-        public String toString() {
-            return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
-                    + ",mClass=" + mClass + ",mSubclass=" + mSubclass
-                    + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
-                    + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]";
-        }
-    }
+    private final AoapServiceManager mAoapServiceManager;
 
     public UsbDeviceHandlerResolver(UsbManager manager, Context context,
             UsbDeviceHandlerResolverCallback deviceListener) {
@@ -405,6 +81,7 @@
         mHandlerThread.start();
         mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
         mPackageManager = context.getPackageManager();
+        mAoapServiceManager = new AoapServiceManager(mContext.getApplicationContext());
     }
 
     /**
@@ -444,10 +121,19 @@
             if (AoapInterface.isDeviceInAoapMode(device)) {
                 mDeviceCallback.onDeviceDispatched();
             } else {
-                DeviceFilter filter =
+                UsbDeviceFilter filter =
                         packageMatches(activityInfo, intent.getAction(), device, true);
+
                 if (filter != null) {
-                    requestAoapSwitch(device, filter);
+                    mHandlerThread.getThreadHandler().post(() -> {
+                        if (mAoapServiceManager.canSwitchDeviceToAoap(device,
+                                ComponentName.unflattenFromString(filter.mAoapService))) {
+                            requestAoapSwitch(device, filter);
+                        } else {
+                            Log.i(TAG, "Ignore AOAP switch for device " + device
+                                    + " handled by " + filter.mAoapService);
+                        }
+                    });
                     return true;
                 }
             }
@@ -474,52 +160,35 @@
         }
 
         Intent intent = createDeviceAttachedIntent(device);
-        List<Pair<ResolveInfo, DeviceFilter>> matches = getDeviceMatches(device, intent, false);
+        List<UsbHandlerPackage> matches = getDeviceMatches(device, intent, false);
         if (LOCAL_LOGD) {
             Log.d(TAG, "matches size: " + matches.size());
         }
-        List<UsbDeviceSettings> settings = new ArrayList<>(matches.size());
-        for (Pair<ResolveInfo, DeviceFilter> info : matches) {
-            UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(device);
-            setting.setHandler(
-                    new ComponentName(
-                            info.first.activityInfo.packageName, info.first.activityInfo.name));
-            settings.add(setting);
+        List<UsbDeviceSettings> settings = new ArrayList<>();
+        for (UsbHandlerPackage pkg : matches) {
+            settings.add(createSettings(device, pkg));
         }
-        DeviceContext deviceContext =
-                new DeviceContext(device, UsbDeviceSettings.constructSettings(device), settings);
-        if (deviceContext.connection != null
-                && AoapInterface.isSupported(mContext, device, deviceContext.connection)) {
-            deviceContext.mActiveDeviceOptions.addAll(getDeviceMatches(device, intent, true));
-            queryNextAoapHandler(deviceContext);
-        } else {
-            deviceProbingComplete(deviceContext);
-        }
-    }
 
-    private void queryNextAoapHandler(DeviceContext context) {
-        Pair<ResolveInfo, DeviceFilter> option = context.mActiveDeviceOptions.peek();
-        if (option == null) {
-            Log.w(TAG, "No more options left.");
-            deviceProbingComplete(context);
-            return;
-        }
-        Intent serviceIntent = new Intent();
-        serviceIntent.setComponent(ComponentName.unflattenFromString(option.second.mAoapService));
-        boolean bound = mContext.bindService(serviceIntent, context.mServiceConnection,
-                Context.BIND_AUTO_CREATE);
-        if (bound) {
-            mHandler.requestServiceConnectionTimeout();
-        } else {
-            if (LOCAL_LOGD) {
-                Log.d(TAG, "Failed to bind to the service");
+        UsbDeviceConnection devConnection = UsbUtil.openConnection(mUsbManager, device);
+        if (devConnection != null && AoapInterface.isSupported(mContext, device, devConnection)) {
+            for (UsbHandlerPackage pkg : getDeviceMatches(device, intent, true)) {
+                if (mAoapServiceManager.isDeviceSupported(device, pkg.mAoapService)) {
+                    settings.add(createSettings(device, pkg));
+                }
             }
-            context.mActiveDeviceOptions.poll();
-            queryNextAoapHandler(context);
         }
+
+        deviceProbingComplete(device, settings);
     }
 
-    private void requestAoapSwitch(UsbDevice device, DeviceFilter filter) {
+    private UsbDeviceSettings createSettings(UsbDevice device, UsbHandlerPackage pkg) {
+        UsbDeviceSettings settings = UsbDeviceSettings.constructSettings(device);
+        settings.setHandler(pkg.mActivity);
+        settings.setAoap(pkg.mAoapService != null);
+        return settings;
+    }
+
+    private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) {
         UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device);
         if (connection == null) {
             Log.e(TAG, "Failed to connect to usb device.");
@@ -540,67 +209,79 @@
         connection.close();
     }
 
-    private void deviceProbingComplete(DeviceContext context) {
+    private void deviceProbingComplete(UsbDevice device, List<UsbDeviceSettings> settings) {
         if (LOCAL_LOGD) {
             Log.d(TAG, "deviceProbingComplete");
         }
-        mDeviceCallback.onHandlersResolveCompleted(context.usbDevice, context.activeDeviceSettings);
+        mDeviceCallback.onHandlersResolveCompleted(device, settings);
     }
 
-    private void doHandleServiceConnectionStateChanged(DeviceContext context) {
-        if (LOCAL_LOGD) {
-            Log.d(TAG, "doHandleServiceConnectionStateChanged: "
-                    + context.mUsbAoapSupportCheckService);
-        }
-        if (context.mUsbAoapSupportCheckService != null) {
-            boolean deviceSupported = false;
-            try {
-                deviceSupported =
-                        context.mUsbAoapSupportCheckService.isDeviceSupported(context.usbDevice);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Call to remote service failed", e);
-            }
-            if (deviceSupported) {
-                Pair<ResolveInfo, DeviceFilter> option = context.mActiveDeviceOptions.peek();
-
-                UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(context.settings);
-                setting.setHandler(
-                        new ComponentName(
-                            option.first.activityInfo.packageName, option.first.activityInfo.name));
-                setting.setAoap(true);
-                context.activeDeviceSettings.add(setting);
-            }
-            mContext.unbindService(context.mServiceConnection);
-        }
-        context.mActiveDeviceOptions.poll();
-        queryNextAoapHandler(context);
-    }
-
-    private List<Pair<ResolveInfo, DeviceFilter>> getDeviceMatches(
+    private List<UsbHandlerPackage> getDeviceMatches(
             UsbDevice device, Intent intent, boolean forAoap) {
-        List<Pair<ResolveInfo, DeviceFilter>> matches = new ArrayList<>();
+        List<UsbHandlerPackage> matches = new ArrayList<>();
         List<ResolveInfo> resolveInfos =
                 mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
         for (ResolveInfo resolveInfo : resolveInfos) {
-            DeviceFilter filter = packageMatches(resolveInfo.activityInfo,
+            if (forAoap && !hasAoapPermission(resolveInfo.resolvePackageName)) {
+                Log.w(TAG, "Package " + resolveInfo.resolvePackageName + " does not hold "
+                        + AOAP_HANDLE_PERMISSION + " permission. Ignore the package.");
+                continue;
+            }
+
+            UsbDeviceFilter filter = packageMatches(resolveInfo.activityInfo,
                     intent.getAction(), device, forAoap);
             if (filter != null) {
-                matches.add(Pair.create(resolveInfo, filter));
+                ActivityInfo ai = resolveInfo.activityInfo;
+                ComponentName activity = new ComponentName(ai.packageName, ai.name);
+                ComponentName aoapService = filter.mAoapService == null
+                        ? null : ComponentName.unflattenFromString(filter.mAoapService);
+
+                if (aoapService != null && !checkServiceRequiresPermission(aoapService)) {
+                    continue;
+                }
+
+                if (aoapService != null || !forAoap) {
+                    matches.add(new UsbHandlerPackage(activity, aoapService));
+                }
             }
         }
         return matches;
     }
 
-    private DeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
+    private boolean checkServiceRequiresPermission(ComponentName serviceName) {
+        Intent intent = new Intent();
+        intent.setComponent(serviceName);
+        boolean found = false;
+        for (ResolveInfo info : mPackageManager.queryIntentServices(intent, 0)) {
+            if (info.serviceInfo != null) {
+                found = true;
+                if ((Manifest.permission.MANAGE_USB.equals(info.serviceInfo.permission))) {
+                    return true;
+                }
+            }
+        }
+        if (found) {
+            Log.w(TAG, "Component " + serviceName + " must be protected with "
+                    + Manifest.permission.MANAGE_USB + " permission");
+        } else {
+            Log.w(TAG, "Component " + serviceName + " not found");
+        }
+        return false;
+    }
+
+    private boolean hasAoapPermission(String packageName) {
+        return mPackageManager
+                .checkPermission(AOAP_HANDLE_PERMISSION, packageName) == PERMISSION_GRANTED;
+    }
+
+    private UsbDeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
             boolean forAoap) {
         if (LOCAL_LOGD) {
             Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: "
                     + forAoap);
         }
         String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device";
-        XmlResourceParser parser = null;
-        try {
-            parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
+        try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager, metaDataName)) {
             if (parser == null) {
                 Log.w(TAG, "no meta-data for " + ai);
                 return null;
@@ -610,7 +291,7 @@
             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                 String tagName = parser.getName();
                 if (device != null && filterTagName.equals(tagName)) {
-                    DeviceFilter filter = DeviceFilter.read(parser, forAoap);
+                    UsbDeviceFilter filter = UsbDeviceFilter.read(parser, forAoap);
                     if (forAoap || filter.matches(device)) {
                         return filter;
                     }
@@ -619,38 +300,24 @@
             }
         } catch (Exception e) {
             Log.w(TAG, "Unable to load component info " + ai.toString(), e);
-        } finally {
-            if (parser != null) parser.close();
         }
         return null;
     }
 
     private class UsbDeviceResolverHandler extends Handler {
         private static final int MSG_RESOLVE_HANDLERS = 0;
-        private static final int MSG_SERVICE_CONNECTION_STATE_CHANGE = 1;
-        private static final int MSG_SERVICE_CONNECTION_TIMEOUT = 2;
         private static final int MSG_COMPLETE_DISPATCH = 3;
 
-        private static final long CONNECT_TIMEOUT_MS = 5000;
-
         private UsbDeviceResolverHandler(Looper looper) {
             super(looper);
         }
 
-        public void requestResolveHandlers(UsbDevice device) {
+        void requestResolveHandlers(UsbDevice device) {
             Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device);
             sendMessage(msg);
         }
 
-        public void requestOnServiceConnectionStateChanged(DeviceContext deviceContext) {
-            sendMessage(obtainMessage(MSG_SERVICE_CONNECTION_STATE_CHANGE, deviceContext));
-        }
-
-        public void requestServiceConnectionTimeout() {
-            sendEmptyMessageDelayed(MSG_SERVICE_CONNECTION_TIMEOUT, CONNECT_TIMEOUT_MS);
-        }
-
-        public void requestCompleteDeviceDispatch() {
+        void requestCompleteDeviceDispatch() {
             sendEmptyMessage(MSG_COMPLETE_DISPATCH);
         }
 
@@ -660,14 +327,6 @@
                 case MSG_RESOLVE_HANDLERS:
                     doHandleResolveHandlers((UsbDevice) msg.obj);
                     break;
-                case MSG_SERVICE_CONNECTION_STATE_CHANGE:
-                    removeMessages(MSG_SERVICE_CONNECTION_TIMEOUT);
-                    doHandleServiceConnectionStateChanged((DeviceContext) msg.obj);
-                    break;
-                case MSG_SERVICE_CONNECTION_TIMEOUT:
-                    Log.i(TAG, "Service connection timeout");
-                    doHandleServiceConnectionStateChanged(null);
-                    break;
                 case MSG_COMPLETE_DISPATCH:
                     mDeviceCallback.onDeviceDispatched();
                     break;
@@ -676,4 +335,14 @@
             }
         }
     }
+
+    private static class UsbHandlerPackage {
+        final ComponentName mActivity;
+        final @Nullable ComponentName mAoapService;
+
+        UsbHandlerPackage(ComponentName activity, @Nullable ComponentName aoapService) {
+            mActivity = activity;
+            mAoapService = aoapService;
+        }
+    }
 }
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
index ec9a89a..2762e6f 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -56,6 +56,8 @@
     private static final boolean LOCAL_LOGD = true;
     private static final boolean LOCAL_LOGV = true;
 
+    private static final int DISPATCH_RETRY_DELAY_MS = 1000;
+    private static final int DISPATCH_RETRY_ATTEMPTS = 5;
 
     private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
     private final Context mContext;
@@ -158,16 +160,16 @@
         mCallback.processingStateChanged(true);
 
         UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
-        if (settings != null && mUsbResolver.dispatch(
-                    device, settings.getHandler(), settings.getAoap())) {
-            if (LOCAL_LOGV) {
-                Log.v(TAG, "Usb Device: " + device + " was sent to component: "
-                        + settings.getHandler());
-            }
-            return;
+
+        if (settings == null) {
+            resolveDevice(device);
+        } else {
+            Object obj =
+                    new UsbHostControllerHandlerDispatchData(
+                            device, settings, DISPATCH_RETRY_ATTEMPTS, true);
+            Message.obtain(mHandler, UsbHostControllerHandler.MSG_DEVICE_DISPATCH, obj)
+                    .sendToTarget();
         }
-        mCallback.titleChanged(generateTitle(mContext, device));
-        mUsbResolver.resolve(device);
     }
 
     /**
@@ -175,7 +177,17 @@
      */
     public void applyDeviceSettings(UsbDeviceSettings settings) {
         mUsbSettingsStorage.saveSettings(settings);
-        mUsbResolver.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
+        Message msg = mHandler.obtainMessage();
+        msg.obj =
+                new UsbHostControllerHandlerDispatchData(
+                        getActiveDevice(), settings, DISPATCH_RETRY_ATTEMPTS, false);
+        msg.what = UsbHostControllerHandler.MSG_DEVICE_DISPATCH;
+        msg.sendToTarget();
+    }
+
+    private void resolveDevice(UsbDevice device) {
+        mCallback.titleChanged(generateTitle(mContext, device));
+        mUsbResolver.resolve(device);
     }
 
     /**
@@ -244,8 +256,34 @@
         }
     }
 
+    private class UsbHostControllerHandlerDispatchData {
+        private final UsbDevice mUsbDevice;
+        private final UsbDeviceSettings mUsbDeviceSettings;
+
+        public int mRetries = 0;
+        public boolean mCanResolve = true;
+
+        public UsbHostControllerHandlerDispatchData(
+                UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings,
+                int retries, boolean canResolve) {
+            mUsbDevice = usbDevice;
+            mUsbDeviceSettings = usbDeviceSettings;
+            mRetries = retries;
+            mCanResolve = canResolve;
+        }
+
+        public UsbDevice getUsbDevice() {
+            return mUsbDevice;
+        }
+
+        public UsbDeviceSettings getUsbDeviceSettings() {
+            return mUsbDeviceSettings;
+        }
+    }
+
     private class UsbHostControllerHandler extends Handler {
         private static final int MSG_DEVICE_REMOVED = 1;
+        private static final int MSG_DEVICE_DISPATCH = 2;
 
         private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
 
@@ -263,6 +301,24 @@
                 case MSG_DEVICE_REMOVED:
                     doHandleDeviceRemoved();
                     break;
+                case MSG_DEVICE_DISPATCH:
+                    UsbHostControllerHandlerDispatchData data =
+                            (UsbHostControllerHandlerDispatchData) msg.obj;
+                    UsbDevice device = data.getUsbDevice();
+                    UsbDeviceSettings settings = data.getUsbDeviceSettings();
+                    if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap())) {
+                        if (data.mRetries > 0) {
+                            --data.mRetries;
+                            Message nextMessage = Message.obtain(msg);
+                            mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS);
+                        } else if (data.mCanResolve) {
+                            resolveDevice(device);
+                        }
+                    } else if (LOCAL_LOGV) {
+                        Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: "
+                                + settings.getHandler());
+                    }
+                    break;
                 default:
                     Log.w(TAG, "Unhandled message: " + msg);
                     super.handleMessage(msg);
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
index 387ae62..d571d5d 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
@@ -15,17 +15,23 @@
  */
 package android.car.usb.handler;
 
+import static android.content.Intent.ACTION_USER_UNLOCKED;
+
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -55,6 +61,39 @@
     private UsbHostController mController;
     private PackageManager mPackageManager;
 
+    private final ResolveBroadcastReceiver mResolveBroadcastReceiver
+            = new ResolveBroadcastReceiver();
+    private boolean mReceiverRegistered = false;
+
+    private void unregisterResolveBroadcastReceiver() {
+        if (mReceiverRegistered) {
+            unregisterReceiver(mResolveBroadcastReceiver);
+            mReceiverRegistered = false;
+        }
+    }
+
+    private void processDevice() {
+        UsbDevice connectedDevice = getDevice();
+
+        if (connectedDevice != null) {
+            mController.processDevice(connectedDevice);
+        } else {
+            unregisterResolveBroadcastReceiver();
+            finish();
+        }
+    }
+
+    private class ResolveBroadcastReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            // We could have been unregistered after receiving the intent but before processing it,
+            // so make sure we are still registered.
+            if (mReceiverRegistered) {
+                processDevice();
+                unregisterResolveBroadcastReceiver();
+            }
+        }
+    }
+
     private final AdapterView.OnItemClickListener mHandlerClickListener =
             new AdapterView.OnItemClickListener() {
         @Override
@@ -85,13 +124,25 @@
     }
 
     @Override
+    public void onPause() {
+        super.onPause();
+        unregisterResolveBroadcastReceiver();
+    }
+
+    @Override
     public void onResume() {
         super.onResume();
-        UsbDevice connectedDevice = getDevice();
-        if (connectedDevice != null) {
-            mController.processDevice(connectedDevice);
+
+        UserManager userManager = getSystemService(UserManager.class);
+        if (userManager.isUserUnlocked() || getUserId() == UserHandle.USER_SYSTEM) {
+            processDevice();
         } else {
-            finish();
+            mReceiverRegistered = true;
+            registerReceiver(mResolveBroadcastReceiver, new IntentFilter(ACTION_USER_UNLOCKED));
+            // in case the car was unlocked while the receiver was being registered
+            if (userManager.isUserUnlocked()) {
+                mResolveBroadcastReceiver.onReceive(this, new Intent(ACTION_USER_UNLOCKED));
+            }
         }
     }
 
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
index 7e8704e..b578161 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
@@ -183,13 +183,18 @@
         return contentValues;
     }
 
-
     private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
         private static final int DATABASE_VERSION = 2;
         private static final String DATABASE_NAME = "usb_devices.db";
 
+        // we are using device protected storage because we may need to access the db before the
+        // user has authenticated
         UsbSettingsDbHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            super(
+                    context.createDeviceProtectedStorageContext(),
+                    DATABASE_NAME,
+                    null,
+                    DATABASE_VERSION);
         }
 
         @Override
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 62c8042..0fbed7d 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -83,6 +83,7 @@
 
 # Automotive specific packages
 PRODUCT_PACKAGES += \
+    CarFrameworkPackageStubs \
     CarService \
     CarDialerApp \
     CarRadioApp \
diff --git a/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml b/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml
index 27dbeda..ab798c2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/layout/alert_dialog_button_bar_material.xml
@@ -37,6 +37,7 @@
             android:id="@*android:id/button3"
             style="@*android:style/CarAction1"
             android:background="@*android:drawable/car_dialog_button_background"
+            android:layout_marginEnd="@*android:dimen/car_padding_4"
             android:layout_width="wrap_content"
             android:layout_height="@*android:dimen/car_card_action_bar_height" />
 
@@ -52,7 +53,6 @@
             android:id="@*android:id/button1"
             style="@*android:style/CarAction1"
             android:background="@*android:drawable/car_dialog_button_background"
-            android:layout_marginStart="@*android:dimen/car_padding_4"
             android:layout_width="wrap_content"
             android:layout_height="@*android:dimen/car_card_action_bar_height" />
         <Space
diff --git a/car_product/sepolicy/public/service.te b/car_product/sepolicy/public/service.te
index 6c514f3..87426f4 100644
--- a/car_product/sepolicy/public/service.te
+++ b/car_product/sepolicy/public/service.te
@@ -1,2 +1,2 @@
-type carservice_service, service_manager_type;
+type carservice_service, app_api_service, service_manager_type;
 type procfsinspector_service, service_manager_type;
diff --git a/evs/manager/Android.mk b/evs/manager/Android.mk
index 375a84c..9545be6 100644
--- a/evs/manager/Android.mk
+++ b/evs/manager/Android.mk
@@ -8,6 +8,7 @@
     Enumerator.cpp \
     HalCamera.cpp \
     VirtualCamera.cpp \
+    HalDisplay.cpp
 
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/evs/manager/Enumerator.cpp b/evs/manager/Enumerator.cpp
index 7e53304..317d473 100644
--- a/evs/manager/Enumerator.cpp
+++ b/evs/manager/Enumerator.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "Enumerator.h"
+#include "HalDisplay.h"
 
 namespace android {
 namespace automotive {
@@ -138,28 +139,34 @@
     sp<IEvsDisplay> pActiveDisplay = mHwEnumerator->openDisplay();
     if (pActiveDisplay == nullptr) {
         ALOGE("EVS Display unavailable");
+
+        return nullptr;
     }
 
     // Remember (via weak pointer) who we think the most recently opened display is so that
     // we can proxy state requests from other callers to it.
-    mActiveDisplay = pActiveDisplay;
-    return pActiveDisplay;
+    // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
+    // wraps the IEvsDisplay object the driver returns.  We may want to remove this
+    // additional class when it is fixed properly.
+    sp<IEvsDisplay> pHalDisplay = new HalDisplay(pActiveDisplay);
+    mActiveDisplay = pHalDisplay;
+
+    return pHalDisplay;
 }
 
 
 Return<void> Enumerator::closeDisplay(const ::android::sp<IEvsDisplay>& display) {
     ALOGD("closeDisplay");
 
-    // Do we still have a display object we think should be active?
     sp<IEvsDisplay> pActiveDisplay = mActiveDisplay.promote();
 
     // Drop the active display
     if (display.get() != pActiveDisplay.get()) {
-        ALOGW("Ignoring call to closeDisplay with unrecognzied display object.");
-        ALOGI("Got %p while active display is %p.", display.get(), pActiveDisplay.get());
+        ALOGW("Ignoring call to closeDisplay with unrecognized display object.");
     } else {
         // Pass this request through to the hardware layer
-        mHwEnumerator->closeDisplay(display);
+        sp<HalDisplay> halDisplay = reinterpret_cast<HalDisplay *>(pActiveDisplay.get());
+        mHwEnumerator->closeDisplay(halDisplay->getHwDisplay());
         mActiveDisplay = nullptr;
     }
 
diff --git a/evs/manager/Enumerator.h b/evs/manager/Enumerator.h
index 4d2c769..4dd3483 100644
--- a/evs/manager/Enumerator.h
+++ b/evs/manager/Enumerator.h
@@ -50,7 +50,7 @@
 
 private:
     sp<IEvsEnumerator>          mHwEnumerator;  // Hardware enumerator
-    wp<IEvsDisplay>             mActiveDisplay; // Hardware display
+    wp<IEvsDisplay>             mActiveDisplay; // Display proxy object warpping hw display
     std::list<sp<HalCamera>>    mCameras;       // Camera proxy objects wrapping hw cameras
 };
 
diff --git a/evs/manager/HalDisplay.cpp b/evs/manager/HalDisplay.cpp
new file mode 100644
index 0000000..c3f72b2
--- /dev/null
+++ b/evs/manager/HalDisplay.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#include <log/log.h>
+#include "HalDisplay.h"
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_0 {
+namespace implementation {
+
+HalDisplay::HalDisplay(sp<IEvsDisplay>& display) :
+  mHwDisplay(display) {
+    // nothing to do.
+}
+
+HalDisplay::~HalDisplay() {
+    shutdown();
+}
+
+void HalDisplay::shutdown() {
+    // simply release a strong pointer to remote display object.
+    mHwDisplay = nullptr;
+}
+
+/**
+ * Returns a strong pointer to remote display object.
+ */
+sp<IEvsDisplay> HalDisplay::getHwDisplay() {
+    return mHwDisplay;
+}
+
+/**
+ * Gets basic display information from a hardware display object
+ * and returns.
+ */
+Return<void> HalDisplay::getDisplayInfo(getDisplayInfo_cb _hidl_cb) {
+    if (mHwDisplay) {
+        mHwDisplay->getDisplayInfo(_hidl_cb);
+    }
+
+    return Void();
+}
+
+/**
+ * Sets the display state as what the clients wants.
+ */
+Return<EvsResult> HalDisplay::setDisplayState(DisplayState state) {
+    if (mHwDisplay) {
+        return mHwDisplay->setDisplayState(state);
+    } else {
+        return EvsResult::UNDERLYING_SERVICE_ERROR;
+    }
+}
+
+/**
+ * Gets current display state from a hardware display object and return.
+ */
+Return<DisplayState> HalDisplay::getDisplayState() {
+    if (mHwDisplay) {
+        return mHwDisplay->getDisplayState();
+    } else {
+        return DisplayState::DEAD;
+    }
+}
+
+/**
+ * Returns a handle to a frame buffer associated with the display.
+ */
+Return<void> HalDisplay::getTargetBuffer(getTargetBuffer_cb _hidl_cb) {
+    if (mHwDisplay) {
+        mHwDisplay->getTargetBuffer(_hidl_cb);
+    }
+
+    return Void();
+}
+
+/**
+ * Notifies the display that the buffer is ready to be used.
+ */
+Return<EvsResult> HalDisplay::returnTargetBufferForDisplay(const BufferDesc& buffer) {
+    if (mHwDisplay) {
+        return mHwDisplay->returnTargetBufferForDisplay(buffer);
+    } else {
+        return EvsResult::OWNERSHIP_LOST;
+    }
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace evs
+} // namespace automotive
+} // namespace android
diff --git a/evs/manager/HalDisplay.h b/evs/manager/HalDisplay.h
new file mode 100644
index 0000000..539c9aa
--- /dev/null
+++ b/evs/manager/HalDisplay.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_0_DISPLAYPROXY_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_0_DISPLAYPROXY_H
+
+#include <android/hardware/automotive/evs/1.0/types.h>
+#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h>
+
+using namespace ::android::hardware::automotive::evs::V1_0;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::hidl_handle;
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_0 {
+namespace implementation {
+
+// TODO: This class has been defined to wrap the IEvsDisplay object the driver
+// returns because of b/129284474 and represents an EVS display to the client
+// application.  With a proper bug fix, we may remove this class and update the
+// manager directly to use the IEvsDisplay object the driver provides.
+class HalDisplay : public IEvsDisplay {
+public:
+    explicit HalDisplay(sp<IEvsDisplay>& display);
+    virtual ~HalDisplay() override;
+
+    inline void         shutdown();
+    sp<IEvsDisplay>     getHwDisplay();
+
+    // Methods from ::android::hardware::automotive::evs::V1_0::IEvsDisplay follow.
+    Return<void> getDisplayInfo(getDisplayInfo_cb _hidl_cb)  override;
+    Return<EvsResult> setDisplayState(DisplayState state)  override;
+    Return<DisplayState> getDisplayState()  override;
+    Return<void> getTargetBuffer(getTargetBuffer_cb _hidl_cb)  override;
+    Return<EvsResult> returnTargetBufferForDisplay(const BufferDesc& buffer)  override;
+
+private:
+    sp<IEvsDisplay>      mHwDisplay;     // The low level display interface that backs this proxy
+};
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif  // ANDROID_AUTOMOTIVE_EVS_V1_0_DISPLAYPROXY_H
diff --git a/evs/manager/VirtualCamera.cpp b/evs/manager/VirtualCamera.cpp
index af90f2c..293f4b4 100644
--- a/evs/manager/VirtualCamera.cpp
+++ b/evs/manager/VirtualCamera.cpp
@@ -45,7 +45,9 @@
         // Note that if we hit this case, no terminating frame will be sent to the client,
         // but they're probably already dead anyway.
         ALOGW("Virtual camera being shutdown while stream is running");
-        mStreamState = STOPPED;
+
+        // Tell the frame delivery pipeline we don't want any more frames
+        mStreamState = STOPPING;
 
         if (mFramesHeld.size() > 0) {
             ALOGW("VirtualCamera destructing with frames in flight.");
@@ -57,6 +59,9 @@
             }
             mFramesHeld.clear();
         }
+
+        // Give the underlying hardware camera the heads up that it might be time to stop
+        mHalCamera->clientStreamEnding();
     }
 
     // Drop our reference to our associated hardware camera
diff --git a/procfs-inspector/server/com.android.car.procfsinspector.rc b/procfs-inspector/server/com.android.car.procfsinspector.rc
index 7faaf69..f069ee1 100644
--- a/procfs-inspector/server/com.android.car.procfsinspector.rc
+++ b/procfs-inspector/server/com.android.car.procfsinspector.rc
@@ -2,6 +2,7 @@
     class core
     user nobody
     group readproc
+    disabled
 
-on boot && property:boot.car_service_created=1
+on property:boot.car_service_created=1
     start com.android.car.procfsinspector
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 76c4d37..8d37772 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -252,6 +252,12 @@
         android:description="@string/car_permission_desc_car_cluster_control" />
 
     <permission
+        android:name="android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_car_handle_usb_aoap_device"
+        android:description="@string/car_permission_desc_car_handle_usb_aoap_device" />
+
+    <permission
         android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"
         android:protectionLevel="system|signature"
         android:label="@string/car_permission_label_car_ux_restrictions_configuration"
@@ -263,6 +269,12 @@
         android:label="@string/car_permission_label_storage_monitoring"
         android:description="@string/car_permission_desc_storage_monitoring" />
 
+    <permission
+        android:name="android.car.permission.CAR_ENROLL_TRUST"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_enroll_trust"
+        android:description="@string/car_permission_desc_enroll_trust" />
+
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
@@ -283,6 +295,7 @@
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
 
     <application android:label="@string/app_title"
                  android:directBootAware="true"
@@ -297,6 +310,21 @@
             </intent-filter>
         </service>
         <service android:name=".PerUserCarService" android:exported="false" />
+
+        <service
+            android:name="com.android.car.trust.CarBleTrustAgent"
+            android:permission="android.permission.BIND_TRUST_AGENT"
+            android:singleUser="true">
+            <intent-filter>
+                <action android:name="android.service.trust.TrustAgentService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <!-- Warning: the meta data must be included if the service is direct boot aware.
+                If not included, the device will crash before boot completes. Rendering the
+                device unusable. -->
+            <meta-data android:name="android.service.trust.trustagent"
+                       android:resource="@xml/car_trust_agent"/>
+        </service>
         <activity android:name="com.android.car.pm.ActivityBlockingActivity"
                   android:excludeFromRecents="true"
                   android:theme="@android:style/Theme.Translucent.NoTitleBar"
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 48f346e..200c4c3 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -158,4 +158,16 @@
     <!-- UI mode for projection activity. See ProjectionOptions class for possible values. -->
     <integer name="config_projectionUiMode" translatable="false">0</integer>
 
+    <!-- service/characteristics uuid for adding new escrow token -->
+    <string name="enrollment_service_uuid" translatable="false">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="enrollment_handle_uuid" translatable="false">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="enrollment_token_uuid" translatable="false">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
+
+    <!-- service/characteristics uuid for unlocking a device -->
+    <string name="unlock_service_uuid" translatable="false">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="unlock_escrow_token_uuid" translatable="false">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="unlock_handle_uuid" translatable="false">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
+
+    <string name="token_handle_shared_preferences" translatable="false">com.android.car.trust.TOKEN_HANDLE</string>
+
 </resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index f74710c..325788b 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -99,6 +99,8 @@
     <string name="car_permission_desc_bind_instrument_cluster_rendering">Receive instrument cluster data</string>
     <string name="car_permission_label_car_ux_restrictions_configuration">UX Restrictions Configuration</string>
     <string name="car_permission_desc_car_ux_restrictions_configuration">Configure UX Restrictions</string>
+    <string name="car_permission_label_car_handle_usb_aoap_device">Communicate with USB device in AOAP mode</string>
+    <string name="car_permission_desc_car_handle_usb_aoap_device">Allows an app to communicate with a device in AOAP mode</string>
 
     <!-- Permission text: apps can handle input events [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_bind_input_service">Car Input Service</string>
@@ -251,6 +253,9 @@
     <!-- Permission text: apps read information of car's power state [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_car_power">Access car power state</string>
 
+    <string name="car_permission_label_enroll_trust">Enroll Trusted Device</string>
+    <string name="car_permission_desc_enroll_trust">Allow Trusted Device Enrollment</string>
+
     <!-- The package name of the media application that will be selected as the default [CHAR LIMIT=NONE] -->
     <string name="default_media_application" translatable="false">com.android.bluetooth</string>
 </resources>
diff --git a/service/res/xml/car_trust_agent.xml b/service/res/xml/car_trust_agent.xml
new file mode 100644
index 0000000..e53932d
--- /dev/null
+++ b/service/res/xml/car_trust_agent.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:priv-android="http://schemas.android.com/apk/prv/res/android"
+             android:settingsActivity=".MainActivity"
+             priv-android:unlockProfile="true" />
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index 6b2169a..aa52e73 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -811,7 +811,7 @@
                 mPropertyEventListener);
         // Get Current restrictions and handle them
         handleUxRestrictionsChanged(mUxRService.getCurrentUxRestrictions());
-        // Register for future changes to the UxRestrictions
+        // Register for future changes to the DrivingStateRestrictions
         mUxRService.registerUxRestrictionsChangeListener(mUxRListener);
         mUserServiceHelper.registerServiceCallback(mServiceCallback);
     }
diff --git a/service/src/com/android/car/CarLocalServices.java b/service/src/com/android/car/CarLocalServices.java
index 2c619af..fe3dc07 100644
--- a/service/src/com/android/car/CarLocalServices.java
+++ b/service/src/com/android/car/CarLocalServices.java
@@ -17,6 +17,7 @@
 package com.android.car;
 
 import android.util.ArrayMap;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -38,6 +39,7 @@
      */
     @SuppressWarnings("unchecked")
     public static <T> T getService(Class<T> type) {
+        Log.d("CarLocalServices", " getService " + type.getSimpleName());
         synchronized (sLocalServiceObjects) {
             return (T) sLocalServiceObjects.get(type);
         }
@@ -51,6 +53,7 @@
             if (sLocalServiceObjects.containsKey(type)) {
                 throw new IllegalStateException("Overriding service registration");
             }
+            Log.d("CarLocalServices", " Adding " + type.getSimpleName());
             sLocalServiceObjects.put(type, service);
         }
     }
@@ -60,6 +63,7 @@
      */
     @VisibleForTesting
     public static <T> void removeServiceForTest(Class<T> type) {
+        Log.d("CarLocalServices", " Removing " + type.getSimpleName());
         synchronized (sLocalServiceObjects) {
             sLocalServiceObjects.remove(type);
         }
@@ -69,6 +73,7 @@
      * Remove all registered services. Should be called when car service restarts.
      */
     public static void removeAllServices() {
+        Log.d("CarLocalServices", " removeAllServices");
         synchronized (sLocalServiceObjects) {
             sLocalServiceObjects.clear();
         }
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
index 47d3137..509ecdd 100644
--- a/service/src/com/android/car/CarService.java
+++ b/service/src/com/android/car/CarService.java
@@ -21,9 +21,7 @@
 import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.IHwBinder.DeathRecipient;
@@ -34,7 +32,6 @@
 import android.util.Log;
 
 import com.android.car.systeminterface.SystemInterface;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.RingBufferIndices;
 
@@ -95,11 +92,11 @@
                 mCanBusErrorNotifier,
                 mVehicleInterfaceName);
         mICarImpl.init();
-        SystemProperties.set("boot.car_service_created", "1");
 
         linkToDeath(mVehicle, mVehicleDeathRecipient);
 
         ServiceManager.addService("car_service", mICarImpl);
+        SystemProperties.set("boot.car_service_created", "1");
         super.onCreate();
     }
 
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index 0fc83dc..247cb6c 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.car;
 
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
 import android.annotation.Nullable;
@@ -24,6 +26,7 @@
 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.CarUxRestrictionsConfiguration;
+import android.car.drivingstate.CarUxRestrictionsManager;
 import android.car.drivingstate.ICarDrivingStateChangeListener;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
 import android.car.drivingstate.ICarUxRestrictionsManager;
@@ -95,7 +98,10 @@
     private final CarPropertyService mCarPropertyService;
     // List of clients listening to UX restriction events.
     private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>();
-    private CarUxRestrictionsConfiguration mCarUxRestrictionsConfiguration;
+    @VisibleForTesting
+    CarUxRestrictionsConfiguration mCarUxRestrictionsConfiguration;
+    @CarUxRestrictionsManager.UxRestrictionMode
+    private int mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
     private CarUxRestrictions mCurrentUxRestrictions;
     private float mCurrentMovingSpeed;
     // Flag to disable broadcasting UXR changes - for development purposes
@@ -359,6 +365,42 @@
     }
 
     /**
+     * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to
+     * be applied in the same driving state. Restrictions for each mode can be configured through
+     * {@link CarUxRestrictionsConfiguration}.
+     *
+     * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
+     *
+     * @param mode See values in {@link CarUxRestrictionsManager.UxRestrictionMode}.
+     * @return {@code true} if mode was successfully changed; {@code false} otherwise.
+     *
+     * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
+     * @see CarUxRestrictionsConfiguration.Builder
+     */
+    @Override
+    public synchronized boolean setRestrictionMode(
+            @CarUxRestrictionsManager.UxRestrictionMode int mode) {
+        if (mRestrictionMode == mode) {
+            return true;
+        }
+
+        addTransitionLog(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
+                "Restriction mode");
+        mRestrictionMode = mode;
+        logd("Set restriction mode to: " + CarUxRestrictionsManager.modeToString(mode));
+
+        handleDispatchUxRestrictions(
+                mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed());
+        return true;
+    }
+
+    @Override
+    @CarUxRestrictionsManager.UxRestrictionMode
+    public synchronized int getRestrictionMode() {
+        return mRestrictionMode;
+    }
+
+    /**
      * Writes configuration into the specified file.
      *
      * IO access on file is not thread safe. Caller should ensure threading protection.
@@ -592,8 +634,8 @@
             return;
         }
 
-        CarUxRestrictions uxRestrictions =
-                mCarUxRestrictionsConfiguration.getUxRestrictions(currentDrivingState, speed);
+        CarUxRestrictions uxRestrictions = mCarUxRestrictionsConfiguration.getUxRestrictions(
+                currentDrivingState, speed, mRestrictionMode);
 
         if (DBG) {
             Log.d(TAG, String.format("DO old->new: %b -> %b",
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 2df562e..35255ee 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -42,7 +42,7 @@
 import com.android.car.internal.FeatureConfiguration;
 import com.android.car.pm.CarPackageManagerService;
 import com.android.car.systeminterface.SystemInterface;
-import com.android.car.trust.CarTrustAgentEnrollmentService;
+import com.android.car.trust.CarTrustedDeviceService;
 import com.android.car.user.CarUserService;
 import com.android.car.vms.VmsBrokerService;
 import com.android.car.vms.VmsClientManager;
@@ -86,7 +86,7 @@
     private final CarDiagnosticService mCarDiagnosticService;
     private final CarStorageMonitoringService mCarStorageMonitoringService;
     private final CarConfigurationService mCarConfigurationService;
-    private final CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+    private final CarTrustedDeviceService mCarTrustedDeviceService;
     private final CarMediaService mCarMediaService;
     private final CarUserManagerHelper mUserManagerHelper;
     private final CarUserService mCarUserService;
@@ -156,10 +156,13 @@
                 new CarConfigurationService(serviceContext, new JsonReaderImpl());
         mCarLocationService = new CarLocationService(mContext, mCarPropertyService,
                 mUserManagerHelper);
-        mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(serviceContext);
+        mCarTrustedDeviceService = new CarTrustedDeviceService(serviceContext);
         mCarMediaService = new CarMediaService(serviceContext);
 
         CarLocalServices.addService(CarUserService.class, mCarUserService);
+        Log.d(TAG, "Adding CarTrustedDeviceService");
+        CarLocalServices.addService(CarTrustedDeviceService.class,
+                mCarTrustedDeviceService);
         CarLocalServices.addService(SystemInterface.class, mSystemInterface);
 
         // Be careful with order. Service depending on other service should be inited later.
@@ -187,7 +190,7 @@
         allServices.add(mVmsClientManager);
         allServices.add(mVmsSubscriberService);
         allServices.add(mVmsPublisherService);
-        allServices.add(mCarTrustAgentEnrollmentService);
+        allServices.add(mCarTrustedDeviceService);
         allServices.add(mCarMediaService);
         allServices.add(mCarLocationService);
         mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
@@ -301,7 +304,7 @@
                 return mCarConfigurationService;
             case Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE:
                 assertTrustAgentEnrollmentPermission(mContext);
-                return mCarTrustAgentEnrollmentService;
+                return mCarTrustedDeviceService.getCarTrustAgentEnrollmentService();
             case Car.CAR_MEDIA_SERVICE:
                 return mCarMediaService;
             default:
@@ -580,6 +583,7 @@
                         break;
                     }
                     mCarProjectionService.setUiMode(Integer.valueOf(args[1]));
+                    break;
                 default:
                     writer.println("Unknown command: \"" + arg + "\"");
                     dumpHelp(writer);
diff --git a/service/src/com/android/car/Utils.java b/service/src/com/android/car/Utils.java
index 284e610..70e1d7c 100644
--- a/service/src/com/android/car/Utils.java
+++ b/service/src/com/android/car/Utils.java
@@ -1,8 +1,25 @@
+/*
+ * 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 android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 
+import java.nio.ByteBuffer;
+
 /**
  * Some potentially useful static methods.
  */
@@ -17,7 +34,7 @@
     }
 
     static String getProfileName(int profile) {
-        switch(profile) {
+        switch (profile) {
             case BluetoothProfile.A2DP_SINK:
                 return "A2DP_SINK";
             case BluetoothProfile.HEADSET_CLIENT:
@@ -46,7 +63,6 @@
      * <p>
      * A specific service in CarService can choose to use a circular buffer of N records to keep
      * track of the last N transitions.
-     *
      */
     public static class TransitionLog {
         private String mServiceName; // name of the service or tag
@@ -78,4 +94,43 @@
                     : "") + " changed from " + mFromState + " to " + mToState;
         }
     }
+
+    /**
+     * Returns a byte buffer corresponding to the passed long argument.
+     *
+     * @param primitive data to convert format.
+     */
+    public static byte[] longToBytes(long primitive) {
+        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+        buffer.putLong(primitive);
+        return buffer.array();
+    }
+
+    /**
+     * Returns a byte buffer corresponding to the passed long argument.
+     *
+     * @param array data to convert format.
+     */
+    public static long bytesToLong(byte[] array) {
+        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+        buffer.put(array);
+        buffer.flip();
+        long value = buffer.getLong();
+        return value;
+    }
+
+    /**
+     * Returns a String in Hex format that is formed from the bytes in the byte array
+     * Useful for debugging
+     *
+     * @param array the byte array
+     * @return the Hex string version of the input byte array
+     */
+    public static String byteArrayToHexString(byte[] array) {
+        StringBuilder sb = new StringBuilder(array.length * 2);
+        for (byte b : array) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+    }
 }
diff --git a/service/src/com/android/car/trust/BleManager.java b/service/src/com/android/car/trust/BleManager.java
new file mode 100644
index 0000000..1d96f93
--- /dev/null
+++ b/service/src/com/android/car/trust/BleManager.java
@@ -0,0 +1,293 @@
+/*
+ * 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.trust;
+
+import static android.bluetooth.BluetoothProfile.GATT_SERVER;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.car.Utils;
+
+/**
+ * A generic class that manages BLE operations like start/stop advertising, notifying connects/
+ * disconnects and reading/writing values to GATT characteristics.
+ *
+ * TODO(b/123248433) This could move to a separate comms library.
+ */
+public abstract class BleManager {
+    private static final String TAG = BleManager.class.getSimpleName();
+
+    private static final int BLE_RETRY_LIMIT = 5;
+    private static final int BLE_RETRY_INTERVAL_MS = 1000;
+
+    private final Handler mHandler = new Handler();
+
+    private final Context mContext;
+    private BluetoothManager mBluetoothManager;
+    private BluetoothLeAdvertiser mAdvertiser;
+    private BluetoothGattServer mGattServer;
+    private int mAdvertiserStartCount;
+
+    BleManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Starts the GATT server with the given {@link BluetoothGattService} and begins
+     * advertising.
+     *
+     * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
+     * Therefore, several retries will be made to ensure advertising is started.
+     *
+     * @param service {@link BluetoothGattService} that will be discovered by clients
+     */
+    protected void startAdvertising(BluetoothGattService service,
+            AdvertiseCallback advertiseCallback) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "startAdvertising: " + service.getUuid().toString());
+        }
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+            Log.e(TAG, "System does not support BLE");
+            return;
+        }
+
+        // Only open one Gatt server.
+        if (mGattServer == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Opening a new GATT Server");
+            }
+            mBluetoothManager = (BluetoothManager) mContext.getSystemService(
+                    Context.BLUETOOTH_SERVICE);
+            mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
+
+            if (mGattServer == null) {
+                Log.e(TAG, "Gatt Server not created");
+                return;
+            }
+        }
+
+        mGattServer.clearServices();
+        mGattServer.addService(service);
+
+        AdvertiseSettings settings = new AdvertiseSettings.Builder()
+                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
+                .setConnectable(true)
+                .build();
+
+        AdvertiseData data = new AdvertiseData.Builder()
+                .setIncludeDeviceName(true)
+                .addServiceUuid(new ParcelUuid(service.getUuid()))
+                .build();
+
+        mAdvertiserStartCount = 0;
+        startAdvertisingInternally(settings, data, advertiseCallback);
+    }
+
+    private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
+            AdvertiseCallback advertiseCallback) {
+        if (BluetoothAdapter.getDefaultAdapter() != null) {
+            mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+        }
+
+        if (mAdvertiser != null) {
+            mAdvertiser.startAdvertising(settings, data, advertiseCallback);
+            mAdvertiserStartCount = 0;
+        } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) {
+            mHandler.postDelayed(
+                    () -> startAdvertisingInternally(settings, data, advertiseCallback),
+                    BLE_RETRY_INTERVAL_MS);
+            mAdvertiserStartCount += 1;
+        } else {
+            Log.e(TAG, "Cannot start BLE Advertisement.  BT Adapter: "
+                    + BluetoothAdapter.getDefaultAdapter() + " Advertise Retry count: "
+                    + mAdvertiserStartCount);
+        }
+    }
+
+    protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
+        if (mAdvertiser != null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "stopAdvertising: ");
+            }
+            mAdvertiser.stopAdvertising(advertiseCallback);
+        }
+    }
+
+    /**
+     * Notifies the characteristic change via {@link BluetoothGattServer}
+     */
+    protected void notifyCharacteristicChanged(BluetoothDevice device,
+            BluetoothGattCharacteristic characteristic, boolean confirm) {
+        if (mGattServer != null) {
+            mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
+        }
+    }
+
+    protected Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Cleans up the BLE GATT server state.
+     */
+    void cleanup() {
+        // Stops the advertiser and GATT server. This needs to be done to avoid leaks
+        if (mAdvertiser != null) {
+            mAdvertiser.cleanup();
+        }
+
+        if (mGattServer != null) {
+            mGattServer.clearServices();
+            try {
+                for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
+                    mGattServer.cancelConnection(d);
+                }
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "Error getting connected devices", e);
+            } finally {
+                stopGattServer();
+            }
+        }
+    }
+
+    /**
+     * Close the GATT Server
+     */
+    void stopGattServer() {
+        if (mGattServer == null) {
+            return;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "stopGattServer");
+        }
+        mGattServer.close();
+        mGattServer = null;
+    }
+
+    /**
+     * Triggered when a device (GATT client) connected.
+     *
+     * @param device Remote device that connected on BLE.
+     */
+    protected void onRemoteDeviceConnected(BluetoothDevice device) {
+    }
+
+    /**
+     * Triggered when a device (GATT client) disconnected.
+     *
+     * @param device Remote device that disconnected on BLE.
+     */
+    protected void onRemoteDeviceDisconnected(BluetoothDevice device) {
+    }
+
+    /**
+     * Triggered when this BleManager receives a write request from a remote
+     * device. Sub-classes should implement how to handle requests.
+     * <p>
+     *
+     * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int,
+     * BluetoothGattCharacteristic, boolean, boolean, int, byte[])
+     */
+    protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId,
+            BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+            responseNeeded, int offset, byte[] value);
+
+    /**
+     * Triggered when this BleManager receives a read request from a remote device.
+     * <p>
+     *
+     * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int,
+     * BluetoothGattCharacteristic)
+     */
+    protected abstract void onCharacteristicRead(BluetoothDevice device,
+            int requestId, int offset, BluetoothGattCharacteristic characteristic);
+
+    private final BluetoothGattServerCallback mGattServerCallback =
+            new BluetoothGattServerCallback() {
+                @Override
+                public void onConnectionStateChange(BluetoothDevice device, int status,
+                        int newState) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "BLE Connection State Change: " + newState);
+                    }
+                    switch (newState) {
+                        case BluetoothProfile.STATE_CONNECTED:
+                            onRemoteDeviceConnected(device);
+                            break;
+                        case BluetoothProfile.STATE_DISCONNECTED:
+                            onRemoteDeviceDisconnected(device);
+                            break;
+                        default:
+                            Log.w(TAG,
+                                    "Connection state not connecting or disconnecting; ignoring: "
+                                            + newState);
+                    }
+                }
+
+                @Override
+                public void onServiceAdded(int status, BluetoothGattService service) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG,
+                                "Service added status: " + status + " uuid: " + service.getUuid());
+                    }
+                }
+
+                @Override
+                public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+                        int offset, BluetoothGattCharacteristic characteristic) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
+                    }
+
+                    mGattServer.sendResponse(device, requestId,
+                            BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
+                    onCharacteristicRead(device, requestId, offset, characteristic);
+                }
+
+                @Override
+                public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+                        BluetoothGattCharacteristic characteristic, boolean preparedWrite,
+                        boolean responseNeeded, int offset, byte[] value) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid()
+                                + "value: " + Utils.byteArrayToHexString(value));
+                    }
+
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+                            offset, value);
+                    onCharacteristicWrite(device, requestId, characteristic,
+                            preparedWrite, responseNeeded, offset, value);
+                }
+            };
+}
diff --git a/service/src/com/android/car/trust/BleService.java b/service/src/com/android/car/trust/BleService.java
deleted file mode 100644
index 3ec1dca..0000000
--- a/service/src/com/android/car/trust/BleService.java
+++ /dev/null
@@ -1,226 +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.trust;
-
-import static android.bluetooth.BluetoothProfile.GATT_SERVER;
-
-import android.app.Service;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattServer;
-import android.bluetooth.BluetoothGattServerCallback;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.BluetoothLeAdvertiser;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.ParcelUuid;
-import android.util.Log;
-
-/**
- * A generic service to start a BLE
- * TODO(b/123248433) This could move to a separate comms library.
- */
-public abstract class BleService extends Service {
-    private static final String TAG = BleService.class.getSimpleName();
-
-    private static final int BLE_RETRY_LIMIT = 5;
-    private static final int BLE_RETRY_INTERVAL_MS = 1000;
-
-    private final Handler mHandler = new Handler();
-
-    private BluetoothManager mBluetoothManager;
-    private BluetoothLeAdvertiser mAdvertiser;
-    private BluetoothGattServer mGattServer;
-    private int mAdvertiserStartCount;
-
-    /**
-     * Starts the GATT server with the given {@link BluetoothGattService} and begins
-     * advertising.
-     *
-     * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
-     * Therefore, several retries will be made to ensure advertising is started.
-     *
-     * @param service {@link BluetoothGattService} that will be discovered by clients
-     */
-    protected void startAdvertising(BluetoothGattService service,
-            AdvertiseCallback advertiseCallback) {
-        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
-            Log.e(TAG, "System does not support BLE");
-            return;
-        }
-
-        // Only open one Gatt server.
-        if (mGattServer == null) {
-            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
-            mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
-
-            if (mGattServer == null) {
-                Log.e(TAG, "Gatt Server not created");
-                return;
-            }
-        }
-
-        mGattServer.clearServices();
-        mGattServer.addService(service);
-
-        AdvertiseSettings settings = new AdvertiseSettings.Builder()
-                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
-                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                .setConnectable(true)
-                .build();
-
-        AdvertiseData data = new AdvertiseData.Builder()
-                .setIncludeDeviceName(true)
-                .addServiceUuid(new ParcelUuid(service.getUuid()))
-                .build();
-
-        mAdvertiserStartCount = 0;
-        startAdvertisingInternally(settings, data, advertiseCallback);
-    }
-
-    private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
-            AdvertiseCallback advertiseCallback) {
-        mAdvertiserStartCount += 1;
-        mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
-        if (mAdvertiser == null && mAdvertiserStartCount < BLE_RETRY_LIMIT) {
-            mHandler.postDelayed(
-                    () -> startAdvertisingInternally(settings, data, advertiseCallback),
-                            BLE_RETRY_INTERVAL_MS);
-        } else {
-            mHandler.removeCallbacks(null);
-            mAdvertiser.startAdvertising(settings, data, advertiseCallback);
-            mAdvertiserStartCount = 0;
-        }
-    }
-
-    protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
-        if (mAdvertiser != null) {
-            mAdvertiser.stopAdvertising(advertiseCallback);
-        }
-    }
-
-    /**
-     * Notifies the characteristic change via {@link BluetoothGattServer}
-     */
-    protected void notifyCharacteristicChanged(BluetoothDevice device,
-            BluetoothGattCharacteristic characteristic, boolean confirm) {
-        if (mGattServer != null) {
-            mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        // Stops the advertiser and GATT server. This needs to be done to avoid leaks
-        if (mAdvertiser != null) {
-            mAdvertiser.cleanup();
-        }
-
-        if (mGattServer != null) {
-            mGattServer.clearServices();
-            try {
-                for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
-                    mGattServer.cancelConnection(d);
-                }
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "Error getting connected devices", e);
-            } finally {
-                mGattServer.close();
-            }
-        }
-        super.onDestroy();
-    }
-
-    // Delegate to subclass
-    protected void onAdvertiseStartSuccess() { }
-    protected void onAdvertiseStartFailure(int errorCode) { }
-    protected void onAdvertiseDeviceConnected(BluetoothDevice device) { }
-    protected void onAdvertiseDeviceDisconnected(BluetoothDevice device) { }
-
-    /**
-     * Triggered when this BleService receives a write request from a remote
-     * device. Sub-classes should implement how to handle requests.
-     */
-    protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId,
-            BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
-            responseNeeded, int offset, byte[] value);
-
-    /**
-     * Triggered when this BleService receives a read request from a remote device.
-     */
-    protected abstract void onCharacteristicRead(BluetoothDevice device,
-            int requestId, int offset, BluetoothGattCharacteristic characteristic);
-
-    private final BluetoothGattServerCallback mGattServerCallback =
-            new BluetoothGattServerCallback() {
-        @Override
-        public void onConnectionStateChange(BluetoothDevice device,
-                final int status, final int newState) {
-            switch (newState) {
-                case BluetoothProfile.STATE_CONNECTED:
-                    onAdvertiseDeviceConnected(device);
-                    break;
-                case BluetoothProfile.STATE_DISCONNECTED:
-                    onAdvertiseDeviceDisconnected(device);
-                    break;
-                default:
-                    Log.w(TAG, "Connection state not connecting or disconnecting; ignoring: "
-                            + newState);
-            }
-        }
-
-        @Override
-        public void onServiceAdded(final int status, BluetoothGattService service) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Service added status: " + status + " uuid: " + service.getUuid());
-            }
-        }
-
-        @Override
-        public void onCharacteristicReadRequest(BluetoothDevice device,
-                int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
-            }
-
-            mGattServer.sendResponse(device, requestId,
-                    BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
-            onCharacteristicRead(device, requestId, offset, characteristic);
-        }
-
-        @Override
-        public void onCharacteristicWriteRequest(final BluetoothDevice device, int requestId,
-                BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
-                responseNeeded, int offset, byte[] value) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid());
-            }
-
-            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
-                    offset, value);
-            onCharacteristicWrite(device, requestId, characteristic,
-                    preparedWrite, responseNeeded, offset, value);
-        }
-    };
-}
diff --git a/service/src/com/android/car/trust/CarBleTrustAgent.java b/service/src/com/android/car/trust/CarBleTrustAgent.java
new file mode 100644
index 0000000..9ea1d42
--- /dev/null
+++ b/service/src/com/android/car/trust/CarBleTrustAgent.java
@@ -0,0 +1,298 @@
+/*
+ * 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.trust;
+
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.trust.TrustAgentService;
+import android.util.Log;
+
+import com.android.car.CarLocalServices;
+import com.android.car.Utils;
+import com.android.car.trust.CarTrustAgentEnrollmentService.CarTrustAgentEnrollmentRequestDelegate;
+import com.android.car.trust.CarTrustAgentUnlockService.CarTrustAgentUnlockDelegate;
+
+/**
+ * A BluetoothLE (BLE) based {@link TrustAgentService} that uses the escrow token unlock APIs.
+ * <p>
+ * This trust agent runs during direct boot and interacts with {@link CarTrustedDeviceService}
+ * to listen for remote devices to trigger an unlock.
+ * <p>
+ * The system {@link com.android.server.trust.TrustManagerService} binds to this agent and uses
+ * the data it receives from this agent to authorize a user in lieu of the PIN/Pattern/Password
+ * credentials.
+ */
+public class CarBleTrustAgent extends TrustAgentService {
+    private static final String TAG = CarBleTrustAgent.class.getSimpleName();
+    private boolean mIsDeviceLocked;
+    private CarTrustedDeviceService mCarTrustedDeviceService;
+    private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+    private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
+
+    @Override
+    public void onCreate() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onCreate()");
+        }
+        super.onCreate();
+        // Registering for more granular BLE specific state changes as against Bluetooth state
+        // changes, helps with reducing latency in getting notified.
+        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+        registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
+
+        // TODO(b/129144535) handle scenarios where CarService crashed.  Maybe retrieve this
+        //  every time we need instead of caching.
+        mCarTrustedDeviceService = CarLocalServices.getService(CarTrustedDeviceService.class);
+        if (mCarTrustedDeviceService == null) {
+            Log.e(TAG, "Cannot retrieve the Trusted device Service");
+            return;
+        }
+        mCarTrustAgentEnrollmentService =
+                mCarTrustedDeviceService.getCarTrustAgentEnrollmentService();
+        setEnrollmentRequestDelegate();
+        mCarTrustAgentUnlockService = mCarTrustedDeviceService.getCarTrustAgentUnlockService();
+        setUnlockRequestDelegate();
+        setManagingTrust(true);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Car Trust agent shutting down");
+        }
+        super.onDestroy();
+        mCarTrustAgentEnrollmentService = null;
+        if (mBluetoothBroadcastReceiver != null) {
+            unregisterReceiver(mBluetoothBroadcastReceiver);
+        }
+    }
+
+    // Overriding TrustAgentService methods
+    @Override
+    public void onDeviceLocked() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onDeviceLocked Current user: " + ActivityManager.getCurrentUser());
+        }
+        super.onDeviceLocked();
+        mIsDeviceLocked = true;
+        if (BluetoothAdapter.getDefaultAdapter().getState() == BluetoothAdapter.STATE_OFF) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Not starting Unlock Advertising yet, since Bluetooth Adapter is off");
+            }
+            return;
+        }
+        if (mCarTrustAgentUnlockService != null) {
+            mCarTrustAgentUnlockService.startUnlockAdvertising();
+        }
+    }
+
+    @Override
+    public void onDeviceUnlocked() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onDeviceUnlocked Current user: " + ActivityManager.getCurrentUser());
+        }
+        super.onDeviceUnlocked();
+        mIsDeviceLocked = false;
+        if (BluetoothAdapter.getDefaultAdapter().getState() == BluetoothAdapter.STATE_OFF) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Not stopping Unlock Advertising, since Bluetooth Adapter is off");
+            }
+            return;
+        }
+        if (mCarTrustAgentUnlockService != null) {
+            mCarTrustAgentUnlockService.stopUnlockAdvertising();
+
+        }
+        revokeTrust();
+    }
+
+    @Override
+    public void onEscrowTokenRemoved(long handle, boolean successful) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenRemoved handle: " + Long.toHexString(handle));
+        }
+        if (mCarTrustAgentEnrollmentService == null) {
+            return;
+        }
+        if (successful) {
+            mCarTrustAgentEnrollmentService.onEscrowTokenRemoved(handle,
+                    ActivityManager.getCurrentUser());
+        }
+    }
+
+    @Override
+    public void onEscrowTokenStateReceived(long handle, int tokenState) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenStateReceived: " + Long.toHexString(handle) + " state: "
+                    + tokenState);
+        }
+        if (mCarTrustAgentEnrollmentService == null) {
+            return;
+        }
+        mCarTrustAgentEnrollmentService.onEscrowTokenActiveStateChanged(handle,
+                tokenState == TOKEN_STATE_ACTIVE);
+    }
+
+    @Override
+    public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenAdded handle: " + Long.toHexString(handle) + "token: "
+                    + Utils.byteArrayToHexString(token));
+        }
+        if (mCarTrustAgentEnrollmentService == null) {
+            return;
+        }
+        mCarTrustAgentEnrollmentService.onEscrowTokenAdded(token, handle, user.getIdentifier());
+    }
+
+    private void setEnrollmentRequestDelegate() {
+        if (mCarTrustAgentEnrollmentService == null) {
+            return;
+        }
+        mCarTrustAgentEnrollmentService.setEnrollmentRequestDelegate(mEnrollDelegate);
+    }
+
+    private void setUnlockRequestDelegate() {
+        if (mCarTrustAgentUnlockService == null) {
+            return;
+        }
+        mCarTrustAgentUnlockService.setUnlockRequestDelegate(mUnlockDelegate);
+    }
+
+    private void unlockUserInternally(int uid, byte[] token, long handle) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "About to unlock user: " + uid);
+            UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+            if (um.isUserUnlocked(UserHandle.of(uid))) {
+                Log.d(TAG, "User currently unlocked");
+            } else {
+                Log.d(TAG, "User currently locked");
+            }
+        }
+        unlockUserWithToken(handle, token, UserHandle.of(uid));
+        grantTrust("Granting trust from escrow token",
+                0, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
+    }
+
+    private final BroadcastReceiver mBluetoothBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction() != null && BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
+                    intent.getAction())) {
+                onBluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
+            }
+        }
+    };
+
+    private void onBluetoothStateChanged(int state) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onBluetoothStateChanged: " + state);
+        }
+        if (!mIsDeviceLocked) {
+            return;
+        }
+        switch (state) {
+            case BluetoothAdapter.STATE_BLE_ON:
+                if (mCarTrustAgentUnlockService != null) {
+                    mCarTrustAgentUnlockService.startUnlockAdvertising();
+                }
+                break;
+            case BluetoothAdapter.STATE_OFF:
+                Log.e(TAG, "Bluetooth Adapter Off in lock screen");
+                if (mCarTrustedDeviceService != null) {
+                    mCarTrustedDeviceService.cleanupBleService();
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    // Implementing Delegates for Enrollment and Unlock.  The CarBleTrustAgent acts as the interface
+    // between the Trust Agent framework and the Car Service.  The Car service handles communicating
+    // with the peer device part and the framework handles the actual authentication.  The
+    // CarBleTrustAgent abstracts these 2 pieces from each other.
+    /**
+     * Implementation of the {@link CarTrustAgentEnrollmentRequestDelegate}
+     */
+    private final CarTrustAgentEnrollmentRequestDelegate mEnrollDelegate =
+            new CarTrustAgentEnrollmentRequestDelegate() {
+                @Override
+                public void addEscrowToken(byte[] token, int uid) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG,
+                                "addEscrowToken. uid: " + uid + " token: "
+                                        + Utils.byteArrayToHexString(
+                                        token));
+                    }
+                    CarBleTrustAgent.this.addEscrowToken(token, UserHandle.of(uid));
+                }
+
+                @Override
+                public void removeEscrowToken(long handle, int uid) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG,
+                                "removeEscrowToken. uid: " + ActivityManager.getCurrentUser()
+                                        + " handle: " + handle);
+                    }
+                    CarBleTrustAgent.this.removeEscrowToken(handle,
+                            UserHandle.of(uid));
+                }
+
+                @Override
+                public void isEscrowTokenActive(long handle, int uid) {
+                    CarBleTrustAgent.this.isEscrowTokenActive(handle, UserHandle.of(uid));
+                }
+            };
+
+    /**
+     * Implementation of the {@link CarTrustAgentUnlockDelegate}
+     */
+    private final CarTrustAgentUnlockDelegate mUnlockDelegate = new CarTrustAgentUnlockDelegate() {
+        /**
+         * Pass the user and token credentials to authenticate with the LockSettingsService.
+         *
+         * @param user   user being authorized
+         * @param token  escrow token for the user
+         * @param handle the handle corresponding to the escrow token
+         */
+        @Override
+        public void onUnlockDataReceived(int user, byte[] token, long handle) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onUnlockDataReceived:" + user + " token: " + Long.toHexString(
+                        Utils.bytesToLong(token)) + " handle: " + Long.toHexString(handle));
+            }
+            if (ActivityManager.getCurrentUser() != user) {
+                // Current behavior is to only authenticate the user we have booted into.
+                // TODO(b/129029418) Make identification & Auth vs Auth-only a
+                // configurable option
+                Log.e(TAG, "Expected User: " + ActivityManager.getCurrentUser()
+                        + " Presented User: " + user);
+                return;
+            } else {
+                unlockUserInternally(user, token, handle);
+            }
+
+        }
+    };
+}
diff --git a/service/src/com/android/car/trust/CarTrustAgentBleManager.java b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
new file mode 100644
index 0000000..9b3db66
--- /dev/null
+++ b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
@@ -0,0 +1,298 @@
+/*
+ * 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.trust;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseSettings;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.car.CarLocalServices;
+import com.android.car.R;
+import com.android.car.Utils;
+
+import java.util.UUID;
+
+/**
+ * A BLE Service that is used for communicating with the trusted peer device.  This extends from a
+ * more generic {@link BLEManager} and has more context on the BLE requirements for the Trusted
+ * device feature. It has knowledge on the GATT services and characteristics that are specific to
+ * the Trusted Device feature.
+ */
+class CarTrustAgentBleManager extends BleManager {
+    private static final String TAG = "CarTrustBLEManager";
+    private static final String CONFIRMATION_SIGNAL = "True";
+    private CarTrustedDeviceService mCarTrustedDeviceService;
+    private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+    private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
+
+    // Enrollment Service and Characteristic UUIDs
+    private UUID mEnrollmentServiceUuid;
+    private UUID mEnrollmentEscrowTokenUuid;
+    private UUID mEnrollmentTokenHandleUuid;
+    private BluetoothGattService mEnrollmentGattService;
+
+    // Unlock Service and Characteristic UUIDs
+    private UUID mUnlockServiceUuid;
+    private UUID mUnlockEscrowTokenUuid;
+    private UUID mUnlockTokenHandleUuid;
+    private BluetoothGattService mUnlockGattService;
+
+    CarTrustAgentBleManager(Context context) {
+        super(context);
+    }
+
+    // Overriding some of the {@link BLEManager} methods to be specific for Trusted Device feature.
+    @Override
+    public void onRemoteDeviceConnected(BluetoothDevice device) {
+        if (getTrustedDeviceService() != null) {
+            getTrustedDeviceService().onRemoteDeviceConnected(device);
+        }
+    }
+
+    @Override
+    public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+        if (getTrustedDeviceService() != null) {
+            getTrustedDeviceService().onRemoteDeviceDisconnected(device);
+        }
+    }
+
+    @Override
+    public void onCharacteristicWrite(BluetoothDevice device, int requestId,
+            BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+            responseNeeded, int offset, byte[] value) {
+        UUID uuid = characteristic.getUuid();
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onCharacteristicWrite received uuid: " + uuid);
+        }
+        if (uuid.equals(mEnrollmentEscrowTokenUuid)) {
+            if (getEnrollmentService() != null) {
+                getEnrollmentService().onEnrollmentDataReceived(value);
+            }
+        } else if (uuid.equals(mUnlockEscrowTokenUuid)) {
+            if (getUnlockService() != null) {
+                getUnlockService().onUnlockTokenReceived(value);
+            }
+        } else if (uuid.equals(mUnlockTokenHandleUuid)) {
+            if (getUnlockService() != null) {
+                getUnlockService().onUnlockHandleReceived(value);
+            }
+        }
+    }
+
+    @Override
+    public void onCharacteristicRead(BluetoothDevice device,
+            int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
+        // Ignored read requests.
+    }
+
+    @Nullable
+    private CarTrustedDeviceService getTrustedDeviceService() {
+        if (mCarTrustedDeviceService == null) {
+            mCarTrustedDeviceService = CarLocalServices.getService(
+                    CarTrustedDeviceService.class);
+        }
+        return mCarTrustedDeviceService;
+    }
+
+    @Nullable
+    private CarTrustAgentEnrollmentService getEnrollmentService() {
+        if (mCarTrustAgentEnrollmentService != null) {
+            return mCarTrustAgentEnrollmentService;
+        }
+
+        if (getTrustedDeviceService() != null) {
+            mCarTrustAgentEnrollmentService =
+                    getTrustedDeviceService().getCarTrustAgentEnrollmentService();
+        }
+        return mCarTrustAgentEnrollmentService;
+    }
+
+    @Nullable
+    private CarTrustAgentUnlockService getUnlockService() {
+        if (mCarTrustAgentUnlockService != null) {
+            return mCarTrustAgentUnlockService;
+        }
+
+        if (getTrustedDeviceService() != null) {
+            mCarTrustAgentUnlockService =
+                    getTrustedDeviceService().getCarTrustAgentUnlockService();
+        }
+        return mCarTrustAgentUnlockService;
+    }
+
+    /**
+     * Setup the BLE GATT server for Enrollment. The GATT server for Enrollment comprises of
+     * one GATT Service and 2 characteristics - one for the escrow token to be generated and sent
+     * from the phone and the other for the handle generated and sent by the Head unit.
+     */
+    void setupEnrollmentBleServer() {
+        mEnrollmentServiceUuid = UUID.fromString(
+                getContext().getString(R.string.enrollment_service_uuid));
+        mEnrollmentEscrowTokenUuid = UUID.fromString(
+                getContext().getString(R.string.enrollment_token_uuid));
+        mEnrollmentTokenHandleUuid = UUID.fromString(
+                getContext().getString(R.string.enrollment_handle_uuid));
+
+        mEnrollmentGattService = new BluetoothGattService(mEnrollmentServiceUuid,
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        // Characteristic to describe the escrow token being used for unlock
+        BluetoothGattCharacteristic tokenCharacteristic = new BluetoothGattCharacteristic(
+                mEnrollmentEscrowTokenUuid,
+                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        // Characteristic to describe the handle being used for this escrow token
+        BluetoothGattCharacteristic handleCharacteristic = new BluetoothGattCharacteristic(
+                mEnrollmentTokenHandleUuid,
+                BluetoothGattCharacteristic.PROPERTY_NOTIFY,
+                BluetoothGattCharacteristic.PERMISSION_READ);
+
+        mEnrollmentGattService.addCharacteristic(tokenCharacteristic);
+        mEnrollmentGattService.addCharacteristic(handleCharacteristic);
+    }
+
+    /**
+     * Setup the BLE GATT server for Unlocking the Head unit.  The GATT server for this phase also
+     * comprises of 1 Service and 2 characteristics.  However both the token and the handle are
+     * sent ftrom the phone to the head unit.
+     */
+    void setupUnlockBleServer() {
+        mUnlockServiceUuid = UUID.fromString(
+                getContext().getString(R.string.unlock_service_uuid));
+        mUnlockEscrowTokenUuid = UUID.fromString(
+                getContext().getString(R.string.unlock_escrow_token_uuid));
+        mUnlockTokenHandleUuid = UUID.fromString(
+                getContext().getString(R.string.unlock_handle_uuid));
+
+        mUnlockGattService = new BluetoothGattService(mUnlockServiceUuid,
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        // Characteristic to describe the escrow token being used for unlock
+        BluetoothGattCharacteristic tokenCharacteristic = new BluetoothGattCharacteristic(
+                mUnlockEscrowTokenUuid,
+                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        // Characteristic to describe the handle being used for this escrow token
+        BluetoothGattCharacteristic handleCharacteristic = new BluetoothGattCharacteristic(
+                mUnlockTokenHandleUuid,
+                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        mUnlockGattService.addCharacteristic(tokenCharacteristic);
+        mUnlockGattService.addCharacteristic(handleCharacteristic);
+    }
+
+    void startEnrollmentAdvertising() {
+        startAdvertising(mEnrollmentGattService, mEnrollmentAdvertisingCallback);
+    }
+
+    void stopEnrollmentAdvertising() {
+        stopAdvertising(mEnrollmentAdvertisingCallback);
+    }
+
+    void startUnlockAdvertising() {
+        startAdvertising(mUnlockGattService, mUnlockAdvertisingCallback);
+    }
+
+    void stopUnlockAdvertising() {
+        stopAdvertising(mUnlockAdvertisingCallback);
+    }
+
+    void disconnectRemoteDevice(BluetoothDevice device) {
+        // TODO(b/129029421) - is closing the GATT Server the right thing to do here?
+    }
+
+    /**
+     * Sends the handle corresponding to the escrow token that was generated by the phone to the
+     * phone.
+     *
+     * @param device the BLE peer device to send the handle to.
+     * @param handle the handle corresponding to the escrow token
+     */
+    void sendEnrollmentHandle(BluetoothDevice device, long handle) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "sendEnrollmentHandle: " + Long.toHexString(handle) + " "
+                    + device.getAddress());
+        }
+        BluetoothGattCharacteristic enrollmentHandle = mEnrollmentGattService.getCharacteristic(
+                mEnrollmentTokenHandleUuid);
+        enrollmentHandle.setValue(Utils.longToBytes(handle));
+        notifyCharacteristicChanged(device, enrollmentHandle, false);
+    }
+
+    /**
+     * Sends the pairing code confirmation signal to the phone
+     *
+     * @param device the BLE peer device to send the signal to.
+     */
+    void sendPairingCodeConfirmation(BluetoothDevice device) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "sendPairingCodeConfirmation: " + device.getAddress());
+        }
+        BluetoothGattCharacteristic confirmation = mEnrollmentGattService.getCharacteristic(
+                mEnrollmentTokenHandleUuid);
+        confirmation.setValue(CONFIRMATION_SIGNAL.getBytes());
+        notifyCharacteristicChanged(device, confirmation, false);
+    }
+
+
+    private final AdvertiseCallback mEnrollmentAdvertisingCallback = new AdvertiseCallback() {
+        @Override
+        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+            super.onStartSuccess(settingsInEffect);
+            if (getEnrollmentService() != null) {
+                getEnrollmentService().onEnrollmentAdvertiseStartSuccess();
+            }
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Successfully started advertising service");
+            }
+        }
+
+        @Override
+        public void onStartFailure(int errorCode) {
+            Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
+
+            super.onStartFailure(errorCode);
+            if (getEnrollmentService() != null) {
+                getEnrollmentService().onEnrollmentAdvertiseStartFailure(errorCode);
+            }
+        }
+    };
+
+    private final AdvertiseCallback mUnlockAdvertisingCallback = new AdvertiseCallback() {
+        @Override
+        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+            super.onStartSuccess(settingsInEffect);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unlock Advertising onStartSuccess");
+            }
+        }
+
+        @Override
+        public void onStartFailure(int errorCode) {
+            Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
+            super.onStartFailure(errorCode);
+        }
+    };
+}
diff --git a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
index cd421e7..230c3af 100644
--- a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
+++ b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
@@ -17,86 +17,165 @@
 package com.android.car.trust;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.bluetooth.BluetoothDevice;
 import android.car.trust.ICarTrustAgentBleCallback;
 import android.car.trust.ICarTrustAgentEnrollment;
 import android.car.trust.ICarTrustAgentEnrollmentCallback;
-import android.content.Context;
+import android.content.SharedPreferences;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.car.CarServiceBase;
+import com.android.car.Utils;
+import com.android.internal.annotations.GuardedBy;
 
 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;
 
 /**
- * A service that enables enrolling a phone as a trusted device for authenticating a user on the
- * IHU.  This implements the APIs that an enrollment app can call to conduct an enrollment.
+ * A service that is part of the CarTrustedDeviceService that is responsible for allowing a
+ * phone to enroll as a trusted device.  The enrolled phone can then be used for authenticating a
+ * user on the HU.  This implements the {@link android.car.trust.CarTrustAgentEnrollmentManager}
+ * APIs that an app like Car Settings can call to conduct an enrollment.
  */
-public class CarTrustAgentEnrollmentService extends ICarTrustAgentEnrollment.Stub implements
-        CarServiceBase {
+public class CarTrustAgentEnrollmentService extends ICarTrustAgentEnrollment.Stub {
     private static final String TAG = "CarTrustAgentEnroll";
-    private final Context mContext;
+    private static final String FAKE_AUTH_STRING = "000000";
+    private final CarTrustedDeviceService mTrustedDeviceService;
     // List of clients listening to Enrollment state change events.
     private final List<EnrollmentStateClient> mEnrollmentStateClients = new ArrayList<>();
-    // List of clients listening to BLE state change events.
+    // List of clients listening to BLE state changes events during enrollment.
     private final List<BleStateChangeClient> mBleStateChangeClients = new ArrayList<>();
+    private final CarTrustAgentBleManager mCarTrustAgentBleManager;
+    private CarTrustAgentEnrollmentRequestDelegate mEnrollmentDelegate;
+    private Object mRemoteDeviceLock = new Object();
+    @GuardedBy("mRemoteDeviceLock")
+    private BluetoothDevice mRemoteEnrollmentDevice;
+    @GuardedBy("this")
+    private boolean mEnrollmentHandshakeAccepted;
+    private final Map<Long, Boolean> mTokenActiveState = new HashMap<>();
 
-    public CarTrustAgentEnrollmentService(Context context) {
-        mContext = context;
+    public CarTrustAgentEnrollmentService(CarTrustedDeviceService service,
+            CarTrustAgentBleManager bleService) {
+        mTrustedDeviceService = service;
+        mCarTrustAgentBleManager = bleService;
     }
 
-    @Override
     public synchronized void init() {
+        mCarTrustAgentBleManager.setupEnrollmentBleServer();
     }
 
-    @Override
     public synchronized void release() {
         for (EnrollmentStateClient client : mEnrollmentStateClients) {
             client.mListenerBinder.unlinkToDeath(client, 0);
         }
+        for (BleStateChangeClient client : mBleStateChangeClients) {
+            client.mListenerBinder.unlinkToDeath(client, 0);
+        }
         mEnrollmentStateClients.clear();
+        setEnrollmentHandshakeAccepted(false);
     }
 
+    // Implementing the ICarTrustAgentEnrollment interface
 
-    // Binder methods
-    // TODO(b/120911995) The methods don't do anything yet.  The implementation will be checked in
-    // a follow up CL.
+    /**
+     * Begin BLE advertisement for Enrollment. This should be called from an app that conducts
+     * the enrollment of the trusted device.
+     */
     @Override
     public void startEnrollmentAdvertising() {
+        // Stop any current broadcasts
+        mTrustedDeviceService.getCarTrustAgentUnlockService().stopUnlockAdvertising();
+        stopEnrollmentAdvertising();
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "startEnrollmentAdvertising");
+        }
+        mCarTrustAgentBleManager.startEnrollmentAdvertising();
     }
 
+    /**
+     * Stop BLE advertisement for Enrollment
+     */
     @Override
     public void stopEnrollmentAdvertising() {
+        mCarTrustAgentBleManager.stopEnrollmentAdvertising();
     }
 
     @Override
     public void initiateEnrollmentHandshake(BluetoothDevice device) {
+        // TODO(b/129029320) - this is not needed since the IHU plays the server
+        // role and the secure handshake is initiated by the client.
     }
 
+    /**
+     * Called by the client to notify that the user has accepted a pairing code or any out-of-band
+     * confirmation, and send confirmation signals to remote bluetooth device.
+     *
+     * @param device the remote Bluetooth device that will receive the signal.
+     */
     @Override
-    public void enrollmentHandshakeAccepted() {
+    public void enrollmentHandshakeAccepted(BluetoothDevice device) {
+        mCarTrustAgentBleManager.sendPairingCodeConfirmation(device);
+        setEnrollmentHandshakeAccepted(true);
     }
 
+    /**
+     * Terminate the Enrollment process.  To be called when an error is encountered during
+     * enrollment.  For example - user pressed cancel on pairing code confirmation or user
+     * navigated away from the app before completing enrollment.
+     */
     @Override
     public void terminateEnrollmentHandshake() {
+        setEnrollmentHandshakeAccepted(false);
+        // Disconnect from BLE
+        mCarTrustAgentBleManager.disconnectRemoteDevice(mRemoteEnrollmentDevice);
     }
 
+    /**
+     * Returns if there is an active token for the given user and handle.
+     *
+     * @param handle handle corresponding to the escrow token
+     * @param uid    user id
+     * @return True if the escrow token is active, false if not
+     */
     @Override
     public boolean isEscrowTokenActive(long handle, int uid) {
+        if (mTokenActiveState.get(handle) != null) {
+            return mTokenActiveState.get(handle);
+        }
         return false;
     }
 
     @Override
-    public void revokeTrust(long handle) {
+    public void removeEscrowToken(long handle, int uid) {
+        mEnrollmentDelegate.removeEscrowToken(handle, uid);
     }
 
+    /**
+     * Get the Handles corresponding to the token for the current user.  The client can use this
+     * to list the trusted devices for the user.  This means that the client should maintain a map
+     * of the handles:Bluetooth device names.
+     *
+     * @param uid user id
+     * @return array of handles for the user.
+     */
     @Override
     public long[] getEnrollmentHandlesForUser(int uid) {
-        return new long[0];
+        Set<String> handlesSet = mTrustedDeviceService.getSharedPrefs().getStringSet(
+                String.valueOf(uid),
+                new HashSet<>());
+        long[] handles = new long[handlesSet.size()];
+        int i = 0;
+        for (String handle : handlesSet) {
+            handles[i++] = Long.valueOf(handle);
+        }
+        return handles;
     }
 
     /**
@@ -125,6 +204,146 @@
         }
     }
 
+    void onEscrowTokenAdded(byte[] token, long handle, int uid) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenAdded handle:" + handle + " uid:" + uid);
+        }
+        mTrustedDeviceService.getSharedPrefs().edit().putInt(String.valueOf(handle), uid).apply();
+        Set<String> handles = mTrustedDeviceService.getSharedPrefs().getStringSet(
+                String.valueOf(uid), new HashSet<>());
+        handles.add(String.valueOf(handle));
+        mTrustedDeviceService.getSharedPrefs().edit().putStringSet(String.valueOf(uid),
+                handles).apply();
+
+        if (mRemoteEnrollmentDevice == null) {
+            Log.e(TAG, "onEscrowTokenAdded() but no remote device connected!");
+            removeEscrowToken(handle, uid);
+            return;
+        }
+        for (EnrollmentStateClient client : mEnrollmentStateClients) {
+            try {
+                client.mListener.onEscrowTokenAdded(handle);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onEscrowTokenAdded dispatch failed", e);
+            }
+        }
+    }
+
+    void onEscrowTokenRemoved(long handle, int uid) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenRemoved handle:" + handle + " uid:" + uid);
+        }
+        for (EnrollmentStateClient client : mEnrollmentStateClients) {
+            try {
+                client.mListener.onEscrowTokenRemoved(handle);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onEscrowTokenAdded dispatch failed", e);
+            }
+        }
+        SharedPreferences.Editor editor = mTrustedDeviceService.getSharedPrefs().edit();
+        editor.remove(String.valueOf(handle));
+        Set<String> handles = mTrustedDeviceService.getSharedPrefs().getStringSet(
+                String.valueOf(uid), new HashSet<>());
+        handles.remove(String.valueOf(handle));
+        editor.putStringSet(String.valueOf(uid), handles);
+        editor.apply();
+    }
+
+    void onEscrowTokenActiveStateChanged(long handle, boolean isTokenActive) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenActiveStateChanged: " + Long.toHexString(handle));
+        }
+        mTokenActiveState.put(handle, isTokenActive);
+        dispatchEscrowTokenActiveStateChanged(handle, isTokenActive);
+        if (isTokenActive) {
+            mCarTrustAgentBleManager.sendEnrollmentHandle(mRemoteEnrollmentDevice, handle);
+        }
+    }
+
+    void onEnrollmentAdvertiseStartSuccess() {
+        for (BleStateChangeClient client : mBleStateChangeClients) {
+            try {
+                client.mListener.onEnrollmentAdvertisingStarted();
+            } catch (RemoteException e) {
+                Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+            }
+        }
+    }
+
+    void onEnrollmentAdvertiseStartFailure(int errorcode) {
+        for (BleStateChangeClient client : mBleStateChangeClients) {
+            try {
+                client.mListener.onEnrollmentAdvertisingFailed(errorcode);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+            }
+        }
+    }
+
+    void onRemoteDeviceConnected(BluetoothDevice device) {
+        synchronized (mRemoteDeviceLock) {
+            mRemoteEnrollmentDevice = device;
+        }
+        for (BleStateChangeClient client : mBleStateChangeClients) {
+            try {
+                client.mListener.onBleEnrollmentDeviceConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+            }
+        }
+        //TODO(b/11788064) Fake Authentication to enable clients to go through the enrollment flow.
+        fakeAuthentication();
+    }
+
+    void onRemoteDeviceDisconnected(BluetoothDevice device) {
+        synchronized (mRemoteDeviceLock) {
+            mRemoteEnrollmentDevice = null;
+        }
+        for (BleStateChangeClient client : mBleStateChangeClients) {
+            try {
+                client.mListener.onBleEnrollmentDeviceDisconnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+            }
+        }
+    }
+
+    void onEnrollmentDataReceived(byte[] value) {
+        if (mEnrollmentDelegate == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Enrollment Delegate not set");
+            }
+            return;
+        }
+        // The phone is not expected to send any data until the user has accepted the
+        // pairing.
+        if (!mEnrollmentHandshakeAccepted) {
+            Log.e(TAG, "User has not accepted the pairing code yet."
+                    + Utils.byteArrayToHexString(value));
+            return;
+        }
+        mEnrollmentDelegate.addEscrowToken(value, ActivityManager.getCurrentUser());
+    }
+
+    // TODO(b/11788064) Fake Authentication until we hook up the crypto lib
+    private void fakeAuthentication() {
+        if (mRemoteEnrollmentDevice == null) {
+            Log.e(TAG, "Remote Device disconnected before Enrollment completed");
+            return;
+        }
+        for (EnrollmentStateClient client : mEnrollmentStateClients) {
+            try {
+                client.mListener.onAuthStringAvailable(mRemoteEnrollmentDevice, FAKE_AUTH_STRING);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+            }
+        }
+    }
+
+    private synchronized void setEnrollmentHandshakeAccepted(boolean accepted) {
+        mEnrollmentHandshakeAccepted = accepted;
+    }
+
     /**
      * Iterates through the list of registered Enrollment State Change clients -
      * {@link EnrollmentStateClient} and finds if the given client is already registered.
@@ -235,6 +454,53 @@
     }
 
     /**
+     * The interface that an enrollment delegate has to implement to add/remove escrow tokens.
+     */
+    interface CarTrustAgentEnrollmentRequestDelegate {
+        /**
+         * Add the given escrow token that was generated by the peer device that is being enrolled.
+         *
+         * @param token the 64 bit token
+         * @param uid   user id
+         */
+        void addEscrowToken(byte[] token, int uid);
+
+        /**
+         * Remove the given escrow token.  This should be called when removing a trusted device.
+         *
+         * @param handle the 64 bit token
+         * @param uid    user id
+         */
+        void removeEscrowToken(long handle, int uid);
+
+        /**
+         * Query if the token is active.  The result is asynchronously delivered through a callback
+         * {@link CarTrustAgentEnrollmentService#onEscrowTokenActiveStateChanged(long, boolean)}
+         *
+         * @param handle the 64 bit token
+         * @param uid    user id
+         */
+        void isEscrowTokenActive(long handle, int uid);
+    }
+
+    void setEnrollmentRequestDelegate(CarTrustAgentEnrollmentRequestDelegate delegate) {
+        mEnrollmentDelegate = delegate;
+    }
+
+    void dump(PrintWriter writer) {
+    }
+
+    private void dispatchEscrowTokenActiveStateChanged(long handle, boolean active) {
+        for (EnrollmentStateClient client : mEnrollmentStateClients) {
+            try {
+                client.mListener.onEscrowTokenActiveStateChanged(handle, active);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Cannot notify client of a Token Activation change: " + active);
+            }
+        }
+    }
+
+    /**
      * Class that holds onto client related information - listener interface, process that hosts the
      * binder object etc.
      * <p>
@@ -301,9 +567,12 @@
             return mListenerBinder == binder;
         }
 
-    }
-
-    @Override
-    public void dump(PrintWriter writer) {
+        public void onEnrollmentAdvertisementStarted() {
+            try {
+                mListener.onEnrollmentAdvertisingStarted();
+            } catch (RemoteException e) {
+                Log.e(TAG, "onEnrollmentAdvertisementStarted() failed", e);
+            }
+        }
     }
 }
diff --git a/service/src/com/android/car/trust/CarTrustAgentUnlockService.java b/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
new file mode 100644
index 0000000..0f5e031
--- /dev/null
+++ b/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
@@ -0,0 +1,196 @@
+/*
+ * 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.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+
+import com.android.car.Utils;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * A service that interacts with the Trust Agent {@link CarBleTrustAgent} and a comms (BLE) service
+ * {@link CarTrustAgentBleManager} to receive the necessary credentials to authenticate
+ * an Android user.
+ */
+public class CarTrustAgentUnlockService {
+    private static final String TAG = "CarTrustAgentUnlock";
+    private final CarTrustedDeviceService mTrustedDeviceService;
+    private final CarTrustAgentBleManager mCarTrustAgentBleManager;
+    private CarTrustAgentUnlockDelegate mUnlockDelegate;
+    // Locks
+    private final Object mTokenLock = new Object();
+    private final Object mHandleLock = new Object();
+    private final Object mDeviceLock = new Object();
+
+    @GuardedBy("mTokenLock")
+    private byte[] mUnlockToken;
+    @GuardedBy("mHandleLock")
+    private byte[] mUnlockHandle;
+    @GuardedBy("mDeviceLock")
+    private BluetoothDevice mRemoteUnlockDevice;
+
+    CarTrustAgentUnlockService(CarTrustedDeviceService service,
+            CarTrustAgentBleManager bleService) {
+        mTrustedDeviceService = service;
+        mCarTrustAgentBleManager = bleService;
+    }
+
+    /**
+     * The interface that an unlock delegate has to implement to get the auth credentials from
+     * the unlock service.
+     */
+    interface CarTrustAgentUnlockDelegate {
+        /**
+         * Called when the Unlock service has the auth credentials to pass.
+         *
+         * @param user   user being authorized
+         * @param token  escrow token for the user
+         * @param handle the handle corresponding to the escrow token
+         */
+        void onUnlockDataReceived(int user, byte[] token, long handle);
+    }
+
+    /**
+     * Set a delegate that implements {@link CarTrustAgentUnlockDelegate}. The delegate will be
+     * handed the auth related data (token and handle) when it is received from the remote
+     * trusted device. The delegate is expected to use that to authorize the user.
+     */
+    void setUnlockRequestDelegate(CarTrustAgentUnlockDelegate delegate) {
+        mUnlockDelegate = delegate;
+    }
+
+    /**
+     * Start Unlock Advertising
+     */
+    void startUnlockAdvertising() {
+        mTrustedDeviceService.getCarTrustAgentEnrollmentService().stopEnrollmentAdvertising();
+        stopUnlockAdvertising();
+        mCarTrustAgentBleManager.startUnlockAdvertising();
+    }
+
+    /**
+     * Stop unlock advertising
+     */
+    void stopUnlockAdvertising() {
+        mCarTrustAgentBleManager.stopUnlockAdvertising();
+        // Also disconnect from the peer.
+        if (mRemoteUnlockDevice != null) {
+            mCarTrustAgentBleManager.disconnectRemoteDevice(mRemoteUnlockDevice);
+        }
+    }
+
+    void init() {
+        mCarTrustAgentBleManager.setupUnlockBleServer();
+    }
+
+    void release() {
+        synchronized (mDeviceLock) {
+            mRemoteUnlockDevice = null;
+        }
+    }
+
+    void onRemoteDeviceConnected(BluetoothDevice device) {
+        synchronized (mDeviceLock) {
+            if (mRemoteUnlockDevice != null) {
+                // TBD, return when this is encountered?
+                Log.e(TAG, "Unexpected: Cannot connect to another device when already connected");
+            }
+            mRemoteUnlockDevice = device;
+        }
+    }
+
+    void onRemoteDeviceDisconnected(BluetoothDevice device) {
+        // sanity checking
+        if (!device.equals(mRemoteUnlockDevice) && device.getAddress() != null) {
+            Log.e(TAG, "Disconnected from an unknown device:" + device.getAddress());
+        }
+        synchronized (mDeviceLock) {
+            mRemoteUnlockDevice = null;
+        }
+    }
+
+    void onUnlockTokenReceived(byte[] value) {
+        synchronized (mTokenLock) {
+            mUnlockToken = value;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Unlock Token: " + mUnlockToken);
+        }
+        if (mUnlockToken == null || mUnlockHandle == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unlock Handle not available yet");
+            }
+            return;
+        }
+        if (mUnlockDelegate == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "No Unlock delegate");
+            }
+            return;
+        }
+        mUnlockDelegate.onUnlockDataReceived(
+                mTrustedDeviceService.getUserHandleByTokenHandle(Utils.bytesToLong(mUnlockHandle)),
+                mUnlockToken,
+                Utils.bytesToLong(mUnlockHandle));
+
+        synchronized (mTokenLock) {
+            mUnlockToken = null;
+        }
+        synchronized (mHandleLock) {
+            mUnlockHandle = null;
+        }
+    }
+
+    void onUnlockHandleReceived(byte[] value) {
+        synchronized (mHandleLock) {
+            mUnlockHandle = value;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Unlock Handle: " + mUnlockHandle);
+        }
+        if (mUnlockToken == null || mUnlockHandle == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unlock Token not available yet");
+            }
+            return;
+        }
+
+        if (mUnlockDelegate == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "No Unlock delegate");
+            }
+            return;
+        }
+        mUnlockDelegate.onUnlockDataReceived(
+                mTrustedDeviceService.getUserHandleByTokenHandle(Utils.bytesToLong(mUnlockHandle)),
+                mUnlockToken,
+                Utils.bytesToLong(mUnlockHandle));
+
+        synchronized (mUnlockToken) {
+            mUnlockToken = null;
+        }
+        synchronized (mHandleLock) {
+            mUnlockHandle = null;
+        }
+    }
+
+    void dump(PrintWriter writer) {
+    }
+}
diff --git a/service/src/com/android/car/trust/CarTrustedDeviceService.java b/service/src/com/android/car/trust/CarTrustedDeviceService.java
new file mode 100644
index 0000000..538f31f
--- /dev/null
+++ b/service/src/com/android/car/trust/CarTrustedDeviceService.java
@@ -0,0 +1,124 @@
+/*
+ * 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.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.android.car.CarServiceBase;
+import com.android.car.R;
+
+import java.io.PrintWriter;
+
+/**
+ * The part of the Car service that enables the Trusted device feature.  Trusted Device is a feature
+ * where a remote device is enrolled as a trusted device that can authorize an Android user in lieu
+ * of the user entering a password or PIN.
+ * <p>
+ * It is comprised of the {@link CarTrustAgentEnrollmentService} for handling enrollment and
+ * {@link CarTrustAgentUnlockService} for handling unlock/auth.
+ *
+ */
+public class CarTrustedDeviceService implements CarServiceBase {
+    private static final String TAG = CarTrustedDeviceService.class.getSimpleName();
+    private final Context mContext;
+    private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+    private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
+    private CarTrustAgentBleManager mCarTrustAgentBleManager;
+    private SharedPreferences mTrustAgentTokenPreferences;
+
+
+    public CarTrustedDeviceService(Context context) {
+        mContext = context;
+        mCarTrustAgentBleManager = new CarTrustAgentBleManager(context);
+        mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(this,
+                mCarTrustAgentBleManager);
+        mCarTrustAgentUnlockService = new CarTrustAgentUnlockService(this,
+                mCarTrustAgentBleManager);
+    }
+
+    @Override
+    public synchronized void init() {
+        mCarTrustAgentEnrollmentService.init();
+        mCarTrustAgentUnlockService.init();
+    }
+
+    @Override
+    public synchronized void release() {
+        mCarTrustAgentBleManager.cleanup();
+        mCarTrustAgentEnrollmentService.release();
+        mCarTrustAgentUnlockService.release();
+    }
+
+    /**
+     * Returns the internal {@link CarTrustAgentEnrollmentService} instance.
+     */
+    public CarTrustAgentEnrollmentService getCarTrustAgentEnrollmentService() {
+        return mCarTrustAgentEnrollmentService;
+    }
+
+    /**
+     * Returns the internal {@link CarTrustAgentUnlockService} instance.
+     */
+    public CarTrustAgentUnlockService getCarTrustAgentUnlockService() {
+        return mCarTrustAgentUnlockService;
+    }
+
+    /**
+     * Returns User Id for the given token handle
+     *
+     * @param handle The handle corresponding to the escrow token
+     * @return User id corresponding to the handle
+     */
+    int getUserHandleByTokenHandle(long handle) {
+        return getSharedPrefs().getInt(String.valueOf(handle), -1);
+    }
+
+    void onRemoteDeviceConnected(BluetoothDevice device) {
+        mCarTrustAgentEnrollmentService.onRemoteDeviceConnected(device);
+        mCarTrustAgentUnlockService.onRemoteDeviceConnected(device);
+    }
+
+    void onRemoteDeviceDisconnected(BluetoothDevice device) {
+        mCarTrustAgentEnrollmentService.onRemoteDeviceDisconnected(device);
+        mCarTrustAgentUnlockService.onRemoteDeviceDisconnected(device);
+    }
+
+    void cleanupBleService() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "cleanupBleService");
+        }
+        mCarTrustAgentBleManager.stopGattServer();
+        mCarTrustAgentBleManager.stopEnrollmentAdvertising();
+        mCarTrustAgentBleManager.stopUnlockAdvertising();
+    }
+
+    SharedPreferences getSharedPrefs() {
+        if (mTrustAgentTokenPreferences != null) {
+            return mTrustAgentTokenPreferences;
+        }
+        mTrustAgentTokenPreferences = mContext.getSharedPreferences(
+                mContext.getString(R.string.token_handle_shared_preferences), Context.MODE_PRIVATE);
+        return mTrustAgentTokenPreferences;
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+    }
+}
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
index 0657cba..0cf62e7 100644
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ b/service/src/com/android/car/vms/VmsClientManager.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -56,10 +55,10 @@
         /**
          * Called when a client connection is established or re-established.
          *
-         * @param clientName String that uniquely identifies the service and user.
-         * @param binder Binder for communicating with the client.
+         * @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 binder);
+        void onClientConnected(String clientName, IBinder clientService);
 
         /**
          * Called when a client connection is terminated.
@@ -114,7 +113,7 @@
     /**
      * Constructor for client managers.
      *
-     * @param context Context to use for registering receivers and binding services.
+     * @param context           Context to use for registering receivers and binding services.
      * @param userManagerHelper User manager for querying current user state.
      */
     public VmsClientManager(Context context, CarUserManagerHelper userManagerHelper) {
@@ -142,10 +141,10 @@
         mContext.unregisterReceiver(mBootCompletedReceiver);
         mContext.unregisterReceiver(mUserSwitchReceiver);
         synchronized (mSystemClients) {
-            unbind(mSystemClients);
+            terminate(mSystemClients);
         }
         synchronized (mCurrentUserClients) {
-            unbind(mCurrentUserClients);
+            terminate(mCurrentUserClients);
         }
     }
 
@@ -194,12 +193,12 @@
     }
 
     private void bindToCurrentUserClients() {
-        UserInfo userInfo = mUserManagerHelper.getCurrentForegroundUserInfo();
+        int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
         synchronized (mCurrentUserClients) {
-            if (mCurrentUser != userInfo.id) {
-                unbind(mCurrentUserClients);
+            if (mCurrentUser != currentUserId) {
+                terminate(mCurrentUserClients);
             }
-            mCurrentUser = userInfo.id;
+            mCurrentUser = currentUserId;
 
             // To avoid the risk of double-binding, clients running as the system user must only
             // ever be bound in bindToSystemClients().
@@ -212,8 +211,9 @@
             String[] clientNames = mContext.getResources().getStringArray(
                     R.array.vmsPublisherUserClients);
             Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)");
+            UserHandle currentUserHandle = UserHandle.of(mCurrentUser);
             for (String clientName : clientNames) {
-                bind(mCurrentUserClients, clientName, userInfo.getUserHandle());
+                bind(mCurrentUserClients, clientName, currentUserHandle);
             }
         }
     }
@@ -244,17 +244,17 @@
         }
     }
 
-    private void unbind(Map<String, ClientConnection> connectionMap) {
+    private void terminate(Map<String, ClientConnection> connectionMap) {
         for (ClientConnection connection : connectionMap.values()) {
-            connection.unbind();
+            connection.terminate();
         }
         connectionMap.clear();
     }
 
-    private void notifyListenersOnClientConnected(String clientName, IBinder binder) {
+    private void notifyListenersOnClientConnected(String clientName, IBinder clientService) {
         synchronized (mListeners) {
             for (ConnectionListener listener : mListeners) {
-                listener.onClientConnected(clientName, binder);
+                listener.onClientConnected(clientName, clientService);
             }
         }
     }
@@ -272,7 +272,8 @@
         private final UserHandle mUser;
         private final String mFullName;
         private boolean mIsBound = false;
-        private IBinder mBinder;
+        private boolean mIsTerminated = false;
+        private IBinder mClientService;
 
         ClientConnection(ComponentName name, UserHandle user) {
             mName = name;
@@ -281,10 +282,12 @@
         }
 
         synchronized boolean bind() {
-            // Ignore if already bound
             if (mIsBound) {
                 return true;
             }
+            if (mIsTerminated) {
+                return false;
+            }
 
             if (DBG) Log.d(TAG, "binding: " + mFullName);
             Intent intent = new Intent();
@@ -307,26 +310,34 @@
                 Log.e(TAG, "While unbinding " + mFullName, t);
             }
             mIsBound = false;
-            if (mBinder != null) {
+            if (mClientService != null) {
                 notifyListenersOnClientDisconnected(mFullName);
             }
-            mBinder = null;
+            mClientService = null;
         }
 
-        void rebind() {
+        synchronized void rebind() {
             unbind();
             if (DBG) {
                 Log.d(TAG,
                         String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
             }
-            mHandler.postDelayed(this::bind, mMillisBeforeRebind);
+            if (!mIsTerminated) {
+                mHandler.postDelayed(this::bind, mMillisBeforeRebind);
+            }
+        }
+
+        synchronized void terminate() {
+            if (DBG) Log.d(TAG, "terminating: " + mFullName);
+            mIsTerminated = true;
+            unbind();
         }
 
         @Override
-        public void onServiceConnected(ComponentName name, IBinder binder) {
+        public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
-            mBinder = binder;
-            notifyListenersOnClientConnected(mFullName, mBinder);
+            mClientService = service;
+            notifyListenersOnClientConnected(mFullName, mClientService);
         }
 
         @Override
@@ -344,7 +355,7 @@
         @Override
         public void onNullBinding(ComponentName name) {
             if (DBG) Log.d(TAG, "onNullBinding: " + mFullName);
-            unbind();
+            terminate();
         }
 
         @Override
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index 68fafcf..088aa68 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -51,7 +51,7 @@
                 android:layout_margin="10dp"
                 android:background="#ffa9a8"
                 android:foreground="?android:attr/selectableItemBackground"
-                android:text="Call (Shows persistent heads-up until swiped away)"
+                android:text="Call (Shows persistent heads-up, only dismissed on cancel)"
                 android:textSize="30sp"/>
 
             <Button
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
index aa4a2eb..245b945 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
@@ -30,7 +30,8 @@
         <TextView
             android:id="@+id/location_info"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:scrollbars="vertical"/>
         <TextView
             android:id="@+id/accel_info"
             android:layout_width="wrap_content"
@@ -57,6 +58,7 @@
         <TextView
             android:id="@+id/sensor_info"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:scrollbars="vertical"/>
     </LinearLayout>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/storagevolumes.xml b/tests/EmbeddedKitchenSinkApp/res/layout/storagevolumes.xml
new file mode 100644
index 0000000..7310acd
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/storagevolumes.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/storage_volumes_list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+</ListView>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index 138cfba..a80c3b4 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -63,6 +63,7 @@
 import com.google.android.car.kitchensink.property.PropertyTestFragment;
 import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
+import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment;
 import com.google.android.car.kitchensink.touch.TouchTestFragment;
 import com.google.android.car.kitchensink.users.UsersFragment;
 import com.google.android.car.kitchensink.vhal.VehicleHalFragment;
@@ -174,6 +175,7 @@
             new FragmentMenuEntry("property test", PropertyTestFragment.class),
             new FragmentMenuEntry("sensors", SensorsTestFragment.class),
             new FragmentMenuEntry("storage lifetime", StorageLifetimeFragment.class),
+            new FragmentMenuEntry("storage volumes", StorageVolumesFragment.class),
             new FragmentMenuEntry("touch test", TouchTestFragment.class),
             new FragmentMenuEntry("users", UsersFragment.class),
             new FragmentMenuEntry("volume test", VolumeTestFragment.class),
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
index e1edea5..6e3d585 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -106,7 +107,9 @@
         mActivity = (KitchenSinkActivity) getHost();
 
         mSensorInfo = (TextView) view.findViewById(R.id.sensor_info);
+        mSensorInfo.setMovementMethod(new ScrollingMovementMethod());
         mLocationInfo = (TextView) view.findViewById(R.id.location_info);
+        mLocationInfo.setMovementMethod(new ScrollingMovementMethod());
         mAccelInfo = (TextView) view.findViewById(R.id.accel_info);
         mGyroInfo = (TextView) view.findViewById(R.id.gyro_info);
         mMagInfo = (TextView) view.findViewById(R.id.mag_info);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagevolumes/StorageVolumesFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagevolumes/StorageVolumesFragment.java
new file mode 100644
index 0000000..f10b0f3
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagevolumes/StorageVolumesFragment.java
@@ -0,0 +1,102 @@
+/*
+ * 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.google.android.car.kitchensink.storagevolumes;
+
+import android.annotation.Nullable;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Listens for changes in content and displays updated list of volumes
+ */
+public class StorageVolumesFragment extends Fragment {
+    private static final String TAG = "CAR.STORAGEVOLUMES.KS";
+
+    private ListView mStorageVolumesList;
+    private StorageManager mStorageManager;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mStorageManager = Objects.requireNonNull(getContext())
+                .getSystemService(StorageManager.class);
+
+        super.onCreate(savedInstanceState);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(
+            @NonNull LayoutInflater inflater,
+            @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        mStorageVolumesList = (ListView) inflater.inflate(R.layout.storagevolumes,
+                container, false);
+        return mStorageVolumesList;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refresh();
+        Objects.requireNonNull(getContext()).getContentResolver()
+                .registerContentObserver(MediaStore.AUTHORITY_URI, true, mContentObserver);
+    }
+
+    @Override
+    public void onPause() {
+        Objects.requireNonNull(getContext()).getContentResolver()
+                .unregisterContentObserver(mContentObserver);
+        super.onPause();
+    }
+
+    private void refresh() {
+        final List<VolumeInfo> volumes = mStorageManager.getVolumes();
+        volumes.sort(VolumeInfo.getDescriptionComparator());
+
+        mStorageVolumesList.setAdapter(new ArrayAdapter<>(getContext(),
+                android.R.layout.simple_list_item_1, volumes.toArray()));
+    }
+
+    private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            if (isResumed()) {
+                Log.d(TAG, "Content has changed for URI " + uri);
+                refresh();
+            }
+        }
+    };
+}
diff --git a/tests/PowerTestService/com.android.car.powertestservice.rc b/tests/PowerTestService/com.android.car.powertestservice.rc
index db27237..117da7e 100644
--- a/tests/PowerTestService/com.android.car.powertestservice.rc
+++ b/tests/PowerTestService/com.android.car.powertestservice.rc
@@ -16,6 +16,7 @@
     class core
     user system
     group system
+    disabled
 
-on boot && property:boot.car_service_created=1
+on property:boot.car_service_created=1
     start com.android.car.powertestservice
diff --git a/tests/ThemePlayground/res/layout/dialog_samples.xml b/tests/ThemePlayground/res/layout/dialog_samples.xml
index e8c398b..47d679f 100644
--- a/tests/ThemePlayground/res/layout/dialog_samples.xml
+++ b/tests/ThemePlayground/res/layout/dialog_samples.xml
@@ -38,20 +38,30 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="Show Dialog"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/showDialogOnlyPositiveBT"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toBottomOf="@+id/setBackground" />
+
+    <Button
+        android:id="@+id/showDialogOnlyPositiveBT"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Show Dialog with only positive button"
+        app:layout_constraintBottom_toTopOf="@+id/showDialogWithCheckboxBT"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/showDialogBT" />
 
     <Button
         android:id="@+id/showDialogWithCheckboxBT"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="Show Dialog With Checkbox"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/showToast"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/showDialogBT" />
+        app:layout_constraintTop_toBottomOf="@+id/showDialogOnlyPositiveBT" />
 
     <Button
         android:id="@+id/showToast"
@@ -61,7 +71,7 @@
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/showDialogWithCheckboxBT" />
+        app:layout_constraintTop_toBottomOf="@+id/showDialogWithCheckboxBT" />
 
     <include layout="@layout/menu_button"/>
 
diff --git a/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java b/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java
index ca07a71..0b311ef 100644
--- a/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java
+++ b/tests/ThemePlayground/src/com/android/car/themeplayground/DialogSamples.java
@@ -36,9 +36,11 @@
         setContentView(R.layout.dialog_samples);
 
         Button mShowDialogBT = findViewById(R.id.showDialogBT);
+        Button mShowDialogOnlyPositiveBT = findViewById(R.id.showDialogOnlyPositiveBT);
         Button mShowDialogWithCheckboxBT = findViewById(R.id.showDialogWithCheckboxBT);
         setupBackgroundColorControls(R.id.dialogLayout);
         mShowDialogBT.setOnClickListener(v -> openDialog(false));
+        mShowDialogOnlyPositiveBT.setOnClickListener(v -> openDialogWithOnlyPositiveButton());
         mShowDialogWithCheckboxBT.setOnClickListener(v -> openDialog(true));
         Button mShowToast = findViewById(R.id.showToast);
         mShowToast.setOnClickListener(v -> showToast());
@@ -72,6 +74,16 @@
         builder.show();
     }
 
+    private void openDialogWithOnlyPositiveButton() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(
+                new ContextThemeWrapper(this, R.style.Theme_Testing_Dialog_Alert));
+        builder.setTitle("Standard Alert Dialog")
+                .setMessage("With a message to show.");
+        builder.setPositiveButton("OK", (dialoginterface, i) -> {
+        });
+        builder.show();
+    }
+
     private void showToast() {
         Toast.makeText(this, "Toast message looks like this",
                 Toast.LENGTH_LONG).show();
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index 369c8f3..f1a25a5 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -17,7 +17,6 @@
 
 import static android.car.CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED;
 import static android.car.CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION;
-import static android.car.CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND;
 
 import android.car.Car;
 import android.car.CarAppFocusManager;
@@ -95,7 +94,6 @@
         CarAppFocusManager manager = createManager();
         manager.addFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
         manager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
-        manager.addFocusListener(listener2, APP_FOCUS_TYPE_VOICE_COMMAND);
 
         manager.removeFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
 
@@ -113,10 +111,6 @@
                 manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, new FocusOwnershipCallback()));
         assertFalse(listener2.waitForFocusChangeAndAssert(
                 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true));
-        assertEquals(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED,
-                manager.requestAppFocus(APP_FOCUS_TYPE_VOICE_COMMAND, new FocusOwnershipCallback()));
-        assertTrue(listener2.waitForFocusChangeAndAssert(
-                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_VOICE_COMMAND, true));
 
         manager.removeFocusListener(listener2, 2);
         manager.removeFocusListener(listener2, 2);    // Double-unregister is OK
@@ -134,9 +128,7 @@
         FocusOwnershipCallback owner1 = new FocusOwnershipCallback();
         FocusOwnershipCallback owner2 = new FocusOwnershipCallback();
         manager1.addFocusListener(change1, APP_FOCUS_TYPE_NAVIGATION);
-        manager1.addFocusListener(change1, APP_FOCUS_TYPE_VOICE_COMMAND);
         manager2.addFocusListener(change2, APP_FOCUS_TYPE_NAVIGATION);
-        manager2.addFocusListener(change2, APP_FOCUS_TYPE_VOICE_COMMAND);
 
 
         assertEquals(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED,
@@ -147,33 +139,19 @@
         Assert.assertArrayEquals(expectedFocuses, manager1.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
         assertTrue(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND));
         assertFalse(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager2.isOwningFocus(owner2,
-                APP_FOCUS_TYPE_VOICE_COMMAND));
         assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 APP_FOCUS_TYPE_NAVIGATION, true));
         assertTrue(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 APP_FOCUS_TYPE_NAVIGATION, true));
 
-        assertEquals(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED,
-                manager1.requestAppFocus(APP_FOCUS_TYPE_VOICE_COMMAND, owner1));
-        assertTrue(owner1.waitForOwnershipGrantAndAssert(
-                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_VOICE_COMMAND));
         expectedFocuses = new int[] {
                 APP_FOCUS_TYPE_NAVIGATION,
-                APP_FOCUS_TYPE_VOICE_COMMAND };
+        };
         assertTrue(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION));
-        assertTrue(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND));
         assertFalse(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager2.isOwningFocus(owner2,
-                APP_FOCUS_TYPE_VOICE_COMMAND));
         Assert.assertArrayEquals(expectedFocuses, manager1.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
-        assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, true));
-        assertTrue(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, true));
 
         // this should be no-op
         change1.reset();
@@ -196,10 +174,7 @@
                 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION));
 
         assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION));
-        assertTrue(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND));
         assertTrue(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager2.isOwningFocus(owner2,
-                APP_FOCUS_TYPE_VOICE_COMMAND));
         Assert.assertArrayEquals(expectedFocuses, manager1.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
         assertTrue(owner1.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
@@ -210,37 +185,23 @@
         change2.reset();
         manager1.abandonAppFocus(owner1, APP_FOCUS_TYPE_NAVIGATION);
         assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION));
-        assertTrue(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND));
         assertTrue(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager2.isOwningFocus(owner2,
-                APP_FOCUS_TYPE_VOICE_COMMAND));
         Assert.assertArrayEquals(expectedFocuses, manager1.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
 
         change1.reset();
         change2.reset();
-        manager1.abandonAppFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND);
         assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND));
         assertTrue(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager2.isOwningFocus(owner2,
-                APP_FOCUS_TYPE_VOICE_COMMAND));
         expectedFocuses = new int[] {APP_FOCUS_TYPE_NAVIGATION};
         Assert.assertArrayEquals(expectedFocuses, manager1.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
-        assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, false));
-        assertTrue(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, false));
 
         change1.reset();
         change2.reset();
         manager2.abandonAppFocus(owner2, APP_FOCUS_TYPE_NAVIGATION);
         assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager1.isOwningFocus(owner1, APP_FOCUS_TYPE_VOICE_COMMAND));
         assertFalse(manager2.isOwningFocus(owner2, APP_FOCUS_TYPE_NAVIGATION));
-        assertFalse(manager2.isOwningFocus(owner2,
-                APP_FOCUS_TYPE_VOICE_COMMAND));
         expectedFocuses = emptyFocus;
         Assert.assertArrayEquals(expectedFocuses, manager1.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
@@ -262,7 +223,6 @@
         FocusChangedListener listener2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         manager1.addFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
-        manager1.addFocusListener(listener1, APP_FOCUS_TYPE_VOICE_COMMAND);
         manager2.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
 
         assertEquals(APP_FOCUS_REQUEST_SUCCEEDED,
@@ -277,26 +237,6 @@
 
         listener1.reset();
         listener2.reset();
-        assertEquals(APP_FOCUS_REQUEST_SUCCEEDED,
-                manager1.requestAppFocus(APP_FOCUS_TYPE_VOICE_COMMAND, owner));
-        assertTrue(owner.waitForOwnershipGrantAndAssert(
-                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_VOICE_COMMAND));
-
-        assertTrue(listener1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, true));
-        assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, true));
-
-        listener1.reset();
-        listener2.reset();
-        manager1.abandonAppFocus(owner, APP_FOCUS_TYPE_VOICE_COMMAND);
-        assertTrue(listener1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, false));
-        assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, false));
-
-        listener1.reset();
-        listener2.reset();
         manager1.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION);
         assertTrue(listener1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 APP_FOCUS_TYPE_NAVIGATION, false));
@@ -333,7 +273,6 @@
         FocusChangedListener listener2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         manager.addFocusListener(listener, APP_FOCUS_TYPE_NAVIGATION);
-        manager.addFocusListener(listener, APP_FOCUS_TYPE_VOICE_COMMAND);
         manager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
 
         assertEquals(APP_FOCUS_REQUEST_SUCCEEDED,
@@ -348,26 +287,6 @@
 
         listener.reset();
         listener2.reset();
-        assertEquals(APP_FOCUS_REQUEST_SUCCEEDED,
-                manager.requestAppFocus(APP_FOCUS_TYPE_VOICE_COMMAND, owner));
-        assertTrue(owner.waitForOwnershipGrantAndAssert(
-                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_VOICE_COMMAND));
-
-        assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, true));
-        assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, true));
-
-        listener.reset();
-        listener2.reset();
-        manager.abandonAppFocus(owner, APP_FOCUS_TYPE_VOICE_COMMAND);
-        assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, false));
-        assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_VOICE_COMMAND, false));
-
-        listener.reset();
-        listener2.reset();
         manager.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION);
         assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 APP_FOCUS_TYPE_NAVIGATION, false));
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java
index f86c76e..5b991a9 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUxRestrictionsConfigurationTest.java
@@ -23,9 +23,13 @@
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO;
 import static android.car.drivingstate.CarUxRestrictionsConfiguration.Builder.SpeedRange.MAX_SPEED;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
 
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.CarUxRestrictionsConfiguration;
+import android.car.drivingstate.CarUxRestrictionsConfiguration.Builder;
+import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
 import android.util.JsonReader;
 import android.util.JsonWriter;
 
@@ -47,39 +51,41 @@
 
     // This test verifies the expected way to build config would succeed.
     public void testConstruction() {
-        new CarUxRestrictionsConfiguration.Builder().build();
+        new Builder().build();
 
-        new CarUxRestrictionsConfiguration.Builder()
+        new Builder()
                 .setMaxStringLength(1)
                 .build();
 
-        new CarUxRestrictionsConfiguration.Builder()
+        new Builder()
                 .setUxRestrictions(DRIVING_STATE_PARKED, false, UX_RESTRICTIONS_BASELINE)
                 .build();
 
-        new CarUxRestrictionsConfiguration.Builder()
+        new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_FULLY_RESTRICTED)
                 .build();
 
-        new CarUxRestrictionsConfiguration.Builder()
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, MAX_SPEED),
-                        true, UX_RESTRICTIONS_FULLY_RESTRICTED)
+        new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                        .setSpeedRange(new Builder.SpeedRange(0f, MAX_SPEED)))
                 .build();
 
-        new CarUxRestrictionsConfiguration.Builder()
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f),
-                        true, UX_RESTRICTIONS_FULLY_RESTRICTED)
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f, MAX_SPEED),
-                        true, UX_RESTRICTIONS_FULLY_RESTRICTED)
+        new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                        .setSpeedRange(new Builder.SpeedRange(0f, 1f)))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                        .setSpeedRange(new Builder.SpeedRange(1f, MAX_SPEED)))
                 .build();
     }
 
     public void testUnspecifiedDrivingStateUsesDefaultRestriction() {
-        CarUxRestrictionsConfiguration config =
-                new CarUxRestrictionsConfiguration.Builder().build();
+        CarUxRestrictionsConfiguration config = new Builder().build();
 
         CarUxRestrictions parkedRestrictions = config.getUxRestrictions(DRIVING_STATE_PARKED, 0f);
         assertTrue(parkedRestrictions.isRequiresDistractionOptimization());
@@ -90,183 +96,245 @@
         assertEquals(movingRestrictions.getActiveRestrictions(), UX_RESTRICTIONS_FULLY_RESTRICTED);
     }
 
-    public void testBuilderValidation_MultipleSpeedRange_NonZeroStart() {
-        CarUxRestrictionsConfiguration.Builder builder =
-                new CarUxRestrictionsConfiguration.Builder();
-        builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(1, 2),
-                true, UX_RESTRICTIONS_FULLY_RESTRICTED);
-        builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(2, MAX_SPEED),
-                true, UX_RESTRICTIONS_FULLY_RESTRICTED);
+    public void testBuilderValidation_UnspecifiedStateUsesRestrictiveDefault() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_FULLY_RESTRICTED)
+                .build();
+        assertTrue(config.getUxRestrictions(DRIVING_STATE_PARKED, 0f)
+                .isRequiresDistractionOptimization());
+        assertTrue(config.getUxRestrictions(DRIVING_STATE_IDLING, 0f)
+                .isRequiresDistractionOptimization());
+    }
 
+    public void testBuilderValidation_NonMovingStateHasOneRestriction() {
+        Builder builder = new Builder();
+        builder.setUxRestrictions(DRIVING_STATE_IDLING,
+                true, UX_RESTRICTIONS_NO_VIDEO);
+        builder.setUxRestrictions(DRIVING_STATE_IDLING,
+                false, UX_RESTRICTIONS_BASELINE);
         try {
             builder.build();
             fail();
-        } catch (IllegalStateException e) {
+        } catch (Exception e) {
+            // Expected exception.
+        }
+    }
+
+    public void testBuilderValidation_PassengerModeNoSpeedRangeOverlap() {
+        Builder builder = new Builder();
+        builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                    .setDistractionOptimizationRequired(true)
+                    .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                    .setSpeedRange(new Builder.SpeedRange(1f, 2f)));
+        builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                    .setDistractionOptimizationRequired(true)
+                    .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                    .setSpeedRange(new Builder.SpeedRange(1f)));
+        try {
+            builder.build();
+            fail();
+        } catch (Exception e) {
+            // Expected exception.
+        }
+    }
+
+    public void testBuilderValidation_PassengerModeCanSpecifySubsetOfSpeedRange() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER)
+                        .setSpeedRange(new Builder.SpeedRange(1f, 2f)))
+                .build();
+
+        assertTrue(config.getUxRestrictions(DRIVING_STATE_MOVING, 1f, UX_RESTRICTION_MODE_PASSENGER)
+                .isRequiresDistractionOptimization());
+    }
+
+    public void testBuilderValidation_MultipleSpeedRange_NonZeroStart() {
+        Builder builder = new Builder();
+        builder.setUxRestrictions(DRIVING_STATE_MOVING,
+                new Builder.SpeedRange(1, 2),
+                true, UX_RESTRICTIONS_FULLY_RESTRICTED);
+        builder.setUxRestrictions(DRIVING_STATE_MOVING,
+                new Builder.SpeedRange(2, MAX_SPEED),
+                true, UX_RESTRICTIONS_FULLY_RESTRICTED);
+        try {
+            builder.build();
+            fail();
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testBuilderValidation_SpeedRange_NonZeroStart() {
-        CarUxRestrictionsConfiguration.Builder builder =
-                new CarUxRestrictionsConfiguration.Builder();
+        Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(1, MAX_SPEED),
+                new Builder.SpeedRange(1, MAX_SPEED),
                 true, UX_RESTRICTIONS_FULLY_RESTRICTED);
-
         try {
             builder.build();
             fail();
-        } catch (IllegalStateException e) {
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testBuilderValidation_SpeedRange_Overlap() {
-        CarUxRestrictionsConfiguration.Builder builder =
-                new CarUxRestrictionsConfiguration.Builder();
+        Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0, 5), true,
+                new Builder.SpeedRange(0, 5), true,
                 UX_RESTRICTIONS_FULLY_RESTRICTED);
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(4), true,
+                new Builder.SpeedRange(4), true,
                 UX_RESTRICTIONS_FULLY_RESTRICTED);
-
         try {
             builder.build();
             fail();
-        } catch (IllegalStateException e) {
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testBuilderValidation_SpeedRange_Gap() {
-        CarUxRestrictionsConfiguration.Builder builder =
-                new CarUxRestrictionsConfiguration.Builder();
+        Builder builder = new Builder();
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0, 5), true,
+                new Builder.SpeedRange(0, 5), true,
                 UX_RESTRICTIONS_FULLY_RESTRICTED);
         builder.setUxRestrictions(DRIVING_STATE_MOVING,
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(8), true,
+                new Builder.SpeedRange(8), true,
                 UX_RESTRICTIONS_FULLY_RESTRICTED);
-
         try {
             builder.build();
             fail();
-        } catch (IllegalStateException e) {
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testBuilderValidation_NonMovingStateCannotUseSpeedRange() {
-        CarUxRestrictionsConfiguration.Builder builder =
-                new CarUxRestrictionsConfiguration.Builder();
+        Builder builder = new Builder();
         try {
             builder.setUxRestrictions(DRIVING_STATE_PARKED,
-                    new CarUxRestrictionsConfiguration.Builder.SpeedRange(0, 5), true,
+                    new Builder.SpeedRange(0, 5), true,
                     UX_RESTRICTIONS_FULLY_RESTRICTED);
-        } catch (IllegalArgumentException e) {
+            fail();
+        } catch (Exception e) {
+            // Expected exception.
+        }
+    }
+
+    public void testBuilderValidation_MultipleMovingRestrictionsShouldAllContainSpeedRange() {
+        Builder builder = new Builder();
+        builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                .setDistractionOptimizationRequired(true)
+                .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED));
+        builder.setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                .setDistractionOptimizationRequired(true)
+                .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                .setSpeedRange(new Builder.SpeedRange(1f)));
+        try {
+            builder.build();
+            fail();
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testSpeedRange_Construction() {
-        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f);
-        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f);
-        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, MAX_SPEED);
+        new Builder.SpeedRange(0f);
+        new Builder.SpeedRange(0f, 1f);
+        new Builder.SpeedRange(0f, MAX_SPEED);
     }
 
     public void testSpeedRange_NegativeMax() {
         try {
-            new CarUxRestrictionsConfiguration.Builder.SpeedRange(2f, -1f);
-        } catch (IllegalArgumentException e) {
+            new Builder.SpeedRange(2f, -1f);
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testSpeedRange_MinGreaterThanMax() {
         try {
-            new CarUxRestrictionsConfiguration.Builder.SpeedRange(5f, 2f);
-        } catch (IllegalArgumentException e) {
+            new Builder.SpeedRange(5f, 2f);
+        } catch (Exception e) {
             // Expected exception.
         }
     }
 
     public void testSpeedRangeComparison_DifferentMin() {
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s1 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f);
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s2 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(2f);
+        Builder.SpeedRange s1 =
+                new Builder.SpeedRange(1f);
+        Builder.SpeedRange s2 =
+                new Builder.SpeedRange(2f);
         assertTrue(s1.compareTo(s2) < 0);
         assertTrue(s2.compareTo(s1) > 0);
     }
 
     public void testSpeedRangeComparison_SameMin() {
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s1 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f);
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s2 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f);
+        Builder.SpeedRange s1 =
+                new Builder.SpeedRange(1f);
+        Builder.SpeedRange s2 =
+                new Builder.SpeedRange(1f);
         assertTrue(s1.compareTo(s2) == 0);
     }
 
     public void testSpeedRangeComparison_SameMinDifferentMax() {
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s1 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f);
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s2 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 2f);
+        Builder.SpeedRange s1 =
+                new Builder.SpeedRange(0f, 1f);
+        Builder.SpeedRange s2 =
+                new Builder.SpeedRange(0f, 2f);
         assertTrue(s1.compareTo(s2) < 0);
         assertTrue(s2.compareTo(s1) > 0);
     }
 
     public void testSpeedRangeComparison_MaxSpeed() {
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s1 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f);
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s2 =
-                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f);
+        Builder.SpeedRange s1 =
+                new Builder.SpeedRange(0f, 1f);
+        Builder.SpeedRange s2 =
+                new Builder.SpeedRange(0f);
         assertTrue(s1.compareTo(s2) < 0);
         assertTrue(s2.compareTo(s1) > 0);
     }
 
     public void testSpeedRangeEquals() {
-        CarUxRestrictionsConfiguration.Builder.SpeedRange s1, s2;
+        Builder.SpeedRange s1, s2;
 
-        s1 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f);
+        s1 = new Builder.SpeedRange(0f);
         assertTrue(s1.equals(s1));
 
-        s1 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f);
-        s2 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f);
+        s1 = new Builder.SpeedRange(1f);
+        s2 = new Builder.SpeedRange(1f);
         assertTrue(s1.compareTo(s2) == 0);
         assertTrue(s1.equals(s2));
 
-        s1 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f);
-        s2 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f);
+        s1 = new Builder.SpeedRange(0f, 1f);
+        s2 = new Builder.SpeedRange(0f, 1f);
         assertTrue(s1.equals(s2));
 
-        s1 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, MAX_SPEED);
-        s2 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, MAX_SPEED);
+        s1 = new Builder.SpeedRange(0f, MAX_SPEED);
+        s2 = new Builder.SpeedRange(0f, MAX_SPEED);
         assertTrue(s1.equals(s2));
 
-        s1 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f);
-        s2 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f);
+        s1 = new Builder.SpeedRange(0f);
+        s2 = new Builder.SpeedRange(1f);
         assertFalse(s1.equals(s2));
 
-        s1 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f);
-        s2 = new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 2f);
+        s1 = new Builder.SpeedRange(0f, 1f);
+        s2 = new Builder.SpeedRange(0f, 2f);
         assertFalse(s1.equals(s2));
     }
 
-    // TODO: add more tests that each verifies setting one filed in builder can be
-    // successfully retrieved out of saved json.
     public void testJsonSerialization_DefaultConstructor() {
         CarUxRestrictionsConfiguration config =
-                new CarUxRestrictionsConfiguration.Builder().build();
+                new Builder().build();
 
         verifyConfigThroughJsonSerialization(config);
     }
 
     public void testJsonSerialization_RestrictionParameters() {
-        CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
+        CarUxRestrictionsConfiguration config = new Builder()
                 .setMaxStringLength(1)
                 .setMaxCumulativeContentItems(1)
                 .setMaxContentDepth(1)
@@ -276,7 +344,7 @@
     }
 
     public void testJsonSerialization_NonMovingStateRestrictions() {
-        CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
+        CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_PARKED, false, UX_RESTRICTIONS_BASELINE)
                 .build();
 
@@ -284,7 +352,7 @@
     }
 
     public void testJsonSerialization_MovingStateNoSpeedRange() {
-        CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
+        CarUxRestrictionsConfiguration config = new Builder()
                 .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_FULLY_RESTRICTED)
                 .build();
 
@@ -292,13 +360,36 @@
     }
 
     public void testJsonSerialization_MovingStateWithSpeedRange() {
-        CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 5f),
-                        true, UX_RESTRICTIONS_FULLY_RESTRICTED)
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(5f, MAX_SPEED),
-                        true, UX_RESTRICTIONS_FULLY_RESTRICTED)
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                        .setSpeedRange(new Builder.SpeedRange(0f, 5f)))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_FULLY_RESTRICTED)
+                        .setSpeedRange(new Builder.SpeedRange(5f, MAX_SPEED)))
+                .build();
+
+        verifyConfigThroughJsonSerialization(config);
+    }
+
+    public void testJsonSerialization_UxRestrictionMode() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                // Passenger mode
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                // Explicitly specify baseline mode
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                        .setMode(UX_RESTRICTION_MODE_BASELINE))
+                // Implicitly defaults to baseline mode
+                .setUxRestrictions(DRIVING_STATE_IDLING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
                 .build();
 
         verifyConfigThroughJsonSerialization(config);
@@ -307,31 +398,45 @@
     public void testDump() {
         CarUxRestrictionsConfiguration[] configs = new CarUxRestrictionsConfiguration[] {
                 // Driving state with no speed range
-                new CarUxRestrictionsConfiguration.Builder()
+                new Builder()
                         .setUxRestrictions(DRIVING_STATE_PARKED, false, UX_RESTRICTIONS_BASELINE)
                         .setUxRestrictions(DRIVING_STATE_IDLING, true, UX_RESTRICTIONS_NO_VIDEO)
                         .setUxRestrictions(DRIVING_STATE_MOVING, true, UX_RESTRICTIONS_NO_VIDEO)
                         .build(),
                 // Parameters
-                new CarUxRestrictionsConfiguration.Builder()
+                new Builder()
                         .setMaxStringLength(1)
                         .setMaxContentDepth(1)
                         .setMaxCumulativeContentItems(1)
                         .build(),
                 // Driving state with single speed range
-                new CarUxRestrictionsConfiguration.Builder()
-                        .setUxRestrictions(DRIVING_STATE_MOVING,
-                                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f),
-                                true, UX_RESTRICTIONS_NO_VIDEO)
+                new Builder()
+                        .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                                .setSpeedRange(new Builder.SpeedRange(0f)))
                         .build(),
                 // Driving state with multiple speed ranges
-                new CarUxRestrictionsConfiguration.Builder()
-                        .setUxRestrictions(DRIVING_STATE_MOVING,
-                                new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f),
-                                true, UX_RESTRICTIONS_NO_VIDEO)
-                        .setUxRestrictions(DRIVING_STATE_MOVING,
-                                new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f),
-                                true, UX_RESTRICTIONS_NO_VIDEO)
+                new Builder()
+                        .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                                .setSpeedRange(new Builder.SpeedRange(0f, 1f)))
+                        .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                                .setSpeedRange(new Builder.SpeedRange(1f)))
+                        .build(),
+                // Driving state with passenger mode
+                new Builder()
+                        .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(false)
+                                .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                                .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                        .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(true)
+                                .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                                .setMode(UX_RESTRICTION_MODE_BASELINE))
                         .build(),
         };
 
@@ -341,14 +446,19 @@
     }
 
     public void testDumpContainsNecessaryInfo() {
-
-        CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(0f, 1f),
-                        true, UX_RESTRICTIONS_NO_VIDEO)
-                .setUxRestrictions(DRIVING_STATE_MOVING,
-                        new CarUxRestrictionsConfiguration.Builder.SpeedRange(1f),
-                        true, UX_RESTRICTIONS_NO_VIDEO)
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                        .setSpeedRange(new Builder.SpeedRange(0f, 1f)))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO)
+                        .setSpeedRange(new Builder.SpeedRange(1f)))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
                 .build();
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         try (PrintWriter writer = new PrintWriter(output)) {
@@ -363,8 +473,74 @@
         assertTrue(dump.contains("Speed Range"));
         assertTrue(dump.contains("Requires DO?"));
         assertTrue(dump.contains("Restrictions"));
+        assertTrue(dump.contains("Passenger mode"));
+        assertTrue(dump.contains("Baseline mode"));
     }
 
+    public void testSetUxRestrictions_UnspecifiedModeDefaultsToBaseline() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
+                .build();
+
+        CarUxRestrictions restrictions = config.getUxRestrictions(DRIVING_STATE_PARKED, 0f);
+        assertTrue(restrictions.isRequiresDistractionOptimization());
+        assertEquals(UX_RESTRICTIONS_NO_VIDEO, restrictions.getActiveRestrictions());
+
+        assertTrue(restrictions.isSameRestrictions(
+                config.getUxRestrictions(DRIVING_STATE_PARKED, 0f, UX_RESTRICTIONS_BASELINE)));
+    }
+
+    public void testSetUxRestrictions_PassengerMode() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER))
+                .setUxRestrictions(DRIVING_STATE_PARKED, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
+                .build();
+
+        CarUxRestrictions passenger = config.getUxRestrictions(
+                DRIVING_STATE_PARKED, 0f, UX_RESTRICTION_MODE_PASSENGER);
+        assertFalse(passenger.isRequiresDistractionOptimization());
+
+        CarUxRestrictions baseline = config.getUxRestrictions(
+                DRIVING_STATE_PARKED, 0f, UX_RESTRICTION_MODE_BASELINE);
+        assertTrue(baseline.isRequiresDistractionOptimization());
+        assertEquals(UX_RESTRICTIONS_NO_VIDEO, baseline.getActiveRestrictions());
+    }
+
+    public void testPassengerModeFallbackToBaseline() {
+        CarUxRestrictionsConfiguration config = new Builder()
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(true)
+                        .setRestrictions(UX_RESTRICTIONS_NO_VIDEO))
+                .setUxRestrictions(DRIVING_STATE_MOVING, new DrivingStateRestrictions()
+                        .setDistractionOptimizationRequired(false)
+                        .setRestrictions(UX_RESTRICTIONS_BASELINE)
+                        .setMode(UX_RESTRICTION_MODE_PASSENGER)
+                        .setSpeedRange(new Builder.SpeedRange(3f)))
+                .build();
+
+        // Retrieve at speed within passenger mode range.
+        CarUxRestrictions passenger = config.getUxRestrictions(
+                DRIVING_STATE_MOVING, 5f, UX_RESTRICTION_MODE_PASSENGER);
+        assertFalse(passenger.isRequiresDistractionOptimization());
+
+        // Retrieve with passenger mode but outside speed range
+        CarUxRestrictions baseline = config.getUxRestrictions(
+                DRIVING_STATE_MOVING, 1f, UX_RESTRICTION_MODE_PASSENGER);
+        assertTrue(baseline.isRequiresDistractionOptimization());
+        assertEquals(UX_RESTRICTIONS_NO_VIDEO, baseline.getActiveRestrictions());
+    }
+
+    /**
+     * Writes input config as json, then reads a config out of json.
+     * Asserts the deserialized config is the same as input.
+     */
     private void verifyConfigThroughJsonSerialization(CarUxRestrictionsConfiguration config) {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out))) {
diff --git a/tests/carservice_test/src/com/android/car/AppFocusTest.java b/tests/carservice_test/src/com/android/car/AppFocusTest.java
index 910a132..3474d88 100644
--- a/tests/carservice_test/src/com/android/car/AppFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/AppFocusTest.java
@@ -43,19 +43,12 @@
         FocusChangedListener listener = new FocusChangedListener();
         FocusOwnershipCallback ownershipListener = new FocusOwnershipCallback();
         manager.addFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
-        manager.addFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND);
         manager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, ownershipListener);
         listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true);
-        manager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, ownershipListener);
-        listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true);
         manager.abandonAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
         listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false);
-        manager.abandonAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND);
-        listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false);
         manager.removeFocusListener(listener);
     }
 
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 a1ff59f..15e7feb 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
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,7 +38,6 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Handler;
@@ -70,7 +70,7 @@
     private Resources mResources;
     @Mock
     private CarUserManagerHelper mUserManager;
-    private UserInfo mUserInfo;
+    private int mUserId;
 
     @Mock
     private VmsClientManager.ConnectionListener mConnectionListener;
@@ -89,16 +89,17 @@
                 5);
         when(mResources.getStringArray(
                 com.android.car.R.array.vmsPublisherSystemClients)).thenReturn(
-                        new String[]{
-                                "com.google.android.apps.vms.test/.VmsSystemClient"
-                        });
+                new String[]{
+                        "com.google.android.apps.vms.test/.VmsSystemClient"
+                });
         when(mResources.getStringArray(
                 com.android.car.R.array.vmsPublisherUserClients)).thenReturn(
-                        new String[]{
-                                "com.google.android.apps.vms.test/.VmsUserClient"
-                        });
-        mUserInfo = new UserInfo(10, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+                new String[]{
+                        "com.google.android.apps.vms.test/.VmsUserClient"
+                });
+
+        mUserId = 10;
+        when(mUserManager.getCurrentForegroundUserId()).thenAnswer((invocation) -> mUserId);
 
         mClientManager = new VmsClientManager(mContext, mUserManager);
         mClientManager.registerConnectionListener(mConnectionListener);
@@ -244,12 +245,12 @@
 
     @Test
     public void testUserSwitchedToSystemUser() {
-        mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = UserHandle.USER_SYSTEM;
         notifyUserSwitched();
 
         // System user should not trigger any binding
-        verifyUserBind(0);
+        verifySystemBind(0);
+        verifyNoBind();
     }
 
     @Test
@@ -502,16 +503,14 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserSwitched();
 
         verify(mContext).unbindService(connection);
         verify(mConnectionListener).onClientDisconnected(
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     @Test
@@ -523,8 +522,7 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = UserHandle.USER_SYSTEM;
         notifyUserSwitched();
 
         verify(mContext).unbindService(connection);
@@ -532,8 +530,7 @@
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
         // User processes will not be bound for system user
-        verifyBind(0, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyNoBind();
     }
 
     @Test
@@ -543,13 +540,11 @@
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserSwitched();
 
         verify(mContext).unbindService(connection);
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     @Test
@@ -561,16 +556,14 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserUnlocked();
 
         verify(mContext).unbindService(connection);
         verify(mConnectionListener).onClientDisconnected(
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     @Test
@@ -582,8 +575,7 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = UserHandle.USER_SYSTEM;
         notifyUserUnlocked();
 
         verify(mContext).unbindService(connection);
@@ -591,8 +583,7 @@
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
         // User processes will not be bound for system user
-        verifyBind(0, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyNoBind();
     }
 
     @Test
@@ -602,13 +593,11 @@
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserUnlocked();
 
         verify(mContext).unbindService(connection);
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     private void resetContext() {
@@ -640,7 +629,12 @@
 
     private void verifyUserBind(int times) {
         verifyBind(times, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+                UserHandle.of(mUserId));
+    }
+
+    private void verifyNoBind() {
+        verify(mContext, never()).bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), any(Handler.class), any(UserHandle.class));
     }
 
     private void verifyBind(int times, String componentName,
diff --git a/tests/usb/AoapHostApp/src/com/google/android/car/usb/aoap/host/AoapSupportCheckService.java b/tests/usb/AoapHostApp/src/com/google/android/car/usb/aoap/host/AoapSupportCheckService.java
index 7825a66..f387a50 100644
--- a/tests/usb/AoapHostApp/src/com/google/android/car/usb/aoap/host/AoapSupportCheckService.java
+++ b/tests/usb/AoapHostApp/src/com/google/android/car/usb/aoap/host/AoapSupportCheckService.java
@@ -15,33 +15,15 @@
  */
 package com.google.android.car.usb.aoap.host;
 
-import android.app.Service;
-import android.car.IUsbAoapSupportCheckService;
-import android.content.Intent;
+import android.car.AoapService;
 import android.hardware.usb.UsbDevice;
-import android.os.IBinder;
 
 /**
  * Service to check is AOAP device supports Android Auto.
  */
-public class AoapSupportCheckService extends Service {
-
-    private final IUsbAoapSupportCheckService.Stub mBinder =
-            new IUsbAoapSupportCheckService.Stub() {
-                public boolean isDeviceSupported(UsbDevice device) {
-                    // TODO: do some check.
-                    return true;
-                }
-            };
-
+public class AoapSupportCheckService extends AoapService {
     @Override
-    public void onCreate() {
-        super.onCreate();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        // Return the interface
-        return mBinder;
+    public int isDeviceSupported(UsbDevice device) {
+        return RESULT_OK;
     }
 }
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java
index d78713f..24ee1ff 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java
@@ -47,7 +47,7 @@
 
 public class E2eCarTestBase {
     private static final String TAG = Utils.concatTag(E2eCarTestBase.class);
-    private static final int DEFAULT_WAIT_TIMEOUT_MS = 1000;
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 5000;
 
     protected IVehicle mVehicle;
     protected Car mCar;
@@ -56,7 +56,7 @@
 
     @Before
     public void connectToVehicleHal() throws Exception {
-        mVehicle = Utils.getVehicle();
+        mVehicle = Utils.getVehicleWithTimeout(DEFAULT_WAIT_TIMEOUT_MS);
         mVehicle.getPropConfigs(
                 Lists.newArrayList(VhalEventGenerator.GENERATE_FAKE_DATA_CONTROLLING_PROPERTY),
                 (status, propConfigs) -> assumeTrue(status == StatusCode.OK));
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
index 56ce568..6399549 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
@@ -183,10 +183,10 @@
 
 
         final int EXPECTED_INVOCATIONS = 1000;  // How many time get/set will be called.
-        final int EXPECTED_DURATION_MS = 2500;
+        final int EXPECTED_DURATION_MS = 3000;
         // This is a stress test and it can be flaky because it shares resources with all currently
         // running process. Let's have this number of attempt before giving up.
-        final int ATTEMPTS = 3;
+        final int ATTEMPTS = 5;
 
         for (int curAttempt = 0; curAttempt < ATTEMPTS; curAttempt++) {
             long missingInvocations = stressTestHvacProperties(mgr, cfg,
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
index 0d6048d..fb09ebd 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.car.vehiclehal.test.Utils.isVhalPropertyAvailable;
 import static com.android.car.vehiclehal.test.Utils.readVhalProperty;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
@@ -37,12 +38,12 @@
 /** Test retrieving the OBD2_FREEZE_FRAME property from VHAL */
 public class Obd2FreezeFrameTest {
     private static final String TAG = Utils.concatTag(Obd2FreezeFrameTest.class);
-
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 5000;
     private IVehicle mVehicle = null;
 
     @Before
     public void setUp() throws Exception {
-        mVehicle = Utils.getVehicle();
+        mVehicle = Utils.getVehicleWithTimeout(DEFAULT_WAIT_TIMEOUT_MS);
         assumeTrue("Freeze frame not available, test-case ignored.", isFreezeFrameAvailable());
     }
 
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
index 25f2454..627e032 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.car.vehiclehal.test.Utils.isVhalPropertyAvailable;
 import static com.android.car.vehiclehal.test.Utils.readVhalProperty;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
@@ -35,12 +36,12 @@
 /** Test retrieving the OBD2_LIVE_FRAME property from VHAL */
 public class Obd2LiveFrameTest {
     private static final String TAG = Utils.concatTag(Obd2LiveFrameTest.class);
-
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 5000;
     private IVehicle mVehicle = null;
 
     @Before
     public void setUp() throws Exception {
-        mVehicle = Utils.getVehicle();
+        mVehicle = Utils.getVehicleWithTimeout(DEFAULT_WAIT_TIMEOUT_MS);
         assumeTrue("Live frame not available, test-case ignored.", isLiveFrameAvailable());
     }
 
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
index 496b504..952f430 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
@@ -16,6 +16,9 @@
 
 package com.android.car.vehiclehal.test;
 
+import static android.os.SystemClock.elapsedRealtime;
+
+import android.annotation.Nullable;
 import android.car.hardware.CarPropertyValue;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
@@ -81,15 +84,32 @@
         return readVhalProperty(vehicle, request, f);
     }
 
-    static IVehicle getVehicle() throws RemoteException {
-        IVehicle service;
+    @Nullable
+    private static IVehicle getVehicle() {
         try {
-            service = IVehicle.getService();
+            return IVehicle.getService();
         } catch (NoSuchElementException ex) {
-            throw new RuntimeException("Couldn't connect to vehicle@2.0", ex);
+            Log.e(TAG, "IVehicle service not registered yet", ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get IVehicle Service ", e);
         }
-        Log.d(TAG, "Connected to IVehicle service: " + service);
-        return service;
+        Log.d(TAG, "Failed to connect to IVehicle service");
+        return null;
+    }
+
+    static IVehicle getVehicleWithTimeout(long waitMilliseconds) {
+        IVehicle vehicle = getVehicle();
+        long endTime = elapsedRealtime() + waitMilliseconds;
+        while (vehicle == null && endTime > elapsedRealtime()) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Sleep was interrupted", e);
+            }
+            vehicle = getVehicle();
+        }
+
+        return vehicle;
     }
 
     /**