Merge "Reset settings controllable restrictions in CarUserManagerHelper"
diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
index bb9991e..c42ea5e 100644
--- a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
+++ b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
@@ -37,7 +37,7 @@
     private static final int NAV_STATE_EVENT_ID = 1;
 
     @Override
-    protected NavigationRenderer getNavigationRenderer() {
+    public NavigationRenderer getNavigationRenderer() {
         NavigationRenderer navigationRenderer = new NavigationRenderer() {
             @Override
             public CarNavigationInstrumentCluster getNavigationProperties() {
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 40ae775..1c34646 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -4,6 +4,7 @@
   public final class Car {
     field public static final String CABIN_SERVICE = "cabin";
     field public static final String CAR_DRIVING_STATE_SERVICE = "drivingstate";
+    field public static final String CAR_EXTRA_CLUSTER_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
     field public static final String CAR_TRUST_AGENT_ENROLLMENT_SERVICE = "trust_enroll";
     field public static final String DIAGNOSTIC_SERVICE = "diagnostic";
     field public static final String HVAC_SERVICE = "hvac";
@@ -138,37 +139,40 @@
 
 package android.car.cluster {
 
-  public class CarInstrumentClusterManager {
-    method public void registerCallback(String, android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
-    method public void startActivity(android.content.Intent) throws android.car.CarNotConnectedException;
-    method public void unregisterCallback(android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
-    field public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
-    field public static final String KEY_EXTRA_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
+  @Deprecated public class CarInstrumentClusterManager {
+    method @Deprecated public void registerCallback(String, android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
+    method @Deprecated public void startActivity(android.content.Intent) throws android.car.CarNotConnectedException;
+    method @Deprecated public void unregisterCallback(android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
+    field @Deprecated public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
+    field @Deprecated public static final String KEY_EXTRA_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
   }
 
-  public static interface CarInstrumentClusterManager.Callback {
-    method public void onClusterActivityStateChanged(String, android.os.Bundle);
+  @Deprecated public static interface CarInstrumentClusterManager.Callback {
+    method @Deprecated public void onClusterActivityStateChanged(String, android.os.Bundle);
   }
 
 }
 
 package android.car.cluster.renderer {
 
-  public abstract class InstrumentClusterRenderer {
-    ctor public InstrumentClusterRenderer();
-    method protected abstract android.car.cluster.renderer.NavigationRenderer createNavigationRenderer();
-    method @Nullable public android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
-    method @UiThread public final void initialize();
-    method public abstract void onCreate(android.content.Context);
-    method public abstract void onStart();
-    method public abstract void onStop();
+  @Deprecated public abstract class InstrumentClusterRenderer {
+    ctor @Deprecated public InstrumentClusterRenderer();
+    method @Deprecated protected abstract android.car.cluster.renderer.NavigationRenderer createNavigationRenderer();
+    method @Deprecated @Nullable public android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
+    method @Deprecated @UiThread public final void initialize();
+    method @Deprecated public abstract void onCreate(android.content.Context);
+    method @Deprecated public abstract void onStart();
+    method @Deprecated public abstract void onStop();
   }
 
   public abstract class InstrumentClusterRenderingService extends android.app.Service {
     ctor public InstrumentClusterRenderingService();
-    method @MainThread protected abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
+    method @MainThread @Nullable public abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
-    method @MainThread protected void onKeyEvent(android.view.KeyEvent);
+    method @MainThread public void onKeyEvent(@NonNull android.view.KeyEvent);
+    method @MainThread public void onNavigationComponentLaunched();
+    method @MainThread public void onNavigationComponentReleased();
+    method protected boolean startNavigationActivity(@NonNull android.content.ComponentName);
   }
 
   @UiThread public abstract class NavigationRenderer {
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 1457648..4a980af 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.car.cluster.CarInstrumentClusterManager;
+import android.car.cluster.ClusterActivityState;
 import android.car.content.pm.CarPackageManager;
 import android.car.diagnostic.CarDiagnosticManager;
 import android.car.drivingstate.CarDrivingStateManager;
@@ -91,8 +92,11 @@
 
     /**
      * Service name for {@link CarInstrumentClusterManager}
+     *
+     * @deprecated CarInstrumentClusterManager is being deprecated
      * @hide
      */
+    @Deprecated
     public static final String CAR_INSTRUMENT_CLUSTER_SERVICE = "cluster_service";
 
     /**
@@ -482,6 +486,26 @@
 
     private static final String CAR_SERVICE_CLASS = "com.android.car.CarService";
 
+    /**
+     * Category used by navigation applications to indicate which activity should be launched on
+     * the instrument cluster when such application holds
+     * {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION} focus.
+     *
+     * @hide
+     */
+    public static final String CAR_CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
+
+    /**
+     * When an activity is launched in the cluster, it will receive {@link ClusterActivityState} in
+     * the intent's extra under this key, containing instrument cluster information such as
+     * unobscured area, visibility, etc.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String CAR_EXTRA_CLUSTER_ACTIVITY_STATE =
+            "android.car.cluster.ClusterActivityState";
+
     private static final long CAR_SERVICE_BIND_RETRY_INTERVAL_MS = 500;
     private static final long CAR_SERVICE_BIND_MAX_RETRY = 20;
 
diff --git a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
index ad54893..7427fcb 100644
--- a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
+++ b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
@@ -23,29 +23,25 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * API to work with instrument cluster.
  *
+ * @deprecated use {@link android.car.CarAppFocusManager} with focus type
+ * {@link android.car.CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION} instead.
+ * InstrumentClusterService will automatically launch a "android.car.cluster.NAVIGATION" activity
+ * from the package holding navigation focus.
+ *
  * @hide
  */
+@Deprecated
 @SystemApi
 public class CarInstrumentClusterManager implements CarManagerBase {
-    private static final String TAG = CarInstrumentClusterManager.class.getSimpleName();
-
-    /** @hide */
+    /**
+     * @deprecated use {@link android.car.Car#CATEGORY_NAVIGATION} instead
+     *
+     * @hide
+     */
     @SystemApi
     public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
 
@@ -54,34 +50,24 @@
      * intent's extra thus activity will know information about unobscured area, etc. upon activity
      * creation.
      *
+     * @deprecated use {@link android.car.Car#CATEGORY_NAVIGATION} instead
+     *
      * @hide
      */
     @SystemApi
     public static final String KEY_EXTRA_ACTIVITY_STATE =
             "android.car.cluster.ClusterActivityState";
 
-    private final EventHandler mHandler;
-    private final Map<String, Set<Callback>> mCallbacksByCategory = new HashMap<>(0);
-    private final Object mLock = new Object();
-    private final Map<String, Bundle> mActivityStatesByCategory = new HashMap<>(0);
-
-    private final IInstrumentClusterManagerService mService;
-
-    private ClusterManagerCallback mServiceToManagerCallback;
-
     /**
      * Starts activity in the instrument cluster.
      *
+     * @deprecated see {@link CarInstrumentClusterManager} deprecation message
+     *
      * @hide
      */
     @SystemApi
     public void startActivity(Intent intent) throws CarNotConnectedException {
-        try {
-            mService.startClusterActivity(intent);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to launch activity (" + intent + ")", e);
-            throw new CarNotConnectedException(e);
-        }
+        // No-op
     }
 
     /**
@@ -92,42 +78,14 @@
      *                         see {@link #CATEGORY_NAVIGATION}
      * @param callback instance of {@link Callback} class to receive events.
      *
+     * @deprecated see {@link CarInstrumentClusterManager} deprecation message
+     *
      * @hide
      */
     @SystemApi
     public void registerCallback(String category, Callback callback)
             throws CarNotConnectedException {
-        Log.i(TAG, "registerCallback, category: " + category + ", callback: " + callback);
-        ClusterManagerCallback callbackToCarService = null;
-        synchronized (mLock) {
-            Set<Callback> callbacks = mCallbacksByCategory.get(category);
-            if (callbacks == null) {
-                callbacks = new HashSet<>(1);
-                mCallbacksByCategory.put(category, callbacks);
-            }
-            if (!callbacks.add(callback)) {
-                Log.w(TAG, "registerCallback: already registered");
-                return;  // already registered
-            }
-
-            if (mActivityStatesByCategory.containsKey(category)) {
-                Log.i(TAG, "registerCallback: sending activity state...");
-                callback.onClusterActivityStateChanged(
-                        category, mActivityStatesByCategory.get(category));
-            }
-
-            if (mServiceToManagerCallback == null) {
-                Log.i(TAG, "registerCallback: registering callback with car service...");
-                mServiceToManagerCallback = new ClusterManagerCallback();
-                callbackToCarService = mServiceToManagerCallback;
-            }
-        }
-        try {
-            mService.registerCallback(callbackToCarService);
-            Log.i(TAG, "registerCallback: done");
-        } catch (RemoteException e) {
-            throw new CarNotConnectedException(e);
-        }
+        // No-op
     }
 
     /**
@@ -135,46 +93,29 @@
      *
      * @param callback previously registered callback
      *
+     * @deprecated see {@link CarInstrumentClusterManager} deprecation message
+     *
      * @hide
      */
     @SystemApi
     public void unregisterCallback(Callback callback) throws CarNotConnectedException {
-        List<String> keysToRemove = new ArrayList<>(1);
-        synchronized (mLock) {
-            for (Map.Entry<String, Set<Callback>> entry : mCallbacksByCategory.entrySet()) {
-                Set<Callback> callbacks = entry.getValue();
-                if (callbacks.remove(callback) && callbacks.isEmpty()) {
-                    keysToRemove.add(entry.getKey());
-                }
-
-            }
-
-            for (String key: keysToRemove) {
-                mCallbacksByCategory.remove(key);
-            }
-
-            if (mCallbacksByCategory.isEmpty()) {
-                try {
-                    mService.unregisterCallback(mServiceToManagerCallback);
-                } catch (RemoteException e) {
-                    throw new CarNotConnectedException(e);
-                }
-                mServiceToManagerCallback = null;
-            }
-        }
+        // No-op
     }
 
     /** @hide */
     public CarInstrumentClusterManager(IBinder service, Handler handler) {
-        mService = IInstrumentClusterManagerService.Stub.asInterface(service);
-
-        mHandler = new EventHandler(handler.getLooper());
+        // No-op
     }
 
-    /** @hide */
+    /**
+     * @deprecated activity state is not longer being reported. See
+     * {@link CarInstrumentClusterManager} deprecation message for more details.
+     *
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     public interface Callback {
-
         /**
          * Notify client that activity state was changed.
          *
@@ -188,54 +129,4 @@
     @Override
     public void onCarDisconnected() {
     }
-
-    private class EventHandler extends Handler {
-
-        final static int MSG_ACTIVITY_STATE = 1;
-
-        EventHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            Log.i(TAG, "handleMessage, message: " + msg);
-            switch (msg.what) {
-                case MSG_ACTIVITY_STATE:
-                    Pair<String, Bundle> info = (Pair<String, Bundle>) msg.obj;
-                    String category = info.first;
-                    Bundle state = info.second;
-                    List<CarInstrumentClusterManager.Callback> callbacks = null;
-                    synchronized (mLock) {
-                        if (mCallbacksByCategory.containsKey(category)) {
-                            callbacks = new ArrayList<>(mCallbacksByCategory.get(category));
-                        }
-                    }
-                    Log.i(TAG, "handleMessage, callbacks: " + callbacks);
-                    if (callbacks != null) {
-                        for (CarInstrumentClusterManager.Callback cb : callbacks) {
-                            cb.onClusterActivityStateChanged(category, state);
-                        }
-                    }
-                    break;
-                default:
-                    Log.e(TAG, "Unexpected message: " + msg.what);
-            }
-        }
-    }
-
-    private class ClusterManagerCallback extends IInstrumentClusterManagerCallback.Stub {
-
-        @Override
-        public void setClusterActivityState(String category, Bundle clusterActivityState)
-                throws RemoteException {
-            Log.i(TAG, "setClusterActivityState, category: " + category);
-            synchronized (mLock) {
-                mActivityStatesByCategory.put(category, clusterActivityState);
-            }
-
-            mHandler.sendMessage(mHandler.obtainMessage(EventHandler.MSG_ACTIVITY_STATE,
-                    new Pair<>(category, clusterActivityState)));
-        }
-    }
 }
\ No newline at end of file
diff --git a/car-lib/src/android/car/cluster/ClusterActivityState.java b/car-lib/src/android/car/cluster/ClusterActivityState.java
index 9a6223c..79f7e76 100644
--- a/car-lib/src/android/car/cluster/ClusterActivityState.java
+++ b/car-lib/src/android/car/cluster/ClusterActivityState.java
@@ -23,6 +23,7 @@
 /**
  * Helper class that represents activity state in the cluster and can be serialized / deserialized
  * to/from bundle.
+ *
  * @hide
  */
 public class ClusterActivityState {
@@ -34,24 +35,65 @@
     private Rect mUnobscuredBounds;
     private Bundle mExtras;
 
+    /**
+     * Returns true if the cluster is currently able to display content, or false if the content
+     * area of the cluster is hidden.
+     */
     public boolean isVisible() {
         return mVisible;
     }
 
+    /**
+     * Get a rectangle inside the cluster content area that is not covered by any decorations.
+     * Activities designed to display content in the instrument cluster can use this information to
+     * determine where to display user-relevant content, while using the rest of the window for
+     * content bleeding. For example, a navigation activity could decide to display current road
+     * inside this rectangle, while drawing additional map background outside this area.
+     * <p>
+     * All values of this {@link Rect} represent absolute coordinates inside the activity canvas.
+     */
     @Nullable public Rect getUnobscuredBounds() {
         return mUnobscuredBounds;
     }
 
+    /**
+     * Get any custom extras that were set on this activity state.
+     */
+    @Nullable public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Sets whether the cluster is currently able to display content, or false if content area of
+     * the cluster is hidden.
+     *
+     * @return this instance for chaining.
+     */
     public ClusterActivityState setVisible(boolean visible) {
         mVisible = visible;
         return this;
     }
 
+    /**
+     * Sets a rectangle inside that cluster content area that is not covered by any decorations.
+     * Activities designed to display content in the cluster can use this to determine where to
+     * display user-relevant content, while using the rest of the window for content bleeding.
+     *
+     * @param unobscuredBounds a {@link Rect} describing the area inside the activity canvas that is
+     *                         not covered by any decorations. All values of this {@link Rect}
+     *                         represent absolute coordinates inside the activity canvas.
+     * @return this instance for chaining.
+     */
     public ClusterActivityState setUnobscuredBounds(Rect unobscuredBounds) {
         mUnobscuredBounds = unobscuredBounds;
         return this;
     }
 
+    /**
+     * Set any custom extras to be included with the activity state.
+     *
+     * @return this instance for chaining.
+     */
     public ClusterActivityState setExtras(Bundle bundle) {
         mExtras = bundle;
         return this;
@@ -60,12 +102,19 @@
     /** Use factory methods instead. */
     private ClusterActivityState() {}
 
+    /**
+     * Creates a {@link ClusterActivityState} with the given visibility and unobscured bounds (see
+     * {@link #setVisible(boolean)} and {@link #setUnobscuredBounds(Rect)} for more details)
+     */
     public static ClusterActivityState create(boolean visible, Rect unobscuredBounds) {
         return new ClusterActivityState()
                 .setVisible(visible)
                 .setUnobscuredBounds(unobscuredBounds);
     }
 
+    /**
+     * Reconstructs a {@link ClusterActivityState} from a {@link Bundle}
+     */
     public static ClusterActivityState fromBundle(Bundle bundle) {
         return new ClusterActivityState()
                 .setVisible(bundle.getBoolean(KEY_VISIBLE, true))
@@ -73,6 +122,10 @@
                 .setExtras(bundle.getBundle(KEY_EXTRAS));
     }
 
+    /**
+     * Returns a {@link Bundle} representation of this instance. This bundle can then be
+     * deserialized using {@link #fromBundle(Bundle)}.
+     */
     public Bundle toBundle() {
         Bundle b = new Bundle();
         b.putBoolean(KEY_VISIBLE, mVisible);
@@ -85,7 +138,8 @@
     public String toString() {
         return this.getClass().getSimpleName() + " {"
                 + "visible: " + mVisible + ", "
-                + "unobscuredBounds: " + mUnobscuredBounds
+                + "unobscuredBounds: " + mUnobscuredBounds + ", "
+                + "extras: " + mExtras
                 + " }";
     }
 }
diff --git a/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl b/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl
index 91a497d..d2f099e 100644
--- a/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl
+++ b/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl
@@ -19,6 +19,8 @@
 
 /**
  * Interface from Car Service to {@link android.car.cluster.CarInstrumentClusterManager}
+ *
+ * @deprecated CarInstrumentClusterManager is deprecated
  * @hide
  */
 interface IInstrumentClusterManagerCallback {
@@ -29,7 +31,6 @@
      *        see {@link android.car.cluster.CarInstrumentClusterManager} for details.
      * @param clusterActivityState is a {@link Bundle} object,
      *        see {@link android.car.cluster.ClusterActivityState} for how to construct the bundle.
-     * @hide
      */
     oneway void setClusterActivityState(String category, in Bundle clusterActivityState);
 }
diff --git a/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl b/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl
index aaaeaee..1f4988d 100644
--- a/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl
+++ b/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl
@@ -22,6 +22,7 @@
 /**
  * API to communicate between {@link CarInstrumentClusterManager} and Car Service.
  *
+ * @deprecated CarInstrumentClusterManager is deprecated
  * @hide
  */
 interface IInstrumentClusterManagerService {
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
index 3458975..7deecc7 100644
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
+++ b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
@@ -15,22 +15,29 @@
  */
 package android.car.cluster.renderer;
 
-import android.car.cluster.renderer.IInstrumentClusterCallback;
 import android.car.cluster.renderer.IInstrumentClusterNavigation;
 import android.view.KeyEvent;
 
 /**
- * Binder API for Instrument Cluster.
+ * Binder API for Instrument Cluster. It defines a communication channel from Car Service to the
+ * cluster vendor implementation.
  *
  * @hide
  */
 interface IInstrumentCluster {
-    /** Returns {@link IInstrumentClusterNavigation} that will be passed to the Nav app */
+    /**
+     * Returns {@link IInstrumentClusterNavigation} that will be passed to the navigation
+     * application.
+     */
     IInstrumentClusterNavigation getNavigationService();
 
-    /** Supplies Instrument Cluster Renderer with current owner of Navigation app context */
+    /**
+     * Supplies Instrument Cluster Renderer with current owner of Navigation app context
+     */
     oneway void setNavigationContextOwner(int uid, int pid);
 
-    /** Called when key event that was addressed to instrument cluster display has been received. */
+    /**
+     * Called when key event that was addressed to instrument cluster display has been received.
+     */
     oneway void onKeyEvent(in KeyEvent keyEvent);
 }
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterCallback.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterCallback.aidl
deleted file mode 100644
index 996dc9e..0000000
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterCallback.aidl
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.car.cluster.renderer;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-
-/**
- * This interface defines the communication channel between the cluster vendor implementation and
- * Car Service.
- *
- * @hide
- */
-interface IInstrumentClusterCallback {
-    /**
-     * Notify Car Service how to launch an activity for particular category.
-     *
-     * @param category cluster activity category,
-     *        see {@link android.car.cluster.CarInstrumentClusterManager} for details.
-     * @param activityOptions this bundle will be converted to {@link android.app.ActivityOptions}
-     *        and used when starting an activity. It may contain information such as virtual display
-     *        id or activity stack id where to start cluster activity.
-     *
-     * @hide
-     */
-    void setClusterActivityLaunchOptions(String category, in Bundle activityOptions);
-
-    /**
-     * Activities launched on virtual display will be in onPause state most of the time, so they
-     * can't really know whether they visible on the screen or not. We need to propagate this
-     * information along with unobscured bounds (and possible other info) from instrument cluster
-     * vendor implementation to activity.
-     *
-     * @param category cluster activity category to which this state applies,
-     *        see {@link android.car.cluster.CarInstrumentClusterManager} for details.
-     * @param clusterActivityState is a {@link Bundle} object,
-     *        see {@link android.car.cluster.ClusterActivityState} for how to construct the bundle.
-     * @hide
-     */
-    void setClusterActivityState(String category, in Bundle clusterActivityState);
-}
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl
index 6f33a9d..c4e9d72 100644
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl
+++ b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl
@@ -16,15 +16,29 @@
 package android.car.cluster.renderer;
 
 import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 
 /**
- * Binder API for Instrument Cluster Navigation.
+ * Binder API for Instrument Cluster Navigation. This represents a direct communication channel
+ * from navigation applications to the cluster vendor implementation.
  *
  * @hide
  */
 interface IInstrumentClusterNavigation {
+    /**
+     * Called when an event is fired to change the navigation state. Content of this events can be
+     * interpreted using androidx.car.car-cluster API.
+     *
+     * @param eventType type of navigation state change
+     * @param bundle {@link android.os.Bundle} containing the description of the navigation state
+     *               change.
+     */
     void onEvent(int eventType, in Bundle bundle);
+
+    /**
+     * Returns attributes of instrument cluster for navigation.
+     */
     CarNavigationInstrumentCluster getInstrumentClusterInfo();
 }
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
index 5086186..be4cfd4 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
@@ -18,16 +18,15 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UiThread;
-import android.car.navigation.CarNavigationInstrumentCluster;
 import android.content.Context;
 
 /**
- * Interface for instrument cluster rendering.
- *
- * TODO: implement instrument cluster feature list and extend API. bug: 32060603
+ * @deprecated This class is unused. Refer to {@link InstrumentClusterRenderingService} for
+ * documentation on how to build a instrument cluster renderer.
  *
  * @hide
  */
+@Deprecated
 @SystemApi
 public abstract class InstrumentClusterRenderer {
 
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index d572d9a..6874106 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -15,30 +15,44 @@
  */
 package android.car.cluster.renderer;
 
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+
 import android.annotation.CallSuper;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.ActivityOptions;
 import android.app.Service;
+import android.car.Car;
 import android.car.CarLibLog;
 import android.car.CarNotConnectedException;
+import android.car.cluster.ClusterActivityState;
 import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
-import android.util.Pair;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
 
 /**
  * A service that used for interaction between Car Service and Instrument Cluster. Car Service may
@@ -58,18 +72,31 @@
  */
 @SystemApi
 public abstract class InstrumentClusterRenderingService extends Service {
-
     private static final String TAG = CarLibLog.TAG_CLUSTER;
 
-    private RendererBinder mRendererBinder;
-
-    /** @hide */
-    public static final String EXTRA_KEY_CALLBACK_SERVICE =
-            "android.car.cluster.IInstrumentClusterCallback";
-
     private final Object mLock = new Object();
+    private RendererBinder mRendererBinder;
+    private Handler mUiHandler = new Handler(Looper.getMainLooper());
+    private ActivityOptions mActivityOptions;
+    private ClusterActivityState mActivityState;
+    private ComponentName mNavigationComponent;
     @GuardedBy("mLock")
-    private IInstrumentClusterCallback mCallback;
+    private ContextOwner mNavContextOwner;
+
+    private static class ContextOwner {
+        final int mUid;
+        final int mPid;
+
+        ContextOwner(int uid, int pid) {
+            mUid = uid;
+            mPid = pid;
+        }
+
+        @Override
+        public String toString() {
+            return "{uid: " + mUid + ", pid: " + mPid + "}";
+        }
+    }
 
     @Override
     @CallSuper
@@ -78,15 +105,6 @@
             Log.d(TAG, "onBind, intent: " + intent);
         }
 
-        if (intent.getExtras().containsKey(EXTRA_KEY_CALLBACK_SERVICE)) {
-            IBinder callbackBinder = intent.getExtras().getBinder(EXTRA_KEY_CALLBACK_SERVICE);
-            synchronized (mLock) {
-                mCallback = IInstrumentClusterCallback.Stub.asInterface(callbackBinder);
-            }
-        } else {
-            Log.w(TAG, "onBind, no callback in extra!");
-        }
-
         if (mRendererBinder == null) {
             mRendererBinder = new RendererBinder(getNavigationRenderer());
         }
@@ -94,196 +112,314 @@
         return mRendererBinder;
     }
 
-    /** Returns {@link NavigationRenderer} or null if it's not supported. */
+    /**
+     * Returns {@link NavigationRenderer} or null if it's not supported. This renderer will be
+     * shared with the navigation context owner (application holding navigation focus).
+     */
     @MainThread
-    protected abstract NavigationRenderer getNavigationRenderer();
+    @Nullable
+    public abstract NavigationRenderer getNavigationRenderer();
 
-    /** Called when key event that was addressed to instrument cluster display has been received. */
+    /**
+     * Called when key event that was addressed to instrument cluster display has been received.
+     */
     @MainThread
-    protected void onKeyEvent(KeyEvent keyEvent) {
+    public void onKeyEvent(@NonNull KeyEvent keyEvent) {
     }
 
     /**
+     * Called when a navigation application becomes a context owner (receives navigation focus) and
+     * its {@link Car#CATEGORY_NAVIGATION} activity is launched.
+     */
+    @MainThread
+    public void onNavigationComponentLaunched() {
+    }
+
+    /**
+     * Called when the current context owner (application holding navigation focus) releases the
+     * focus and its {@link Car#CAR_CATEGORY_NAVIGATION} activity is ready to be replaced by a
+     * system default.
+     */
+    @MainThread
+    public void onNavigationComponentReleased() {
+    }
+
+    /**
+     * Updates the cluster navigation activity by checking which activity to show (an activity of
+     * the {@link #mNavContextOwner}). If not yet launched, it will do so.
+     */
+    private void updateNavigationActivity() {
+        ContextOwner contextOwner = getNavigationContextOwner();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, String.format("updateNavigationActivity (mActivityOptions: %s, "
+                    + "mActivityState: %s, mNavContextOwnerUid: %s)", mActivityOptions,
+                    mActivityState, contextOwner));
+        }
+
+        if (contextOwner == null || contextOwner.mUid == 0 || mActivityOptions == null
+                || mActivityState == null || !mActivityState.isVisible()) {
+            // We are not yet ready to display an activity on the cluster
+            if (mNavigationComponent != null) {
+                mNavigationComponent = null;
+                onNavigationComponentReleased();
+            }
+            return;
+        }
+
+        ComponentName component = getNavigationComponentByOwner(contextOwner);
+        if (Objects.equals(mNavigationComponent, component)) {
+            // We have already launched this component.
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Already launched component: " + component);
+            }
+            return;
+        }
+
+        if (component == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "No component found for owner: " + contextOwner);
+            }
+            return;
+        }
+
+        if (!startNavigationActivity(component)) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unable to launch component: " + component);
+            }
+            return;
+        }
+
+        mNavigationComponent = component;
+        onNavigationComponentLaunched();
+    }
+
+    /**
+     * Returns a component with category {@link Car#CAR_CATEGORY_NAVIGATION} from the same package
+     * as the given navigation context owner.
+     */
+    @Nullable
+    private ComponentName getNavigationComponentByOwner(ContextOwner contextOwner) {
+        for (String packageName : getPackageNamesForUid(contextOwner)) {
+            ComponentName component = getComponentFromPackage(packageName);
+            if (component != null) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Found component: " + component);
+                }
+                return component;
+            }
+        }
+        return null;
+    }
+
+    private String[] getPackageNamesForUid(ContextOwner contextOwner) {
+        if (contextOwner == null || contextOwner.mUid == 0 || contextOwner.mPid == 0) {
+            return new String[0];
+        }
+        String[] packageNames  = getPackageManager().getPackagesForUid(contextOwner.mUid);
+        return packageNames != null ? packageNames : new String[0];
+    }
+
+    private ContextOwner getNavigationContextOwner() {
+        synchronized (mLock) {
+            return mNavContextOwner;
+        }
+    }
+
+    @Nullable
+    private ComponentName getComponentFromPackage(@NonNull String packageName) {
+        PackageManager packageManager = getPackageManager();
+
+        // Check package permission.
+        if (packageManager.checkPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER, packageName)
+                != PERMISSION_GRANTED) {
+            Log.i(TAG, String.format("Package '%s' doesn't have permission %s", packageName,
+                    Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER));
+            return null;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Car.CAR_CATEGORY_NAVIGATION)
+                .setPackage(packageName);
+        List<ResolveInfo> resolveList = packageManager.queryIntentActivities(intent,
+                PackageManager.GET_RESOLVED_FILTER);
+        if (resolveList == null || resolveList.isEmpty()
+                || resolveList.get(0).getComponentInfo() == null) {
+            Log.i(TAG, "Failed to resolve an intent: " + intent);
+            return null;
+        }
+
+        // In case of multiple matching activities in the same package, we pick the first one.
+        return resolveList.get(0).getComponentInfo().getComponentName();
+    }
+
+    /**
+     * Starts an activity on the cluster using the given component.
      *
+     * @return false if the activity couldn't be started.
+     */
+    protected boolean startNavigationActivity(@NonNull ComponentName component) {
+        // Create an explicit intent.
+        Intent intent = new Intent();
+        intent.setComponent(component);
+        intent.putExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE, mActivityState.toBundle());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            startActivityAsUser(intent, mActivityOptions.toBundle(), UserHandle.CURRENT);
+            Log.i(TAG, String.format("Activity launched: %s (options: %s, displayId: %d)",
+                    mActivityOptions, intent, mActivityOptions.getLaunchDisplayId()));
+        } catch (ActivityNotFoundException ex) {
+            Log.w(TAG, "Unable to find activity for intent: " + intent);
+            return false;
+        } catch (Exception ex) {
+            // Catch all other possible exception to prevent service disruption by misbehaving
+            // applications.
+            Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", ex);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @deprecated Use {@link #setClusterActivityLaunchOptions(ActivityOptions)} instead.
+     *
+     * @hide
+     */
+    @Deprecated
+    public void setClusterActivityLaunchOptions(String category, ActivityOptions activityOptions)
+            throws CarNotConnectedException {
+        setClusterActivityLaunchOptions(activityOptions);
+    }
+
+    /**
      * Sets configuration for activities that should be launched directly in the instrument
      * cluster.
      *
-     * @param category category of cluster activity
      * @param activityOptions contains information of how to start cluster activity (on what display
-     *                        or activity stack.
+     *                        or activity stack).
      *
      * @hide
      */
-    public void setClusterActivityLaunchOptions(String category,
-            ActivityOptions activityOptions) throws CarNotConnectedException {
-        IInstrumentClusterCallback cb;
-        synchronized (mLock) {
-            cb = mCallback;
-        }
-        if (cb == null) throw new CarNotConnectedException();
-        try {
-            cb.setClusterActivityLaunchOptions(category, activityOptions.toBundle());
-        } catch (RemoteException e) {
-            throw new CarNotConnectedException(e);
-        }
+    public void setClusterActivityLaunchOptions(ActivityOptions activityOptions) {
+        mActivityOptions = activityOptions;
+        updateNavigationActivity();
     }
 
     /**
-     *
-     * @param category cluster activity category,
-     *        see {@link android.car.cluster.CarInstrumentClusterManager}
-     * @param state pass information about activity state,
-     *        see {@link android.car.cluster.ClusterActivityState}
-     * @return true if information was sent to Car Service
-     * @throws CarNotConnectedException
+     * @deprecated Use {@link #setClusterActivityState(ClusterActivityState)} instead.
      *
      * @hide
      */
-    public void setClusterActivityState(String category, Bundle state)
-            throws CarNotConnectedException {
-        IInstrumentClusterCallback cb;
-        synchronized (mLock) {
-            cb = mCallback;
-        }
-        if (cb == null) throw new CarNotConnectedException();
-        try {
-            cb.setClusterActivityState(category, state);
-        } catch (RemoteException e) {
-            throw new CarNotConnectedException(e);
-        }
+    @Deprecated
+    public void setClusterActivityState(String category, Bundle state) throws
+            CarNotConnectedException {
+        setClusterActivityState(ClusterActivityState.fromBundle(state));
     }
 
+    /**
+     * Set activity state (such as unobscured bounds).
+     *
+     * @param state pass information about activity state, see
+     *              {@link android.car.cluster.ClusterActivityState}
+     *
+     * @hide
+     */
+    public void setClusterActivityState(ClusterActivityState state) {
+        mActivityState = state;
+        updateNavigationActivity();
+    }
 
+    @CallSuper
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         writer.println("**" + getClass().getSimpleName() + "**");
         writer.println("renderer binder: " + mRendererBinder);
         if (mRendererBinder != null) {
             writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer);
-            String owner = "none";
-            synchronized (mLock) {
-                if (mRendererBinder.mNavContextOwner != null) {
-                    owner = "[uid: " + mRendererBinder.mNavContextOwner.first
-                            + ", pid: " + mRendererBinder.mNavContextOwner.second + "]";
-                }
-            }
-            writer.println("navigation focus owner: " + owner);
         }
-        IInstrumentClusterCallback cb;
-        synchronized (mLock) {
-            cb = mCallback;
-        }
-        writer.println("callback: " + cb);
+        writer.println("navigation focus owner: " + getNavigationContextOwner());
+        writer.println("activity options: " + mActivityOptions);
+        writer.println("activity state: " + mActivityState);
+        writer.println("current nav component: " + mNavigationComponent);
+        writer.println("current nav packages: " + Arrays.toString(getPackageNamesForUid(
+                getNavigationContextOwner())));
     }
 
     private class RendererBinder extends IInstrumentCluster.Stub {
-
         private final NavigationRenderer mNavigationRenderer;
-        private final UiHandler mUiHandler;
-
-        @GuardedBy("mLock")
-        private NavigationBinder mNavigationBinder;
-        @GuardedBy("mLock")
-        private Pair<Integer, Integer> mNavContextOwner;
 
         RendererBinder(NavigationRenderer navigationRenderer) {
             mNavigationRenderer = navigationRenderer;
-            mUiHandler = new UiHandler(InstrumentClusterRenderingService.this);
         }
 
         @Override
         public IInstrumentClusterNavigation getNavigationService() throws RemoteException {
-            synchronized (mLock) {
-                if (mNavigationBinder == null) {
-                    mNavigationBinder = new NavigationBinder(mNavigationRenderer);
-                    if (mNavContextOwner != null) {
-                        mNavigationBinder.setNavigationContextOwner(
-                                mNavContextOwner.first, mNavContextOwner.second);
-                    }
-                }
-                return mNavigationBinder;
-            }
+            return new NavigationBinder(mNavigationRenderer);
         }
 
         @Override
         public void setNavigationContextOwner(int uid, int pid) throws RemoteException {
             synchronized (mLock) {
-                mNavContextOwner = new Pair<>(uid, pid);
-                if (mNavigationBinder != null) {
-                    mNavigationBinder.setNavigationContextOwner(uid, pid);
-                }
+                mNavContextOwner = new ContextOwner(uid, pid);
             }
+            mUiHandler.post(InstrumentClusterRenderingService.this::updateNavigationActivity);
         }
 
         @Override
         public void onKeyEvent(KeyEvent keyEvent) throws RemoteException {
-            mUiHandler.doKeyEvent(keyEvent);
+            mUiHandler.post(() -> InstrumentClusterRenderingService.this.onKeyEvent(keyEvent));
         }
     }
 
     private class NavigationBinder extends IInstrumentClusterNavigation.Stub {
-
-        private final NavigationRenderer mNavigationRenderer;  // Thread-safe navigation renderer.
-
-        private volatile Pair<Integer, Integer> mNavContextOwner;
+        private final NavigationRenderer mNavigationRenderer;
 
         NavigationBinder(NavigationRenderer navigationRenderer) {
-            mNavigationRenderer = ThreadSafeNavigationRenderer.createFor(
-                    Looper.getMainLooper(),
-                    navigationRenderer);
-        }
-
-        void setNavigationContextOwner(int uid, int pid) {
-            mNavContextOwner = new Pair<>(uid, pid);
+            mNavigationRenderer = navigationRenderer;
         }
 
         @Override
         public void onEvent(int eventType, Bundle bundle) throws RemoteException {
             assertContextOwnership();
-            mNavigationRenderer.onEvent(eventType, bundle);
+            mUiHandler.post(() -> {
+                if (mNavigationRenderer != null) {
+                    mNavigationRenderer.onEvent(eventType, bundle);
+                }
+            });
         }
 
         @Override
         public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException {
-            return mNavigationRenderer.getNavigationProperties();
+            return runAndWaitResult(() -> mNavigationRenderer.getNavigationProperties());
         }
 
         private void assertContextOwnership() {
             int uid = getCallingUid();
             int pid = getCallingPid();
 
-            Pair<Integer, Integer> owner = mNavContextOwner;
-            if (owner == null || owner.first != uid || owner.second != pid) {
-                throw new IllegalStateException("Client (uid:" + uid + ", pid: " + pid + ") is"
-                        + " not an owner of APP_FOCUS_TYPE_NAVIGATION");
+            synchronized (mLock) {
+                if (mNavContextOwner.mUid != uid || mNavContextOwner.mPid != pid) {
+                    throw new IllegalStateException("Client {uid:" + uid + ", pid: " + pid + "} is"
+                            + " not an owner of APP_FOCUS_TYPE_NAVIGATION " + mNavContextOwner);
+                }
             }
         }
     }
 
-    private static class UiHandler extends Handler {
-        private static int KEY_EVENT = 0;
-        private final WeakReference<InstrumentClusterRenderingService> mRefService;
+    private <E> E runAndWaitResult(final Supplier<E> supplier) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<E> result = new AtomicReference<>();
 
-        UiHandler(InstrumentClusterRenderingService service) {
-            mRefService = new WeakReference<>(service);
+        mUiHandler.post(() -> {
+            result.set(supplier.get());
+            latch.countDown();
+        });
+
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
         }
-
-        @Override
-        public void handleMessage(Message msg) {
-            InstrumentClusterRenderingService service = mRefService.get();
-            if (service == null) {
-                return;
-            }
-
-            if (msg.what == KEY_EVENT) {
-                service.onKeyEvent((KeyEvent) msg.obj);
-            } else {
-                throw new IllegalArgumentException("Unexpected message: " + msg);
-            }
-        }
-
-        void doKeyEvent(KeyEvent event) {
-            sendMessage(obtainMessage(KEY_EVENT, event));
-        }
+        return result.get();
     }
 }
diff --git a/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java
deleted file mode 100644
index 251b670..0000000
--- a/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.car.cluster.renderer;
-
-import android.annotation.Nullable;
-import android.car.navigation.CarNavigationInstrumentCluster;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import java.lang.ref.WeakReference;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * A wrapper over {@link NavigationRenderer} that runs all its methods in the context of provided
- * looper. It is guaranteed that all calls will be invoked in order they were called.
- */
-// TODO(deanh): Does this class even need to exist?
-/* package */ class ThreadSafeNavigationRenderer extends NavigationRenderer {
-
-    private final Handler mHandler;
-    private final NavigationRenderer mRenderer;
-
-    private final static int MSG_EVENT = 1;
-
-    /** Creates thread-safe {@link NavigationRenderer}. Returns null if renderer == null */
-    @Nullable
-    static NavigationRenderer createFor(Looper looper, NavigationRenderer renderer) {
-        return renderer == null ? null : new ThreadSafeNavigationRenderer(looper, renderer);
-    }
-
-    private ThreadSafeNavigationRenderer(Looper looper, NavigationRenderer renderer) {
-        mRenderer = renderer;
-        mHandler = new NavigationRendererHandler(looper, renderer);
-    }
-
-    @Override
-    public CarNavigationInstrumentCluster getNavigationProperties() {
-        if (mHandler.getLooper() == Looper.myLooper()) {
-            return mRenderer.getNavigationProperties();
-        } else {
-            return runAndWaitResult(mHandler,
-                    new RunnableWithResult<CarNavigationInstrumentCluster>() {
-                        @Override
-                        protected CarNavigationInstrumentCluster createResult() {
-                            return mRenderer.getNavigationProperties();
-                        }
-                    });
-        }
-    }
-
-    @Override
-    public void onEvent(int eventType, Bundle bundle) {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_EVENT, eventType, 0, bundle));
-    }
-
-    private static class NavigationRendererHandler extends RendererHandler<NavigationRenderer> {
-
-        NavigationRendererHandler(Looper looper, NavigationRenderer renderer) {
-            super(looper, renderer);
-        }
-
-        @Override
-        public void handleMessage(Message msg, NavigationRenderer renderer) {
-            switch (msg.what) {
-                case MSG_EVENT:
-                    Bundle bundle = (Bundle) msg.obj;
-                    renderer.onEvent(msg.arg1, bundle);
-                    break;
-                default:
-                    throw new IllegalArgumentException("Msg: " + msg.what);
-            }
-        }
-    }
-
-    private static <E> E runAndWaitResult(Handler handler, final RunnableWithResult<E> runnable) {
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        Runnable wrappedRunnable = new Runnable() {
-            @Override
-            public void run() {
-                runnable.run();
-                latch.countDown();
-            }
-        };
-
-        handler.post(wrappedRunnable);
-
-        try {
-            latch.await();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-        return runnable.getResult();
-    }
-
-    private static abstract class RunnableWithResult<T> implements Runnable {
-        private volatile T result;
-
-        protected abstract T createResult();
-
-        @Override
-        public void run() {
-            result = createResult();
-        }
-
-        public T getResult() {
-            return result;
-        }
-    }
-
-    private static abstract class RendererHandler<T> extends Handler {
-
-        private final WeakReference<T> mRendererRef;
-
-        RendererHandler(Looper looper, T renderer) {
-            super(looper);
-            mRendererRef = new WeakReference<>(renderer);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            T renderer = mRendererRef.get();
-            if (renderer != null) {
-                handleMessage(msg, renderer);
-            }
-        }
-
-        public abstract void handleMessage(Message msg, T renderer);
-    }
-}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index 360f6b4..16c0e94 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -15,38 +15,24 @@
  */
 package com.android.car.cluster;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.app.ActivityOptions;
-import android.car.Car;
 import android.car.CarAppFocusManager;
-import android.car.cluster.CarInstrumentClusterManager;
 import android.car.cluster.IInstrumentClusterManagerCallback;
 import android.car.cluster.IInstrumentClusterManagerService;
 import android.car.cluster.renderer.IInstrumentCluster;
-import android.car.cluster.renderer.IInstrumentClusterCallback;
 import android.car.cluster.renderer.IInstrumentClusterNavigation;
-import android.car.cluster.renderer.InstrumentClusterRenderingService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
 import android.os.Message;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.view.KeyEvent;
 
 import com.android.car.AppFocusService;
@@ -59,11 +45,7 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 
 /**
  * Service responsible for interaction with car's instrument cluster.
@@ -71,41 +53,39 @@
  * @hide
  */
 @SystemApi
-public class InstrumentClusterService implements CarServiceBase,
-        FocusOwnershipCallback, KeyEventListener {
-
+public class InstrumentClusterService implements CarServiceBase, FocusOwnershipCallback,
+        KeyEventListener {
     private static final String TAG = CarLog.TAG_CLUSTER;
-    private static final Boolean DBG = false;
+    private static final ContextOwner NO_OWNER = new ContextOwner(0, 0);
 
     private final Context mContext;
-
     private final AppFocusService mAppFocusService;
     private final CarInputService mCarInputService;
-    private final PackageManager mPackageManager;
-    private final Object mSync = new Object();
-
-    private final ClusterServiceCallback mClusterCallback = new ClusterServiceCallback();
+    /**
+     * TODO: (b/121277787) Remove this on master.
+     * @deprecated CarInstrumentClusterManager is being deprecated.
+     */
+    @Deprecated
     private final ClusterManagerService mClusterManagerService = new ClusterManagerService();
-
+    private final Object mSync = new Object();
     @GuardedBy("mSync")
-    private ContextOwner mNavContextOwner;
+    private ContextOwner mNavContextOwner = NO_OWNER;
     @GuardedBy("mSync")
     private IInstrumentCluster mRendererService;
-    @GuardedBy("mSync")
-    private final HashMap<String, ClusterActivityInfo> mActivityInfoByCategory = new HashMap<>();
-    @GuardedBy("mSync")
-    private final HashMap<IBinder, ManagerCallbackInfo> mManagerCallbacks = new HashMap<>();
-
     // If renderer service crashed / stopped and this class fails to rebind with it immediately,
     // we should wait some time before next attempt. This may happen during APK update for example.
     private DeferredRebinder mDeferredRebinder;
-
+    // Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound
+    // (although not necessarily connected)
     private boolean mRendererBound = false;
 
+    /**
+     * Connection to {@link android.car.cluster.renderer.InstrumentClusterRendererService}
+     */
     private final ServiceConnection mRendererServiceConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder binder) {
-            if (DBG) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
             }
             IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder);
@@ -114,14 +94,16 @@
                 mRendererService = service;
                 navContextOwner = mNavContextOwner;
             }
-            if (navContextOwner !=  null && service != null) {
-                notifyNavContextOwnerChanged(service, navContextOwner.uid, navContextOwner.pid);
+            if (navContextOwner != null && service != null) {
+                notifyNavContextOwnerChanged(service, navContextOwner);
             }
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "onServiceDisconnected, name: " + name);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onServiceDisconnected, name: " + name);
+            }
             mContext.unbindService(this);
             mRendererBound = false;
 
@@ -141,12 +123,11 @@
         mContext = context;
         mAppFocusService = appFocusService;
         mCarInputService = carInputService;
-        mPackageManager = mContext.getPackageManager();
     }
 
     @Override
     public void init() {
-        if (DBG) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "init");
         }
 
@@ -157,7 +138,7 @@
 
     @Override
     public void release() {
-        if (DBG) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "release");
         }
 
@@ -173,51 +154,51 @@
         writer.println("**" + getClass().getSimpleName() + "**");
         writer.println("bound with renderer: " + mRendererBound);
         writer.println("renderer service: " + mRendererService);
+        writer.println("context owner: " + mNavContextOwner);
     }
 
     @Override
     public void onFocusAcquired(int appType, int uid, int pid) {
-        if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
-            return;
-        }
-
-        IInstrumentCluster service;
-        synchronized (mSync) {
-            mNavContextOwner = new ContextOwner(uid, pid);
-            service = mRendererService;
-        }
-
-        if (service != null) {
-            notifyNavContextOwnerChanged(service, uid, pid);
-        }
+        changeNavContextOwner(appType, uid, pid, true);
     }
 
     @Override
     public void onFocusAbandoned(int appType, int uid, int pid) {
+        changeNavContextOwner(appType, uid, pid, false);
+    }
+
+    private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) {
         if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
             return;
         }
 
         IInstrumentCluster service;
+        ContextOwner requester = new ContextOwner(uid, pid);
+        ContextOwner newOwner = acquire ? requester : NO_OWNER;
         synchronized (mSync) {
-            if (mNavContextOwner == null
-                    || mNavContextOwner.uid != uid
-                    || mNavContextOwner.pid != pid) {
-                return;  // Nothing to do here, no active focus or not owned by this client.
+            if ((acquire && Objects.equals(mNavContextOwner, requester))
+                    || (!acquire && !Objects.equals(mNavContextOwner, requester))) {
+                // Nothing to do here. Either the same owner is acquiring twice, or someone is
+                // abandoning a focus they didn't have.
+                Log.w(TAG, "Invalid nav context owner change (acquiring: " + acquire
+                        + "), current owner: [" + mNavContextOwner
+                        + "], requester: [" + requester + "]");
+                return;
             }
 
-            mNavContextOwner = null;
+            mNavContextOwner = newOwner;
             service = mRendererService;
         }
 
         if (service != null) {
-            notifyNavContextOwnerChanged(service, 0, 0);
+            notifyNavContextOwnerChanged(service, newOwner);
         }
     }
 
-    private static void notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid) {
+    private static void notifyNavContextOwnerChanged(IInstrumentCluster service,
+            ContextOwner owner) {
         try {
-            service.setNavigationContextOwner(uid, pid);
+            service.setNavigationContextOwner(owner.uid, owner.pid);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to call setNavigationContextOwner", e);
         }
@@ -234,23 +215,14 @@
 
         Intent intent = new Intent();
         intent.setComponent(ComponentName.unflattenFromString(rendererService));
-        Bundle extras = new Bundle();
-        extras.putBinder(
-                InstrumentClusterRenderingService.EXTRA_KEY_CALLBACK_SERVICE,
-                mClusterCallback);
-        intent.putExtras(extras);
         return mContext.bindServiceAsUser(intent, mRendererServiceConnection,
                 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM);
     }
 
     @Nullable
     public IInstrumentClusterNavigation getNavigationService() {
-        IInstrumentCluster service;
-        synchronized (mSync) {
-            service = mRendererService;
-        }
-
         try {
+            IInstrumentCluster service = getInstrumentClusterRendererService();
             return service == null ? null : service.getNavigationService();
         } catch (RemoteException e) {
             Log.e(TAG, "getNavigationServiceBinder" , e);
@@ -258,21 +230,21 @@
         }
     }
 
+    /**
+     * @deprecated {@link android.car.cluster.CarInstrumentClusterManager} is now deprecated.
+     */
+    @Deprecated
     public IInstrumentClusterManagerService.Stub getManagerService() {
         return mClusterManagerService;
     }
 
     @Override
     public boolean onKeyEvent(KeyEvent event) {
-        if (DBG) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event);
         }
 
-        IInstrumentCluster service;
-        synchronized (mSync) {
-            service = mRendererService;
-        }
-
+        IInstrumentCluster service = getInstrumentClusterRendererService();
         if (service != null) {
             try {
                 service.onKeyEvent(event);
@@ -283,6 +255,14 @@
         return true;
     }
 
+    private IInstrumentCluster getInstrumentClusterRendererService() {
+        IInstrumentCluster service;
+        synchronized (mSync) {
+            service = mRendererService;
+        }
+        return service;
+    }
+
     private static class ContextOwner {
         final int uid;
         final int pid;
@@ -291,257 +271,47 @@
             this.uid = uid;
             this.pid = pid;
         }
-    }
 
-    private static class ClusterActivityInfo {
-        Bundle launchOptions;  // ActivityOptions
-        Bundle state;          // ClusterActivityState
-    }
-
-    private void enforcePermission(String permission) {
-        int callingUid = Binder.getCallingUid();
-        int callingPid = Binder.getCallingPid();
-        if (Binder.getCallingUid() == Process.myUid()) {
-            if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
-                throw new SecurityException("Permission " + permission + " is not granted to "
-                        + "client {uid: " + callingUid + ", pid: " + callingPid + "}");
-            }
-        }
-    }
-
-    private void enforceClusterControlPermission() {
-        enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
-    }
-
-    private void doStartClusterActivity(Intent intent) {
-        enforceClusterControlPermission();
-
-        // Category from given intent should match category from cluster vendor implementation.
-        List<ResolveInfo> resolveList = mPackageManager.queryIntentActivities(intent,
-                PackageManager.GET_RESOLVED_FILTER);
-        if (resolveList == null || resolveList.isEmpty()) {
-            Log.w(TAG, "Failed to resolve an intent: " + intent);
-            return;
-        }
-
-        resolveList = checkPermission(resolveList, Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER);
-        if (resolveList.isEmpty()) {
-            Log.w(TAG, String.format("intent didn't have permission %s: %s",
-                    Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER, intent));
-            return;
-        }
-
-        // TODO(b/63861009): we may have multiple navigation apps that eligible to be launched in
-        // the cluster. We need to resolve intent that may have multiple activity candidates, right
-        // now we pickup the first one that matches registered category (resolveList is sorted
-        // priority).
-        Pair<ResolveInfo, ClusterActivityInfo> attributedResolveInfo =
-                findClusterActivityOptions(resolveList);
-        if (attributedResolveInfo == null) {
-            Log.w(TAG, "Unable to start an activity with intent: " + intent + " in the cluster: "
-                    + "category intent didn't match with any categories from vendor "
-                    + "implementation");
-            return;
-        }
-        ClusterActivityInfo opts = attributedResolveInfo.second;
-
-        // Intent was already checked for permission and resolved, make it explicit.
-        intent.setComponent(attributedResolveInfo.first.getComponentInfo().getComponentName());
-
-        intent.putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE, opts.state);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        // Virtual display could be private and not available to calling process.
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mContext.startActivityAsUser(intent, opts.launchOptions, UserHandle.CURRENT);
-            Log.i(TAG, String.format("activity launched: %s (options: %s, displayId: %d)",
-                    opts.launchOptions, intent, new ActivityOptions(opts.launchOptions)
-                            .getLaunchDisplayId()));
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private List<ResolveInfo> checkPermission(List<ResolveInfo> resolveList,
-            String permission) {
-        List<ResolveInfo> permittedResolveList = new ArrayList<>(resolveList.size());
-        for (ResolveInfo info : resolveList) {
-            String pkgName = info.getComponentInfo().packageName;
-            if (mPackageManager.checkPermission(permission, pkgName) == PERMISSION_GRANTED) {
-                permittedResolveList.add(info);
-            } else {
-                Log.w(TAG, "Permission " + permission + " not granted for "
-                        + info.getComponentInfo());
-            }
-
-        }
-        return permittedResolveList;
-    }
-
-    private void doRegisterManagerCallback(IInstrumentClusterManagerCallback callback)
-            throws RemoteException {
-        enforceClusterControlPermission();
-        IBinder binder = callback.asBinder();
-
-        List<Pair<String, Bundle>> knownActivityStates = null;
-        ManagerCallbackDeathRecipient deathRecipient = new ManagerCallbackDeathRecipient(binder);
-        synchronized (mSync) {
-            if (mManagerCallbacks.containsKey(binder)) {
-                Log.w(TAG, "Manager callback already registered for binder: " + binder);
-                return;
-            }
-            mManagerCallbacks.put(binder, new ManagerCallbackInfo(callback, deathRecipient));
-            if (!mActivityInfoByCategory.isEmpty()) {
-                knownActivityStates = new ArrayList<>(mActivityInfoByCategory.size());
-                for (Map.Entry<String, ClusterActivityInfo> it : mActivityInfoByCategory.entrySet()) {
-                    knownActivityStates.add(new Pair<>(it.getKey(), it.getValue().state));
-                }
-            }
-        }
-        binder.linkToDeath(deathRecipient, 0);
-
-        // Notify manager immediately with known states.
-        if (knownActivityStates != null) {
-            for (Pair<String, Bundle> it : knownActivityStates) {
-                callback.setClusterActivityState(it.first, it.second);
-            }
-        }
-    }
-
-    private void doUnregisterManagerCallback(IBinder binder) throws RemoteException {
-        enforceClusterControlPermission();
-        ManagerCallbackInfo info;
-        synchronized (mSync) {
-            info = mManagerCallbacks.get(binder);
-            if (info == null) {
-                Log.w(TAG, "Unable to unregister manager callback binder: " + binder + " because "
-                        + "it wasn't previously registered.");
-                return;
-            }
-            mManagerCallbacks.remove(binder);
-        }
-        binder.unlinkToDeath(info.deathRecipient, 0);
-    }
-
-    @Nullable
-    private Pair<ResolveInfo, ClusterActivityInfo> findClusterActivityOptions(
-            List<ResolveInfo> resolveList) {
-        synchronized (mSync) {
-            Set<String> registeredCategories = mActivityInfoByCategory.keySet();
-
-            for (ResolveInfo resolveInfo : resolveList) {
-                if (resolveInfo.filter == null) {
-                    continue;
-                }
-                for (String category : registeredCategories) {
-                    if (resolveInfo.filter.hasCategory(category)) {
-                        ClusterActivityInfo categoryInfo = mActivityInfoByCategory.get(category);
-                        return new Pair<>(resolveInfo, categoryInfo);
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    private class ManagerCallbackDeathRecipient implements DeathRecipient {
-        private final IBinder mBinder;
-
-        ManagerCallbackDeathRecipient(IBinder binder) {
-            mBinder = binder;
+        @Override
+        public String toString() {
+            return "uid: " + uid + ", pid: " + pid;
         }
 
         @Override
-        public void binderDied() {
-            try {
-                doUnregisterManagerCallback(mBinder);
-            } catch (RemoteException e) {
-                // Ignore, shutdown route.
-            }
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            ContextOwner that = (ContextOwner) o;
+            return uid == that.uid && pid == that.pid;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(uid, pid);
         }
     }
 
+    /**
+     * TODO: (b/121277787) Remove on master
+     * @deprecated CarClusterManager is being deprecated.
+     */
+    @Deprecated
     private class ClusterManagerService extends IInstrumentClusterManagerService.Stub {
-
         @Override
         public void startClusterActivity(Intent intent) throws RemoteException {
-            doStartClusterActivity(intent);
+            // No op.
         }
 
         @Override
         public void registerCallback(IInstrumentClusterManagerCallback callback)
                 throws RemoteException {
-            doRegisterManagerCallback(callback);
+            // No op.
         }
 
         @Override
         public void unregisterCallback(IInstrumentClusterManagerCallback callback)
                 throws RemoteException {
-            doUnregisterManagerCallback(callback.asBinder());
-        }
-    }
-
-    @GuardedBy("mSync")
-    private ClusterActivityInfo getOrCreateActivityInfoLocked(String category) {
-        return mActivityInfoByCategory.computeIfAbsent(category, k -> new ClusterActivityInfo());
-    }
-
-    /** This is communication channel from vendor cluster implementation to Car Service. */
-    private class ClusterServiceCallback extends IInstrumentClusterCallback.Stub {
-
-        @Override
-        public void setClusterActivityLaunchOptions(String category, Bundle activityOptions)
-                throws RemoteException {
-            doSetActivityLaunchOptions(category, activityOptions);
-        }
-
-        @Override
-        public void setClusterActivityState(String category, Bundle clusterActivityState)
-                throws RemoteException {
-            doSetClusterActivityState(category, clusterActivityState);
-        }
-    }
-
-    /** Called from cluster vendor implementation */
-    private void doSetActivityLaunchOptions(String category, Bundle activityOptions) {
-        if (DBG) {
-            Log.d(TAG, "doSetActivityLaunchOptions, category: " + category
-                    + ", options: " + activityOptions);
-        }
-        synchronized (mSync) {
-            ClusterActivityInfo info = getOrCreateActivityInfoLocked(category);
-            info.launchOptions = activityOptions;
-        }
-    }
-
-    /** Called from cluster vendor implementation */
-    private void doSetClusterActivityState(String category, Bundle clusterActivityState)
-            throws RemoteException {
-        if (DBG) {
-            Log.d(TAG, "doSetClusterActivityState, category: " + category
-                    + ", state: " + clusterActivityState);
-        }
-
-        List<ManagerCallbackInfo> managerCallbacks;
-        synchronized (mSync) {
-            ClusterActivityInfo info = getOrCreateActivityInfoLocked(category);
-            info.state = clusterActivityState;
-            managerCallbacks = new ArrayList<>(mManagerCallbacks.values());
-        }
-
-        for (ManagerCallbackInfo cbInfo : managerCallbacks) {
-            cbInfo.callback.setClusterActivityState(category, clusterActivityState);
-        }
-    }
-
-    private static class ManagerCallbackInfo {
-        final IInstrumentClusterManagerCallback callback;
-        final ManagerCallbackDeathRecipient deathRecipient;
-
-        ManagerCallbackInfo(IInstrumentClusterManagerCallback callback,
-                ManagerCallbackDeathRecipient deathRecipient) {
-            this.callback = callback;
-            this.deathRecipient = deathRecipient;
+            // No op.
         }
     }
 
diff --git a/service/src/com/android/car/trust/BleService.java b/service/src/com/android/car/trust/BleService.java
new file mode 100644
index 0000000..3ec1dca
--- /dev/null
+++ b/service/src/com/android/car/trust/BleService.java
@@ -0,0 +1,226 @@
+/*
+ * 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/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
index 8a383fd..31aad7d 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
@@ -165,7 +165,7 @@
     }
 
     @Override
-    protected void onKeyEvent(KeyEvent keyEvent) {
+    public void onKeyEvent(KeyEvent keyEvent) {
         Log.d(TAG, "onKeyEvent, keyEvent: " + keyEvent);
         Bundle data = new Bundle();
         data.putParcelable(MSG_KEY_KEY_EVENT, keyEvent);
@@ -194,7 +194,7 @@
     }
 
     @Override
-    protected NavigationRenderer getNavigationRenderer() {
+    public NavigationRenderer getNavigationRenderer() {
         NavigationRenderer navigationRenderer = new NavigationRenderer() {
             @Override
             public CarNavigationInstrumentCluster getNavigationProperties() {
@@ -243,7 +243,7 @@
         if (args != null && args.length > 0) {
             execShellCommand(args);
         } else {
-            writer.println("* dump " + getClass().getCanonicalName() + " *");
+            super.dump(fd, writer, args);
             writer.println("DisplayProvider: " + mDisplayProvider);
         }
     }
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index c8bff38..f8e8cbc 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -15,7 +15,6 @@
  */
 package android.car.cluster.sample;
 
-import static android.car.cluster.CarInstrumentClusterManager.CATEGORY_NAVIGATION;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.LOCAL_BINDING_ACTION;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_DISPLAY_ID;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_STATE;
@@ -33,7 +32,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.car.cluster.CarInstrumentClusterManager;
+import android.car.Car;
 import android.car.cluster.ClusterActivityState;
 import android.car.cluster.sample.sensors.Sensors;
 import android.content.ActivityNotFoundException;
@@ -340,7 +339,7 @@
 
     private void reportNavDisplay(VirtualDisplay virtualDisplay) {
         Bundle data = new Bundle();
-        data.putString(MSG_KEY_CATEGORY, CATEGORY_NAVIGATION);
+        data.putString(MSG_KEY_CATEGORY, Car.CAR_CATEGORY_NAVIGATION);
         data.putInt(MSG_KEY_ACTIVITY_DISPLAY_ID, virtualDisplay.mDisplayId);
         data.putBundle(MSG_KEY_ACTIVITY_STATE, ClusterActivityState
                 .create(virtualDisplay.mDisplayId != Display.INVALID_DISPLAY,
@@ -445,7 +444,7 @@
             if (navigationActivity == null) {
                 throw new ActivityNotFoundException();
             }
-            Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION)
+            Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Car.CAR_CATEGORY_NAVIGATION)
                     .setPackage(navigationActivity.getPackageName())
                     .setComponent(navigationActivity)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -467,7 +466,7 @@
     /**
      * Returns a default navigation activity to show in the cluster.
      * In the current implementation we search for an activity with the
-     * {@link CarInstrumentClusterManager#CATEGORY_NAVIGATION} category from the same navigation app
+     * {@link Car#CAR_CATEGORY_NAVIGATION} category from the same navigation app
      * selected from CarLauncher (see CarLauncher#getMapsIntent()).
      * Alternatively, other implementations could:
      * <ul>
@@ -486,7 +485,7 @@
                 PackageManager.MATCH_DEFAULT_ONLY, userId);
 
         // Get all possible cluster activities
-        intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION);
+        intent = new Intent(Intent.ACTION_MAIN).addCategory(Car.CAR_CATEGORY_NAVIGATION);
         List<ResolveInfo> candidates = pm.queryIntentActivitiesAsUser(intent, 0, userId);
 
         // If there is a select navigation app, try finding a matching auxiliary navigation activity
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
index 9b3a2ec..a301935 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
@@ -71,7 +71,7 @@
         mManeuver.setImageDrawable(getManeuverIcon(step != null ? step.getManeuver() : null));
         mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
         mSegment.setText(getSegmentString(state.getCurrentSegment()));
-        mCue.setRichText(step.getCue());
+        mCue.setRichText(step != null ? step.getCue() : null);
 
         if (step.getLanes().size() > 0) {
             mLane.setLanes(step.getLanes());
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 2709e5d..2c4713c 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -93,13 +93,13 @@
                   android:theme="@android:style/Theme.Material.Light.Dialog"
                   android:launchMode="singleTop">
         </activity>
+
         <activity android:name=".cluster.FakeClusterNavigationActivity"
                   android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                   android:launchMode="singleInstance"
                   android:resizeableActivity="true"
-                  android:allowEmbedded="true"
-                  android:permission="android.car.permission.CAR_DISPLAY_IN_CLUSTER">
-            <intent-filter android:priority="-1">
+                  android:allowEmbedded="true">
+            <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.car.cluster.NAVIGATION"/>
             </intent-filter>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/avatar1.png b/tests/EmbeddedKitchenSinkApp/res/drawable/avatar1.png
new file mode 100644
index 0000000..94e4cf1
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/avatar1.png
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/avatar2.png b/tests/EmbeddedKitchenSinkApp/res/drawable/avatar2.png
new file mode 100644
index 0000000..10021f2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/avatar2.png
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
index d244904..a29296c 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -48,12 +48,5 @@
                 android:padding="20dp"
                 android:text="@string/cluster_stop"
                 android:id="@+id/cluster_stop_button"/>
-            <Button
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_margin="10dp"
-                android:padding="20dp"
-                android:text="@string/cluster_start_activity"
-                android:id="@+id/cluster_start_activity"/>
     </LinearLayout>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index 1dc40f6..022aa9f 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -13,73 +13,113 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical" >
-    <Button
-        android:id="@+id/cancel_all_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Cancel All"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/importance_high_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Importance: HIGH (Shows heads-up)"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/importance_high_button_2"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Importance: HIGH (Shows heads-up) 2"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/importance_default_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Importance: DEFAULT"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/importance_low_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Importance: LOW"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/importance_min_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Importance: MIN"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/ongoing_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Ongoing"
-        android:textSize="35sp"/>
-    <Button
-        android:id="@+id/category_message_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Category: CATEGORY_MESSAGE"
-        android:textSize="35sp"/>
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fadeScrollbars="false"
+            android:fillViewport="true">
+
     <LinearLayout
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal" >
-         <Button
-        android:id="@+id/category_car_emerg_button"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="Category: CATEGORY_EMERG"
-        android:textSize="35sp"/>
-         <Button
-        android:id="@+id/category_car_warning_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Category: CATEGORY_WARN"
-        android:textSize="35sp"/>
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="A notification will not show as a heads-up unless specified. Progress bar and ongoing notifications can only be dismissed by the CANCEL ALL button."/>
+
+        <Button
+            android:id="@+id/cancel_all_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Cancel All"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/importance_high_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="1 Importance: HIGH (Shows heads-up)"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/importance_high_button_2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="2 Importance: HIGH (Updates 1's heads-up)"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/importance_high_button_3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="3 Importance: DEFAULT (Updates 1 in notification center, but doesn't update the heads-up)"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/category_message_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Category: CATEGORY_MESSAGE (Shows heads-up)"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/category_car_emergency_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Category: CATEGORY_CAR_EMERGENCY (Shows heads-up)"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/category_car_warning_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Category: CATEGORY_CAR_WARNING (Shows heads-up)"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/category_car_info_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Category: CATEGORY_CAR_INFORMATION"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/progress_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Category: PROGRESS"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/importance_default_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Importance: DEFAULT"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/importance_low_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Importance: LOW"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/importance_min_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Importance: MIN"
+            android:textSize="20sp"/>
+
+        <Button
+            android:id="@+id/ongoing_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Ongoing"
+            android:textSize="20sp"/>
     </LinearLayout>
-</LinearLayout>
+</ScrollView>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/users.xml b/tests/EmbeddedKitchenSinkApp/res/layout/users.xml
new file mode 100644
index 0000000..5a862bf
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/users.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <ListView
+        android:id="@+id/user_restrictions_list"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:scrollbars="vertical"/>
+
+    <Button
+        android:id="@+id/apply_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/users_button_padding"
+        android:textSize="@dimen/users_button_text_size"
+        android:text="@string/users_apply_button" />
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
index eb6b739..437d69d 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
@@ -63,4 +63,10 @@
     <dimen name="car_keyline_2">108dp</dimen>
     <dimen name="car_keyline_3">152dp</dimen>
     <dimen name="car_keyline_4">182dp</dimen>
+
+    <!-- Users -->
+    <dimen name="users_button_padding">10dp</dimen>
+    <dimen name="users_button_text_size">24sp</dimen>
+    <dimen name="users_checkbox_padding">5dp</dimen>
+    <dimen name="users_checkbox_text_size">32sp</dimen>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 5894892..f1a645e 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -301,4 +301,7 @@
     <string name="weblink_nytimes" translatable="false">www.nytimes.com</string>
     <string name="weblink_support_name" translatable="false">support.google.com</string>
     <string name="weblink_support" translatable="false">https://support.google.com/chrome/answer/95414?hl=en&amp;ref_topic=7438008</string>
+
+    <!-- Users -->
+    <string name="users_apply_button" translatable="false">Apply</string>
 </resources>
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 4d888d3..3c220ca 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -61,11 +61,12 @@
 import com.google.android.car.kitchensink.setting.CarServiceSettingsActivity;
 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
 import com.google.android.car.kitchensink.touch.TouchTestFragment;
+import com.google.android.car.kitchensink.users.UsersFragment;
 import com.google.android.car.kitchensink.vhal.VehicleHalFragment;
 import com.google.android.car.kitchensink.volume.VolumeTestFragment;
 import com.google.android.car.kitchensink.weblinks.WebLinksTestFragment;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 
@@ -145,47 +146,40 @@
         }
     }
 
-    private final List<MenuEntry> mMenuEntries = new ArrayList<MenuEntry>() {
-        {
-            add("alert window", AlertDialogTestFragment.class);
-            add("assistant", CarAssistantFragment.class);
-            add("audio", AudioTestFragment.class);
-            add("bluetooth headset",BluetoothHeadsetFragment.class);
-            add("bluetooth messaging test", MapMceTestFragment.class);
-            add("carboard", KeyboardTestFragment.class);
-            add("cubes test", CubesTestFragment.class);
-            add("diagnostic", DiagnosticTestFragment.class);
-            add("display info", DisplayInfoFragment.class);
-            add("hvac", HvacTestFragment.class);
-            add("inst cluster", InstrumentClusterFragment.class);
-            add("input test", InputTestFragment.class);
-            add("notification", NotificationFragment.class);
-            add("orientation test", OrientationTestFragment.class);
-            add("power test", PowerTestFragment.class);
-            add("property test", PropertyTestFragment.class);
-            add("sensors", SensorsTestFragment.class);
-            add("storage lifetime", StorageLifetimeFragment.class);
-            add("touch test", TouchTestFragment.class);
-            add("volume test", VolumeTestFragment.class);
-            add("vehicle hal", VehicleHalFragment.class);
-            add("car service settings", () -> {
+    private final List<MenuEntry> mMenuEntries = Arrays.asList(
+            new FragmentMenuEntry("activity view", ActivityViewTestFragment.class),
+            new FragmentMenuEntry("alert window", AlertDialogTestFragment.class),
+            new FragmentMenuEntry("assistant", CarAssistantFragment.class),
+            new FragmentMenuEntry("audio", AudioTestFragment.class),
+            new FragmentMenuEntry("bluetooth headset", BluetoothHeadsetFragment.class),
+            new FragmentMenuEntry("bluetooth messaging test", MapMceTestFragment.class),
+            new OnClickMenuEntry("car service settings", () -> {
                 Intent intent = new Intent(KitchenSinkActivity.this,
-                    CarServiceSettingsActivity.class);
+                        CarServiceSettingsActivity.class);
                 startActivity(intent);
-            });
-            add("activity view", ActivityViewTestFragment.class);
-            add("connectivity", ConnectivityFragment.class);
-            add("web links", WebLinksTestFragment.class);
-            add("quit", KitchenSinkActivity.this::finish);
-        }
+            }),
+            new FragmentMenuEntry("carboard", KeyboardTestFragment.class),
+            new FragmentMenuEntry("connectivity", ConnectivityFragment.class),
+            new FragmentMenuEntry("cubes test", CubesTestFragment.class),
+            new FragmentMenuEntry("diagnostic", DiagnosticTestFragment.class),
+            new FragmentMenuEntry("display info", DisplayInfoFragment.class),
+            new FragmentMenuEntry("hvac", HvacTestFragment.class),
+            new FragmentMenuEntry("inst cluster", InstrumentClusterFragment.class),
+            new FragmentMenuEntry("input test", InputTestFragment.class),
+            new FragmentMenuEntry("notification", NotificationFragment.class),
+            new FragmentMenuEntry("orientation test", OrientationTestFragment.class),
+            new FragmentMenuEntry("power test", PowerTestFragment.class),
+            new FragmentMenuEntry("property test", PropertyTestFragment.class),
+            new FragmentMenuEntry("sensors", SensorsTestFragment.class),
+            new FragmentMenuEntry("storage lifetime", StorageLifetimeFragment.class),
+            new FragmentMenuEntry("touch test", TouchTestFragment.class),
+            new FragmentMenuEntry("users", UsersFragment.class),
+            new FragmentMenuEntry("volume test", VolumeTestFragment.class),
+            new FragmentMenuEntry("vehicle hal", VehicleHalFragment.class),
+            new FragmentMenuEntry("web links", WebLinksTestFragment.class),
+            new OnClickMenuEntry("quit", KitchenSinkActivity.this::finish)
+    );
 
-        <T extends Fragment> void add(String text, Class<T> clazz) {
-            add(new FragmentMenuEntry(text, clazz));
-        }
-        void add(String text, ClickHandler onClick) {
-            add(new OnClickMenuEntry(text, onClick));
-        }
-    };
     private Car mCarApi;
     private CarHvacManager mHvacManager;
     private CarPowerManager mPowerManager;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java
index d50f053..1ef5449 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java
@@ -18,14 +18,10 @@
 
 import android.app.Activity;
 import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.cluster.CarInstrumentClusterManager;
 import android.car.cluster.ClusterActivityState;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.util.Log;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
@@ -35,14 +31,9 @@
 /**
  * Fake navigation activity for instrument cluster.
  */
-public class FakeClusterNavigationActivity
-        extends Activity
-        implements CarInstrumentClusterManager.Callback {
-
+public class FakeClusterNavigationActivity extends Activity {
     private final static String TAG = FakeClusterNavigationActivity.class.getSimpleName();
 
-    private Car mCarApi;
-    private CarInstrumentClusterManager mClusterManager;
     private ImageView mUnobscuredArea;
 
     @Override
@@ -52,26 +43,27 @@
         setContentView(R.layout.fake_cluster_navigation_activity);
         mUnobscuredArea = findViewById(R.id.unobscuredArea);
 
-        mCarApi = Car.createCar(this /* context */, new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                onCarConnected(mCarApi);
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName name) {
-                onCarDisconnected(mCarApi);
-            }
-        });
-        Log.i(TAG, "Connecting to car api...");
-        mCarApi.connect();
+        handleIntent(getIntent());
     }
 
-
     @Override
-    public void onClusterActivityStateChanged(String category, Bundle clusterActivityState) {
-        ClusterActivityState state = ClusterActivityState.fromBundle(clusterActivityState);
-        Log.i(TAG, "onClusterActivityStateChanged, category: " + category + ", state: " + state);
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleIntent(intent);
+    }
+
+    private void handleIntent(Intent intent) {
+        if (intent == null) {
+            Log.w(TAG, "Received a null intent");
+            return;
+        }
+        Bundle bundle = intent.getBundleExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+        if (bundle == null) {
+            Log.w(TAG, "Received an intent without " + Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+            return;
+        }
+        ClusterActivityState state = ClusterActivityState.fromBundle(bundle);
+        Log.i(TAG, "handling intent with state: " + state);
 
         Rect unobscured = state.getUnobscuredBounds();
         RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
@@ -79,26 +71,4 @@
         lp.setMargins(unobscured.left, unobscured.top, 0, 0);
         mUnobscuredArea.setLayoutParams(lp);
     }
-
-    private void onCarConnected(Car car) {
-        Log.i(TAG, "onCarConnected, car: " + car);
-        try {
-            mClusterManager = (CarInstrumentClusterManager) car.getCarManager(
-                    android.car.Car.CAR_INSTRUMENT_CLUSTER_SERVICE);
-        } catch (CarNotConnectedException e) {
-            throw new IllegalStateException(e);
-        }
-
-        try {
-            Log.i(TAG, "registering callback...");
-            mClusterManager.registerCallback(CarInstrumentClusterManager.CATEGORY_NAVIGATION, this);
-            Log.i(TAG, "callback registered");
-        } catch (android.car.CarNotConnectedException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    private void onCarDisconnected(Car car) {
-
-    }
 }
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index 7e40b5b..a711cbb 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -19,10 +19,8 @@
 import android.car.Car;
 import android.car.CarAppFocusManager;
 import android.car.CarNotConnectedException;
-import android.car.cluster.CarInstrumentClusterManager;
 import android.car.navigation.CarNavigationStatusManager;
 import android.content.ComponentName;
-import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
@@ -39,7 +37,6 @@
 import androidx.car.cluster.navigation.NavigationState;
 import androidx.fragment.app.Fragment;
 
-import com.google.android.car.kitchensink.KitchenSinkActivity;
 import com.google.android.car.kitchensink.R;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -161,7 +158,6 @@
 
         view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
         view.findViewById(R.id.cluster_stop_button).setOnClickListener(v -> stopCluster());
-        view.findViewById(R.id.cluster_start_activity).setOnClickListener(v -> startNavActivity());
 
         mTurnByTurnButton = view.findViewById(R.id.cluster_turn_left_button);
         mTurnByTurnButton.setOnClickListener(v -> toggleSendTurn());
@@ -175,32 +171,6 @@
         super.onCreate(savedInstanceState);
     }
 
-    private void startNavActivity() {
-        CarInstrumentClusterManager clusterManager;
-        try {
-            clusterManager = (CarInstrumentClusterManager) mCarApi.getCarManager(
-                    android.car.Car.CAR_INSTRUMENT_CLUSTER_SERVICE);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to get CarInstrumentClusterManager", e);
-            Toast.makeText(getContext(), "Failed to get CarInstrumentClusterManager",
-                    Toast.LENGTH_LONG).show();
-            return;
-        }
-
-        // Implicit intent ("startActivity" method doesn't work with explicit intents)
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(CarInstrumentClusterManager.CATEGORY_NAVIGATION);
-        intent.setPackage(KitchenSinkActivity.class.getPackage().getName());
-        try {
-            clusterManager.startActivity(intent);
-        } catch (android.car.CarNotConnectedException e) {
-            Log.e(TAG, "Failed to startActivity in cluster", e);
-            Toast.makeText(getContext(), getText(R.string.cluster_start_activity_failed),
-                    Toast.LENGTH_LONG).show();
-            return;
-        }
-    }
-
     /**
      * Enables/disables sending turn-by-turn data through the {@link CarNavigationStatusManager}
      */
@@ -264,6 +234,7 @@
 
     private void initCluster() {
         if (hasFocus()) {
+            Log.i(TAG, "Already has focus");
             return;
         }
         try {
@@ -271,11 +242,9 @@
                     CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
             mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
                     mFocusCallback);
-            if (!hasFocus()) {
-                throw new RuntimeException("Focus was not acquired.");
-            }
+            Log.i(TAG, "Focus requested");
         } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to set active focus", e);
+            Log.e(TAG, "Failed to request focus", e);
         }
     }
 
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 d7230fc..413368f 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
@@ -1,7 +1,5 @@
 package com.google.android.car.kitchensink.notification;
 
-import static android.security.KeyStore.getApplicationContext;
-
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -11,153 +9,156 @@
 import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Button;
 
 import androidx.fragment.app.Fragment;
 
 import com.google.android.car.kitchensink.KitchenSinkActivity;
 import com.google.android.car.kitchensink.R;
 
+import java.util.HashMap;
+
 /**
  * Test fragment that can send all sorts of notifications.
  */
 public class NotificationFragment extends Fragment {
-    private static final String CHANNEL_ID_1 = "kitchensink.channel1";
-    private static final String CHANNEL_ID_2 = "kitchensink.channel2";
-    private static final String CHANNEL_ID_3 = "kitchensink.channel3";
-    private static final String CHANNEL_ID_4 = "kitchensink.channel4";
-    private static final String CHANNEL_ID_5 = "kitchensink.channel5";
-    private static final String CHANNEL_ID_6 = "kitchensink.channel6";
+    private static final String IMPORTANCE_HIGH_ID = "importance_high";
+    private static final String IMPORTANCE_DEFAULT_ID = "importance_default";
+    private static final String IMPORTANCE_LOW_ID = "importance_low";
+    private static final String IMPORTANCE_MIN_ID = "importance_min";
+    private static final String IMPORTANCE_NONE_ID = "importance_none";
+    private int mCurrentNotificationId = 0;
+    private NotificationManager mManager;
+    private Context mContext;
+    private Handler mHandler = new Handler();
+    private HashMap<Integer, Runnable> mUpdateRunnables = new HashMap<>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mManager =
+                (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+        mContext = getActivity();
+
+        mManager.createNotificationChannel(new NotificationChannel(
+                IMPORTANCE_HIGH_ID, "Importance High", NotificationManager.IMPORTANCE_HIGH));
+
+        mManager.createNotificationChannel(new NotificationChannel(
+                IMPORTANCE_DEFAULT_ID,
+                "Importance Default",
+                NotificationManager.IMPORTANCE_DEFAULT));
+
+        mManager.createNotificationChannel(new NotificationChannel(
+                IMPORTANCE_LOW_ID, "Importance Low", NotificationManager.IMPORTANCE_LOW));
+
+        mManager.createNotificationChannel(new NotificationChannel(
+                IMPORTANCE_MIN_ID, "Importance Min", NotificationManager.IMPORTANCE_MIN));
+
+        mManager.createNotificationChannel(new NotificationChannel(
+                IMPORTANCE_NONE_ID, "Importance None", NotificationManager.IMPORTANCE_NONE));
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
         View view = inflater.inflate(R.layout.notification_fragment, container, false);
-        Button cancelAllButton = view.findViewById(R.id.cancel_all_button);
-        Button importanceHighButton = view.findViewById(R.id.importance_high_button);
-        Button importanceHighButton2 = view.findViewById(R.id.importance_high_button_2);
-        Button importanceLowButton = view.findViewById(R.id.importance_low_button);
-        Button importanceMinButton = view.findViewById(R.id.importance_min_button);
-        Button importanceDefaultButton = view.findViewById(R.id.importance_default_button);
-        Button ongoingButton = view.findViewById(R.id.ongoing_button);
-        Button messageButton = view.findViewById(R.id.category_message_button);
-        Button emerg = view.findViewById(R.id.category_car_emerg_button);
-        Button warn = view.findViewById(R.id.category_car_warning_button);
 
-        NotificationManager manager =
-                (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+        initCancelAllButton(view);
+        initHeadsupAndUpdatesBotton(view);
+        initImportanceDefaultButton(view);
+        initImportanceLowButton(view);
+        initImportanceMinButton(view);
+        initOngoingButton(view);
+        initMessagingStyleButton(view);
+        initProgressButton(view);
+        initCarCategoriesButton(view);
 
-        // cancel all button
-        cancelAllButton.setOnClickListener(v -> manager.cancelAll());
+        return view;
+    }
 
-        // importance high notifications
-        NotificationChannel highImportanceChannel =
-                new NotificationChannel(
-                        CHANNEL_ID_1, "Importance High", NotificationManager.IMPORTANCE_HIGH);
-        manager.createNotificationChannel(highImportanceChannel);
-
-        importanceHighButton.setOnClickListener(v -> {
-
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
-                    .setContentTitle("Importance High")
-                    .setContentText("blah")
-                    .setSmallIcon(R.drawable.car_ic_mode)
-                    .build();
-            manager.notify(1, notification);
+    private void initCancelAllButton(View view) {
+        view.findViewById(R.id.cancel_all_button).setOnClickListener(v -> {
+            for (Runnable runnable : mUpdateRunnables.values()) {
+                mHandler.removeCallbacks(runnable);
+            }
+            mUpdateRunnables.clear();
+            mManager.cancelAll();
         });
+    }
 
-        importanceHighButton2.setOnClickListener(v -> {
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
-                    .setContentTitle("Importance High 2")
-                    .setContentText("blah blah blah")
-                    .setSmallIcon(R.drawable.car_ic_mode)
-                    .build();
-            manager.notify(2, notification);
-        });
+    private void initHeadsupAndUpdatesBotton(View view) {
+        int id = mCurrentNotificationId++;
+        Intent mIntent = new Intent(getActivity(), KitchenSinkActivity.class);
+        PendingIntent mPendingIntent = PendingIntent.getActivity(getActivity(), 0, mIntent, 0);
 
-        // importance default
-        importanceDefaultButton.setOnClickListener(v -> {
-            NotificationChannel channel =
-                    new NotificationChannel(
-                            CHANNEL_ID_3,
-                            "Importance Default",
-                            NotificationManager.IMPORTANCE_DEFAULT);
-            manager.createNotificationChannel(channel);
+        Notification notification1 = new Notification
+                .Builder(getActivity(), IMPORTANCE_HIGH_ID)
+                .setContentTitle("Importance High")
+                .setContentText("blah")
+                .setSmallIcon(R.drawable.car_ic_mode)
+                .addAction(
+                        new Notification.Action.Builder(null, "Action1", mPendingIntent).build())
+                .addAction(
+                        new Notification.Action.Builder(null, "Action2", mPendingIntent).build())
+                .addAction(
+                        new Notification.Action.Builder(null, "Action3", mPendingIntent).build())
+                .setColor(mContext.getColor(android.R.color.holo_red_light))
+                .build();
 
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_3)
-                    .setContentTitle("Importance Default")
-                    .setSmallIcon(R.drawable.car_ic_mode)
-                    .build();
-            manager.notify(4, notification);
-        });
+        view.findViewById(R.id.importance_high_button).setOnClickListener(
+                v -> mManager.notify(id, notification1)
+        );
 
-        // importance low
-        importanceLowButton.setOnClickListener(v -> {
-            NotificationChannel channel =
-                    new NotificationChannel(
-                            CHANNEL_ID_4, "Importance Low", NotificationManager.IMPORTANCE_LOW);
-            manager.createNotificationChannel(channel);
+        Notification notification2 = new Notification
+                .Builder(getActivity(), IMPORTANCE_HIGH_ID)
+                .setContentTitle("This is an instant update")
+                .setContentText("of the previous one with IMPORTANCE_HIGH")
+                .setSmallIcon(R.drawable.car_ic_mode)
+                .setColor(mContext.getColor(android.R.color.holo_red_light))
+                .addAction(
+                        new Notification.Action.Builder(null, "Action?", mPendingIntent).build())
+                .build();
+        view.findViewById(R.id.importance_high_button_2).setOnClickListener(
+                v -> mManager.notify(id, notification2));
 
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_4)
-                    .setContentTitle("Importance Low")
-                    .setContentText("low low low")
-                    .setSmallIcon(R.drawable.car_ic_mode)
-                    .build();
-            manager.notify(5, notification);
-        });
+        Notification notification3 = new Notification
+                .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
+                .setContentTitle("This is an update")
+                .setContentText("of the previous one with IMPORTANCE_DEFAULT")
+                .setSmallIcon(R.drawable.car_ic_mode)
+                .setColor(mContext.getColor(android.R.color.holo_red_light))
+                .build();
+        view.findViewById(R.id.importance_high_button_3).setOnClickListener(
+                v -> mManager.notify(id, notification3));
+    }
 
-        // importance min
-        importanceMinButton.setOnClickListener(v -> {
-            NotificationChannel channel =
-                    new NotificationChannel(
-                            CHANNEL_ID_5, "Importance Min", NotificationManager.IMPORTANCE_MIN);
-            manager.createNotificationChannel(channel);
+    private void initMessagingStyleButton(View view) {
 
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_5)
-                    .setContentTitle("Importance Min")
-                    .setContentText("min min min")
-                    .setSmallIcon(R.drawable.car_ic_mode)
-                    .build();
-            manager.notify(6, notification);
-        });
+        Intent intent = new Intent(getActivity(), KitchenSinkActivity.class);
+        PendingIntent pendingIntent = PendingIntent.getActivity(getActivity(), 0, intent, 0);
 
-        // ongoing
-        ongoingButton.setOnClickListener(v -> {
-            NotificationChannel channel =
-                    new NotificationChannel(
-                            CHANNEL_ID_6, "Ongoing", NotificationManager.IMPORTANCE_DEFAULT);
-            manager.createNotificationChannel(channel);
-
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_6)
-                    .setContentTitle("Playing music or something")
-                    .setSmallIcon(R.drawable.car_ic_mode)
-                    .setOngoing(true)
-                    .build();
-            manager.notify(7, notification);
-        });
-
-        // category message
-        messageButton.setOnClickListener(v -> {
-            NotificationChannel channel =
-                    new NotificationChannel(
-                            CHANNEL_ID_2, "Message", NotificationManager.IMPORTANCE_HIGH);
-            manager.createNotificationChannel(channel);
-
-            Intent intent = new Intent(getActivity(), KitchenSinkActivity.class);
-            PendingIntent readIntent = PendingIntent.getActivity(getActivity(), 0, intent, 0);
+        view.findViewById(R.id.category_message_button).setOnClickListener(v -> {
 
             RemoteInput remoteInput = new RemoteInput.Builder("voice reply").build();
-            PendingIntent replyIntent = PendingIntent.getBroadcast(getApplicationContext(),
+            PendingIntent replyIntent = PendingIntent.getBroadcast(
+                    view.getContext().getApplicationContext(),
                     12345,
                     intent,
                     PendingIntent.FLAG_UPDATE_CURRENT);
 
-            Person personJohn = new Person.Builder().setName("John Doe").build();
-            Person personJane = new Person.Builder().setName("Jane Roe").build();
+            Person personJohn = new Person.Builder()
+                    .setName("John Doe")
+                    .setIcon(Icon.createWithResource(v.getContext(), R.drawable.avatar1))
+                    .build();
+            Person personJane = new Person.Builder()
+                    .setName("Jane Roe")
+                    .setIcon(Icon.createWithResource(v.getContext(), R.drawable.avatar2))
+                    .build();
             Notification.MessagingStyle messagingStyle =
                     new Notification.MessagingStyle(personJohn)
                             .setConversationTitle("Whassup")
@@ -169,45 +170,146 @@
                             .addMessage(new Notification.MessagingStyle.Message(
                                     "message", System.currentTimeMillis(), personJane));
 
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_2)
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_HIGH_ID)
                     .setContentTitle("Message from someone")
                     .setContentText("hi")
                     .setCategory(Notification.CATEGORY_MESSAGE)
                     .setSmallIcon(R.drawable.car_ic_mode)
                     .setStyle(messagingStyle)
                     .setAutoCancel(true)
+                    .setColor(mContext.getColor(android.R.color.holo_green_light))
                     .addAction(
-                            new Notification.Action.Builder(null, "read", readIntent).build())
+                            new Notification.Action.Builder(null, "read", pendingIntent).build())
                     .addAction(
                             new Notification.Action.Builder(null, "reply", replyIntent)
                                     .addRemoteInput(remoteInput).build())
-                    .extend(new Notification.CarExtender().setColor(R.color.car_red_500))
                     .build();
-            manager.notify(3, notification);
+            mManager.notify(mCurrentNotificationId++, notification);
         });
 
-        emerg.setOnClickListener(v -> {
+    }
 
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
+    private void initCarCategoriesButton(View view) {
+        view.findViewById(R.id.category_car_emergency_button).setOnClickListener(v -> {
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
                     .setContentTitle("OMG")
                     .setContentText("This is of top importance")
                     .setCategory(Notification.CATEGORY_CAR_EMERGENCY)
                     .setSmallIcon(R.drawable.car_ic_mode)
                     .build();
-            manager.notify(10, notification);
+            mManager.notify(mCurrentNotificationId++, notification);
         });
 
-        warn.setOnClickListener(v -> {
+        view.findViewById(R.id.category_car_warning_button).setOnClickListener(v -> {
 
-            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_MIN_ID)
                     .setContentTitle("OMG -ish ")
                     .setContentText("This is of less importance but still")
                     .setCategory(Notification.CATEGORY_CAR_WARNING)
                     .setSmallIcon(R.drawable.car_ic_mode)
                     .build();
-            manager.notify(11, notification);
+            mManager.notify(mCurrentNotificationId++, notification);
         });
 
-        return view;
+        view.findViewById(R.id.category_car_info_button).setOnClickListener(v -> {
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
+                    .setContentTitle("Car information")
+                    .setContentText("Oil change due")
+                    .setCategory(Notification.CATEGORY_CAR_INFORMATION)
+                    .setColor(mContext.getColor(android.R.color.holo_purple))
+                    .setColorized(true)
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            mManager.notify(mCurrentNotificationId++, notification);
+        });
+
+    }
+
+    private void initProgressButton(View view) {
+        view.findViewById(R.id.progress_button).setOnClickListener(v -> {
+            int id = mCurrentNotificationId++;
+
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
+                    .setContentTitle("Progress")
+                    .setProgress(100, 0, false)
+                    .setContentInfo("0%")
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            mManager.notify(id, notification);
+
+            Runnable runnable = new Runnable() {
+                int mProgress = 3;
+
+                @Override
+                public void run() {
+                    Notification updateNotification = new Notification
+                            .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
+                            .setContentTitle("Progress")
+                            .setProgress(100, mProgress, false)
+                            .setContentInfo(mProgress + "%")
+                            .setSmallIcon(R.drawable.car_ic_mode)
+                            .build();
+                    mManager.notify(id, updateNotification);
+                    mProgress += 3;
+                    mProgress %= 100;
+                    mHandler.postDelayed(this, 1000);
+                }
+            };
+            mUpdateRunnables.put(id, runnable);
+            mHandler.post(runnable);
+        });
+    }
+
+    private void initImportanceDefaultButton(View view) {
+        view.findViewById(R.id.importance_default_button).setOnClickListener(v -> {
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
+                    .setContentTitle("Importance Default")
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            mManager.notify(mCurrentNotificationId++, notification);
+        });
+    }
+
+    private void initImportanceLowButton(View view) {
+        view.findViewById(R.id.importance_low_button).setOnClickListener(v -> {
+
+            Notification notification = new Notification.Builder(getActivity(), IMPORTANCE_LOW_ID)
+                    .setContentTitle("Importance Low")
+                    .setContentText("low low low")
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            mManager.notify(mCurrentNotificationId++, notification);
+        });
+    }
+
+    private void initImportanceMinButton(View view) {
+        view.findViewById(R.id.importance_min_button).setOnClickListener(v -> {
+
+            Notification notification = new Notification.Builder(getActivity(), IMPORTANCE_MIN_ID)
+                    .setContentTitle("Importance Min")
+                    .setContentText("min min min")
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            mManager.notify(mCurrentNotificationId++, notification);
+        });
+    }
+
+    private void initOngoingButton(View view) {
+        view.findViewById(R.id.ongoing_button).setOnClickListener(v -> {
+
+            Notification notification = new Notification
+                    .Builder(getActivity(), IMPORTANCE_DEFAULT_ID)
+                    .setContentTitle("Playing music or something")
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .setOngoing(true)
+                    .build();
+            mManager.notify(mCurrentNotificationId++, notification);
+        });
     }
 }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserRestrictionAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserRestrictionAdapter.java
new file mode 100644
index 0000000..00c7192
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserRestrictionAdapter.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.google.android.car.kitchensink.users;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.List;
+
+/**
+ * Adapter to display a set of user restrictions
+ */
+public class UserRestrictionAdapter extends BaseAdapter {
+
+    private final Context mContext;
+    private final List<UserRestrictionListItem> mItems;
+
+    public UserRestrictionAdapter(Context context, List<UserRestrictionListItem> items) {
+        mContext = context;
+        mItems = items;
+    }
+
+    @Override
+    public int getCount() {
+        return mItems.size();
+    }
+
+    @Override
+    public Object getItem(int index) {
+        return mItems.get(index);
+    }
+
+    @Override
+    public long getItemId(int index) {
+        return index;
+    }
+
+    @Override
+    public View getView(int index, View convertView, ViewGroup parent) {
+        return convertView == null
+                ? createCheckBox((UserRestrictionListItem) getItem(index))
+                : convertView;
+    }
+
+    private CheckBox createCheckBox(UserRestrictionListItem item) {
+        Resources resources = mContext.getResources();
+        CheckBox checkBox = new CheckBox(mContext);
+        checkBox.setTextSize(resources.getDimensionPixelSize(R.dimen.users_checkbox_text_size));
+        int padding = resources.getDimensionPixelSize(R.dimen.users_checkbox_padding);
+        checkBox.setPadding(padding, padding, padding, padding);
+        checkBox.setText(item.getKey());
+        checkBox.setOnCheckedChangeListener((v, isChecked) -> item.setIsChecked(isChecked));
+        checkBox.setChecked(item.getIsChecked());
+        return checkBox;
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserRestrictionListItem.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserRestrictionListItem.java
new file mode 100644
index 0000000..0454f75
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserRestrictionListItem.java
@@ -0,0 +1,42 @@
+/*
+ * 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.users;
+
+/**
+ * Represents a user restriction in a list.  Contains the key for the user restriction and the
+ * "checked" status of the checkbox in the list.
+ */
+public class UserRestrictionListItem {
+    private final String mKey;
+    private boolean mIsChecked;
+
+    public UserRestrictionListItem(String key, boolean isChecked) {
+        mKey = key;
+        mIsChecked = isChecked;
+    }
+
+    public String getKey() {
+        return mKey;
+    }
+
+    public void setIsChecked(boolean value) {
+        mIsChecked = value;
+    }
+
+    public boolean getIsChecked() {
+        return mIsChecked;
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UsersFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UsersFragment.java
new file mode 100644
index 0000000..3272c30
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UsersFragment.java
@@ -0,0 +1,96 @@
+/*
+ * 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.users;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Manipulate users in various ways
+ */
+public class UsersFragment extends Fragment {
+
+    private static final List<String> CONFIGURABLE_USER_RESTRICTIONS =
+            Arrays.asList(
+                    UserManager.DISALLOW_ADD_USER,
+                    UserManager.DISALLOW_BLUETOOTH,
+                    UserManager.DISALLOW_FACTORY_RESET,
+                    UserManager.DISALLOW_INSTALL_APPS,
+                    UserManager.DISALLOW_MODIFY_ACCOUNTS,
+                    UserManager.DISALLOW_OUTGOING_CALLS,
+                    UserManager.DISALLOW_REMOVE_USER,
+                    UserManager.DISALLOW_SMS,
+                    UserManager.DISALLOW_UNINSTALL_APPS,
+                    UserManager.DISALLOW_USER_SWITCH
+            );
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.users, container, false);
+    }
+
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        ListView userRestrictionsList = view.findViewById(R.id.user_restrictions_list);
+        userRestrictionsList.setAdapter(
+                new UserRestrictionAdapter(getContext(), createUserRestrictionItems()));
+
+        Button applyButton = view.findViewById(R.id.apply_button);
+        applyButton.setOnClickListener(v -> {
+            UserRestrictionAdapter adapter =
+                    (UserRestrictionAdapter) userRestrictionsList.getAdapter();
+            int count = adapter.getCount();
+            UserManager userManager =
+                    (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+
+            // Iterate through all of the user restrictions and set their values
+            for (int i = 0; i < count; i++) {
+                UserRestrictionListItem item = (UserRestrictionListItem) adapter.getItem(i);
+                userManager.setUserRestriction(item.getKey(), item.getIsChecked());
+            }
+
+            Toast.makeText(
+                    getContext(), "User restrictions have been set!", Toast.LENGTH_SHORT)
+                    .show();
+        });
+    }
+
+    private List<UserRestrictionListItem> createUserRestrictionItems() {
+        UserManager userManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+        ArrayList<UserRestrictionListItem> list = new ArrayList<>();
+        for (String key : CONFIGURABLE_USER_RESTRICTIONS) {
+            list.add(new UserRestrictionListItem(key, userManager.hasUserRestriction(key)));
+        }
+        return list;
+    }
+}
diff --git a/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java b/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java
index 15296d9..066072e 100644
--- a/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java
+++ b/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java
@@ -46,12 +46,12 @@
     }
 
     @Override
-    protected NavigationRenderer getNavigationRenderer() {
+    public NavigationRenderer getNavigationRenderer() {
         return mController.getNavigationRenderer();
     }
 
     @Override
-    protected void onKeyEvent(KeyEvent keyEvent) {
+    public void onKeyEvent(KeyEvent keyEvent) {
         // No need to handle key events in this implementation.
     }
 }
diff --git a/tests/obd2_app/res/values/arrays.xml b/tests/obd2_app/res/values/arrays.xml
index 2bd7ce3..3b3c4ac 100644
--- a/tests/obd2_app/res/values/arrays.xml
+++ b/tests/obd2_app/res/values/arrays.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 <resources>
-  <string-array name="scan_delay_entries">
+  <string-array name="scan_delay_entries" translatable="false">
     <item>2 seconds</item>
     <item>5 seconds</item>
     <item>10 seconds</item>
   </string-array>
-  <string-array name="scan_delay_entryValues">
+  <string-array name="scan_delay_entryValues" translatable="false">
     <item>2</item>
     <item>5</item>
     <item>10</item>