Merge "More multi-user fixes on SampleClusterService"
diff --git a/car-cluster-logging-renderer/Android.mk b/car-cluster-logging-renderer/Android.mk
index 27b0bbc..0f0435a 100644
--- a/car-cluster-logging-renderer/Android.mk
+++ b/car-cluster-logging-renderer/Android.mk
@@ -32,5 +32,8 @@
 
 LOCAL_JAVA_LIBRARIES += android.car
 
+LOCAL_STATIC_ANDROID_LIBRARIES += \
+    androidx.car_car-cluster
+
 include $(BUILD_PACKAGE)
 endif
diff --git a/car-cluster-logging-renderer/AndroidManifest.xml b/car-cluster-logging-renderer/AndroidManifest.xml
index 5fc8d57..81fa46d 100644
--- a/car-cluster-logging-renderer/AndroidManifest.xml
+++ b/car-cluster-logging-renderer/AndroidManifest.xml
@@ -15,8 +15,14 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="android.car.cluster.loggingrenderer"
+      android:sharedUserId="android.uid.system"
       android:versionCode="1"
       android:versionName="1.0">
+
+    <protected-broadcast android:name="android.car.cluster.NAVIGATION_STATE_UPDATE"/>
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+
     <application android:label="@string/app_name"
                  android:icon="@drawable/ic_launcher"
                  android:directBootAware="true"
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 381b7ee..bb9991e 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
@@ -18,17 +18,23 @@
 import android.car.cluster.renderer.InstrumentClusterRenderingService;
 import android.car.cluster.renderer.NavigationRenderer;
 import android.car.navigation.CarNavigationInstrumentCluster;
-import android.graphics.Bitmap;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Log;
+
+import androidx.car.cluster.navigation.NavigationState;
+import androidx.versionedparcelable.ParcelUtils;
+
 import com.google.android.collect.Lists;
 
 /**
  * Dummy implementation of {@link LoggingClusterRenderingService} to log all interaction.
  */
 public class LoggingClusterRenderingService extends InstrumentClusterRenderingService {
-
     private static final String TAG = LoggingClusterRenderingService.class.getSimpleName();
+    private static final String NAV_STATE_BUNDLE_KEY = "navstate";
+    private static final int NAV_STATE_EVENT_ID = 1;
 
     @Override
     protected NavigationRenderer getNavigationRenderer() {
@@ -45,7 +51,26 @@
 
             @Override
             public void onEvent(int eventType, Bundle bundle) {
-                Log.i(TAG, "onEvent, eventType: " + eventType + ", bundle: " + bundle);
+                StringBuilder bundleSummary = new StringBuilder();
+                if (eventType == NAV_STATE_EVENT_ID) {
+                    bundle.setClassLoader(ParcelUtils.class.getClassLoader());
+                    NavigationState navState = NavigationState
+                            .fromParcelable(bundle.getParcelable(NAV_STATE_BUNDLE_KEY));
+                    bundleSummary.append(navState.toString());
+
+                    // Sending broadcast for testing.
+                    Intent intent = new Intent("android.car.cluster.NAVIGATION_STATE_UPDATE");
+                    intent.putExtra(NAV_STATE_BUNDLE_KEY, bundle);
+                    sendBroadcastAsUser(intent, UserHandle.ALL);
+                } else {
+                    for (String key : bundle.keySet()) {
+                        bundleSummary.append(key);
+                        bundleSummary.append("=");
+                        bundleSummary.append(bundle.get(key));
+                        bundleSummary.append(" ");
+                    }
+                }
+                Log.i(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
             }
         };
 
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml
index 9a6717a..8e9369a 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml
@@ -16,9 +16,9 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@*android:dimen/car_icon_size"
-        android:height="@*android:dimen/car_icon_size"
-        android:tint="@*android:color/car_teal_700"
+        android:width="@dimen/checkbox_size"
+        android:height="@dimen/checkbox_size"
+        android:tint="@color/checkbox_color_checked"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml
index 522935c..c4916ee 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml
@@ -16,9 +16,9 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@*android:dimen/car_icon_size"
-        android:height="@*android:dimen/car_icon_size"
-        android:tint="@*android:color/car_tint"
+        android:width="@dimen/checkbox_size"
+        android:height="@dimen/checkbox_size"
+        android:tint="@color/checkbox_color_unchecked"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml b/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml
index 58f6ea3..b9874a5 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml
@@ -50,7 +50,7 @@
             <CheckBox
                 android:id="@+id/do_not_ask_checkbox"
                 android:layout_width="wrap_content"
-                android:layout_height="@dimen/checkbox_height"
+                android:layout_height="wrap_content"
                 android:layout_marginStart="@dimen/checkbox_start_margin"
                 android:layout_marginEnd="@dimen/checkbox_end_margin"
                 android:gravity="center_vertical"
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/values/colors.xml b/car_product/overlay/packages/apps/PackageInstaller/res/values/colors.xml
new file mode 100644
index 0000000..c3f7b7b
--- /dev/null
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <color name="checkbox_color_unchecked">@*android:color/car_tint</color>
+    <color name="checkbox_color_checked">@*android:color/accent_device_default_light</color>
+</resources>
\ No newline at end of file
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml b/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml
index 0fc8ed5..078ad7f 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml
@@ -30,11 +30,13 @@
     <dimen name="checkbox_end_margin">@*android:dimen/car_keyline_1</dimen>
 
     <dimen name="checkbox_left_padding">@*android:dimen/car_keyline_1_keyline_3_diff</dimen>
-    <dimen name="checkbox_text_size">@*android:dimen/car_body3_size</dimen>
-    <dimen name="checkbox_height">@*android:dimen/car_single_line_list_item_height</dimen>
+    <dimen name="checkbox_text_size">@*android:dimen/car_label1_size</dimen>
+    <dimen name="checkbox_text_height">@*android:dimen/car_single_line_list_item_height</dimen>
 
     <dimen name="permission_dialog_button_margin_top">@*android:dimen/car_padding_2</dimen>
     <dimen name="permission_dialog_allow_button_height">@*android:dimen/car_dialog_action_bar_height</dimen>
 
     <dimen name="permission_dialog_appearance_padding_end">@*android:dimen/car_keyline_1</dimen>
+
+    <dimen name="checkbox_size">@*android:dimen/car_primary_icon_size</dimen>
 </resources>
\ No newline at end of file
diff --git a/service/res/drawable/exit_button_background.xml b/service/res/drawable/button_background.xml
similarity index 85%
rename from service/res/drawable/exit_button_background.xml
rename to service/res/drawable/button_background.xml
index 92d32fd..c714124 100644
--- a/service/res/drawable/exit_button_background.xml
+++ b/service/res/drawable/button_background.xml
@@ -14,6 +14,6 @@
 limitations under the License.
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/exit_button_background"/>
-    <corners android:radius="@dimen/exit_button_radius"/>
+    <solid android:color="@color/button_background"/>
+    <corners android:radius="@dimen/button_radius"/>
 </shape>
diff --git a/service/res/drawable/exit_button_ripple_background.xml b/service/res/drawable/button_ripple_background.xml
similarity index 92%
rename from service/res/drawable/exit_button_ripple_background.xml
rename to service/res/drawable/button_ripple_background.xml
index 54fd00f..418c508 100644
--- a/service/res/drawable/exit_button_ripple_background.xml
+++ b/service/res/drawable/button_ripple_background.xml
@@ -16,5 +16,5 @@
 <ripple
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:color="@color/ripple_background">
-    <item android:drawable="@drawable/exit_button_background" />
+    <item android:drawable="@drawable/button_background" />
 </ripple>
diff --git a/service/res/layout/activity_blocking.xml b/service/res/layout/activity_blocking.xml
index 687afbe..5eddbf9 100644
--- a/service/res/layout/activity_blocking.xml
+++ b/service/res/layout/activity_blocking.xml
@@ -19,19 +19,55 @@
     android:layout_height="match_parent"
     android:orientation="vertical"
     android:background="@color/activity_blocking_activity_background"
-    android:padding="@dimen/blocking_activity_padding"
     android:gravity="center">
     <TextView
-        android:id="@+id/activity_blocked_title"
+        android:id="@+id/blocking_text"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:textColor="@color/blocking_text"
         android:textAppearance="@style/ActivityBlockingActivityText" />
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_marginTop="@dimen/common_margin"
+        android:orientation="horizontal"
+        android:gravity="center">
+        <ImageView
+            android:id="@+id/blocked_app_icon"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+        <TextView
+            android:id="@+id/blocked_app_name"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginStart="@dimen/common_margin"
+            android:textColor="@color/blocking_text"
+            android:textAppearance="@style/ActivityBlockingActivityText" />
+    </LinearLayout>
+
     <Button
         android:id="@+id/exit"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:layout_marginTop="@dimen/exit_button_top_margin"
+        android:layout_marginTop="@dimen/common_margin"
         android:text="@string/exit_button"
-        style="@style/ExitButtonStyle"/>
+        style="@style/ButtonStyle"/>
+
+    <!-- Widgets to display debug info. They should not show for non-user build. -->
+    <Button
+        android:id="@+id/toggle_debug_info"
+        android:visibility="gone"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/common_margin"
+        android:text="@string/debug_button_text"
+        style="@style/ButtonStyle"/>
+
+    <TextView
+        android:id="@+id/debug_info"
+        android:visibility="gone"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/common_margin"
+        android:textAppearance="@style/ActivityBlockingActivityText" />
 </LinearLayout>
diff --git a/service/res/values-h1920dp/dimens.xml b/service/res/values-h1920dp/dimens.xml
index 938fb17..42de507 100644
--- a/service/res/values-h1920dp/dimens.xml
+++ b/service/res/values-h1920dp/dimens.xml
@@ -19,8 +19,6 @@
 <resources>
     <!-- Text size in ActivityBlockingActivity. -->
     <dimen name="blocking_text_size">40sp</dimen>
-    <!-- Padding of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_padding">28dp</dimen>
-    <!-- Top margin of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_top_margin">20dp</dimen>
+    <!-- Padding of button in ActivityBlockingActivity. -->
+    <dimen name="button_padding">28dp</dimen>
 </resources>
diff --git a/service/res/values-h800dp/dimens.xml b/service/res/values-h800dp/dimens.xml
index 9594554..6dd33fe 100644
--- a/service/res/values-h800dp/dimens.xml
+++ b/service/res/values-h800dp/dimens.xml
@@ -17,6 +17,6 @@
 */
 -->
 <resources>
-    <!-- Padding of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_padding">28dp</dimen>
+    <!-- Padding of button in ActivityBlockingActivity. -->
+    <dimen name="button_padding">28dp</dimen>
 </resources>
diff --git a/service/res/values-night/colors.xml b/service/res/values-night/colors.xml
index 65f8fb1..6f30b68 100644
--- a/service/res/values-night/colors.xml
+++ b/service/res/values-night/colors.xml
@@ -18,17 +18,17 @@
 -->
 <resources>
     <!-- Semi-transparent background color of blocking activity. -->
-    <!-- In Call background scrim: black @ 80% -->
-    <color name="activity_blocking_activity_background">#cc000000</color>
+    <!-- In Call background scrim: black -->
+    <color name="activity_blocking_activity_background">@android:color/black</color>
 
     <!-- Color of text in blocking activity. -->
     <color name="blocking_text">#fff5f5f5</color>
 
-    <!-- Background color of exit button. -->
-    <color name="exit_button_background">#ff80cbc4</color>
+    <!-- Background color of button. -->
+    <color name="button_background">#ff80cbc4</color>
 
-    <!-- Color of exit button text. -->
-    <color name="exit_button_text">#ff212121</color>
+    <!-- Color of button text. -->
+    <color name="button_text">#ff212121</color>
 
     <!-- Ripple color. -->
     <color name="ripple_background">#3dffffff</color>
diff --git a/service/res/values-w1280dp/dimens.xml b/service/res/values-w1280dp/dimens.xml
deleted file mode 100644
index f5469f2..0000000
--- a/service/res/values-w1280dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
-    <!-- Padding of ActivityBlockingActivity. -->
-    <dimen name="blocking_activity_padding">148dp</dimen>
-</resources>
diff --git a/service/res/values-w1920dp/dimens.xml b/service/res/values-w1920dp/dimens.xml
deleted file mode 100644
index 75d2deb..0000000
--- a/service/res/values-w1920dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
-    <!-- Padding of ActivityBlockingActivity. -->
-    <dimen name="blocking_activity_padding">192dp</dimen>
-</resources>
diff --git a/service/res/values-w690dp/dimens.xml b/service/res/values-w690dp/dimens.xml
deleted file mode 100644
index 0b06ada..0000000
--- a/service/res/values-w690dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
-    <!-- Padding of ActivityBlockingActivity. -->
-    <dimen name="blocking_activity_padding">112dp</dimen>
-</resources>
diff --git a/service/res/values/colors.xml b/service/res/values/colors.xml
index ed86a3b..6764116 100644
--- a/service/res/values/colors.xml
+++ b/service/res/values/colors.xml
@@ -18,18 +18,18 @@
 -->
 <resources>
     <!-- Semi-transparent background color of blocking activity. -->
-    <!-- In Call background scrim: grey 200 @ 90% -->
-    <color name="activity_blocking_activity_background">#e6eeeeee</color>
+    <!-- In Call background scrim: grey 200 -->
+    <color name="activity_blocking_activity_background">#ffeeeeee</color>
 
     <!-- Color of text in blocking activity. -->
     <color name="blocking_text">#ff212121</color>
 
-    <!-- Background color of exit button. -->
-    <color name="exit_button_background">#ff00796b</color>
+    <!-- Background color of button. -->
+    <color name="button_background">#ff00796b</color>
 
-    <!-- Color of exit button text. -->
-    <color name="exit_button_text">#fffafafa</color>
+    <!-- Color of button text. -->
+    <color name="button_text">#fffafafa</color>
 
-    <!-- Ripple color of exit button background. -->
+    <!-- Ripple color of button background. -->
     <color name="ripple_background">#3d000000</color>
 </resources>
diff --git a/service/res/values/dimens.xml b/service/res/values/dimens.xml
index f75a358..23653db 100644
--- a/service/res/values/dimens.xml
+++ b/service/res/values/dimens.xml
@@ -19,18 +19,14 @@
 <resources>
     <!-- Text size in ActivityBlockingActivity. -->
     <dimen name="blocking_text_size">32sp</dimen>
-    <!-- Padding of ActivityBlockingActivity. -->
-    <dimen name="blocking_activity_padding">20dp</dimen>
-
-    <!-- Minimum height of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_min_height">56dp</dimen>
-    <!-- Minimum width of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_min_width">156dp</dimen>
-    <!-- Padding of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_padding">16dp</dimen>
-    <!-- Radius of exit button in ActivityBlockingActivity. -->
-    <dimen name="exit_button_radius">4dp</dimen>
-    <!-- Top margin of exit button. It should be half the height of
-         button (exit_button_min_height). -->
-    <dimen name="exit_button_top_margin">28dp</dimen>
+    <!-- Widget margin. It should be half the height of button. -->
+    <dimen name="common_margin">28dp</dimen>
+    <!-- Minimum height of button in ActivityBlockingActivity. -->
+    <dimen name="button_min_height">56dp</dimen>
+    <!-- Minimum width of button in ActivityBlockingActivity. -->
+    <dimen name="button_min_width">156dp</dimen>
+    <!-- Padding of button in ActivityBlockingActivity. -->
+    <dimen name="button_padding">16dp</dimen>
+    <!-- Radius of button in ActivityBlockingActivity. -->
+    <dimen name="button_radius">4dp</dimen>
 </resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 2de39f1..599e41f 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -103,10 +103,12 @@
     <string name="car_can_bus_failure_desc">CAN bus does not respond. Unplug and plug back headunit
         box and restart the car</string>
 
-    <!-- Blocking activity: Message to show to user when an activity is not allowed during driving. [CHAR LIMIT=120] -->
-    <string name="activity_blocked_string">For your safety, this activity isn’t available while you’re driving</string>
-    <!-- Blocking activity: Button text that restarts the current blocked application. [CHAR LIMIT=10] -->
-    <string name="exit_button">OK</string>
+    <!-- Blocking activity: Message to show to user when a feature of current application is not allowed. [CHAR LIMIT=120] -->
+    <string name="activity_blocked_text">For your safety, this activity isn’t available while you’re driving</string>
+    <!-- Blocking activity: Text for button that shows debug info for non-user build. [CHAR LIMIT=10] -->
+    <string name="debug_button_text">Debug Info</string>
+    <!-- Blocking activity: Text for button that restarts the current blocked application. [CHAR LIMIT=15] -->
+    <string name="exit_button">Restart App</string>
 
     <!-- Permission text: apps can control diagnostic data [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_diag_read">Diagnostic Data</string>
diff --git a/service/res/values/styles.xml b/service/res/values/styles.xml
index 4098e96..35b0b29 100644
--- a/service/res/values/styles.xml
+++ b/service/res/values/styles.xml
@@ -17,21 +17,22 @@
     <!-- TextAppearances for ActivityBlockingActivity. -->
     <style name="ActivityBlockingActivityText">
         <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:textColor">@color/blocking_text</item>
         <item name="android:textStyle">normal</item>
         <item name="android:textSize">@dimen/blocking_text_size</item>
     </style>
 
-    <!-- Style of exit button. -->
-    <style name="ExitButtonStyle">
-        <item name="android:background">@drawable/exit_button_ripple_background</item>
+    <!-- Style for buttons in ActivityBlockingActivity. -->
+    <style name="ButtonStyle">
+        <item name="android:background">@drawable/button_ripple_background</item>
         <item name="android:fontFamily">sans-serif-medium</item>
-        <item name="android:minHeight">@dimen/exit_button_min_height</item>
-        <item name="android:minWidth">@dimen/exit_button_min_width</item>
-        <item name="android:paddingHorizontal">@dimen/exit_button_padding</item>
+        <item name="android:minHeight">@dimen/button_min_height</item>
+        <item name="android:minWidth">@dimen/button_min_width</item>
+        <item name="android:paddingHorizontal">@dimen/button_padding</item>
         <item name="android:textAllCaps">true</item>
         <item name="android:textAlignment">center</item>
         <item name="android:textAppearance">@style/ActivityBlockingActivityText</item>
-        <item name="android:textColor">@color/exit_button_text</item>
+        <item name="android:textColor">@color/button_text</item>
     </style>
 </resources>
 
diff --git a/service/src/com/android/car/BluetoothAutoConnectStateMachine.java b/service/src/com/android/car/BluetoothAutoConnectStateMachine.java
index 981db5b..cf9b98e 100644
--- a/service/src/com/android/car/BluetoothAutoConnectStateMachine.java
+++ b/service/src/com/android/car/BluetoothAutoConnectStateMachine.java
@@ -48,12 +48,9 @@
     public static final int CONNECT_TIMEOUT = 103;
     public static final int DEVICE_CONNECTED = 104;
     public static final int DEVICE_DISCONNECTED = 105;
-    // The following is used when PBAP and MAP should be connected to,
-    // after device connects on HFP.
-    public static final int CHECK_CLIENT_PROFILES = 1006;
+    public static final int ADAPTER_OFF = 106;
 
     public static final int CONNECTION_TIMEOUT_MS = 8000;
-    static final int CONNECT_MORE_PROFILES_TIMEOUT_MS = 2000;
 
 
     BluetoothAutoConnectStateMachine(BluetoothDeviceConnectionPolicy policy) {
@@ -84,9 +81,7 @@
 
     /**
      * Idle State is the Initial State, when the system is accepting incoming 'CONNECT' requests.
-     * Attempts a connection whenever the state transitions into Idle.
-     * If the policy finds a device to connect on a profile, transitions to Processing.
-     * If there is nothing to connect to, wait for the next 'CONNECT' message to try next.
+     * Upon 'CONNECT' message move to processing and beging connecting to devices.
      */
     private class Idle extends State {
         @Override
@@ -94,7 +89,6 @@
             if (DBG) {
                 Log.d(TAG, "Enter Idle");
             }
-            connectToBluetoothDevice();
         }
 
         @Override
@@ -107,29 +101,21 @@
                     if (DBG) {
                         Log.d(TAG, "Idle->Connect:");
                     }
-                    connectToBluetoothDevice();
+                    transitionTo(mProcessing);
                     break;
                 }
 
                 case DEVICE_CONNECTED: {
-                    if (DBG) {
-                        Log.d(TAG, "Idle->DeviceConnected: Ignored");
-                    }
-                    break;
-                }
-
-                case CHECK_CLIENT_PROFILES: {
-                    removeMessages(CHECK_CLIENT_PROFILES);
                     BluetoothDeviceConnectionPolicy.ConnectionParams params =
                             (BluetoothDeviceConnectionPolicy.ConnectionParams) msg.obj;
-                    BluetoothDevice device = params.getBluetoothDevice();
-                    // After pairing/disconnect, always try to connect to both PBAP and MAP
-                    if (DBG) {
-                        Log.d(TAG, "try to connect to PBAP/MAP after pairing or disconnect: "
-                                + Utils.getDeviceDebugInfo(device));
+                    if (params.getBluetoothProfile() == BluetoothProfile.HEADSET_CLIENT) {
+                        mPolicy.connectToDeviceOnProfile(BluetoothProfile.PBAP_CLIENT,
+                                params.getBluetoothDevice());
+                        mPolicy.connectToDeviceOnProfile(BluetoothProfile.MAP_CLIENT,
+                                params.getBluetoothDevice());
+                    } else if (DBG) {
+                        Log.d(TAG, "Idle->DeviceConnected: Ignored");
                     }
-                    mPolicy.connectToDeviceOnProfile(BluetoothProfile.PBAP_CLIENT, device);
-                    mPolicy.connectToDeviceOnProfile(BluetoothProfile.MAP_CLIENT, device);
                     break;
                 }
 
@@ -143,24 +129,6 @@
             return true;
         }
 
-        /**
-         * Instruct the policy to find and connect to a device on a connectable profile.
-         * If the policy reports that there is nothing to connect to, stay in the Idle state.
-         * If it found a {device, profile} combination to attempt a connection, move to
-         * Processing state
-         */
-        private void connectToBluetoothDevice() {
-            boolean deviceToConnectFound = mPolicy.findDeviceToConnect();
-            if (deviceToConnectFound) {
-                transitionTo(mProcessing);
-            } else {
-                // Stay in Idle State and wait for the next 'CONNECT' message.
-                if (DBG) {
-                    Log.d(TAG, "Idle->No device to connect");
-                }
-            }
-        }
-
         @Override
         public void exit() {
             if (DBG) {
@@ -173,20 +141,30 @@
     /**
      * Processing state indicates the system is processing a auto connect trigger and will ignore
      * connection requests.
+     * If there are no devices to connect or upon completion, transition back to idle.
      */
     private class Processing extends State {
+        int mConnectionsInProgress;
         @Override
         public void enter() {
             if (DBG) {
                 Log.d(TAG, "Enter Processing");
             }
-
+            mConnectionsInProgress = 0;
+            sendMessageDelayed(CONNECT_TIMEOUT, CONNECTION_TIMEOUT_MS);
+            for (Integer profile : mPolicy.mProfilesToConnect) {
+                connectDeviceOnProfile(profile);
+            }
+            if (mConnectionsInProgress == 0) {
+                transitionTo(mIdle);
+            }
         }
 
         @Override
         public boolean processMessage(Message msg) {
             if (DBG) {
                 Log.d(TAG, "Processing processMessage " + msg.what);
+                Log.d(TAG, "Connections in Progress = " + mConnectionsInProgress);
             }
             BluetoothDeviceConnectionPolicy.ConnectionParams params;
             switch (msg.what) {
@@ -203,10 +181,21 @@
                 case DEVICE_CONNECTED:
                     // fall through
                 case DEVICE_DISCONNECTED: {
-                    removeMessages(CONNECT_TIMEOUT);
-                    transitionTo(mIdle);
+                    mConnectionsInProgress--;
+                    params = (BluetoothDeviceConnectionPolicy.ConnectionParams) msg.obj;
+                    connectDeviceOnProfile(params.getBluetoothProfile());
+
+                    if (mConnectionsInProgress == 0) {
+                        transitionTo(mIdle);
+                    } else {
+                        removeMessages(CONNECT_TIMEOUT);
+                        sendMessageDelayed(CONNECT_TIMEOUT, CONNECTION_TIMEOUT_MS);
+                    }
                     break;
                 }
+                case ADAPTER_OFF:
+                    transitionTo(mIdle);
+                    break;
 
                 default:
                     if (DBG) {
@@ -222,6 +211,19 @@
             if (DBG) {
                 Log.d(TAG, "Exit Processing");
             }
+            removeMessages(CONNECT_TIMEOUT);
+
+        }
+
+        void connectDeviceOnProfile(int profile) {
+            BluetoothDevicesInfo devInfo = mPolicy.mProfileToConnectableDevicesMap.get(profile);
+            if (devInfo != null && devInfo.isProfileConnectableLocked()) {
+                BluetoothDevice device = devInfo.getNextDeviceInQueueLocked();
+                if (device != null) {
+                    mConnectionsInProgress++;
+                    mPolicy.connectToDeviceOnProfile(profile, device);
+                }
+            }
         }
     }
 
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index f9c8a1e..51bbf8d 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -97,7 +97,7 @@
     private final Object mSetupLock = new Object();
 
     // The main data structure that holds on to the {profile:list of known and connectible devices}
-    private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
+    HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
 
     /// TODO(vnori): fix this. b/70029056
     private static final int NUM_SUPPORTED_PHONE_CONNECTIONS = 4; // num of HFP and PBAP connections
@@ -133,10 +133,11 @@
     private final FastPairProvider mFastPairProvider;
 
     // The Bluetooth profiles that the CarService will try to auto-connect on.
-    private final List<Integer> mProfilesToConnect;
+    final List<Integer> mProfilesToConnect;
     private final List<Integer> mPrioritiesSupported;
     private static final int MAX_CONNECT_RETRIES = 1;
     private static final int PROFILE_NOT_AVAILABLE = -1;
+    private int mUserId;
 
     // Device & Profile currently being connected on
     private ConnectionParams mConnectionInFlight;
@@ -318,6 +319,8 @@
                     initiateConnection();
                 } else if (currState == BluetoothAdapter.STATE_OFF) {
                     // Write currently connected device snapshot to file.
+                    mBluetoothAutoConnectStateMachine.sendMessage(
+                            BluetoothAutoConnectStateMachine.ADAPTER_OFF);
                     writeDeviceInfoToSettings();
                     resetBluetoothDevicesConnectionInfo();
                 }
@@ -539,6 +542,7 @@
                 }
                 return;
             }
+            mUserId = ActivityManager.getCurrentUser();
             mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this);
             readAndRebuildDeviceMapFromSettings();
             setupBluetoothEventsIntentFilterLocked();
@@ -1217,32 +1221,6 @@
                     + deviceThatConnected);
         }
 
-        // If the device just connected to HEADSET_CLIENT profile, initiate
-        // connections on PBAP & MAP profiles but let that begin after a timeout period.
-        // timeout allows A2DP profile to complete its connection, so that there is no race
-        // condition between
-        //         Phone trying to connect on A2DP
-        //         and, Car trying to connect on PBAP & MAP.
-        if (didConnect && profileToUpdate == BluetoothProfile.HEADSET_CLIENT) {
-            // Unlock the profiles PBAP, MAP in BluetoothDevicesInfo, so that they can be
-            // connected on.
-            for (Integer profile : Arrays.asList(BluetoothProfile.PBAP_CLIENT,
-                    BluetoothProfile.MAP_CLIENT)) {
-                BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
-                if (devInfo == null) {
-                    Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
-                    return false;
-                }
-                devInfo.setDeviceAvailableToConnectLocked(true);
-            }
-            if (DBG) {
-                Log.d(TAG, "connect to PBAP/MAP after disconnect: ");
-            }
-            mBluetoothAutoConnectStateMachine.sendMessageDelayed(
-                    BluetoothAutoConnectStateMachine.CHECK_CLIENT_PROFILES, params,
-                    BluetoothAutoConnectStateMachine.CONNECT_MORE_PROFILES_TIMEOUT_MS);
-        }
-
         // If the connection update is on a different profile or device (a very rare possibility),
         // it is handled automatically.  Just logging it here.
         if (DBG) {
@@ -1422,18 +1400,17 @@
             if (DBG) {
                 Log.d(TAG, "Profile: " + profileToUpdate + " Writing: " + joinedDeviceNames);
             }
-            long userId = ActivityManager.getCurrentUser();
             switch (profileToUpdate) {
                 case BluetoothProfile.A2DP_SINK:
                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
                             KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES,
-                            joinedDeviceNames, (int) userId);
+                            joinedDeviceNames, mUserId);
                     break;
 
                 case BluetoothProfile.HEADSET_CLIENT:
                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
                             KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES,
-                            joinedDeviceNames, (int) userId);
+                            joinedDeviceNames, mUserId);
                     break;
 
                 case BluetoothProfile.PBAP_CLIENT:
@@ -1443,12 +1420,12 @@
                 case BluetoothProfile.MAP_CLIENT:
                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
                             KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES,
-                            joinedDeviceNames, (int) userId);
+                            joinedDeviceNames, mUserId);
                     break;
                 case BluetoothProfile.PAN:
                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
                             KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES,
-                            joinedDeviceNames, (int) userId);
+                            joinedDeviceNames, mUserId);
                     break;
             }
         }
@@ -1489,26 +1466,25 @@
         }
         // Read from Settings.Secure for the current user.  There are 3 keys 1 each for Phone
         // (HFP & PBAP), 1 for Music (A2DP) and 1 for Messaging device (MAP)
-        long userId = ActivityManager.getCurrentUser();
         for (Integer profile : mProfilesToConnect) {
             switch (profile) {
                 case BluetoothProfile.A2DP_SINK:
                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                            KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, (int) userId);
+                            KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, mUserId);
                     break;
                 case BluetoothProfile.PBAP_CLIENT:
                     // fall through
                 case BluetoothProfile.HEADSET_CLIENT:
                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                            KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, (int) userId);
+                            KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, mUserId);
                     break;
                 case BluetoothProfile.MAP_CLIENT:
                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                            KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, (int) userId);
+                            KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, mUserId);
                     break;
                 case BluetoothProfile.PAN:
                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                            KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES, (int) userId);
+                            KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES, mUserId);
                 default:
                     Log.e(TAG, "Unexpected profile");
                     break;
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 2081148..736ffc8 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -35,6 +35,7 @@
 import android.util.Slog;
 import android.util.TimingsTraceLog;
 
+import com.android.car.audio.CarAudioService;
 import com.android.car.cluster.InstrumentClusterService;
 import com.android.car.garagemode.GarageModeService;
 import com.android.car.hal.VehicleHal;
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index 2df9b00..468dde4 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -85,6 +85,7 @@
         void onActivityLaunch(TopTaskInfoContainer topTask);
     }
 
+    private static final int INVALID_STACK_ID = -1;
     private final Context mContext;
     private final IActivityManager mAm;
     private final ProcessObserver mProcessObserver;
@@ -97,7 +98,7 @@
     private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>();
     /** K: uid, V : list of pid */
     private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>();
-    private int mFocusedStackId = -1;
+    private int mFocusedStackId = INVALID_STACK_ID;
 
     /**
      * Temporary container to dispatch tasks for onActivityLaunch. Only used in handler thread.
@@ -253,7 +254,7 @@
             Log.e(CarLog.TAG_AM, "cannot getTasks", e);
             return;
         }
-        int focusedStackId = -1;
+        int focusedStackId = INVALID_STACK_ID;
         try {
             // TODO(b/66955160): Someone on the Auto-team should probably re-work the code in the
             // synchronized block below based on this new API.
@@ -269,10 +270,13 @@
         ActivityLaunchListener listener;
         synchronized (this) {
             listener = mActivityLaunchListener;
+            Set<Integer> allStackIds = new ArraySet<>(infos.size());
+            Set<Integer> stackIdsToRemove = new ArraySet<>(infos.size());
             for (StackInfo info : infos) {
                 int stackId = info.stackId;
+                allStackIds.add(info.stackId);
                 if (info.taskNames.length == 0 || !info.visible) { // empty stack or not shown
-                    mTopTasks.remove(stackId);
+                    stackIdsToRemove.add(stackId);
                     continue;
                 }
                 TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer(
@@ -290,6 +294,19 @@
                     }
                 }
             }
+            for (int i = 0; i < mTopTasks.size(); i++) {
+                TopTaskInfoContainer topTask = mTopTasks.valueAt(i);
+                if (topTask == null) {
+                    Log.wtf(CarLog.TAG_AM, "unexpected null value in sparse array");
+                    continue;
+                }
+                if (!allStackIds.contains(mTopTasks.keyAt(i))) {
+                    stackIdsToRemove.add(mTopTasks.keyAt(i));
+                }
+            }
+            for (int stackIdToRemove : stackIdsToRemove) {
+                mTopTasks.remove(stackIdToRemove);
+            }
             mFocusedStackId = focusedStackId;
         }
         if (listener != null) {
diff --git a/service/src/com/android/car/SystemStateControllerService.java b/service/src/com/android/car/SystemStateControllerService.java
index e0b1161..c15ab6d 100644
--- a/service/src/com/android/car/SystemStateControllerService.java
+++ b/service/src/com/android/car/SystemStateControllerService.java
@@ -18,6 +18,8 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import com.android.car.audio.CarAudioService;
+
 import java.io.PrintWriter;
 
 public class SystemStateControllerService implements CarServiceBase {
diff --git a/service/src/com/android/car/Utils.java b/service/src/com/android/car/Utils.java
index 78f5a8e..284e610 100644
--- a/service/src/com/android/car/Utils.java
+++ b/service/src/com/android/car/Utils.java
@@ -10,6 +10,9 @@
     static final Boolean DBG = false;
 
     static String getDeviceDebugInfo(BluetoothDevice device) {
+        if (device == null) {
+            return "(null)";
+        }
         return "(name = " + device.getName() + ", addr = " + device.getAddress() + ")";
     }
 
diff --git a/service/src/com/android/car/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
similarity index 99%
rename from service/src/com/android/car/CarAudioDeviceInfo.java
rename to service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 65e3510..2e35c56 100644
--- a/service/src/com/android/car/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car;
+package com.android.car.audio;
 
 import android.media.AudioDeviceInfo;
 import android.media.AudioDevicePort;
@@ -24,6 +24,7 @@
 import android.media.AudioPort;
 import android.util.Log;
 
+import com.android.car.CarLog;
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
similarity index 99%
rename from service/src/com/android/car/CarAudioService.java
rename to service/src/com/android/car/audio/CarAudioService.java
index 1e2e481..ebfe93e 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car;
+package com.android.car.audio;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -54,6 +54,10 @@
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 
+import com.android.car.BinderInterfaceContainer;
+import com.android.car.CarLog;
+import com.android.car.CarServiceBase;
+import com.android.car.R;
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
@@ -65,6 +69,9 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
+/**
+ * Service responsible for interaction with car's audio system.
+ */
 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
 
     private static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
diff --git a/service/src/com/android/car/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
similarity index 92%
rename from service/src/com/android/car/CarVolumeGroup.java
rename to service/src/com/android/car/audio/CarVolumeGroup.java
index 448979b..eff3b4c 100644
--- a/service/src/com/android/car/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -13,8 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package com.android.car;
+package com.android.car.audio;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -60,7 +59,7 @@
         mContexts = contexts;
 
         mStoredGainIndex = Settings.Global.getInt(mContentResolver,
-                CarAudioManager.getVolumeSettingsKeyForGroup(mId), -1);;
+                CarAudioManager.getVolumeSettingsKeyForGroup(mId), -1);
     }
 
     int getId() {
@@ -71,6 +70,9 @@
         return mContexts;
     }
 
+    /**
+     * @return Array of bus numbers in this {@link CarVolumeGroup}
+     */
     int[] getBusNumbers() {
         final int[] busNumbers = new int[mBusToCarAudioDeviceInfos.size()];
         for (int i = 0; i < busNumbers.length; i++) {
@@ -123,7 +125,7 @@
         }
     }
 
-    int getDefaultGainIndex() {
+    private int getDefaultGainIndex() {
         return getIndexForGain(mDefaultGain);
     }
 
@@ -139,16 +141,20 @@
         return mCurrentGainIndex;
     }
 
+    /**
+     * Sets the gain on this group, gain will be set on all buses within same bus.
+     * @param gainIndex The gain index
+     */
     void setCurrentGainIndex(int gainIndex) {
         int gainInMillibels = getGainForIndex(gainIndex);
 
         Preconditions.checkArgument(
                 gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
-                "Gain out of range (" +
-                        mMinGain + ":" +
-                        mMaxGain +") " +
-                        gainInMillibels + "index " +
-                        gainIndex);
+                "Gain out of range ("
+                        + mMinGain + ":"
+                        + mMaxGain + ") "
+                        + gainInMillibels + "index "
+                        + gainIndex);
 
         for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
             CarAudioDeviceInfo info = mBusToCarAudioDeviceInfos.valueAt(i);
@@ -174,6 +180,9 @@
         return (gainInMillibel - mMinGain) / mStepSize;
     }
 
+    /**
+     * Gets {@link AudioDevicePort} from a context number
+     */
     @Nullable
     AudioDevicePort getAudioDevicePortForContext(int contextNumber) {
         final int busNumber = mContextToBus.get(contextNumber, -1);
@@ -191,6 +200,7 @@
                 + " buses: " + Arrays.toString(getBusNumbers());
     }
 
+    /** Writes to dumpsys output */
     void dump(PrintWriter writer) {
         writer.println("CarVolumeGroup " + mId);
         writer.printf("\tGain in millibel (min / max / default/ current): %d %d %d %d\n",
diff --git a/service/src/com/android/car/CarVolumeGroupsHelper.java b/service/src/com/android/car/audio/CarVolumeGroupsHelper.java
similarity index 87%
rename from service/src/com/android/car/CarVolumeGroupsHelper.java
rename to service/src/com/android/car/audio/CarVolumeGroupsHelper.java
index af2c6dd..eddcd4f 100644
--- a/service/src/com/android/car/CarVolumeGroupsHelper.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupsHelper.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car;
+package com.android.car.audio;
 
 import android.annotation.XmlRes;
 import android.content.Context;
@@ -23,12 +23,18 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.car.CarLog;
+import com.android.car.R;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * A helper class loads all volume groups from the configuration XML file.
+ */
 /* package */ class CarVolumeGroupsHelper {
 
     private static final String TAG_VOLUME_GROUPS = "volumeGroups";
@@ -43,14 +49,18 @@
         mXmlConfiguration = xmlConfiguration;
     }
 
+    /**
+     * @return all {@link CarVolumeGroup} read from configuration.
+     */
     CarVolumeGroup[] loadVolumeGroups() {
         List<CarVolumeGroup> carVolumeGroups = new ArrayList<>();
         try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
             AttributeSet attrs = Xml.asAttributeSet(parser);
             int type;
             // Traverse to the first start tag
-            while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
                     && type != XmlResourceParser.START_TAG) {
+                // ignored
             }
 
             if (!TAG_VOLUME_GROUPS.equals(parser.getName())) {
@@ -58,7 +68,7 @@
             }
             int outerDepth = parser.getDepth();
             int id = 0;
-            while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
                     && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
                 if (type == XmlResourceParser.END_TAG) {
                     continue;
@@ -80,7 +90,7 @@
 
         List<Integer> contexts = new ArrayList<>();
         int innerDepth = parser.getDepth();
-        while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+        while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
                 && (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
             if (type == XmlResourceParser.END_TAG) {
                 continue;
diff --git a/service/src/com/android/car/pm/ActivityBlockingActivity.java b/service/src/com/android/car/pm/ActivityBlockingActivity.java
index b39910e..fd792a6 100644
--- a/service/src/com/android/car/pm/ActivityBlockingActivity.java
+++ b/service/src/com/android/car/pm/ActivityBlockingActivity.java
@@ -15,6 +15,11 @@
  */
 package com.android.car.pm;
 
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME;
+
 import android.app.Activity;
 import android.car.Car;
 import android.car.CarNotConnectedException;
@@ -24,32 +29,39 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
+import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.car.CarLog;
 import com.android.car.R;
 
 /**
  * Default activity that will be launched when the current foreground activity is not allowed.
- * Additional information on blocked Activity will be passed as extra in Intent
- * via {@link #INTENT_KEY_BLOCKED_ACTIVITY} key.
+ * Additional information on blocked Activity should be passed as intent extras.
  */
 public class ActivityBlockingActivity extends Activity {
-    public static final String INTENT_KEY_BLOCKED_ACTIVITY = "blocked_activity";
-    public static final String EXTRA_BLOCKED_TASK = "blocked_task";
-
     private static final int INVALID_TASK_ID = -1;
 
     private Car mCar;
     private CarUxRestrictionsManager mUxRManager;
 
-    private TextView mBlockedTitle;
+    private TextView mBlockingText;
+    private TextView mBlockedAppName;
+    private ImageView mBlockedAppIcon;
     private Button mExitButton;
+
     // Exiting depends on Car connection, which might not be available at the time exit was
     // requested (e.g. user presses Exit Button). In that case, we record exiting was requested, and
     // Car connection will perform exiting once it is established.
@@ -61,9 +73,12 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_blocking);
 
-        mBlockedTitle = findViewById(R.id.activity_blocked_title);
+        mBlockingText = findViewById(R.id.blocking_text);
+        mBlockedAppName = findViewById(R.id.blocked_app_name);
+        mBlockedAppIcon = findViewById(R.id.blocked_app_icon);
         mExitButton = findViewById(R.id.exit);
-        mExitButton.setOnClickListener(v -> handleFinish());
+
+        mBlockingText.setText(getString(R.string.activity_blocked_text));
 
         // Listen to the CarUxRestrictions so this blocking activity can be dismissed when the
         // restrictions are lifted.
@@ -72,7 +87,7 @@
             public void onServiceConnected(ComponentName name, IBinder service) {
                 try {
                     if (mExitRequested) {
-                        handleFinish();
+                        handleRestartingTask();
                     }
                     mUxRManager = (CarUxRestrictionsManager) mCar.getCarManager(
                             Car.CAR_UX_RESTRICTION_SERVICE);
@@ -99,25 +114,82 @@
     protected void onResume() {
         super.onResume();
 
-        // Display message about the current blocked activity, and optionally show an exit button
+        // Display info about the current blocked activity, and optionally show an exit button
         // to restart the blocked task (stack of activities) if its root activity is DO.
+        mBlockedTaskId = getIntent().getIntExtra(BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID,
+                INVALID_TASK_ID);
 
         // blockedActivity is expected to be always passed in as the topmost activity of task.
-        String blockedActivity = getIntent().getStringExtra(INTENT_KEY_BLOCKED_ACTIVITY);
-        mBlockedTitle.setText(getString(R.string.activity_blocked_string));
-        if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
-            Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
+        String blockedActivity = getIntent().getStringExtra(
+                BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
+        if (!TextUtils.isEmpty(blockedActivity)) {
+            if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+                Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
+            }
+            // Show application icon and name of blocked activity.
+            Drawable appIcon = findApplicationIcon(blockedActivity);
+            if (appIcon != null) {
+                mBlockedAppIcon.setImageDrawable(appIcon);
+            } else {
+                mBlockedAppIcon.setVisibility(View.GONE);
+            }
+            mBlockedAppName.setText(findHumanReadableLabel(blockedActivity));
         }
 
-        // taskId is available as extra if the task can be restarted.
-        mBlockedTaskId = getIntent().getIntExtra(EXTRA_BLOCKED_TASK, INVALID_TASK_ID);
+        boolean isRootDO = getIntent().getBooleanExtra(
+                BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, false);
 
-        mExitButton.setVisibility(mBlockedTaskId == INVALID_TASK_ID ? View.GONE : View.VISIBLE);
-        if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG) && mBlockedTaskId == INVALID_TASK_ID) {
-            Log.d(CarLog.TAG_AM, "Blocked task ID is not available. Hiding exit button.");
+        // Display a button to restart task if root task is DO.
+        boolean showButton = mBlockedTaskId != INVALID_TASK_ID && isRootDO;
+        mExitButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
+        mExitButton.setOnClickListener(v -> handleRestartingTask());
+
+        // Show more debug info for non-user build.
+        if (Build.IS_ENG || Build.IS_USERDEBUG) {
+            displayDebugInfo();
         }
     }
 
+    private void displayDebugInfo() {
+        String blockedActivity = getIntent().getStringExtra(
+                BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
+        String rootActivity = getIntent().getStringExtra(BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME);
+
+        TextView debugInfo = findViewById(R.id.debug_info);
+        debugInfo.setText(getDebugInfo(blockedActivity, rootActivity));
+
+        // We still want to ensure driving safety for non-user build;
+        // toggle visibility of debug info with this button.
+        Button toggleDebug = findViewById(R.id.toggle_debug_info);
+        toggleDebug.setVisibility(View.VISIBLE);
+        toggleDebug.setOnClickListener(v -> {
+            boolean isDebugVisible = debugInfo.getVisibility() == View.VISIBLE;
+            debugInfo.setVisibility(isDebugVisible ? View.GONE : View.VISIBLE);
+        });
+    }
+
+    private String getDebugInfo(String blockedActivity, String rootActivity) {
+        StringBuilder debug = new StringBuilder();
+
+        ComponentName blocked = ComponentName.unflattenFromString(blockedActivity);
+        debug.append("Blocked activity is ")
+                .append(blocked.getShortClassName())
+                .append("\nBlocked activity package is ")
+                .append(blocked.getPackageName());
+
+        if (rootActivity != null) {
+            ComponentName root = ComponentName.unflattenFromString(rootActivity);
+            // Optionally show root activity info if it differs from the blocked activity.
+            if (!root.equals(blocked)) {
+                debug.append("\n\nRoot activity is ").append(root.getShortClassName());
+            }
+            if (!root.getPackageName().equals(blocked.getPackageName())) {
+                debug.append("\nRoot activity package is ").append(root.getPackageName());
+            }
+        }
+        return debug.toString();
+    }
+
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
@@ -149,7 +221,51 @@
         }
     }
 
-    private void handleFinish() {
+    // Finds the icon of the application (package) the component belongs to.
+    @Nullable
+    private Drawable findApplicationIcon(String flattenComponentName) {
+        ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName);
+        try {
+            return getPackageManager().getApplicationIcon(componentName.getPackageName());
+        } catch (PackageManager.NameNotFoundException e) {
+            if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+                Log.i(CarLog.TAG_AM, "Could not find package for component name "
+                        + componentName.toString());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a human-readable string for {@code flattenComponentName}.
+     *
+     * <p>It first attempts to return the application label for this activity. If that fails,
+     * it will return the last part in the activity name.
+     */
+    private String findHumanReadableLabel(String flattenComponentName) {
+        ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName);
+        String label = null;
+        // Attempt to find application label.
+        try {
+            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
+                    componentName.getPackageName(), 0);
+            CharSequence appLabel = getPackageManager().getApplicationLabel(applicationInfo);
+            if (appLabel != null) {
+                label = appLabel.toString();
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+                Log.i(CarLog.TAG_AM, "Could not find package for component name "
+                        + componentName.toString());
+            }
+        }
+        if (TextUtils.isEmpty(label)) {
+            label = componentName.getClass().getSimpleName();
+        }
+        return label;
+    }
+
+    private void handleRestartingTask() {
         if (!mCar.isConnected()) {
             mExitRequested = true;
             return;
@@ -158,9 +274,12 @@
             return;
         }
 
-        // Lock on self (assuming single instance) to avoid restarting the same task twice.
+        // Lock on self to avoid restarting the same task twice.
         synchronized (this) {
             try {
+                if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+                    Log.i(CarLog.TAG_AM, "Restarting task " + mBlockedTaskId);
+                }
                 CarPackageManager carPm = (CarPackageManager)
                         mCar.getCarManager(Car.PACKAGE_SERVICE);
                 carPm.restartTask(mBlockedTaskId);
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index ed51a78..1c50db9 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -141,6 +141,32 @@
     // To track if we received the boot complete intent.
     private boolean mBootLockedIntentRx;
 
+    /**
+     * Name of blocked activity.
+     *
+     * @hide
+     */
+    public static final String BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME = "blocked_activity";
+    /**
+     * int task id of the blocked task.
+     *
+     * @hide
+     */
+    public static final String BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID = "blocked_task_id";
+    /**
+     * Name of root activity of blocked task.
+     *
+     * @hide
+     */
+    public static final String BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME = "root_activity_name";
+    /**
+     * Boolean indicating whether the root activity is distraction-optimized (DO).
+     * Blocking screen should show a button to restart the task if {@code true}.
+     *
+     * @hide
+     */
+    public static final String BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO = "is_root_activity_do";
+
     public CarPackageManagerService(Context context,
             CarUxRestrictionsManagerService uxRestrictionsService,
             SystemActivityMonitoringService systemActivityMonitoringService) {
@@ -935,45 +961,64 @@
                     " not allowed, will block, number of tasks in stack:" +
                     topTask.stackInfo.taskIds.length);
         }
-        StringBuilder blockedActivityLog = new StringBuilder();
-        Intent newActivityIntent = new Intent();
-        newActivityIntent.setComponent(mActivityBlockingActivity);
-        newActivityIntent.putExtra(
-                ActivityBlockingActivity.INTENT_KEY_BLOCKED_ACTIVITY,
-                topTask.topActivity.flattenToString());
-        blockedActivityLog.append("Blocked activity ")
-                .append(topTask.topActivity.flattenToShortString())
-                .append(". Task id ").append(topTask.taskId);
 
-        // If root activity of blocked task is DO, also pass its task id into blocking activity,
-        // which uses the id to display a button for restarting the blocked task.
+        // Figure out the root activity of blocked task.
+        String taskRootActivity = null;
         for (int i = 0; i < topTask.stackInfo.taskIds.length; i++) {
             // topTask.taskId is the task that should be blocked.
             if (topTask.stackInfo.taskIds[i] == topTask.taskId) {
                 // stackInfo represents an ActivityStack. Its fields taskIds and taskNames
                 // are 1:1 mapped, where taskNames is the name of root activity in this task.
-                String taskRootActivity = topTask.stackInfo.taskNames[i];
-
-                ComponentName rootActivityName = ComponentName.unflattenFromString(
-                        taskRootActivity);
-                if (isActivityDistractionOptimized(
-                        rootActivityName.getPackageName(), rootActivityName.getClassName())) {
-                    newActivityIntent.putExtra(
-                            ActivityBlockingActivity.EXTRA_BLOCKED_TASK, topTask.taskId);
-                    if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
-                        Log.i(CarLog.TAG_PACKAGE, "Blocked task " + topTask.taskId
-                                + " has DO root activity " + taskRootActivity);
-                    }
-                    blockedActivityLog.append(". Root DO activity ")
-                            .append(rootActivityName.flattenToShortString());
-                }
+                taskRootActivity = topTask.stackInfo.taskNames[i];
                 break;
             }
         }
-        addLog(blockedActivityLog.toString());
+
+        boolean isRootDO = false;
+        if (taskRootActivity != null) {
+            ComponentName componentName = ComponentName.unflattenFromString(taskRootActivity);
+            isRootDO = isActivityDistractionOptimized(
+                    componentName.getPackageName(), componentName.getClassName());
+        }
+
+        Intent newActivityIntent = createBlockingActivityIntent(
+                mActivityBlockingActivity, topTask.topActivity.flattenToShortString(),
+                topTask.taskId, taskRootActivity, isRootDO);
+
+        // Intent contains all info to debug what is blocked - log into both logcat and dumpsys.
+        String log = "Starting blocking activity with intent: " + newActivityIntent.toUri(0);
+        if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
+            Log.i(CarLog.TAG_PACKAGE, log);
+        }
+        addLog(log);
         mSystemActivityMonitoringService.blockActivity(topTask, newActivityIntent);
     }
 
+    /**
+     * Creates an intent to start blocking activity.
+     *
+     * @param blockingActivity the activity to launch
+     * @param blockedActivity the activity being blocked
+     * @param blockedTaskId the blocked task id, which contains the blocked activity
+     * @param taskRootActivity root activity of the blocked task
+     *
+     * @return an intent to launch the blocking activity.
+     */
+    private static Intent createBlockingActivityIntent(ComponentName blockingActivity,
+            String blockedActivity, int blockedTaskId, String taskRootActivity, boolean isRootDo) {
+        Intent newActivityIntent = new Intent();
+        newActivityIntent.setComponent(blockingActivity);
+        newActivityIntent.putExtra(
+                BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME, blockedActivity);
+        newActivityIntent.putExtra(
+                BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID, blockedTaskId);
+        newActivityIntent.putExtra(
+                BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME, taskRootActivity);
+        newActivityIntent.putExtra(
+                BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, isRootDo);
+        return newActivityIntent;
+    }
+
     private void blockTopActivitiesIfNecessary() {
         boolean restricted = mUxRestrictionsListener.isRestricted();
         if (!restricted) {
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index eebb9d1..8716aed 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -95,8 +95,7 @@
             int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
             if (currentUser > UserHandle.USER_SYSTEM
                         && mCarUserManagerHelper.isPersistentUser(currentUser)) {
-                mCarUserManagerHelper.setLastActiveUser(
-                        currentUser, /* skipGlobalSetting= */ false);
+                mCarUserManagerHelper.setLastActiveUser(currentUser);
             }
         }
     }
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index 8239f3c..e6f457e 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -41,17 +41,29 @@
 LOCAL_DEX_PREOPT := false
 
 LOCAL_STATIC_ANDROID_LIBRARIES += \
+    car-service-lib-for-test \
+    car-apps-common \
     androidx.car_car \
-    car-service-lib-for-test
+    androidx.car_car-cluster
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android.hidl.base-V1.0-java \
     android.hardware.automotive.vehicle-V2.0-java \
     vehicle-hal-support-lib \
-    com.android.car.keventreader-client
+    com.android.car.keventreader-client \
+    kitchensink-gson
 
 LOCAL_JAVA_LIBRARIES += android.car
 
 include $(BUILD_PACKAGE)
 
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    kitchensink-gson:libs/gson-2.1.jar
+
+include $(BUILD_MULTI_PREBUILT)
+
+include $(CLEAR_VARS)
+
 endif #TARGET_BUILD_PDK
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
new file mode 100644
index 0000000..09396a0
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
new file mode 100644
index 0000000..83c5c99
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json b/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json
new file mode 100644
index 0000000..2aa5cd0
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json
@@ -0,0 +1,55 @@
+[
+  {
+    "mSteps": [
+      {
+        "mDistance": {
+          "mMeters": 200,
+          "mDisplayValue": "0.2",
+          "mDisplayUnit": { "mValues": [ "KILOMETERS" ] }
+        },
+        "mManeuver": {
+          "mType": { "mValues": [ "DEPART" ] }
+        }
+      }
+    ],
+    "mDestinations": [
+      {
+        "mTitle": "Home",
+        "mAddress": "123 Main st",
+        "mDistance": {
+          "mMeters": 2000,
+          "mDisplayValue": "2",
+          "mDisplayUnit": { "mValues": [ "KILOMETERS" ] }
+        }
+      }
+    ]
+  },
+  {
+    "mSteps": [
+      {
+        "mDistance": {
+          "mMeters": 91,
+          "mDisplayValue": "300",
+          "mDisplayUnit": { "mValues": [ "FEET" ] }
+        },
+        "mManeuver": {
+          "mType": { "mValues": [ "TURN_NORMAL_LEFT" ] }
+        }
+      }
+    ]
+  },
+  {
+    "mSteps": [
+      {
+        "mDistance": {
+          "mMeters": 3218,
+          "mDisplayValue": "2",
+          "mDisplayUnit": { "mValues": [ "MILES" ] }
+        },
+        "mManeuver": {
+          "mType": { "mValues": [ "TURN_NORMAL_RIGHT" ] }
+        }
+      }
+    ]
+  }
+]
\ 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 4f5fc5b..ad4b93a 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
@@ -33,22 +33,38 @@
 import android.view.ViewGroup;
 import android.widget.Toast;
 
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.car.cluster.navigation.NavigationState;
 import androidx.fragment.app.Fragment;
 
 import com.google.android.car.kitchensink.R;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.stream.Collectors;
 
 /**
  * Contains functions to test instrument cluster API.
  */
 public class InstrumentClusterFragment extends Fragment {
-    private static final String TAG = InstrumentClusterFragment.class.getSimpleName();
+    private static final String TAG = "Cluster.KitchenSink";
 
     private static final int DISPLAY_IN_CLUSTER_PERMISSION_REQUEST = 1;
 
     private CarNavigationStatusManager mCarNavigationStatusManager;
     private CarAppFocusManager mCarAppFocusManager;
     private Car mCarApi;
+    private Timer mTimer;
+    private NavigationState[] mNavStateData;
 
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
             @Override
@@ -81,6 +97,37 @@
         mCarApi.connect();
     }
 
+    /**
+     * Loads sample navigation data from the "nav_state_data.json" file.
+     */
+    @NonNull
+    private NavigationState[] getNavStateData() {
+        try {
+            Gson gson = new GsonBuilder().create();
+            String navStateData = getRawResourceAsString(R.raw.nav_state_data);
+            NavigationState[] navigationState = gson.fromJson(navStateData,
+                    NavigationState[].class);
+            return navigationState;
+        } catch (IOException ex) {
+            Log.e(TAG, "Unable to read navigation state data", ex);
+            return new NavigationState[0];
+        }
+    }
+
+    /**
+     * Loads a raw resource as a single string.
+     */
+    @NonNull
+    private String getRawResourceAsString(@IdRes int resId) throws IOException {
+        InputStream inputStream = getResources().openRawResource(resId);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+        StringBuilder builder = new StringBuilder();
+        for (String line = null; (line = reader.readLine()) != null; ) {
+            builder.append(line).append("\n");
+        }
+        return builder.toString();
+    }
+
     @Nullable
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -88,7 +135,7 @@
         View view = inflater.inflate(R.layout.instrument_cluster, container, false);
 
         view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
-        view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> sendTurn());
+        view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> toogleSendTurn());
         view.findViewById(R.id.cluster_start_activity).setOnClickListener(v -> startNavActivity());
 
         return view;
@@ -97,7 +144,6 @@
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         initCarApi();
-
         super.onCreate(savedInstanceState);
     }
 
@@ -126,12 +172,46 @@
         }
     }
 
-    private void sendTurn() {
-        // TODO(deanh): Make this actually meaningful.
-        Bundle bundle = new Bundle();
-        bundle.putString("someName", "someValue time=" + System.currentTimeMillis());
+    /**
+     * Enables/disables sending turn-by-turn data through the {@link CarNavigationStatusManager}
+     */
+    private void toogleSendTurn() {
+        // If we haven't yet load the sample navigation state data, do so.
+        if (mNavStateData == null) {
+            mNavStateData = getNavStateData();
+            Log.i(TAG, "Loaded: " + Arrays.asList(mNavStateData)
+                    .stream()
+                    .map(n -> n.toString())
+                    .collect(Collectors.joining(", ")));
+        }
+
+        // Toggle a timer to send update periodically.
+        if (mTimer == null) {
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                private int mPos;
+
+                @Override
+                public void run() {
+                    sendTurn(mNavStateData[mPos]);
+                    mPos = (mPos + 1) % mNavStateData.length;
+                }
+            }, 0, 1000);
+        } else {
+            mTimer.cancel();
+            mTimer = null;
+        }
+    }
+
+    /**
+     * Sends one update of the navigation state through the {@link CarNavigationStatusManager}
+     */
+    private void sendTurn(@NonNull NavigationState state) {
         try {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable("navstate", state.toParcelable());
             mCarNavigationStatusManager.sendEvent(1, bundle);
+            Log.i(TAG, "Sending nav state: " + state);
         } catch(CarNotConnectedException e) {
             Log.e(TAG, "Failed to send turn information.", e);
         }
@@ -185,8 +265,6 @@
         } catch (CarNotConnectedException e) {
             Log.e(TAG, "Failed to get owned focus", e);
         }
-
-        // TODO(deanh): re-implement this using sendEvent()
     }
 
     @Override
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
index 83b1b29..f731bec 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
@@ -39,6 +39,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -91,6 +92,8 @@
         doReturn(mActivityManager).when(mContext).getSystemService(Context.ACTIVITY_SERVICE);
         doReturn(InstrumentationRegistry.getTargetContext().getResources())
                 .when(mContext).getResources();
+        doReturn(InstrumentationRegistry.getTargetContext().getContentResolver())
+                .when(mContext).getContentResolver();
         doReturn(mContext).when(mContext).getApplicationContext();
         mCarUserManagerHelper = new CarUserManagerHelper(mContext);
 
@@ -776,8 +779,7 @@
         UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
         UserInfo otherUser3 = createUserInfoForId(lastActiveUserId);
 
-        mCarUserManagerHelper.setLastActiveUser(
-                lastActiveUserId, /* skipGlobalSettings= */ true);
+        setLastActiveUser(lastActiveUserId);
         mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
 
         assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
@@ -791,8 +793,7 @@
         UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
         UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
 
-        mCarUserManagerHelper.setLastActiveUser(
-                lastActiveUserId, /* skipGlobalSettings= */ true);
+        setLastActiveUser(lastActiveUserId);
         mockGetUsers(mSystemUser, otherUser1, otherUser2);
 
         assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId - 2);
@@ -842,4 +843,9 @@
         }
         doReturn(testUsers).when(mUserManager).getUsers(true);
     }
+
+    private void setLastActiveUser(int userId) {
+        Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+                Settings.Global.LAST_ACTIVE_USER_ID, userId);
+    }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index addb53c..2e3386b 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -152,8 +152,7 @@
 
         mCarUserService.onReceive(mMockContext, intent);
 
-        verify(mCarUserManagerHelper).setLastActiveUser(
-                lastActiveUserId, /* skipGlobalSetting= */ false);
+        verify(mCarUserManagerHelper).setLastActiveUser(lastActiveUserId);
     }
 
     /**
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index fdf3ade..bb3976f 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -84,7 +84,6 @@
     private final Context mContext;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
-    private int mLastActiveUser = UserHandle.USER_SYSTEM;
     private Bitmap mDefaultGuestUserIcon;
     private ArrayList<OnUsersUpdateListener> mUpdateListeners;
     private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@@ -164,10 +163,21 @@
      * Set last active user.
      *
      * @param userId last active user id.
-     * @param skipGlobalSetting whether to skip set the global settings value.
      */
+    public void setLastActiveUser(int userId) {
+        Settings.Global.putInt(
+                mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
+    }
+
+    /**
+     * Set last active user.
+     *
+     * @param userId last active user id.
+     * @param skipGlobalSetting whether to skip set the global settings value.
+     * @deprecated Use {@link #setDefaultBootUser(int)} instead.
+     */
+    @Deprecated
     public void setLastActiveUser(int userId, boolean skipGlobalSetting) {
-        mLastActiveUser = userId;
         if (!skipGlobalSetting) {
             Settings.Global.putInt(
                     mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
@@ -192,9 +202,6 @@
      * @return user id of the last active user.
      */
     public int getLastActiveUser() {
-        if (mLastActiveUser != UserHandle.USER_SYSTEM) {
-            return mLastActiveUser;
-        }
         return Settings.Global.getInt(
             mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID,
             /* default user id= */ UserHandle.USER_SYSTEM);