Merge "Fix permission check in car-usb-handler"
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 120676f..0975e90 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -59,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();
@@ -830,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;
@@ -876,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;
@@ -904,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;
@@ -917,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();
@@ -945,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);
@@ -976,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;
@@ -996,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();
@@ -1038,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/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/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/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/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 2ce3d45..8d37772 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -269,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" />
@@ -289,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"
@@ -303,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..1d3fd1b 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -129,6 +129,10 @@
          device specific model id. -->
     <integer name="fastPairModelId">0x000000</integer>
 
+    <!-- Maximum allowed time to run garage mode. Note that 15 min (=900sec) is the minimum required
+         duration and this should not be made shorter. -->
+    <integer name="maxGarageModeRunningDurationInSecs">900</integer>
+
     <!-- The garage mode configuration, specifying the time after shutdown to reboot into garage
          mode and the number of attempts at that time to try before moving to the next wake up
          time. This is intended to be a back-off pattern. -->
@@ -158,4 +162,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 3121d1d..325788b 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -253,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/CarBluetoothUserService.java b/service/src/com/android/car/CarBluetoothUserService.java
index 5a57f19..3b2701a 100644
--- a/service/src/com/android/car/CarBluetoothUserService.java
+++ b/service/src/com/android/car/CarBluetoothUserService.java
@@ -109,42 +109,40 @@
      */
     @Override
     public boolean isBluetoothConnectionProxyAvailable(int profile) {
-        switch (profile) {
-            case BluetoothProfile.A2DP_SINK:
-                if (mBluetoothA2dpSink != null) {
-                    return true;
-                }
-                break;
-            case BluetoothProfile.HEADSET_CLIENT:
-                if (mBluetoothHeadsetClient != null) {
-                    return true;
-                }
-                break;
-            case BluetoothProfile.PBAP_CLIENT:
-                if (mBluetoothPbapClient != null) {
-                    return true;
-                }
-                break;
-            case BluetoothProfile.MAP_CLIENT:
-                if (mBluetoothMapClient != null) {
-                    return true;
-                }
-                break;
-            case BluetoothProfile.PAN:
-                if (mBluetoothPan != null) {
-                    return true;
-                }
-                break;
+        synchronized (this) {
+            switch (profile) {
+                case BluetoothProfile.A2DP_SINK:
+                    if (mBluetoothA2dpSink != null) {
+                        return true;
+                    }
+                    break;
+                case BluetoothProfile.HEADSET_CLIENT:
+                    if (mBluetoothHeadsetClient != null) {
+                        return true;
+                    }
+                    break;
+                case BluetoothProfile.PBAP_CLIENT:
+                    if (mBluetoothPbapClient != null) {
+                        return true;
+                    }
+                    break;
+                case BluetoothProfile.MAP_CLIENT:
+                    if (mBluetoothMapClient != null) {
+                        return true;
+                    }
+                    break;
+                case BluetoothProfile.PAN:
+                    if (mBluetoothPan != null) {
+                        return true;
+                    }
+                    break;
+            }
         }
         return false;
     }
 
     @Override
     public void bluetoothConnectToProfile(int profile, BluetoothDevice device) {
-        if (!isBluetoothConnectionProxyAvailable(profile)) {
-            Log.e(TAG, "Cannot connect to Profile. Proxy Unavailable");
-            return;
-        }
         if (device == null) {
             Log.e(TAG, "Cannot connect to profile on null device");
             return;
@@ -153,39 +151,36 @@
             Log.d(TAG, "Trying to connect to " + device.getName() + " (" + device.getAddress()
                     + ") Profile: " + Utils.getProfileName(profile));
         }
-        switch (profile) {
-            case BluetoothProfile.A2DP_SINK:
-                mBluetoothA2dpSink.connect(device);
-                break;
-
-            case BluetoothProfile.HEADSET_CLIENT:
-                mBluetoothHeadsetClient.connect(device);
-                break;
-
-            case BluetoothProfile.MAP_CLIENT:
-                mBluetoothMapClient.connect(device);
-                break;
-
-            case BluetoothProfile.PBAP_CLIENT:
-                mBluetoothPbapClient.connect(device);
-                break;
-
-            case BluetoothProfile.PAN:
-                mBluetoothPan.connect(device);
-                break;
-
-            default:
-                Log.d(TAG, "Unknown profile");
-                break;
+        synchronized (this) {
+            if (!isBluetoothConnectionProxyAvailable(profile)) {
+                Log.e(TAG, "Cannot connect to Profile. Proxy Unavailable");
+                return;
+            }
+            switch (profile) {
+                case BluetoothProfile.A2DP_SINK:
+                    mBluetoothA2dpSink.connect(device);
+                    break;
+                case BluetoothProfile.HEADSET_CLIENT:
+                    mBluetoothHeadsetClient.connect(device);
+                    break;
+                case BluetoothProfile.MAP_CLIENT:
+                    mBluetoothMapClient.connect(device);
+                    break;
+                case BluetoothProfile.PBAP_CLIENT:
+                    mBluetoothPbapClient.connect(device);
+                    break;
+                case BluetoothProfile.PAN:
+                    mBluetoothPan.connect(device);
+                    break;
+                default:
+                    Log.d(TAG, "Unknown profile");
+                    break;
+            }
         }
     }
 
     @Override
     public void bluetoothDisconnectFromProfile(int profile, BluetoothDevice device) {
-        if (!isBluetoothConnectionProxyAvailable(profile)) {
-            Log.e(TAG, "Cannot disconnect from profile. Proxy Unavailable");
-            return;
-        }
         if (device == null) {
             Log.e(TAG, "Cannot disconnect from profile on null device");
             return;
@@ -194,30 +189,31 @@
             Log.d(TAG, "Trying to disconnect from " + device.getName() + " (" + device.getAddress()
                     + ") Profile: " + Utils.getProfileName(profile));
         }
-        switch (profile) {
-            case BluetoothProfile.A2DP_SINK:
-                mBluetoothA2dpSink.disconnect(device);
-                break;
-
-            case BluetoothProfile.HEADSET_CLIENT:
-                mBluetoothHeadsetClient.disconnect(device);
-                break;
-
-            case BluetoothProfile.MAP_CLIENT:
-                mBluetoothMapClient.disconnect(device);
-                break;
-
-            case BluetoothProfile.PBAP_CLIENT:
-                mBluetoothPbapClient.disconnect(device);
-                break;
-
-            case BluetoothProfile.PAN:
-                mBluetoothPan.disconnect(device);
-                break;
-
-            default:
-                Log.d(TAG, "Unknown profile");
-                break;
+        synchronized (this) {
+            if (!isBluetoothConnectionProxyAvailable(profile)) {
+                Log.e(TAG, "Cannot disconnect from profile. Proxy Unavailable");
+                return;
+            }
+            switch (profile) {
+                case BluetoothProfile.A2DP_SINK:
+                    mBluetoothA2dpSink.disconnect(device);
+                    break;
+                case BluetoothProfile.HEADSET_CLIENT:
+                    mBluetoothHeadsetClient.disconnect(device);
+                    break;
+                case BluetoothProfile.MAP_CLIENT:
+                    mBluetoothMapClient.disconnect(device);
+                    break;
+                case BluetoothProfile.PBAP_CLIENT:
+                    mBluetoothPbapClient.disconnect(device);
+                    break;
+                case BluetoothProfile.PAN:
+                    mBluetoothPan.disconnect(device);
+                    break;
+                default:
+                    Log.d(TAG, "Unknown profile");
+                    break;
+            }
         }
     }
 
@@ -228,31 +224,33 @@
      */
     @Override
     public int getProfilePriority(int profile, BluetoothDevice device) {
-        if (!isBluetoothConnectionProxyAvailable(profile)) {
-            Log.e(TAG, "Cannot get profile priority. Proxy Unavailable");
-            return BluetoothProfile.PRIORITY_UNDEFINED;
-        }
         if (device == null) {
             Log.e(TAG, "Cannot get profile priority on null device");
             return BluetoothProfile.PRIORITY_UNDEFINED;
         }
         int priority;
-        switch (profile) {
-            case BluetoothProfile.A2DP_SINK:
-                priority = mBluetoothA2dpSink.getPriority(device);
-                break;
-            case BluetoothProfile.HEADSET_CLIENT:
-                priority = mBluetoothHeadsetClient.getPriority(device);
-                break;
-            case BluetoothProfile.MAP_CLIENT:
-                priority = mBluetoothMapClient.getPriority(device);
-                break;
-            case BluetoothProfile.PBAP_CLIENT:
-                priority = mBluetoothPbapClient.getPriority(device);
-                break;
-            default:
-                Log.d(TAG, "Unknown Profile");
+        synchronized (this) {
+            if (!isBluetoothConnectionProxyAvailable(profile)) {
+                Log.e(TAG, "Cannot get profile priority. Proxy Unavailable");
                 return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
+            switch (profile) {
+                case BluetoothProfile.A2DP_SINK:
+                    priority = mBluetoothA2dpSink.getPriority(device);
+                    break;
+                case BluetoothProfile.HEADSET_CLIENT:
+                    priority = mBluetoothHeadsetClient.getPriority(device);
+                    break;
+                case BluetoothProfile.MAP_CLIENT:
+                    priority = mBluetoothMapClient.getPriority(device);
+                    break;
+                case BluetoothProfile.PBAP_CLIENT:
+                    priority = mBluetoothPbapClient.getPriority(device);
+                    break;
+                default:
+                    Log.d(TAG, "Unknown Profile");
+                    return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
         }
         if (DBG) {
             Log.d(TAG, Utils.getProfileName(profile) + " priority for " + device.getName() + " ("
@@ -269,10 +267,6 @@
      */
     @Override
     public void setProfilePriority(int profile, BluetoothDevice device, int priority) {
-        if (!isBluetoothConnectionProxyAvailable(profile)) {
-            Log.e(TAG, "Cannot set profile priority. Proxy Unavailable");
-            return;
-        }
         if (device == null) {
             Log.e(TAG, "Cannot set profile priority on null device");
             return;
@@ -281,24 +275,31 @@
             Log.d(TAG, "Setting " + Utils.getProfileName(profile) + " priority for "
                     + device.getName() + " (" + device.getAddress() + ") to " + priority);
         }
-        switch (profile) {
-            case BluetoothProfile.A2DP_SINK:
-                mBluetoothA2dpSink.setPriority(device, priority);
-                break;
-            case BluetoothProfile.HEADSET_CLIENT:
-                mBluetoothHeadsetClient.setPriority(device, priority);
-                break;
-            case BluetoothProfile.MAP_CLIENT:
-                mBluetoothMapClient.setPriority(device, priority);
-                break;
-            case BluetoothProfile.PBAP_CLIENT:
-                mBluetoothPbapClient.setPriority(device, priority);
-                break;
-            default:
-                Log.d(TAG, "Unknown Profile");
-                break;
+        synchronized (this) {
+            if (!isBluetoothConnectionProxyAvailable(profile)) {
+                Log.e(TAG, "Cannot set profile priority. Proxy Unavailable");
+                return;
+            }
+            switch (profile) {
+                case BluetoothProfile.A2DP_SINK:
+                    mBluetoothA2dpSink.setPriority(device, priority);
+                    break;
+                case BluetoothProfile.HEADSET_CLIENT:
+                    mBluetoothHeadsetClient.setPriority(device, priority);
+                    break;
+                case BluetoothProfile.MAP_CLIENT:
+                    mBluetoothMapClient.setPriority(device, priority);
+                    break;
+                case BluetoothProfile.PBAP_CLIENT:
+                    mBluetoothPbapClient.setPriority(device, priority);
+                    break;
+                default:
+                    Log.d(TAG, "Unknown Profile");
+                    break;
+            }
         }
     }
+
     /**
      * All the BluetoothProfile.ServiceListeners to get the Profile Proxy objects
      */
@@ -308,32 +309,30 @@
                     if (DBG) {
                         Log.d(TAG, "OnServiceConnected profile: " + profile);
                     }
-                    switch (profile) {
-                        case BluetoothProfile.A2DP_SINK:
-                            mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
-                            break;
-
-                        case BluetoothProfile.HEADSET_CLIENT:
-                            mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
-                            break;
-
-                        case BluetoothProfile.PBAP_CLIENT:
-                            mBluetoothPbapClient = (BluetoothPbapClient) proxy;
-                            break;
-
-                        case BluetoothProfile.MAP_CLIENT:
-                            mBluetoothMapClient = (BluetoothMapClient) proxy;
-                            break;
-
-                        case BluetoothProfile.PAN:
-                            mBluetoothPan = (BluetoothPan) proxy;
-                            break;
-
-                        default:
-                            if (DBG) {
-                                Log.d(TAG, "Unhandled profile");
-                            }
-                            break;
+                    synchronized (this) {
+                        switch (profile) {
+                            case BluetoothProfile.A2DP_SINK:
+                                mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
+                                break;
+                            case BluetoothProfile.HEADSET_CLIENT:
+                                mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
+                                break;
+                            case BluetoothProfile.PBAP_CLIENT:
+                                mBluetoothPbapClient = (BluetoothPbapClient) proxy;
+                                break;
+                            case BluetoothProfile.MAP_CLIENT:
+                                mBluetoothMapClient = (BluetoothMapClient) proxy;
+                                break;
+                            case BluetoothProfile.PAN:
+                                mBluetoothPan = (BluetoothPan) proxy;
+                                break;
+                            default:
+                                if (DBG) {
+                                    Log.d(TAG, "Unhandled profile connected: "
+                                            + Utils.getProfileName(profile));
+                                }
+                                break;
+                        }
                     }
                 }
 
@@ -341,32 +340,30 @@
                     if (DBG) {
                         Log.d(TAG, "onServiceDisconnected profile: " + profile);
                     }
-                    switch (profile) {
-                        case BluetoothProfile.A2DP_SINK:
-                            mBluetoothA2dpSink = null;
-                            break;
-
-                        case BluetoothProfile.HEADSET_CLIENT:
-                            mBluetoothHeadsetClient = null;
-                            break;
-
-                        case BluetoothProfile.PBAP_CLIENT:
-                            mBluetoothPbapClient = null;
-                            break;
-
-                        case BluetoothProfile.MAP_CLIENT:
-                            mBluetoothMapClient = null;
-                            break;
-
-                        case BluetoothProfile.PAN:
-                            mBluetoothPan = null;
-                            break;
-
-                        default:
-                            if (DBG) {
-                                Log.d(TAG, "Unhandled profile");
-                            }
-                            break;
+                    synchronized (this) {
+                        switch (profile) {
+                            case BluetoothProfile.A2DP_SINK:
+                                mBluetoothA2dpSink = null;
+                                break;
+                            case BluetoothProfile.HEADSET_CLIENT:
+                                mBluetoothHeadsetClient = null;
+                                break;
+                            case BluetoothProfile.PBAP_CLIENT:
+                                mBluetoothPbapClient = null;
+                                break;
+                            case BluetoothProfile.MAP_CLIENT:
+                                mBluetoothMapClient = null;
+                                break;
+                            case BluetoothProfile.PAN:
+                                mBluetoothPan = null;
+                                break;
+                            default:
+                                if (DBG) {
+                                    Log.d(TAG, "Unhandled profile disconnected: "
+                                            + Utils.getProfileName(profile));
+                                }
+                                break;
+                        }
                     }
                 }
             };
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/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 8cf7c46..0780889 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -57,6 +57,7 @@
     private final SystemInterface mSystemInterface;
     private final PowerManagerCallbackList mPowerManagerListeners = new PowerManagerCallbackList();
     private final Map<IBinder, Integer> mPowerManagerListenerTokens = new ConcurrentHashMap<>();
+    private final Object mSimulationSleepObject = new Object();
 
     @GuardedBy("this")
     private CpmsState mCurrentState;
@@ -74,6 +75,10 @@
     private PowerHandler mHandler;
     @GuardedBy("this")
     private boolean mTimerActive;
+    @GuardedBy("mSimulationSleepObject")
+    private boolean mInSimulatedDeepSleepMode = false;
+    @GuardedBy("mSimulationSleepObject")
+    private boolean mWakeFromSimulatedSleep = false;
     private int mNextWakeupSec = 0;
     private int mTokenValue = 1;
     private boolean mShutdownOnFinish = false;
@@ -85,8 +90,10 @@
     private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
     private static final int SHUTDOWN_EXTEND_MAX_MS = 5000;
 
-    // Use one hour for now
-    private static int sShutdownPrepareTimeMs = 60 * 60 * 1000;
+    // maxGarageModeRunningDurationInSecs should be equal or greater than this. 15 min for now.
+    private static final int MIN_MAX_GARAGE_MODE_DURATION_MS = 15 * 60 * 1000;
+
+    private static int sShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
 
     private class PowerManagerCallbackList extends RemoteCallbackList<ICarPowerStateListener> {
         /**
@@ -107,6 +114,15 @@
         mHal = powerHal;
         mSystemInterface = systemInterface;
         mCarUserManagerHelper = carUserManagerHelper;
+        sShutdownPrepareTimeMs = mContext.getResources().getInteger(
+                R.integer.maxGarageModeRunningDurationInSecs) * 1000;
+        if (sShutdownPrepareTimeMs < MIN_MAX_GARAGE_MODE_DURATION_MS) {
+            Log.w(CarLog.TAG_POWER,
+                    "maxGarageModeRunningDurationInSecs smaller than minimum required, resource:"
+                    + sShutdownPrepareTimeMs + "(ms) while should exceed:"
+                    +  MIN_MAX_GARAGE_MODE_DURATION_MS + "(ms), Ignore resource.");
+            sShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
+        }
     }
 
     /**
@@ -135,7 +151,7 @@
 
     @Override
     public void init() {
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             mHandlerThread = new HandlerThread(CarLog.TAG_POWER);
             mHandlerThread.start();
             mHandler = new PowerHandler(mHandlerThread.getLooper());
@@ -155,7 +171,7 @@
     @Override
     public void release() {
         HandlerThread handlerThread;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             releaseTimerLocked();
             mCurrentState = null;
             mHandler.cancelAll();
@@ -181,13 +197,14 @@
         writer.print(",mLastSleepEntryTime:" + mLastSleepEntryTime);
         writer.print(",mNextWakeupSec:" + mNextWakeupSec);
         writer.print(",mTokenValue:" + mTokenValue);
-        writer.println(",mShutdownOnFinish:" + mShutdownOnFinish);
+        writer.print(",mShutdownOnFinish:" + mShutdownOnFinish);
+        writer.println(",sShutdownPrepareTimeMs:" + sShutdownPrepareTimeMs);
     }
 
     @Override
     public void onApPowerStateChange(PowerState state) {
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             mPendingPowerStates.addFirst(new CpmsState(state));
             handler = mHandler;
         }
@@ -205,7 +222,7 @@
     private void onApPowerStateChange(int apState, int carPowerStateListenerState) {
         CpmsState newState = new CpmsState(apState, carPowerStateListenerState);
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             mPendingPowerStates.addFirst(newState);
             handler = mHandler;
         }
@@ -215,13 +232,13 @@
     private void doHandlePowerStateChange() {
         CpmsState state;
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             state = mPendingPowerStates.peekFirst();
             mPendingPowerStates.clear();
             if (state == null) {
                 return;
             }
-            Log.i(CarLog.TAG_POWER, "doHandlePowerStateChange: newState=" + state.mState);
+            Log.i(CarLog.TAG_POWER, "doHandlePowerStateChange: newState=" + state.name());
             if (!needPowerStateChangeLocked(state)) {
                 Log.d(CarLog.TAG_POWER, "doHandlePowerStateChange no change needed");
                 return;
@@ -243,6 +260,9 @@
             case CpmsState.SHUTDOWN_PREPARE:
                 handleShutdownPrepare(state);
                 break;
+            case CpmsState.SIMULATE_SLEEP:
+                simulateShutdownPrepare();
+                break;
             case CpmsState.WAIT_FOR_FINISH:
                 handleWaitForFinish(state);
                 break;
@@ -298,13 +318,13 @@
                 || !mSystemInterface.isSystemSupportingDeepSleep()
                 || !newState.mCanSleep;
         if (newState.mCanPostpone) {
-            Log.i(CarLog.TAG_POWER, "starting shutdown postpone");
+            Log.i(CarLog.TAG_POWER, "starting shutdown prepare");
             sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE);
             mHal.sendShutdownPrepare();
             doHandlePreprocessing();
         } else {
             Log.i(CarLog.TAG_POWER, "starting shutdown immediately");
-            synchronized (this) {
+            synchronized (CarPowerManagementService.this) {
                 releaseTimerLocked();
             }
             // Notify hal that we are shutting down and since it is immediate, don't schedule next
@@ -315,6 +335,15 @@
         }
     }
 
+    // Simulate system shutdown to Deep Sleep
+    private void simulateShutdownPrepare() {
+        mSystemInterface.setDisplayState(false);
+        Log.i(CarLog.TAG_POWER, "starting shutdown prepare");
+        sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE);
+        mHal.sendShutdownPrepare();
+        doHandlePreprocessing();
+    }
+
     private void handleWaitForFinish(CpmsState state) {
         sendPowerManagerEvent(state.mCarPowerStateListenerState);
         switch (state.mCarPowerStateListenerState) {
@@ -328,17 +357,23 @@
     }
 
     private void handleFinish() {
-        if (mShutdownOnFinish) {
+        boolean mustShutDown;
+        boolean simulatedMode;
+        synchronized (mSimulationSleepObject) {
+            simulatedMode = mInSimulatedDeepSleepMode;
+            mustShutDown = mShutdownOnFinish && !simulatedMode;
+        }
+        if (mustShutDown) {
             // shutdown HU
             mSystemInterface.shutdown();
         } else {
-            doHandleDeepSleep();
+            doHandleDeepSleep(simulatedMode);
         }
     }
 
     @GuardedBy("this")
     private void releaseTimerLocked() {
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             if (mTimer != null) {
                 mTimer.cancel();
             }
@@ -351,7 +386,7 @@
         int pollingCount = (sShutdownPrepareTimeMs / SHUTDOWN_POLLING_INTERVAL_MS) + 1;
         Log.i(CarLog.TAG_POWER, "processing before shutdown expected for: "
                 + sShutdownPrepareTimeMs + " ms, adding polling:" + pollingCount);
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             mProcessingStartTime = SystemClock.elapsedRealtime();
             releaseTimerLocked();
             mTimer = new Timer();
@@ -409,23 +444,29 @@
         }
     }
 
-    private void doHandleDeepSleep() {
+    private void doHandleDeepSleep(boolean simulatedMode) {
         // keep holding partial wakelock to prevent entering sleep before enterDeepSleep call
         // enterDeepSleep should force sleep entry even if wake lock is kept.
         mSystemInterface.switchToPartialWakeLock();
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             handler = mHandler;
         }
         handler.cancelProcessingComplete();
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             mLastSleepEntryTime = SystemClock.elapsedRealtime();
         }
-        if (!mSystemInterface.enterDeepSleep()) {
-            // System did not suspend.  VHAL should transition CPMS to shutdown.
-            Log.e(CarLog.TAG_POWER, "Sleep did not succeed.  Need to shutdown");
+        if (simulatedMode) {
+            simulateSleepByLooping();
+        } else {
+            boolean sleepSucceeded = mSystemInterface.enterDeepSleep();
+            if (!sleepSucceeded) {
+                // VHAL should transition CPMS to shutdown.
+                Log.e(CarLog.TAG_POWER, "Sleep did not succeed. Now attempting to shut down.");
+                mSystemInterface.shutdown();
+            }
         }
-        // On wake, reset nextWakeup time.  If not set again, system will suspend/shutdown forever.
+        // On wake, reset nextWakeup time. If not set again, system will suspend/shutdown forever.
         mNextWakeupSec = 0;
         mSystemInterface.refreshDisplayBrightness();
         onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerStateListener.SUSPEND_EXIT);
@@ -448,23 +489,26 @@
             case CpmsState.SUSPEND:
                 return newState.mState == CpmsState.WAIT_FOR_VHAL;
             case CpmsState.ON:
-                return newState.mState == CpmsState.SHUTDOWN_PREPARE;
+                return (newState.mState == CpmsState.SHUTDOWN_PREPARE)
+                    || (newState.mState == CpmsState.SIMULATE_SLEEP);
             case CpmsState.SHUTDOWN_PREPARE:
                 // If VHAL sends SHUTDOWN_IMMEDIATELY while in SHUTDOWN_PREPARE state, do it.
                 return ((newState.mState == CpmsState.SHUTDOWN_PREPARE) && !newState.mCanPostpone)
                     || (newState.mState == CpmsState.WAIT_FOR_FINISH)
                     || (newState.mState == CpmsState.WAIT_FOR_VHAL);
+            case CpmsState.SIMULATE_SLEEP:
+                return true;
             case CpmsState.WAIT_FOR_FINISH:
                 return newState.mState == CpmsState.SUSPEND;
             default:
                 Log.e(CarLog.TAG_POWER, "Unhandled state transition:  currentState="
-                        + mCurrentState.mState + ", newState=" + newState.mState);
+                        + mCurrentState.name() + ", newState=" + newState.name());
                 return false;
         }
     }
 
     private void doHandleProcessingComplete() {
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             releaseTimerLocked();
             if (!mShutdownOnFinish && mLastSleepEntryTime > mProcessingStartTime) {
                 // entered sleep after processing start. So this could be duplicate request.
@@ -483,7 +527,7 @@
     @Override
     public void onDisplayBrightnessChange(int brightness) {
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             handler = mHandler;
         }
         handler.handleDisplayBrightnessChange(brightness);
@@ -499,7 +543,7 @@
 
     public void handleMainDisplayChanged(boolean on) {
         PowerHandler handler;
-        synchronized (this) {
+        synchronized (CarPowerManagementService.this) {
             handler = mHandler;
         }
         handler.handleMainDisplayStateChange(on);
@@ -575,7 +619,7 @@
             mNextWakeupSec = seconds;
         } else {
             Log.d(CarLog.TAG_POWER, "Tried to schedule next wake up, but already had shorter "
-                    + " scheduled time");
+                    + "scheduled time");
         }
     }
 
@@ -584,10 +628,11 @@
         if (currentToken == token) {
             mPowerManagerListenerTokens.remove(binder);
             if (mPowerManagerListenerTokens.isEmpty() &&
-                    (mCurrentState.mState == CpmsState.SHUTDOWN_PREPARE)) {
+                    (mCurrentState.mState == CpmsState.SHUTDOWN_PREPARE
+                     || mCurrentState.mState == CpmsState.SIMULATE_SLEEP)) {
                 PowerHandler powerHandler;
                 // All apps are ready to shutdown/suspend.
-                synchronized (this) {
+                synchronized (CarPowerManagementService.this) {
                     if (!mShutdownOnFinish) {
                         if (mLastSleepEntryTime > mProcessingStartTime
                                 && mLastSleepEntryTime < SystemClock.elapsedRealtime()) {
@@ -680,7 +725,7 @@
 
         @Override
         public void run() {
-            synchronized (this) {
+            synchronized (CarPowerManagementService.this) {
                 if (!mTimerActive) {
                     // Ignore timer expiration since we got cancelled
                     return;
@@ -704,6 +749,7 @@
         public static final int SHUTDOWN_PREPARE = 2;
         public static final int WAIT_FOR_FINISH = 3;
         public static final int SUSPEND = 4;
+        public static final int SIMULATE_SLEEP = 5;
 
         /* Config values from AP_POWER_STATE_REQ */
         public final boolean mCanPostpone;
@@ -759,12 +805,26 @@
         }
 
         CpmsState(int state, int carPowerStateListenerState) {
-            this.mCanPostpone = false;
-            this.mCanSleep = false;
+            this.mCanPostpone = (state == SIMULATE_SLEEP);
+            this.mCanSleep = (state == SIMULATE_SLEEP);
             this.mCarPowerStateListenerState = carPowerStateListenerState;
             this.mState = state;
         }
 
+        public String name() {
+            String baseName;
+            switch(mState) {
+                case WAIT_FOR_VHAL:     baseName = "WAIT_FOR_VHAL";    break;
+                case ON:                baseName = "ON";               break;
+                case SHUTDOWN_PREPARE:  baseName = "SHUTDOWN_PREPARE"; break;
+                case WAIT_FOR_FINISH:   baseName = "WAIT_FOR_FINISH";  break;
+                case SUSPEND:           baseName = "SUSPEND";          break;
+                case SIMULATE_SLEEP:    baseName = "SIMULATE_SLEEP";   break;
+                default:                baseName = "<unknown>";        break;
+            }
+            return baseName + "(" + mState + ")";
+        }
+
         private static int cpmsStateToPowerStateListenerState(int state) {
             int powerStateListenerState = 0;
 
@@ -807,8 +867,57 @@
         public String toString() {
             return "CpmsState canSleep:" + mCanSleep + ", canPostpone=" + mCanPostpone
                     + ", carPowerStateListenerState=" + mCarPowerStateListenerState
-                    + ", CpmsState=" + mState;
+                    + ", CpmsState=" + this.name();
         }
     }
 
+    /**
+     * Resume after a manually-invoked suspend.
+     * Invoked using "adb shell dumpsys activity service com.android.car resume".
+     */
+    public void forceSimulatedResume() {
+        synchronized (mSimulationSleepObject) {
+            mWakeFromSimulatedSleep = true;
+            mSimulationSleepObject.notify();
+        }
+    }
+
+    /**
+     * Manually enter simulated suspend (Deep Sleep) mode
+     * Invoked using "adb shell dumpsys activity service com.android.car suspend".
+     * This is similar to 'onApPowerStateChange()' except that it needs to create a CpmsState
+     * that is not directly derived from a VehicleApPowerStateReq.
+     */
+    public void forceSimulatedSuspend() {
+        synchronized (mSimulationSleepObject) {
+            mInSimulatedDeepSleepMode = true;
+            mWakeFromSimulatedSleep = false;
+        }
+        PowerHandler handler;
+        synchronized (this) {
+            mPendingPowerStates.addFirst(new CpmsState(CpmsState.SIMULATE_SLEEP,
+                                                       CarPowerStateListener.SHUTDOWN_PREPARE));
+            handler = mHandler;
+        }
+        handler.handlePowerStateChange();
+    }
+
+    // In a real Deep Sleep, the hardware removes power from the CPU (but retains power
+    // on the RAM). This puts the processor to sleep. Upon some external signal, power
+    // is re-applied to the CPU, and processing resumes right where it left off.
+    // We simulate this behavior by simply going into a loop.
+    // We exit the loop when forceResume() is called.
+    private void simulateSleepByLooping() {
+        Log.i(CarLog.TAG_POWER, "Starting to simulate Deep Sleep by looping");
+        synchronized (mSimulationSleepObject) {
+            while (!mWakeFromSimulatedSleep) {
+                try {
+                    mSimulationSleepObject.wait();
+                } catch (InterruptedException ignored) {
+                }
+            }
+            mInSimulatedDeepSleepMode = false;
+        }
+        Log.i(CarLog.TAG_POWER, "Exit Deep Sleep simulation loop");
+    }
 }
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..d508d5e 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:
@@ -476,6 +479,8 @@
         private static final String COMMAND_GET_DO_ACTIVITIES = "get-do-activities";
         private static final String COMMAND_GET_CARPROPERTYCONFIG = "get-carpropertyconfig";
         private static final String COMMAND_PROJECTION_UI_MODE = "projection-ui-mode";
+        private static final String COMMAND_RESUME = "resume";
+        private static final String COMMAND_SUSPEND = "suspend";
 
         private static final String PARAM_DAY_MODE = "day";
         private static final String PARAM_NIGHT_MODE = "night";
@@ -502,6 +507,10 @@
             pw.println("\t  Get Distraction Optimized activities in given package.");
             pw.println("\tget-carpropertyconfig [propertyId]");
             pw.println("\t  Get a CarPropertyConfig by Id in Hex or list all CarPropertyConfigs");
+            pw.println("\tsuspend");
+            pw.println("\t  Suspend the system to Deep Sleep.");
+            pw.println("\tresume");
+            pw.println("\t  Wake the system up after a 'suspend.'");
         }
 
         public void exec(String[] args, PrintWriter writer) {
@@ -580,6 +589,15 @@
                         break;
                     }
                     mCarProjectionService.setUiMode(Integer.valueOf(args[1]));
+                    break;
+                case COMMAND_RESUME:
+                    mCarPowerManagementService.forceSimulatedResume();
+                    writer.println("Resume: Simulating resuming from Deep Sleep");
+                    break;
+                case COMMAND_SUSPEND:
+                    mCarPowerManagementService.forceSimulatedSuspend();
+                    writer.println("Resume: Simulating powering down to Deep Sleep");
+                    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/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 97f284b..719046c 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -143,6 +143,7 @@
     // slightly differently.
     // If a focus request for the same listener (clientId) is received while that listener is
     // already in the focus stack, we REJECT it outright unless it is for the same USAGE.
+    // If it is for the same USAGE, we replace the old request with the new one.
     // The default audio framework's behavior is to remove the previous entry in the stack (no-op
     // if the requester is already holding focus).
     int evaluateFocusRequest(AudioFocusInfo afi) {
@@ -164,8 +165,11 @@
         final int requestedContext = mCarAudioService.getContextForUsage(
                 afi.getAttributes().getUsage());
 
-        // If we happen find an entry that this new request should replace, we'll store it here.
-        FocusEntry deprecatedBlockedEntry = null;
+        // If we happen to find entries that this new request should replace, we'll store them here.
+        // This happens when a client makes a second AF request on the same listener.
+        // After we've granted audio focus to our current request, we'll abandon these requests.
+        FocusEntry replacedCurrentEntry = null;
+        FocusEntry replacedBlockedEntry = null;
 
         // Scan all active and pending focus requests.  If any should cause rejection of
         // this new request, then we're done.  Keep a list of those against whom we're exclusive
@@ -173,7 +177,7 @@
         Log.i(TAG, "Scanning focus holders...");
         final ArrayList<FocusEntry> losers = new ArrayList<FocusEntry>();
         for (FocusEntry entry : mFocusHolders.values()) {
-            Log.i(TAG, entry.mAfi.getClientId());
+            Log.d(TAG, "Evaluating focus holder: " + entry.getClientId());
 
             // If this request is for Notifications and a current focus holder has specified
             // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request.
@@ -190,12 +194,18 @@
             // (because the app would have no way to know to which request a later event applied)
             if (afi.getClientId().equals(entry.mAfi.getClientId())) {
                 if (entry.mAudioContext == requestedContext) {
-                    // Trivially accept if this request is a duplicate
-                    Log.i(TAG, "Duplicate request from focus holder is accepted");
-                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+                    // This is a request from a current focus holder.
+                    // Abandon the previous request (without sending a LOSS notification to it),
+                    // and don't check the interaction matrix for it.
+                    Log.i(TAG, "Replacing accepted request from same client");
+                    replacedCurrentEntry = entry;
+                    continue;
                 } else {
                     // Trivially reject a request for a different USAGE
-                    Log.i(TAG, "Different request from focus holder is rejected");
+                    Log.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
+                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
+                            + "request focus for " + afi.getAttributes().usageToString() + " on "
+                            + "same listener.");
                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                 }
             }
@@ -214,9 +224,7 @@
                     // must get a LOSS.
                     // If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
                     // they must get a LOSS message even if ducking would otherwise be allowed.
-                    if ((!allowDucking) ||
-                            (entry.mAfi.getFlags() &
-                                    AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
+                    if (!allowDucking || entry.wantsPauseInsteadOfDucking()) {
                         // The new request will cause audio book to lose focus and pause
                         losers.add(entry);
                     }
@@ -243,12 +251,15 @@
                     // Evaluate it as if it were a new request, but note that we should remove
                     // the old pending request, and move it.
                     // We do not want to evaluate the new request against itself.
-                    Log.i(TAG, "Duplicate request while waiting is being evaluated");
-                    deprecatedBlockedEntry = entry;
+                    Log.i(TAG, "Replacing pending request from same client");
+                    replacedBlockedEntry = entry;
                     continue;
                 } else {
                     // Trivially reject a request for a different USAGE
-                    Log.i(TAG, "Different request while waiting is rejected");
+                    Log.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
+                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
+                            + "request focus for " + afi.getAttributes().usageToString() + " on "
+                            + "same listener.");
                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                 }
             }
@@ -267,7 +278,7 @@
                     // If ducking is not allowed by the requester, or the pending focus holder had
                     // set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
                     // then the pending holder must stay "lost" until this requester goes away.
-                    if ((!allowDucking) || entry.wantsPauseInsteadOfDucking()) {
+                    if (!allowDucking || entry.wantsPauseInsteadOfDucking()) {
                         // The new request is yet another reason this entry cannot regain focus yet
                         blocked.add(entry);
                     }
@@ -278,6 +289,19 @@
         // Now that we've decided we'll grant focus, construct our new FocusEntry
         FocusEntry newEntry = new FocusEntry(afi, requestedContext);
 
+        // These entries have permanently lost focus as a result of this request, so they
+        // should be removed from all blocker lists.
+        ArrayList<FocusEntry> permanentlyLost = new ArrayList<>();
+
+        if (replacedCurrentEntry != null) {
+            mFocusHolders.remove(replacedCurrentEntry.getClientId());
+            permanentlyLost.add(replacedCurrentEntry);
+        }
+        if (replacedBlockedEntry != null) {
+            mFocusLosers.remove(replacedBlockedEntry.getClientId());
+            permanentlyLost.add(replacedBlockedEntry);
+        }
+
 
         // Now that we're sure we'll accept this request, update any requests which we would
         // block but are already out of focus but waiting to come back
@@ -290,6 +314,7 @@
                 sendFocusLoss(entry, permanent);
                 final FocusEntry deadEntry = mFocusLosers.remove(entry.mAfi.getClientId());
                 assert deadEntry != null;
+                permanentlyLost.add(entry);
             } else {
                 // Note that this new request is yet one more reason we can't (yet) have focus
                 entry.mBlockers.add(newEntry);
@@ -306,7 +331,9 @@
             // The entry no longer holds focus, so take it out of the holders list
             mFocusHolders.remove(entry.mAfi.getClientId());
 
-            if (!permanent) {
+            if (permanent) {
+                permanentlyLost.add(entry);
+            } else {
                 // Add ourselves to the list of requests waiting to get focus back and
                 // note why we lost focus so we can tell when it's time to get it back
                 mFocusLosers.put(entry.mAfi.getClientId(), entry);
@@ -314,10 +341,14 @@
             }
         }
 
-        // If we encountered a duplicate of this request that was pending, but now we're going to
-        // grant focus, we need to remove the old pending request (without sending a LOSS message).
-        if (deprecatedBlockedEntry != null) {
-            mFocusLosers.remove(deprecatedBlockedEntry.mAfi.getClientId());
+        // Now that all new blockers have been added, clear out any other requests that have been
+        // permanently lost as a result of this request. Treat them as abandoned - if they're on
+        // any blocker lists, remove them. If any focus requests become unblocked as a result,
+        // re-grant them. (This can happen when a GAIN_TRANSIENT_MAY_DUCK request replaces a
+        // GAIN_TRANSIENT request from the same listener.)
+        for (FocusEntry entry : permanentlyLost) {
+            Log.d(TAG, "Cleaning up entry " + entry.getClientId());
+            removeFocusEntryAndRestoreUnblockedWaiters(entry);
         }
 
         // Finally, add the request we're granting to the focus holders' list
@@ -330,7 +361,7 @@
 
     @Override
     public synchronized void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
-        Log.i(TAG, "onAudioFocusRequest " + afi);
+        Log.i(TAG, "onAudioFocusRequest " + afi.getClientId());
 
         int response = evaluateFocusRequest(afi);
 
@@ -346,7 +377,7 @@
      * */
     @Override
     public synchronized void onAudioFocusAbandon(AudioFocusInfo afi) {
-        Log.i(TAG, "onAudioFocusAbandon " + afi);
+        Log.i(TAG, "onAudioFocusAbandon " + afi.getClientId());
 
         // Remove this entry from our active or pending list
         FocusEntry deadEntry = mFocusHolders.remove(afi.getClientId());
@@ -368,6 +399,10 @@
             }
         }
 
+        removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);
+    }
+
+    private void removeFocusEntryAndRestoreUnblockedWaiters(FocusEntry deadEntry) {
         // Remove this entry from the blocking list of any pending requests
         Iterator<FocusEntry> it = mFocusLosers.values().iterator();
         while (it.hasNext()) {
@@ -378,6 +413,7 @@
 
             // Any entry whose blocking list becomes empty should regain focus
             if (entry.mBlockers.isEmpty()) {
+                Log.i(TAG, "Restoring unblocked entry " + entry.getClientId());
                 // Pull this entry out of the focus losers list
                 it.remove();
 
@@ -399,7 +435,6 @@
         }
     }
 
-
     public synchronized void dump(PrintWriter writer) {
         writer.println("*CarAudioFocus*");
 
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index 914190d..98bb41b 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -65,8 +65,9 @@
         mHandler = controller.getHandler();
 
         mRunnable = () -> {
-            if (areAnyIdleJobsRunning()) {
-                LOG.d("Some jobs are still running. Need to wait more ...");
+            int numberRunning = numberOfIdleJobsRunning();
+            if (numberRunning > 0) {
+                LOG.d("" + numberRunning + " jobs are still running. Need to wait more ...");
                 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_UPDATE_FREQUENCY_MS);
             } else {
                 LOG.d("No jobs are currently running.");
@@ -150,15 +151,15 @@
         mHandler.removeCallbacks(mRunnable);
     }
 
-    private boolean areAnyIdleJobsRunning() {
+    private int numberOfIdleJobsRunning() {
         List<JobInfo> startedJobs = mJobScheduler.getStartedJobs();
+        int count = 0;
         for (JobSnapshot snap : mJobScheduler.getAllJobSnapshots()) {
-            if (startedJobs.contains(snap.getJobInfo())) {
-                if (snap.getJobInfo().isRequireDeviceIdle()) {
-                    return true;
-                }
+            if (startedJobs.contains(snap.getJobInfo())
+                    && snap.getJobInfo().isRequireDeviceIdle()) {
+                count++;
             }
         }
-        return false;
+        return count;
     }
 }
diff --git a/service/src/com/android/car/hal/PowerHalService.java b/service/src/com/android/car/hal/PowerHalService.java
index 3972984..ffa5f7a 100644
--- a/service/src/com/android/car/hal/PowerHalService.java
+++ b/service/src/com/android/car/hal/PowerHalService.java
@@ -69,6 +69,34 @@
     @VisibleForTesting
     public static final int SHUTDOWN_ONLY = VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY;
 
+    private static String powerStateReportName(int state) {
+        String baseName;
+        switch(state) {
+            case SET_WAIT_FOR_VHAL:      baseName = "WAIT_FOR_VHAL";      break;
+            case SET_DEEP_SLEEP_ENTRY:   baseName = "DEEP_SLEEP_ENTRY";   break;
+            case SET_DEEP_SLEEP_EXIT:    baseName = "DEEP_SLEEP_EXIT";    break;
+            case SET_SHUTDOWN_POSTPONE:  baseName = "SHUTDOWN_POSTPONE";  break;
+            case SET_SHUTDOWN_START:     baseName = "SHUTDOWN_START";     break;
+            case SET_ON:                 baseName = "ON";                 break;
+            case SET_SHUTDOWN_PREPARE:   baseName = "SHUTDOWN_PREPARE";   break;
+            case SET_SHUTDOWN_CANCELLED: baseName = "SHUTDOWN_CANCELLED"; break;
+            default:                     baseName = "<unknown>";          break;
+        }
+        return baseName + "(" + state + ")";
+    }
+
+    private static String powerStateReqName(int state) {
+        String baseName;
+        switch(state) {
+            case VehicleApPowerStateReq.ON:               baseName = "ON";               break;
+            case VehicleApPowerStateReq.SHUTDOWN_PREPARE: baseName = "SHUTDOWN_PREPARE"; break;
+            case VehicleApPowerStateReq.CANCEL_SHUTDOWN:  baseName = "CANCEL_SHUTDOWN";  break;
+            case VehicleApPowerStateReq.FINISHED:         baseName = "FINISHED";         break;
+            default:                                      baseName = "<unknown>";        break;
+        }
+        return baseName + "(" + state + ")";
+    }
+
     public interface PowerEventListener {
         /**
          * Received power state change event.
@@ -252,7 +280,8 @@
             int[] values = { state, additionalParam };
             try {
                 mHal.set(VehicleProperty.AP_POWER_STATE_REPORT, 0).to(values);
-                Log.i(CarLog.TAG_POWER, "setPowerState=" + state + " param=" + additionalParam);
+                Log.i(CarLog.TAG_POWER, "setPowerState=" + powerStateReportName(state)
+                        + " param=" + additionalParam);
             } catch (PropertyTimeoutException e) {
                 Log.e(CarLog.TAG_POWER, "cannot set to AP_POWER_STATE_REPORT", e);
             }
@@ -359,8 +388,8 @@
                 case AP_POWER_STATE_REQ:
                     int state = v.value.int32Values.get(VehicleApPowerStateReqIndex.STATE);
                     int param = v.value.int32Values.get(VehicleApPowerStateReqIndex.ADDITIONAL);
-                    Log.i(CarLog.TAG_POWER, "Received AP_POWER_STATE_REQ=" + state
-                            + " param=" + param);
+                    Log.i(CarLog.TAG_POWER, "Received AP_POWER_STATE_REQ="
+                            + powerStateReqName(state) + " param=" + param);
                     listener.onApPowerStateChange(new PowerState(state, param));
                     break;
                 case DISPLAY_BRIGHTNESS:
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/audio.xml b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
index deb4f13..983a1ef 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
@@ -65,11 +65,21 @@
                 android:layout_height="wrap_content"
                 android:text="@string/focus_gain" />
             <RadioButton
+                android:id="@+id/focus_gain_transient"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/focus_gain_transient" />
+            <RadioButton
                 android:id="@+id/focus_gain_transient_duck"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@string/focus_gain_transient_duck" />
             <RadioButton
+                android:id="@+id/focus_gain_transient_exclusive"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/focus_gain_transient_exclusive" />
+            <RadioButton
                 android:id="@+id/focus_release"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
@@ -148,6 +158,12 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/speaker_phone_off" />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_weight="1">
         <Button
             android:id="@+id/button_microphone_on"
             android:layout_width="wrap_content"
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/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index b0efa9d..1b4b6f7 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -104,7 +104,9 @@
     <string name="stop_wav" translatable="false">Stop WAV</string>
     <string name="audio_focus" translatable="false">Audio Focus</string>
     <string name="focus_gain" translatable="false">Gain</string>
-    <string name="focus_gain_transient_duck" translatable="false">Gain,Transient,Duck</string>
+    <string name="focus_gain_transient" translatable="false">Gain (Transient)</string>
+    <string name="focus_gain_transient_duck" translatable="false">Gain (Transient, May Duck)</string>
+    <string name="focus_gain_transient_exclusive" translatable="false">Gain (Transient, Exclusive)</string>
     <string name="focus_release" translatable="false">Release</string>
     <string name="request" translatable="false">request</string>
     <string name="release" translatable="false">release</string>
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/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index eae8485..cf5a56a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Handler;
@@ -354,6 +355,7 @@
         private final RadioGroup mRequestSelection;
         private final TextView mText;
         private final AudioFocusListener mFocusListener;
+        private AudioFocusRequest mFocusRequest;
 
         public FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text) {
             mText = text;
@@ -363,16 +365,32 @@
             mFocusListener = new AudioFocusListener();
             requestButton.setOnClickListener(v -> {
                 int selectedButtonId = mRequestSelection.getCheckedRadioButtonId();
-                int focusRequest = AudioManager.AUDIOFOCUS_GAIN;
-                if (selectedButtonId == R.id.focus_gain_transient_duck) {
-                    focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
-                } else if (selectedButtonId == R.id.focus_release) {
-                    mAudioManager.abandonAudioFocus(mFocusListener);
-                    setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
-                    return;
+                int focusRequest;
+                switch (selectedButtonId) {
+                    case R.id.focus_gain:
+                        focusRequest = AudioManager.AUDIOFOCUS_GAIN;
+                        break;
+                    case R.id.focus_gain_transient:
+                        focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
+                        break;
+                    case R.id.focus_gain_transient_duck:
+                        focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+                        break;
+                    case R.id.focus_gain_transient_exclusive:
+                        focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
+                        break;
+                    case R.id.focus_release:
+                    default:
+                        abandonAudioFocus();
+                        return;
                 }
-                int ret = mAudioManager.requestAudioFocus(mFocusListener,
-                        AudioManager.STREAM_MUSIC, focusRequest);
+                mFocusRequest = new AudioFocusRequest.Builder(focusRequest)
+                        .setAudioAttributes(new AudioAttributes.Builder()
+                                .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+                                .build())
+                        .setOnAudioFocusChangeListener(mFocusListener)
+                        .build();
+                int ret = mAudioManager.requestAudioFocus(mFocusRequest);
                 Log.i(TAG, "requestAudioFocus returned " + ret);
                 if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                     setFocusText(AUDIO_FOCUS_STATE_GAIN);
@@ -388,7 +406,8 @@
             if (DBG) {
                 Log.i(TAG, "abandonAudioFocus");
             }
-            mAudioManager.abandonAudioFocus(mFocusListener);
+            mAudioManager.abandonAudioFocusRequest(mFocusRequest);
+            mFocusRequest = null;
             setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
         }
 
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
index 6d4f7cd..a6d81c9 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
@@ -506,6 +506,7 @@
                     .setContentTitle("+1 1231231234")
                     .setContentText("Shows persistent heads-up")
                     .setCategory(Notification.CATEGORY_CALL)
+                    .setOngoing(true)
                     .setSmallIcon(R.drawable.car_ic_mode)
                     .setFullScreenIntent(pendingIntent, true)
                     .setColor(mContext.getColor(android.R.color.holo_red_light))
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/GarageModeTestApp/res/values/styles.xml b/tests/GarageModeTestApp/res/values/styles.xml
index 1f98397..f7cefc2 100644
--- a/tests/GarageModeTestApp/res/values/styles.xml
+++ b/tests/GarageModeTestApp/res/values/styles.xml
@@ -28,6 +28,7 @@
   </style>
   <style name="Checkbox">
     <item name="android:background">@drawable/border</item>
+    <item name="android:textColor">#4389F7</item>
   </style>
   <style name="SpinnerLabel">
     <item name="android:layout_width">wrap_content</item>
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/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/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;
     }
 
     /**