Merge "[CPMS] Fixing double shutdown event"
diff --git a/TrustAgent/Android.mk b/TrustAgent/Android.mk
deleted file mode 100644
index bef1402..0000000
--- a/TrustAgent/Android.mk
+++ /dev/null
@@ -1,48 +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.
-#
-
-ifneq ($(TARGET_BUILD_PDK), true)
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_JAVA_LIBRARIES += android.car
-
-LOCAL_STATIC_ANDROID_LIBRARIES += \
-    androidx.car_car
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CarTrustAgentService
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_PRIVILEGED_MODULE := true
-
-# Remove this to verify permission checks are working correctly
-LOCAL_CERTIFICATE := platform
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
-
-endif
diff --git a/TrustAgent/AndroidManifest.xml b/TrustAgent/AndroidManifest.xml
deleted file mode 100644
index 2b7944d..0000000
--- a/TrustAgent/AndroidManifest.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.car.trust">
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
-
-    <!-- Need Bluetooth LE -->
-    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
-
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
-    <!-- Needed to unlock user -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-    <!-- TODO(b/77717079) INTERACT_ACROSS_USERS_FULL should have granted INTERACT_ACROSS_USERS -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
-    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-
-    <application
-        android:label="@string/app_name"
-        android:theme="@style/Theme.Car.NoActionBar"
-        android:supportsRtl="true">
-
-        <service
-            android:name=".CarBleTrustAgent"
-            android:permission="android.permission.BIND_TRUST_AGENT"
-            android:directBootAware="true"
-            android:exported="true"
-            android:singleUser="true">
-            <intent-filter>
-                <action android:name="android.service.trust.TrustAgentService" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-            <!-- Warning: the meta data must be included if the service is direct boot aware.
-                 If not included, the device will crash before boot completes. Rendering the
-                 device unusable. -->
-            <meta-data android:name="android.service.trust.trustagent"
-                       android:resource="@xml/car_sample_trust_agent"/>
-        </service>
-
-        <!-- CarTrustAgentBleService needs to be direct boot aware, since the trust agent
-             binds to it during direct boot.-->
-        <service
-            android:name=".CarTrustAgentBleService"
-            android:directBootAware="true"
-            android:singleUser="true">
-            <!-- Warning: the meta data must be included if the service is direct boot aware.
-                 If not included, the device will crash before boot completes. Rendering the device
-                 unusable. -->
-            <meta-data android:name="android.service.trust.trustagent"
-                       android:resource="@xml/car_sample_trust_agent"/>
-        </service>
-
-        <activity
-            android:name=".CarEnrolmentActivity"
-            android:label="@string/app_name"
-            android:exported="true"
-            android:launchMode="singleInstance">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-
-    </application>
-</manifest>
diff --git a/TrustAgent/res/layout/car_enrolment_activity.xml b/TrustAgent/res/layout/car_enrolment_activity.xml
deleted file mode 100644
index 70632f7..0000000
--- a/TrustAgent/res/layout/car_enrolment_activity.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:background="@android:color/black"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/car_app_bar_height" >
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:layout_centerVertical="true"
-            android:layout_marginStart="@dimen/car_margin"
-            android:text="@string/app_name"
-            android:textAppearance="@style/TextAppearance.Car.Body1.Light" />
-
-        <Button
-            android:id="@+id/revoke_trust_button"
-            android:layout_width="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_toStartOf="@+id/start_button"
-            android:layout_marginEnd="@dimen/car_padding_4"
-            android:text="@string/revoke_trust"
-            style="?android:attr/borderlessButtonStyle" />
-
-        <Button
-            android:id="@+id/start_button"
-            android:layout_width="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:layout_centerVertical="true"
-            android:layout_marginEnd="@dimen/car_keyline_1"
-            android:text="@string/start_advertising"
-            style="?android:attr/buttonStyle" />
-
-        <View
-            android:background="@color/car_list_divider_light"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/car_list_divider_height"
-            android:layout_alignParentBottom="true" />
-    </RelativeLayout>
-
-    <androidx.car.widget.PagedListView
-        android:id="@+id/list"
-        android:layout_marginTop="@dimen/car_padding_4"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        app:showPagedListViewDivider="false"
-        app:gutter="both"/>
-</LinearLayout>
diff --git a/TrustAgent/res/layout/output_line.xml b/TrustAgent/res/layout/output_line.xml
deleted file mode 100644
index 53eba57..0000000
--- a/TrustAgent/res/layout/output_line.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 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.
-  -->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/output"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:textAppearance="@style/TextAppearance.Car.Body3.Light" />
diff --git a/TrustAgent/res/values/strings.xml b/TrustAgent/res/values/strings.xml
deleted file mode 100644
index c8dcc18..0000000
--- a/TrustAgent/res/values/strings.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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>
-    <string name="app_name">CarTrustAgentService</string>
-    <string name="trust_granted_explanation">Unlock via escrow token, now granting trust</string>
-
-    <!-- service/characteristics uuid for unlocking a device -->
-    <string name="unlock_service_uuid" translatable="false">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
-    <string name="unlock_escrow_token_uiid" translatable="false">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
-    <string name="unlock_handle_uiid" translatable="false">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
-
-    <!-- service/characteristics uuid for adding new escrow token -->
-    <string name="enrollment_service_uuid" translatable="false">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
-    <string name="enrollment_handle_uuid" translatable="false">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
-    <string name="enrollment_token_uuid" translatable="false">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
-
-    <string name="pref_key_token_handle" translatable="false">token-handle-key</string>
-    <string name="pref_key_escrow_token" translatable="false">escrow-token-key</string>
-
-    <string name="unlock_button">Unlock Car</string>
-    <string name="enroll_button">Enroll New Token</string>
-
-    <string name="start_advertising">Start Advertising</string>
-    <string name="revoke_trust">Revoke Trust</string>
-
-    <string name="token_handle_shared_preferences" translatable="false">com.android.car.trust.TOKEN_HANDLE</string>
-
-</resources>
diff --git a/TrustAgent/res/xml/car_sample_trust_agent.xml b/TrustAgent/res/xml/car_sample_trust_agent.xml
deleted file mode 100644
index 0c7d91d..0000000
--- a/TrustAgent/res/xml/car_sample_trust_agent.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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
-  -->
-<trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
-             xmlns:priv-android="http://schemas.android.com/apk/prv/res/android"
-             android:settingsActivity=".MainActivity"
-             priv-android:unlockProfile="true" />
diff --git a/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
deleted file mode 100644
index 1dfbd50..0000000
--- a/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
+++ /dev/null
@@ -1,303 +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 com.android.car.trust;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.UserSwitchObserver;
-import android.bluetooth.BluetoothAdapter;
-import android.car.trust.ICarTrustAgentBleService;
-import android.car.trust.ICarTrustAgentTokenRequestDelegate;
-import android.car.trust.ICarTrustAgentUnlockCallback;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.trust.TrustAgentService;
-import android.util.Log;
-
-/**
- * A BluetoothLE (BLE) based {@link TrustAgentService} that uses the escrow token unlock APIs. </p>
- *
- * This trust agent runs during direct boot and binds to a BLE service that listens for remote
- * devices to trigger an unlock. <p/>
- *
- * The permissions for this agent must be enabled as priv-app permissions for it to start.
- */
-public class CarBleTrustAgent extends TrustAgentService {
-
-    private static final String TAG = CarBleTrustAgent.class.getSimpleName();
-
-    /**
-     * {@link CarTrustAgentBleService} will callback this function when it receives both
-     * handle and token.
-     */
-    private final ICarTrustAgentUnlockCallback mUnlockCallback =
-            new ICarTrustAgentUnlockCallback.Stub() {
-        @Override
-        public void onUnlockDataReceived(byte[] token, long handle) throws RemoteException {
-            UserHandle userHandle = getUserHandleByTokenHandle(handle);
-            if (userHandle == null) {
-                Log.e(TAG, "Unable to find user by token handle " + handle);
-                return;
-            }
-
-            int uid = userHandle.getIdentifier();
-            if (ActivityManager.getCurrentUser() != uid) {
-                Log.d(TAG, "Switch to user: " + uid);
-                // Try to unlock when user switch completes
-                ActivityManager.getService().registerUserSwitchObserver(
-                        getUserSwitchObserver(uid, token, handle), TAG);
-                ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
-                am.switchUser(uid);
-            } else {
-                unlockUserInternally(uid, token, handle);
-            }
-        }
-    };
-
-    /**
-     * Delegates the escrow token API calls from {@link CarTrustAgentBleService} to
-     * {@link TrustAgentService}. Due to the asynchronous nature, the results will be posted to
-     * {@link CarTrustAgentBleService} by the following calls
-     * <ul>
-     *     <li>{@link #onEscrowTokenAdded(byte[], long, UserHandle)}</li>
-     *     <li>{@link #onEscrowTokenRemoved(long, boolean)}</li>
-     *     <li>{@link #onEscrowTokenStateReceived(long, int)}</li>
-     * </ul>
-     */
-    private final ICarTrustAgentTokenRequestDelegate mTokenRequestDelegate =
-            new ICarTrustAgentTokenRequestDelegate.Stub() {
-        @Override
-        public void revokeTrust() {
-            CarBleTrustAgent.this.revokeTrust();
-        }
-
-        @Override
-        public void addEscrowToken(byte[] token, int uid) {
-            CarBleTrustAgent.this.addEscrowToken(token, UserHandle.of(uid));
-        }
-
-        @Override
-        public void removeEscrowToken(long handle, int uid) {
-            CarBleTrustAgent.this.removeEscrowToken(handle, UserHandle.of(uid));
-        }
-
-        @Override
-        public void isEscrowTokenActive(long handle, int uid) {
-            CarBleTrustAgent.this.isEscrowTokenActive(handle, UserHandle.of(uid));
-        }
-    };
-
-    /**
-     * Service connection to {@link CarTrustAgentBleService}
-     */
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.d(TAG, "CarTrustAgentBleService connected");
-            mCarTrustAgentBleServiceBound = true;
-            mCarTrustAgentBleService = ICarTrustAgentBleService.Stub.asInterface(service);
-            try {
-                mCarTrustAgentBleService.registerUnlockCallback(mUnlockCallback);
-                mCarTrustAgentBleService.setTokenRequestDelegate(mTokenRequestDelegate);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error registerUnlockCallback", e);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.d(TAG, "CarTrustAgentBleService disconnected");
-            if (mCarTrustAgentBleService != null) {
-                try {
-                    mCarTrustAgentBleService.unregisterUnlockCallback(mUnlockCallback);
-                    mCarTrustAgentBleService.setTokenRequestDelegate(null);
-                    mCarTrustAgentBleService.stopUnlockAdvertising();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error unregisterUnlockCallback", e);
-                }
-                mCarTrustAgentBleService = null;
-                mCarTrustAgentBleServiceBound = false;
-            }
-        }
-    };
-
-    /**
-     * Receives the bluetooth state change broadcasts. Bluetooth is restarted when switching user,
-     * we need to ensure calling {@link ICarTrustAgentBleService#startUnlockAdvertising} after
-     * bluetooth is started.
-     */
-    private final BroadcastReceiver mBluetoothBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case BluetoothAdapter.ACTION_STATE_CHANGED:
-                    onBluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
-                    break;
-            }
-        }
-    };
-
-    private ICarTrustAgentBleService mCarTrustAgentBleService;
-    private boolean mCarTrustAgentBleServiceBound;
-
-    /**
-     * TODO: Currently it relies on {@link #onDeviceLocked()} and {@link #onDeviceUnlocked()}
-     * callback, and these callbacks won't happen if the user has unlocked once.
-     */
-    private boolean mIsOnLockScreen;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        setManagingTrust(true);
-        bindService(new Intent(this, CarTrustAgentBleService.class),
-                mServiceConnection, Context.BIND_AUTO_CREATE);
-        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
-        registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.d(TAG, "Car Trust agent shutting down");
-        if (mCarTrustAgentBleServiceBound) {
-            unbindService(mServiceConnection);
-        }
-        unregisterReceiver(mBluetoothBroadcastReceiver);
-        super.onDestroy();
-    }
-
-    @Override
-    public void onDeviceLocked() {
-        super.onDeviceLocked();
-        mIsOnLockScreen = true;
-        if (mCarTrustAgentBleServiceBound) {
-            try {
-                mCarTrustAgentBleService.startUnlockAdvertising();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error startUnlockAdvertising", e);
-            }
-        }
-    }
-
-    @Override
-    public void onDeviceUnlocked() {
-        super.onDeviceUnlocked();
-        mIsOnLockScreen = false;
-        if (mCarTrustAgentBleServiceBound) {
-            try {
-                mCarTrustAgentBleService.stopUnlockAdvertising();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error stopUnlockAdvertising", e);
-            }
-        }
-        // Revoke trust right after to enable keyguard when switching user
-        revokeTrust();
-    }
-
-    private UserSwitchObserver getUserSwitchObserver(int uid,
-            byte[] token, long handle) {
-        return new UserSwitchObserver() {
-            @Override
-            public void onUserSwitchComplete(int newUserId) throws RemoteException {
-                if (uid != newUserId) return;
-                unlockUserInternally(uid, token, handle);
-                ActivityManager.getService().unregisterUserSwitchObserver(this);
-            }
-
-            @Override
-            public void onLockedBootComplete(int newUserId) {
-                // ignored.
-            }
-        };
-    }
-
-    private void unlockUserInternally(int uid, byte[] token, long handle) {
-        Log.d(TAG, "About to unlock user: " + uid);
-        unlockUserWithToken(handle, token, UserHandle.of(uid));
-        grantTrust("Granting trust from escrow token",
-                0, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
-    }
-
-    private void onBluetoothStateChanged(int state) {
-        Log.d(TAG, "onBluetoothStateChanged: " + state);
-        if ((state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON)
-                && mCarTrustAgentBleServiceBound
-                && mIsOnLockScreen) {
-            try {
-                mCarTrustAgentBleService.startUnlockAdvertising();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error startUnlockAdvertising", e);
-            }
-        }
-    }
-
-    @Override
-    public void onEscrowTokenRemoved(long handle, boolean successful) {
-        if (mCarTrustAgentBleServiceBound) {
-            try {
-                mCarTrustAgentBleService.onEscrowTokenRemoved(handle, successful);
-                Log.v(TAG, "Callback onEscrowTokenRemoved");
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onEscrowTokenRemoved", e);
-            }
-        }
-    }
-
-    @Override
-    public void onEscrowTokenStateReceived(long handle, int tokenState) {
-        boolean isActive = tokenState == TOKEN_STATE_ACTIVE;
-        if (mCarTrustAgentBleServiceBound) {
-            try {
-                mCarTrustAgentBleService.onEscrowTokenActiveStateChanged(handle, isActive);
-                Log.v(TAG, "Callback onEscrowTokenActiveStateChanged");
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onEscrowTokenActiveStateChanged", e);
-            }
-        }
-    }
-
-    @Override
-    public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
-        if (mCarTrustAgentBleServiceBound) {
-            try {
-                mCarTrustAgentBleService.onEscrowTokenAdded(token, handle, user.getIdentifier());
-                Log.v(TAG, "Callback onEscrowTokenAdded");
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onEscrowTokenAdded", e);
-            }
-        }
-    }
-
-    private @Nullable UserHandle getUserHandleByTokenHandle(long tokenHandle) {
-        if (mCarTrustAgentBleServiceBound) {
-            try {
-                int userId = mCarTrustAgentBleService.getUserIdByEscrowTokenHandle(tokenHandle);
-                return userId < 0 ? null : UserHandle.of(userId);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error getUserHandleByTokenHandle");
-            }
-        }
-        return null;
-    }
-}
diff --git a/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
deleted file mode 100644
index f93c1b4..0000000
--- a/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
+++ /dev/null
@@ -1,318 +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 com.android.car.trust;
-
-import android.Manifest;
-import android.app.Activity;
-import android.bluetooth.BluetoothDevice;
-import android.car.trust.ICarTrustAgentBleCallback;
-import android.car.trust.ICarTrustAgentBleService;
-import android.car.trust.ICarTrustAgentEnrolmentCallback;
-import android.car.trust.ICarTrustAgentTokenResponseCallback;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.car.widget.PagedListView;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Setup activity that binds {@link CarTrustAgentBleService} and starts the enrolment process.
- */
-public class CarEnrolmentActivity extends Activity {
-
-    private static final String TAG = CarEnrolmentActivity.class.getSimpleName();
-
-    private static final String SP_HANDLE_KEY = "sp-test";
-    private static final int FINE_LOCATION_REQUEST_CODE = 42;
-
-    private PagedListView mList;
-    private OutputAdapter mOutputAdapter;
-    private BluetoothDevice mBluetoothDevice;
-    private ICarTrustAgentBleService mCarTrustAgentBleService;
-    private boolean mCarTrustAgentBleServiceBound;
-    private SharedPreferences mPrefs;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.car_enrolment_activity);
-
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(/* context= */ this);
-
-        findViewById(R.id.start_button).setOnClickListener(v -> {
-            if (!mCarTrustAgentBleServiceBound) {
-                Intent bindIntent = new Intent(this, CarTrustAgentBleService.class);
-                bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
-            }
-        });
-
-        findViewById(R.id.revoke_trust_button).setOnClickListener(v -> {
-            if (mCarTrustAgentBleServiceBound) {
-                try {
-                    mCarTrustAgentBleService.revokeTrust();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error revokeTrust", e);
-                }
-            }
-        });
-
-        mOutputAdapter = new OutputAdapter();
-
-        mList = findViewById(R.id.list);
-        mList.setAdapter(mOutputAdapter);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
-                != PackageManager.PERMISSION_GRANTED) {
-            requestPermissions(
-                    new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
-                    FINE_LOCATION_REQUEST_CODE);
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        if (mCarTrustAgentBleServiceBound) {
-            unbindService(mServiceConnection);
-            mCarTrustAgentBleServiceBound = false;
-        }
-    }
-
-    private void appendOutputText(String text) {
-        runOnUiThread(() -> {
-            mOutputAdapter.addOutput(text);
-            mList.scrollToPosition(mOutputAdapter.getItemCount() - 1);
-        });
-    }
-
-    private void addEscrowToken(byte[] token) throws RemoteException {
-        if (!mCarTrustAgentBleServiceBound) {
-            Log.e(TAG, "No CarTrustAgentBleService bounded");
-            return;
-        }
-        mCarTrustAgentBleService.addEscrowToken(token, UserHandle.myUserId());
-    }
-
-    private void checkTokenHandle() {
-        long tokenHandle = mPrefs.getLong(SP_HANDLE_KEY, -1);
-        if (tokenHandle != -1) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Checking handle active: " + tokenHandle);
-            }
-
-            if (mCarTrustAgentBleServiceBound) {
-                try {
-                    // Due to the asynchronous nature of isEscrowTokenActive in
-                    // TrustAgentService, query result will be delivered via
-                    // {@link #mCarTrustAgentTokenResponseCallback}
-                    mCarTrustAgentBleService.isEscrowTokenActive(tokenHandle,
-                            UserHandle.myUserId());
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error isEscrowTokenActive", e);
-                }
-            }
-        } else {
-            appendOutputText("No handles found");
-        }
-    }
-
-    /**
-     * Receives escrow token callbacks, registered on {@link CarTrustAgentBleService}
-     */
-    private final ICarTrustAgentTokenResponseCallback mCarTrustAgentTokenResponseCallback =
-            new ICarTrustAgentTokenResponseCallback.Stub() {
-        @Override
-        public void onEscrowTokenAdded(byte[] token, long handle, int uid) {
-            runOnUiThread(() -> {
-                mPrefs.edit().putLong(SP_HANDLE_KEY, handle).apply();
-
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "stored new handle for user: " + uid);
-                }
-            });
-
-            if (mBluetoothDevice == null) {
-                Log.e(TAG, "No active bluetooth found to add escrow token");
-                return;
-            }
-
-            try {
-                // Notify the enrolment client that escrow token has been added
-                mCarTrustAgentBleService.sendEnrolmentHandle(mBluetoothDevice, handle);
-                appendOutputText("Escrow Token Added. Handle: " + handle);
-                appendOutputText("Lock and unlock the device to activate token");
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error sendEnrolmentHandle", e);
-            }
-        }
-
-        @Override
-        public void onEscrowTokenRemoved(long handle, boolean successful) {
-            appendOutputText("Escrow token Removed. Handle: " + handle);
-        }
-
-        @Override
-        public void onEscrowTokenActiveStateChanged(long handle, boolean active) {
-            appendOutputText("Is token active? " + active + " handle: " + handle);
-        }
-    };
-
-    /**
-     * Receives BLE state change callbacks, registered on {@link CarTrustAgentBleService}
-     */
-    private final ICarTrustAgentBleCallback mBleConnectionCallback =
-            new ICarTrustAgentBleCallback.Stub() {
-        @Override
-        public void onBleServerStartSuccess() {
-            appendOutputText("Server started");
-        }
-
-        @Override
-        public void onBleServerStartFailure(int errorCode) {
-            appendOutputText("Server failed to start, error code: " + errorCode);
-        }
-
-        @Override
-        public void onBleDeviceConnected(BluetoothDevice device) {
-            mBluetoothDevice = device;
-            appendOutputText("Device connected: " + device.getName()
-                    + " address: " + device.getAddress());
-        }
-
-        @Override
-        public void onBleDeviceDisconnected(BluetoothDevice device) {
-            mBluetoothDevice = null;
-            appendOutputText("Device disconnected: " + device.getName()
-                    + " address: " + device.getAddress());
-        }
-    };
-
-    /**
-     * {@link CarTrustAgentBleService} will callback this when receives enrolment data.
-     *
-     * <p>Here is the place we can prompt to the user on HU whether or not to add this
-     * {@link #mBluetoothDevice} as a trust device.
-     */
-    private final ICarTrustAgentEnrolmentCallback mEnrolmentCallback =
-            new ICarTrustAgentEnrolmentCallback.Stub() {
-        @Override
-        public void onEnrolmentDataReceived(byte[] token) {
-            appendOutputText("Enrolment data received ");
-            try {
-                addEscrowToken(token);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error addEscrowToken", e);
-            }
-        }
-    };
-
-    /**
-     * Service connection to {@link CarTrustAgentBleService}
-     */
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            mCarTrustAgentBleServiceBound = true;
-            mCarTrustAgentBleService = ICarTrustAgentBleService.Stub.asInterface(service);
-            try {
-                mCarTrustAgentBleService.registerBleCallback(mBleConnectionCallback);
-                mCarTrustAgentBleService.registerEnrolmentCallback(mEnrolmentCallback);
-                mCarTrustAgentBleService.setTokenResponseCallback(
-                        mCarTrustAgentTokenResponseCallback);
-                mCarTrustAgentBleService.startEnrolmentAdvertising();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error startEnrolmentAdvertising", e);
-            }
-            checkTokenHandle();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (mCarTrustAgentBleService != null) {
-                try {
-                    mCarTrustAgentBleService.unregisterBleCallback(mBleConnectionCallback);
-                    mCarTrustAgentBleService.unregisterEnrolmentCallback(mEnrolmentCallback);
-                    mCarTrustAgentBleService.setTokenResponseCallback(null);
-                    mCarTrustAgentBleService.stopEnrolmentAdvertising();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error unregister callbacks", e);
-                }
-                mCarTrustAgentBleService = null;
-            }
-            mCarTrustAgentBleServiceBound = false;
-        }
-    };
-
-    /** Adapter that displays the output of TrustAgent connections. */
-    private static class OutputAdapter extends RecyclerView.Adapter<OutputAdapter.ViewHolder> {
-        private final List<String> mOutput = new ArrayList<>();
-
-        /** Adds the given string as output to be displayed. */
-        public void addOutput(String output) {
-            mOutput.add(output);
-            notifyItemInserted(mOutput.size() - 1);
-        }
-
-        @Override
-        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-            return new ViewHolder(LayoutInflater.from(parent.getContext())
-                    .inflate(R.layout.output_line, parent, false));
-        }
-
-        @Override
-        public void onBindViewHolder(ViewHolder holder, int position) {
-            holder.mTextView.setText(mOutput.get(position));
-        }
-
-        @Override
-        public int getItemCount() {
-            return mOutput.size();
-        }
-
-        /** ViewHolder for {@link OutputAdapter}. */
-        public static class ViewHolder extends RecyclerView.ViewHolder {
-            private final TextView mTextView;
-
-            ViewHolder(View itemView) {
-                super(itemView);
-                mTextView = itemView.findViewById(R.id.output);
-            }
-        }
-    }
-}
diff --git a/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java b/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java
deleted file mode 100644
index d70fd2f..0000000
--- a/TrustAgent/src/com/android/car/trust/CarTrustAgentBleService.java
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-package com.android.car.trust;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseSettings;
-import android.car.trust.ICarTrustAgentBleCallback;
-import android.car.trust.ICarTrustAgentBleService;
-import android.car.trust.ICarTrustAgentEnrolmentCallback;
-import android.car.trust.ICarTrustAgentTokenRequestDelegate;
-import android.car.trust.ICarTrustAgentTokenResponseCallback;
-import android.car.trust.ICarTrustAgentUnlockCallback;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.IBinder;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-import java.text.DateFormat;
-import java.util.UUID;
-
-/**
- * Abstracts enrolment and unlock token exchange via BluetoothLE (BLE).
- * {@link CarBleTrustAgent} and any enrolment client should bind to
- * {@link ICarTrustAgentBleService}.
- */
-public class CarTrustAgentBleService extends SimpleBleServer {
-    private static final String TAG = CarTrustAgentBleService.class.getSimpleName();
-
-    private RemoteCallbackList<ICarTrustAgentBleCallback> mBleCallbacks;
-    private RemoteCallbackList<ICarTrustAgentEnrolmentCallback> mEnrolmentCallbacks;
-    private RemoteCallbackList<ICarTrustAgentUnlockCallback> mUnlockCallbacks;
-    private ICarTrustAgentTokenRequestDelegate mTokenRequestDelegate;
-    private ICarTrustAgentTokenResponseCallback mTokenResponseCallback;
-    private CarTrustAgentBleWrapper mCarTrustBleService;
-
-    private UUID mEnrolmentEscrowTokenUuid;
-    private UUID mEnrolmentTokenHandleUuid;
-    private BluetoothGattService mEnrolmentGattService;
-
-    private UUID mUnlockEscrowTokenUuid;
-    private UUID mUnlockTokenHandleUuid;
-    private BluetoothGattService mUnlockGattService;
-
-    private byte[] mCurrentUnlockToken;
-    private Long mCurrentUnlockHandle;
-
-    private SharedPreferences mTokenHandleSharedPreferences;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mBleCallbacks = new RemoteCallbackList<>();
-        mEnrolmentCallbacks = new RemoteCallbackList<>();
-        mUnlockCallbacks = new RemoteCallbackList<>();
-        mCarTrustBleService = new CarTrustAgentBleWrapper();
-
-        mEnrolmentEscrowTokenUuid = UUID.fromString(getString(R.string.enrollment_token_uuid));
-        mEnrolmentTokenHandleUuid = UUID.fromString(getString(R.string.enrollment_handle_uuid));
-        mUnlockEscrowTokenUuid = UUID.fromString(getString(R.string.unlock_escrow_token_uiid));
-        mUnlockTokenHandleUuid = UUID.fromString(getString(R.string.unlock_handle_uiid));
-
-        setupEnrolmentBleServer();
-        setupUnlockBleServer();
-
-        mTokenHandleSharedPreferences = getSharedPreferences(
-                getString(R.string.token_handle_shared_preferences),
-                MODE_PRIVATE);
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mCarTrustBleService;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        // keep it alive.
-        return START_STICKY;
-    }
-
-    @Override
-    public void onCharacteristicWrite(final BluetoothDevice device, int requestId,
-            BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
-            responseNeeded, int offset, byte[] value) {
-        UUID uuid = characteristic.getUuid();
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "onCharacteristicWrite received uuid: " + uuid);
-        }
-
-        if (uuid.equals(mEnrolmentEscrowTokenUuid)) {
-            final int callbackCount = mEnrolmentCallbacks.beginBroadcast();
-            for (int i = 0; i < callbackCount; i++) {
-                try {
-                    mEnrolmentCallbacks.getBroadcastItem(i).onEnrolmentDataReceived(value);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error callback onEnrolmentDataReceived", e);
-                }
-            }
-            mEnrolmentCallbacks.finishBroadcast();
-        } else if (uuid.equals(mUnlockEscrowTokenUuid)) {
-            mCurrentUnlockToken = value;
-            maybeSendUnlockToken();
-        } else if (uuid.equals(mUnlockTokenHandleUuid)) {
-            mCurrentUnlockHandle = getLong(value);
-            maybeSendUnlockToken();
-        }
-    }
-
-    @Override
-    public void onCharacteristicRead(BluetoothDevice device,
-            int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
-        // Ignored read requests.
-    }
-
-    @Override
-    protected void onAdvertiseStartSuccess() {
-        final int callbackCount = mBleCallbacks.beginBroadcast();
-        for (int i = 0; i < callbackCount; i++) {
-            try {
-                mBleCallbacks.getBroadcastItem(i).onBleServerStartSuccess();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onBleServerStartSuccess", e);
-            }
-        }
-        mBleCallbacks.finishBroadcast();
-    }
-
-    @Override
-    protected void onAdvertiseStartFailure(int errorCode) {
-        final int callbackCount = mBleCallbacks.beginBroadcast();
-        for (int i = 0; i < callbackCount; i++) {
-            try {
-                mBleCallbacks.getBroadcastItem(i).onBleServerStartFailure(errorCode);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onBleServerStartFailure", e);
-            }
-        }
-        mBleCallbacks.finishBroadcast();
-    }
-
-    @Override
-    protected void onAdvertiseDeviceConnected(BluetoothDevice device) {
-        final int callbackCount = mBleCallbacks.beginBroadcast();
-        for (int i = 0; i < callbackCount; i++) {
-            try {
-                mBleCallbacks.getBroadcastItem(i).onBleDeviceConnected(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onBleDeviceConnected", e);
-            }
-        }
-        mBleCallbacks.finishBroadcast();
-    }
-
-    @Override
-    protected void onAdvertiseDeviceDisconnected(BluetoothDevice device) {
-        final int callbackCount = mBleCallbacks.beginBroadcast();
-        for (int i = 0; i < callbackCount; i++) {
-            try {
-                mBleCallbacks.getBroadcastItem(i).onBleDeviceDisconnected(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onBleDeviceDisconnected", e);
-            }
-        }
-        mBleCallbacks.finishBroadcast();
-    }
-
-    @Override
-    public void onDestroy() {
-        stopAdvertising(mEnrolmentAdvertisingCallback);
-        stopAdvertising(mUnlockAdvertisingCallback);
-        super.onDestroy();
-    }
-
-    private void setupEnrolmentBleServer() {
-        mEnrolmentGattService = new BluetoothGattService(
-                UUID.fromString(getString(R.string.enrollment_service_uuid)),
-                BluetoothGattService.SERVICE_TYPE_PRIMARY);
-
-        // Characteristic to describe the escrow token being used for unlock
-        BluetoothGattCharacteristic enrolmentEscrowToken = new BluetoothGattCharacteristic(
-                mEnrolmentEscrowTokenUuid,
-                BluetoothGattCharacteristic.PROPERTY_WRITE,
-                BluetoothGattCharacteristic.PERMISSION_WRITE);
-
-        // Characteristic to describe the handle being used for this escrow token
-        BluetoothGattCharacteristic enrolmentTokenHandle = new BluetoothGattCharacteristic(
-                mEnrolmentTokenHandleUuid,
-                BluetoothGattCharacteristic.PROPERTY_NOTIFY,
-                BluetoothGattCharacteristic.PERMISSION_READ);
-
-        mEnrolmentGattService.addCharacteristic(enrolmentEscrowToken);
-        mEnrolmentGattService.addCharacteristic(enrolmentTokenHandle);
-    }
-
-    private void setupUnlockBleServer() {
-        mUnlockGattService = new BluetoothGattService(
-                UUID.fromString(getString(R.string.unlock_service_uuid)),
-                BluetoothGattService.SERVICE_TYPE_PRIMARY);
-
-        // Characteristic to describe the escrow token being used for unlock
-        BluetoothGattCharacteristic unlockEscrowToken = new BluetoothGattCharacteristic(
-                mUnlockEscrowTokenUuid,
-                BluetoothGattCharacteristic.PROPERTY_WRITE,
-                BluetoothGattCharacteristic.PERMISSION_WRITE);
-
-        // Characteristic to describe the handle being used for this escrow token
-        BluetoothGattCharacteristic unlockTokenHandle = new BluetoothGattCharacteristic(
-                mUnlockTokenHandleUuid,
-                BluetoothGattCharacteristic.PROPERTY_WRITE,
-                BluetoothGattCharacteristic.PERMISSION_WRITE);
-
-        mUnlockGattService.addCharacteristic(unlockEscrowToken);
-        mUnlockGattService.addCharacteristic(unlockTokenHandle);
-    }
-
-    private synchronized void maybeSendUnlockToken() {
-        if (mCurrentUnlockToken == null || mCurrentUnlockHandle == null) {
-            return;
-        }
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Handle and token both received, requesting unlock. Time: "
-                    + DateFormat.getDateInstance(DateFormat.SHORT).format(
-                            System.currentTimeMillis()));
-        }
-
-        int callbackCount = mUnlockCallbacks.beginBroadcast();
-        for (int i = 0; i < callbackCount; i++) {
-            try {
-                mUnlockCallbacks.getBroadcastItem(i).onUnlockDataReceived(
-                        mCurrentUnlockToken, mCurrentUnlockHandle);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error callback onUnlockDataReceived", e);
-            }
-        }
-        mUnlockCallbacks.finishBroadcast();
-        mCurrentUnlockHandle = null;
-        mCurrentUnlockToken = null;
-    }
-
-    private static byte[] getBytes(long primitive) {
-        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
-        buffer.putLong(0, primitive);
-        return buffer.array();
-    }
-
-    private static long getLong(byte[] bytes) {
-        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
-        buffer.put(bytes);
-        buffer.flip();
-        return buffer.getLong();
-    }
-
-    private final AdvertiseCallback mEnrolmentAdvertisingCallback = new AdvertiseCallback() {
-        @Override
-        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
-            super.onStartSuccess(settingsInEffect);
-            onAdvertiseStartSuccess();
-
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Successfully started advertising service");
-            }
-        }
-
-        @Override
-        public void onStartFailure(int errorCode) {
-            Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
-
-            super.onStartFailure(errorCode);
-            onAdvertiseStartFailure(errorCode);
-        }
-    };
-
-    private final AdvertiseCallback mUnlockAdvertisingCallback = new AdvertiseCallback() {
-        @Override
-        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
-            super.onStartSuccess(settingsInEffect);
-            onAdvertiseStartSuccess();
-
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Successfully started advertising service");
-            }
-        }
-
-        @Override
-        public void onStartFailure(int errorCode) {
-            Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
-
-            super.onStartFailure(errorCode);
-            onAdvertiseStartFailure(errorCode);
-        }
-    };
-
-    private final class CarTrustAgentBleWrapper extends ICarTrustAgentBleService.Stub {
-        @Override
-        public void registerBleCallback(ICarTrustAgentBleCallback callback) {
-            mBleCallbacks.register(callback);
-        }
-
-        @Override
-        public void unregisterBleCallback(ICarTrustAgentBleCallback callback) {
-            mBleCallbacks.unregister(callback);
-        }
-
-        @Override
-        public void startEnrolmentAdvertising() {
-            stopEnrolmentAdvertising();
-            stopUnlockAdvertising();
-
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "startEnrolmentAdvertising");
-            }
-            startAdvertising(mEnrolmentGattService, mEnrolmentAdvertisingCallback);
-        }
-
-        @Override
-        public void stopEnrolmentAdvertising() {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "stopEnrolmentAdvertising");
-            }
-
-            stopAdvertising(mEnrolmentAdvertisingCallback);
-        }
-
-        @Override
-        public void sendEnrolmentHandle(BluetoothDevice device, long handle) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "sendEnrolmentHandle: " + handle);
-            }
-            BluetoothGattCharacteristic enrolmentTokenHandle =
-                    mEnrolmentGattService.getCharacteristic(mEnrolmentTokenHandleUuid);
-            enrolmentTokenHandle.setValue(getBytes(handle));
-            notifyCharacteristicChanged(device, enrolmentTokenHandle, false);
-        }
-
-        @Override
-        public void registerEnrolmentCallback(ICarTrustAgentEnrolmentCallback callback) {
-            mEnrolmentCallbacks.register(callback);
-        }
-
-        @Override
-        public void unregisterEnrolmentCallback(ICarTrustAgentEnrolmentCallback callback) {
-            mEnrolmentCallbacks.unregister(callback);
-        }
-
-        @Override
-        public void startUnlockAdvertising() {
-            stopUnlockAdvertising();
-            stopEnrolmentAdvertising();
-
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "startUnlockAdvertising");
-            }
-            startAdvertising(mUnlockGattService, mUnlockAdvertisingCallback);
-        }
-
-        @Override
-        public void stopUnlockAdvertising() {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "stopUnlockAdvertising");
-            }
-            stopAdvertising(mUnlockAdvertisingCallback);
-        }
-
-        @Override
-        public void registerUnlockCallback(ICarTrustAgentUnlockCallback callback) {
-            mUnlockCallbacks.register(callback);
-        }
-
-        @Override
-        public void unregisterUnlockCallback(ICarTrustAgentUnlockCallback callback) {
-            mUnlockCallbacks.unregister(callback);
-        }
-
-        @Override
-        public void setTokenRequestDelegate(ICarTrustAgentTokenRequestDelegate delegate) {
-            mTokenRequestDelegate = delegate;
-        }
-
-        @Override
-        public void revokeTrust() throws RemoteException {
-            if (mTokenRequestDelegate != null) {
-                mTokenRequestDelegate.revokeTrust();
-            }
-        }
-
-        @Override
-        public void addEscrowToken(byte[] token, int uid) throws RemoteException {
-            if (mTokenRequestDelegate != null) {
-                mTokenRequestDelegate.addEscrowToken(token, uid);
-            }
-        }
-
-        @Override
-        public void removeEscrowToken(long handle, int uid) throws RemoteException {
-            if (mTokenRequestDelegate != null) {
-                mTokenRequestDelegate.removeEscrowToken(handle, uid);
-            }
-        }
-
-        @Override
-        public void isEscrowTokenActive(long handle, int uid) throws RemoteException {
-            if (mTokenRequestDelegate != null) {
-                mTokenRequestDelegate.isEscrowTokenActive(handle, uid);
-            }
-        }
-
-        @Override
-        public void setTokenResponseCallback(ICarTrustAgentTokenResponseCallback callback) {
-            mTokenResponseCallback = callback;
-        }
-
-        @Override
-        public void onEscrowTokenAdded(byte[] token, long handle, int uid)
-                throws RemoteException {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onEscrowTokenAdded handle:" + handle + " uid:" + uid);
-            }
-
-            mTokenHandleSharedPreferences.edit()
-                    .putInt(String.valueOf(handle), uid)
-                    .apply();
-            if (mTokenResponseCallback != null) {
-                mTokenResponseCallback.onEscrowTokenAdded(token, handle, uid);
-            }
-        }
-
-        @Override
-        public void onEscrowTokenRemoved(long handle, boolean successful) throws RemoteException {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onEscrowTokenRemoved handle:" + handle);
-            }
-
-            mTokenHandleSharedPreferences.edit()
-                    .remove(String.valueOf(handle))
-                    .apply();
-            if (mTokenResponseCallback != null) {
-                mTokenResponseCallback.onEscrowTokenRemoved(handle, successful);
-            }
-        }
-
-        @Override
-        public void onEscrowTokenActiveStateChanged(long handle, boolean active)
-                throws RemoteException {
-            if (mTokenResponseCallback != null) {
-                mTokenResponseCallback.onEscrowTokenActiveStateChanged(handle, active);
-            }
-        }
-
-        @Override
-        public int getUserIdByEscrowTokenHandle(long tokenHandle) {
-            return mTokenHandleSharedPreferences.getInt(String.valueOf(tokenHandle), -1);
-        }
-    }
-}
diff --git a/TrustAgent/src/com/android/car/trust/SimpleBleServer.java b/TrustAgent/src/com/android/car/trust/SimpleBleServer.java
deleted file mode 100644
index 260f5ac..0000000
--- a/TrustAgent/src/com/android/car/trust/SimpleBleServer.java
+++ /dev/null
@@ -1,225 +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 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
- */
-public abstract class SimpleBleServer extends Service {
-    private static final String TAG = SimpleBleServer.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, final 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/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl
deleted file mode 100644
index 9f28db9..0000000
--- a/car-lib/src/android/car/trust/ICarTrustAgentBleCallback.aidl
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.car.trust;
-
-import android.bluetooth.BluetoothDevice;
-
-/**
- * Callback interface for BLE connection state changes.
- *
- * @hide
- */
-oneway interface ICarTrustAgentBleCallback {
-    /**
-     * Called when the GATT server is started and BLE is successfully advertising.
-     */
-    void onBleServerStartSuccess();
-
-    /**
-     * Called when the BLE advertisement fails to start.
-     * see AdvertiseCallback#ADVERTISE_FAILED_* for possible error codes.
-     */
-    void onBleServerStartFailure(int errorCode);
-
-    /**
-     * Called when a device is connected.
-     */
-    void onBleDeviceConnected(in BluetoothDevice device);
-
-    /**
-     * Called when a device is disconnected.
-     */
-    void onBleDeviceDisconnected(in BluetoothDevice device);
-}
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl b/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl
deleted file mode 100644
index 4dec6a0..0000000
--- a/car-lib/src/android/car/trust/ICarTrustAgentBleService.aidl
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.car.trust;
-
-import android.bluetooth.BluetoothDevice;
-import android.car.trust.ICarTrustAgentBleCallback;
-import android.car.trust.ICarTrustAgentEnrolmentCallback;
-import android.car.trust.ICarTrustAgentTokenRequestDelegate;
-import android.car.trust.ICarTrustAgentTokenResponseCallback;
-import android.car.trust.ICarTrustAgentUnlockCallback;
-
-/**
- * Binder interface for CarTrustAgentBleService.
- *
- * @hide
- */
-interface ICarTrustAgentBleService {
-    /** General BLE */
-    void registerBleCallback(in ICarTrustAgentBleCallback callback);
-    void unregisterBleCallback(in ICarTrustAgentBleCallback callback);
-
-    /** Enrolment */
-    void startEnrolmentAdvertising();
-    void stopEnrolmentAdvertising();
-    void sendEnrolmentHandle(in BluetoothDevice device, long handle);
-    void registerEnrolmentCallback(in ICarTrustAgentEnrolmentCallback callback);
-    void unregisterEnrolmentCallback(in ICarTrustAgentEnrolmentCallback callback);
-
-    /** Unlock */
-    void startUnlockAdvertising();
-    void stopUnlockAdvertising();
-    void registerUnlockCallback(in ICarTrustAgentUnlockCallback callback);
-    void unregisterUnlockCallback(in ICarTrustAgentUnlockCallback callback);
-
-    /** Token request */
-    void setTokenRequestDelegate(in ICarTrustAgentTokenRequestDelegate delegate);
-    void revokeTrust();
-    void addEscrowToken(in byte[] token, int uid);
-    void removeEscrowToken(long handle, int uid);
-    void isEscrowTokenActive(long handle, int uid);
-
-    /** Token response */
-    void setTokenResponseCallback(in ICarTrustAgentTokenResponseCallback callback);
-    void onEscrowTokenAdded(in byte[] token, long handle, int uid);
-    void onEscrowTokenRemoved(long handle, boolean successful);
-    void onEscrowTokenActiveStateChanged(long handle, boolean active);
-
-    /** Management */
-    int getUserIdByEscrowTokenHandle(long tokenHandle);
-}
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrolmentCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrolmentCallback.aidl
deleted file mode 100644
index 7f12404..0000000
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrolmentCallback.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.car.trust;
-
-/**
- * Callback interface for CarTrustAgentService enrolment.
- *
- * @hide
- */
-oneway interface ICarTrustAgentEnrolmentCallback {
-    /** Called when the enrolment data is received */
-    void onEnrolmentDataReceived(in byte[] token);
-}
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentTokenRequestDelegate.aidl b/car-lib/src/android/car/trust/ICarTrustAgentTokenRequestDelegate.aidl
deleted file mode 100644
index 3c61d4b..0000000
--- a/car-lib/src/android/car/trust/ICarTrustAgentTokenRequestDelegate.aidl
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.car.trust;
-
-/**
- * Delegate interface for escrow token exchange request.
- *
- * TrustAgentService uses async pattern and we therefore divide the
- * request and response into two separate AIDL interfaces.
- *
- * CarBleTrustAgent would implement this interface and set itself as a delegate.
- *
- * @hide
- */
-interface ICarTrustAgentTokenRequestDelegate {
-    /** Called to revoke trust */
-    void revokeTrust();
-
-    /** Called to add escrow token for foreground user */
-    void addEscrowToken(in byte[] token, int uid);
-
-    /** Called to remove escrow token for foreground user */
-    void removeEscrowToken(long handle, int uid);
-
-    /** Called to query if the foreground user has active escrow token */
-    void isEscrowTokenActive(long handle, int uid);
-}
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentTokenResponseCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentTokenResponseCallback.aidl
deleted file mode 100644
index 5dbc3bc..0000000
--- a/car-lib/src/android/car/trust/ICarTrustAgentTokenResponseCallback.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.car.trust;
-
-/**
- * Callback interface for escrow token exchange response.
- *
- * TrustAgentService uses async pattern and we therefore divide the
- * request and response into two separate AIDL interfaces.
- *
- * @hide
- */
-interface ICarTrustAgentTokenResponseCallback {
-    /** Called after escrow token is added for foreground user */
-    void onEscrowTokenAdded(out byte[] token, long handle, int uid);
-
-    /** Called after escrow token is removed for foreground user */
-    void onEscrowTokenRemoved(long handle, boolean successful);
-
-    /** Called after escrow token active state is changed for foreground user */
-    void onEscrowTokenActiveStateChanged(long handle, boolean active);
-}
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentUnlockCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentUnlockCallback.aidl
deleted file mode 100644
index e280bf0..0000000
--- a/car-lib/src/android/car/trust/ICarTrustAgentUnlockCallback.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.car.trust;
-
-/**
- * Callback interface for CarTrustAgentService unlock.
- *
- * @hide
- */
-oneway interface ICarTrustAgentUnlockCallback {
-    /** Called when the unlock token is received */
-    void onUnlockDataReceived(in byte[] token, long handle);
-}
diff --git a/car-usb-handler/res/values/strings.xml b/car-usb-handler/res/values/strings.xml
index be1e7a2..cb59e27 100644
--- a/car-usb-handler/res/values/strings.xml
+++ b/car-usb-handler/res/values/strings.xml
@@ -17,9 +17,6 @@
     <string name="app_name">USB Handler</string>
 
     <!-- USB Manager Settings -->
-    <string name="usb_title">USB Devices settings</string>
-    <string name="usb_description">Customize your USB Devices settings</string>
-    <string name="usb_available_devices">Connected devices</string>
     <string name="usb_saved_devices">Saved devices</string>
     <string name="usb_pref_delete_title">Remove handling app for USB device</string>
     <string name="usb_pref_delete_message">Are you sure you wan to delete dafault handling app for %1$s?</string>
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index c2161bc..f4a67c1 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -86,7 +86,6 @@
 # Automotive specific packages
 PRODUCT_PACKAGES += \
     CarService \
-    CarTrustAgentService \
     CarDialerApp \
     CarRadioApp \
     OverviewApp \
@@ -111,9 +110,8 @@
 PRODUCT_SYSTEM_SERVER_JARS += car-frameworks-service
 
 # Boot animation
-# Use the G boot animation zip file for faster boot
 PRODUCT_COPY_FILES += \
-    vendor/google/products/sailfish/bootanimation-1080-256col.zip:system/media/bootanimation.zip
+    packages/services/Car/car_product/bootanimations/bootanimation-832.zip:system/media/bootanimation.zip
 
 PRODUCT_LOCALES := en_US af_ZA am_ET ar_EG bg_BG bn_BD ca_ES cs_CZ da_DK de_DE el_GR en_AU en_GB en_IN es_ES es_US et_EE eu_ES fa_IR fi_FI fr_CA fr_FR gl_ES hi_IN hr_HR hu_HU hy_AM in_ID is_IS it_IT iw_IL ja_JP ka_GE km_KH ko_KR ky_KG lo_LA lt_LT lv_LV km_MH kn_IN mn_MN ml_IN mk_MK mr_IN ms_MY my_MM ne_NP nb_NO nl_NL pl_PL pt_BR pt_PT ro_RO ru_RU si_LK sk_SK sl_SI sr_RS sv_SE sw_TZ ta_IN te_IN th_TH tl_PH tr_TR uk_UA vi_VN zh_CN zh_HK zh_TW zu_ZA en_XA ar_XB
 
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
index c0c979c..32783b9 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
@@ -94,4 +94,8 @@
         <item name="thumb">@drawable/car_seekbar_thumb</item>
     </style>
 
+    <style name="Widget.DeviceDefault.ActionBar.Solid" parent="Widget.Material.ActionBar.Solid">
+        <item name="textSize">@dimen/car_body3_size</item>
+    </style>
+
 </resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml b/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml
index 228079e..23a56b5 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/themes_device_defaults.xml
@@ -41,6 +41,7 @@
         <item name="textAppearanceListItem">@style/TextAppearance.DeviceDefault.Large</item>
         <item name="textAppearanceListItemSmall">@style/TextAppearance.DeviceDefault.Large</item>
         <item name="textAppearanceListItemSecondary">@style/TextAppearance.DeviceDefault.Small</item>
+        <item name="actionBarSize">@dimen/car_app_bar_height</item>
     </style>
     <style name="Theme.DeviceDefault.Dialog" parent="Theme.Material.Dialog">
         <item name="textAppearanceLarge">@style/TextAppearance.DeviceDefault.Large</item>
@@ -94,6 +95,10 @@
         <item name="textAppearanceListItemSmall">@style/TextAppearance.DeviceDefault.Large</item>
         <item name="textAppearanceListItemSecondary">@style/TextAppearance.DeviceDefault.Small</item>
     </style>
+
+    <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.DeviceDefault.Dialog.Alert">
+    </style>
+
     <!-- The light theme is defined to be the same as the default since currently there is only one
         defined theme palette -->
     <style name="Theme.DeviceDefault.Light"/>
diff --git a/tests/DirectRenderingClusterSample/AndroidManifest.xml b/tests/DirectRenderingClusterSample/AndroidManifest.xml
index 4a44d73..660f460 100644
--- a/tests/DirectRenderingClusterSample/AndroidManifest.xml
+++ b/tests/DirectRenderingClusterSample/AndroidManifest.xml
@@ -41,6 +41,11 @@
     <uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
     <!-- Required to watch activities running on the cluster -->
     <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
+    <!-- Required to show car sensor data -->
+    <uses-permission android:name="android.car.permission.CAR_ENERGY"/>
+    <uses-permission android:name="android.car.permission.CAR_POWERTRAIN"/>
+    <uses-permission android:name="android.car.permission.CAR_INFO"/>
+    <uses-permission android:name="android.car.permission.CAR_SPEED"/>
 
     <application android:label="@string/app_name"
                  android:icon="@mipmap/ic_launcher"
diff --git a/tests/DirectRenderingClusterSample/res/color/icon_color.xml b/tests/DirectRenderingClusterSample/res/color/icon_color.xml
new file mode 100644
index 0000000..1ce35ee
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/res/color/icon_color.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@color/icon_selected"
+        android:state_selected="true"/>
+    <item
+        android:color="@color/icon_selected"
+        android:state_focused="true"/>
+    <item
+        android:color="@color/icon_unselected"/>
+</selector>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/drawable/btn_car_info.xml b/tests/DirectRenderingClusterSample/res/drawable/btn_car_info.xml
deleted file mode 100644
index 437ad41..0000000
--- a/tests/DirectRenderingClusterSample/res/drawable/btn_car_info.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" >
-        <layer-list>
-            <item android:drawable="@drawable/ic_car_info"/>
-            <item android:drawable="@drawable/focused_button_shape"/>
-        </layer-list>
-    </item>
-    <item android:drawable="@drawable/ic_car_info"/>
-</selector>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/drawable/btn_music.xml b/tests/DirectRenderingClusterSample/res/drawable/btn_music.xml
deleted file mode 100644
index 65e01b6..0000000
--- a/tests/DirectRenderingClusterSample/res/drawable/btn_music.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" >
-        <layer-list>
-            <item android:drawable="@drawable/ic_music"/>
-            <item android:drawable="@drawable/focused_button_shape"/>
-        </layer-list>
-    </item>
-    <item android:drawable="@drawable/ic_music"/>
-</selector>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/drawable/btn_nav.xml b/tests/DirectRenderingClusterSample/res/drawable/btn_nav.xml
deleted file mode 100644
index 9f9c6bc..0000000
--- a/tests/DirectRenderingClusterSample/res/drawable/btn_nav.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" >
-        <layer-list>
-            <item android:drawable="@drawable/ic_nav"/>
-            <item android:drawable="@drawable/focused_button_shape"/>
-        </layer-list>
-    </item>
-    <item android:drawable="@drawable/ic_nav"/>
-</selector>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/drawable/btn_phone.xml b/tests/DirectRenderingClusterSample/res/drawable/btn_phone.xml
deleted file mode 100644
index 2a6e249..0000000
--- a/tests/DirectRenderingClusterSample/res/drawable/btn_phone.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" >
-        <layer-list>
-            <item android:drawable="@drawable/ic_phone"/>
-            <item android:drawable="@drawable/focused_button_shape"/>
-        </layer-list>
-    </item>
-    <item android:drawable="@drawable/ic_phone"/>
-</selector>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/drawable/speedometer.xml b/tests/DirectRenderingClusterSample/res/drawable/speedometer.xml
new file mode 100644
index 0000000..078a4b6
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/res/drawable/speedometer.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="200dp"
+    android:height="200dp"
+    android:viewportHeight="64"
+    android:viewportWidth="64">
+
+    <path
+        android:pathData="M0,32
+        A32,32 0 1,1 64,32
+        A32,32 0 1,1 0,32 Z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:centerX="32"
+                android:centerY="32"
+                android:gradientRadius="32"
+                android:type="radial">
+                <item
+                    android:color="#FF000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#FF000000"
+                    android:offset="0.94" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+
+    <path
+        android:fillColor="#000"
+        android:strokeWidth="0.25"
+        android:pathData="M2,32
+        A30,30 0 1,1 62,32
+        A30,30 0 1,1 2,32 Z">
+        <aapt:attr name="android:strokeColor">
+            <gradient
+                android:startX="0"
+                android:startY="10"
+                android:startColor="#000"
+                android:endX="0"
+                android:endY="150"
+                android:endColor="#000"
+                android:centerColor="#DDD"
+                android:type="linear"/>
+        </aapt:attr>
+    </path>
+
+</vector>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/layout/activity_main.xml b/tests/DirectRenderingClusterSample/res/layout/activity_main.xml
index 2cb9662..fa8db14 100644
--- a/tests/DirectRenderingClusterSample/res/layout/activity_main.xml
+++ b/tests/DirectRenderingClusterSample/res/layout/activity_main.xml
@@ -1,80 +1,237 @@
 <?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/activity_main"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@android:color/background_dark"
-    tools:context=".MainClusterActivity"
-    android:windowIsFloating="true">
+    android:windowIsFloating="true"
+    tools:context=".MainClusterActivity">
+
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/left_unobscured"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/speedometer_overlap_width"/>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/right_unobscured"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/speedometer_overlap_width"/>
+
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/gauges_top"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/speedometer_top"/>
+
+    <androidx.viewpager.widget.ViewPager
+        android:id="@+id/pager"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/info"/>
 
     <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-        <androidx.viewpager.widget.ViewPager
-            xmlns:android="http://schemas.android.com/apk/res/android"
-            android:id="@+id/pager"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            />
+        android:id="@+id/info"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/info_height"
+        app:layout_constraintLeft_toRightOf="@+id/left_unobscured"
+        app:layout_constraintRight_toLeftOf="@+id/right_unobscured"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:gravity="center">
 
         <LinearLayout
-            android:layout_gravity="center"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:orientation="horizontal">
+            android:orientation="vertical"
+            android:gravity="start">
 
-            <Button
-                android:id="@+id/btn_nav"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:background="@drawable/btn_nav"
-                android:layout_margin="10dp"
-                android:focusableInTouchMode="true" />
-            <Button
-                android:id="@+id/btn_phone"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_margin="10dp"
-                android:background="@drawable/btn_phone"
-                android:focusableInTouchMode="true" />
-            <Button
-                android:id="@+id/btn_music"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_margin="10dp"
-                android:background="@drawable/btn_music"
-                android:focusableInTouchMode="true" />
-            <Button
-                android:id="@+id/btn_car_info"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_margin="10dp"
-                android:background="@drawable/btn_car_info"
-                android:focusableInTouchMode="true" />
-
-            <Space
-                android:layout_width="20dp"
-                android:layout_height="1dp" />
-
-            <include
-                android:id="@+id/navigation_state"
-                layout="@layout/include_navigation_state"
+            <TextView
+                android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"/>
+                android:includeFontPadding="false"
+                android:text="@string/info_fuel_label"
+                android:textSize="@dimen/info_label_text_size"/>
 
+            <TextView
+                android:id="@+id/info_fuel"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_value_empty"
+                android:textSize="@dimen/info_value_text_size"/>
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_range_label"
+                android:textSize="@dimen/info_label_text_size"/>
+
+            <TextView
+                android:id="@+id/info_range"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_value_empty"
+                android:textSize="@dimen/info_value_text_size"/>
+        </LinearLayout>
+
+        <include
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:id="@+id/navigation_state"
+            layout="@layout/include_navigation_state"/>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="end">
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_speed_label"
+                android:textSize="@dimen/info_label_text_size"/>
+
+            <TextView
+                android:id="@+id/info_speed"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_value_empty"
+                android:textSize="@dimen/info_value_text_size"/>
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_rpm_label"
+                android:textSize="@dimen/info_label_text_size"/>
+
+            <TextView
+                android:id="@+id/info_rpm"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:includeFontPadding="false"
+                android:text="@string/info_value_empty"
+                android:textSize="@dimen/info_value_text_size"/>
         </LinearLayout>
     </LinearLayout>
 
-    <TextView
-            android:id="@+id/text_overlay"
-            android:layout_width="wrap_content"
+    <ImageView
+        android:id="@+id/left_gauge"
+        android:layout_width="@dimen/speedometer_width"
+        android:layout_height="@dimen/speedometer_height"
+        android:src="@drawable/speedometer"
+        android:elevation="2dp"
+        app:layout_constraintTop_toBottomOf="@+id/gauges_top"
+        app:layout_constraintRight_toLeftOf="@+id/left_unobscured"/>
+
+    <ImageView
+        android:id="@+id/right_gauge"
+        android:layout_width="@dimen/speedometer_width"
+        android:layout_height="@dimen/speedometer_height"
+        android:src="@drawable/speedometer"
+        android:elevation="2dp"
+        app:layout_constraintTop_toBottomOf="@+id/gauges_top"
+        app:layout_constraintLeft_toRightOf="@+id/right_unobscured"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:translationZ="4dp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <Button
+            android:id="@+id/btn_nav"
+            android:layout_width="@dimen/facet_icon_size"
+            android:layout_height="@dimen/facet_icon_size"
+            android:layout_margin="@dimen/facet_icon_margin"
+            android:background="@drawable/ic_nav"
+            android:backgroundTint="@color/icon_color"
+            android:focusableInTouchMode="true" />
+        <Button
+            android:id="@+id/btn_phone"
+            android:layout_width="@dimen/facet_icon_size"
+            android:layout_height="@dimen/facet_icon_size"
+            android:layout_margin="@dimen/facet_icon_margin"
+            android:background="@drawable/ic_phone"
+            android:backgroundTint="@color/icon_color"
+            android:focusableInTouchMode="true" />
+        <Button
+            android:id="@+id/btn_music"
+            android:layout_width="@dimen/facet_icon_size"
+            android:layout_height="@dimen/facet_icon_size"
+            android:layout_margin="@dimen/facet_icon_margin"
+            android:background="@drawable/ic_music"
+            android:backgroundTint="@color/icon_color"
+            android:focusableInTouchMode="true" />
+        <Button
+            android:id="@+id/btn_car_info"
+            android:layout_width="@dimen/facet_icon_size"
+            android:layout_height="@dimen/facet_icon_size"
+            android:layout_margin="@dimen/facet_icon_margin"
+            android:background="@drawable/ic_car_info"
+            android:backgroundTint="@color/icon_color"
+            android:focusableInTouchMode="true" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:translationZ="4dp"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <TextView
+            android:id="@+id/gear_parked"
             android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            android:background="@android:color/background_light"
-            android:textSize="30sp"
-    />
-</RelativeLayout>
+            android:layout_width="wrap_content"
+            android:layout_marginHorizontal="@dimen/gear_icon_margin"
+            android:text="@string/gear_parked"
+            android:textColor="@color/icon_color"
+            android:textSize="@dimen/gear_text_size"/>
+
+        <TextView
+            android:id="@+id/gear_reverse"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginHorizontal="@dimen/gear_icon_margin"
+            android:text="@string/gear_reverse"
+            android:textColor="@color/icon_color"
+            android:textSize="@dimen/gear_text_size"/>
+
+        <TextView
+            android:id="@+id/gear_neutral"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginHorizontal="@dimen/gear_icon_margin"
+            android:text="@string/gear_neutral"
+            android:textColor="@color/icon_color"
+            android:textSize="@dimen/gear_text_size"/>
+
+        <TextView
+            android:id="@+id/gear_drive"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginHorizontal="@dimen/gear_icon_margin"
+            android:text="@string/gear_drive"
+            android:textColor="@color/icon_color"
+            android:textSize="@dimen/gear_text_size"/>
+
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/DirectRenderingClusterSample/res/layout/fragment_navigation.xml b/tests/DirectRenderingClusterSample/res/layout/fragment_navigation.xml
index 865a3b5..54894c2 100644
--- a/tests/DirectRenderingClusterSample/res/layout/fragment_navigation.xml
+++ b/tests/DirectRenderingClusterSample/res/layout/fragment_navigation.xml
@@ -34,13 +34,13 @@
 
     <ImageView
         android:layout_width="match_parent"
-        android:layout_height="30dp"
+        android:layout_height="@dimen/navigation_gradient_height"
         android:src="@drawable/gradient_top"
         app:layout_constraintTop_toTopOf="parent"/>
 
     <ImageView
         android:layout_width="match_parent"
-        android:layout_height="30dp"
+        android:layout_height="@dimen/navigation_gradient_height"
         android:src="@drawable/gradient_bottom"
         app:layout_constraintBottom_toBottomOf="parent"/>
 
diff --git a/tests/DirectRenderingClusterSample/res/layout/include_navigation_state.xml b/tests/DirectRenderingClusterSample/res/layout/include_navigation_state.xml
index 1946f0c..e566363 100644
--- a/tests/DirectRenderingClusterSample/res/layout/include_navigation_state.xml
+++ b/tests/DirectRenderingClusterSample/res/layout/include_navigation_state.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
     android:orientation="horizontal">
 
     <ImageView
diff --git a/tests/DirectRenderingClusterSample/res/values/colors.xml b/tests/DirectRenderingClusterSample/res/values/colors.xml
index a71c0d5..6798b91 100644
--- a/tests/DirectRenderingClusterSample/res/values/colors.xml
+++ b/tests/DirectRenderingClusterSample/res/values/colors.xml
@@ -4,4 +4,9 @@
     <color name="colorPrimaryDark">#303F9F</color>
     <color name="colorAccent">#FF4081</color>
     <color name="darkBlue">#2b2b77</color>
+
+    <!-- Gear and facet icon colors -->
+    <color name="icon_selected">#6EDDFF</color>
+    <color name="icon_unselected">#1B378A</color>
+
 </resources>
diff --git a/tests/DirectRenderingClusterSample/res/values/dimens.xml b/tests/DirectRenderingClusterSample/res/values/dimens.xml
index 47c8224..843da89 100644
--- a/tests/DirectRenderingClusterSample/res/values/dimens.xml
+++ b/tests/DirectRenderingClusterSample/res/values/dimens.xml
@@ -2,4 +2,21 @@
     <!-- Default screen margins, per the Android Design guidelines. -->
     <dimen name="activity_horizontal_margin">16dp</dimen>
     <dimen name="activity_vertical_margin">16dp</dimen>
+    <!-- Size and position of speedometers -->
+    <dimen name="speedometer_height">600dp</dimen>
+    <dimen name="speedometer_width">600dp</dimen>
+    <dimen name="speedometer_overlap_width">90dp</dimen>
+    <dimen name="speedometer_top">20dp</dimen>
+    <!-- Navigation fragment gradients -->
+    <dimen name="navigation_gradient_height">15dp</dimen>
+    <!-- Facet buttons -->
+    <dimen name="facet_icon_size">30dp</dimen>
+    <dimen name="facet_icon_margin">5dp</dimen>
+    <!-- Gears -->
+    <dimen name="gear_text_size">28sp</dimen>
+    <dimen name="gear_icon_margin">5dp</dimen>
+    <!-- Information space -->
+    <dimen name="info_height">80dp</dimen>
+    <dimen name="info_label_text_size">10sp</dimen>
+    <dimen name="info_value_text_size">20sp</dimen>
 </resources>
diff --git a/tests/DirectRenderingClusterSample/res/values/strings.xml b/tests/DirectRenderingClusterSample/res/values/strings.xml
index e86cf60..e362910 100644
--- a/tests/DirectRenderingClusterSample/res/values/strings.xml
+++ b/tests/DirectRenderingClusterSample/res/values/strings.xml
@@ -4,6 +4,20 @@
     <!-- TODO: Remove or change this placeholder text -->
     <string name="hello_blank_fragment">Hello blank fragment</string>
 
-    <!-- Message to show when a navigation app hasn't been selected yet. [CHAR LIMIT=100]-->
+    <!-- Message to show when a navigation app hasn't been selected yet. [CHAR LIMIT=100] -->
     <string name="select_nav_app">Select a navigation app on the main unit.</string>
+
+    <!-- Gears texts [CHAR LIMIT=1] -->
+    <string name="gear_parked">P</string>
+    <string name="gear_reverse">R</string>
+    <string name="gear_neutral">N</string>
+    <string name="gear_drive">D</string>
+
+    <!-- Information labels (shown next to driving directions) [CHAR LIMIT=30] -->
+    <string name="info_fuel_label">Fuel</string>
+    <string name="info_speed_label">Speed</string>
+    <string name="info_range_label">Range</string>
+    <string name="info_rpm_label">RPM</string>
+    <string name="info_value_empty">-</string>
+
 </resources>
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java
index 8b69e65..d102a49 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ActivityMonitor.java
@@ -22,6 +22,7 @@
 import android.app.IProcessObserver;
 import android.app.TaskStackListener;
 import android.content.ComponentName;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -51,6 +52,7 @@
     private IActivityManager mActivityManager;
     // Listeners of top activity changes, indexed by the displayId they are interested on.
     private final Map<Integer, Set<ActivityListener>> mListeners = new HashMap<>();
+    private final Handler mHandler = new Handler();
     private final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
         @Override
         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
@@ -120,19 +122,25 @@
         mActivityManager = null;
     }
 
+    /**
+     * Notifies listeners on changes of top activities. {@link ActivityManager} might trigger
+     * updates on threads different than UI.
+     */
     private void notifyTopActivities() {
-        try {
-            List<StackInfo> infos = mActivityManager.getAllStackInfos();
-            for (StackInfo info : infos) {
-                Set<ActivityListener> listeners = mListeners.get(info.displayId);
-                if (listeners != null && !listeners.isEmpty()) {
-                    for (ActivityListener listener : listeners) {
-                        listener.onTopActivityChanged(info.displayId, info.topActivity);
+        mHandler.post(() -> {
+            try {
+                List<StackInfo> infos = mActivityManager.getAllStackInfos();
+                for (StackInfo info : infos) {
+                    Set<ActivityListener> listeners = mListeners.get(info.displayId);
+                    if (listeners != null && !listeners.isEmpty()) {
+                        for (ActivityListener listener : listeners) {
+                            listener.onTopActivityChanged(info.displayId, info.topActivity);
+                        }
                     }
                 }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Cannot getTasks", e);
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Cannot getTasks", e);
-        }
+        });
     }
 }
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
index 74125c3..8a383fd 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
@@ -15,29 +15,17 @@
  */
 package android.car.cluster.sample;
 
-import static android.car.cluster.CarInstrumentClusterManager.CATEGORY_NAVIGATION;
-import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import static java.lang.Integer.parseInt;
 
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.car.CarNotConnectedException;
-import android.car.cluster.CarInstrumentClusterManager;
 import android.car.cluster.ClusterActivityState;
 import android.car.cluster.renderer.InstrumentClusterRenderingService;
 import android.car.cluster.renderer.NavigationRenderer;
 import android.car.navigation.CarNavigationInstrumentCluster;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Bundle;
@@ -64,7 +52,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Implementation of {@link InstrumentClusterRenderingService} which renders an activity on a
@@ -83,29 +70,15 @@
     static final int MSG_ON_KEY_EVENT = 3;
     static final int MSG_REGISTER_CLIENT = 4;
     static final int MSG_UNREGISTER_CLIENT = 5;
-    static final int MSG_ON_FREE_NAVIGATION_ACTIVITY_STATE_CHANGED = 6;
     static final String MSG_KEY_CATEGORY = "category";
     static final String MSG_KEY_ACTIVITY_DISPLAY_ID = "activity_display_id";
     static final String MSG_KEY_ACTIVITY_STATE = "activity_state";
     static final String MSG_KEY_KEY_EVENT = "key_event";
-    static final String MSG_KEY_FREE_NAVIGATION_ACTIVITY_NAME = "free_navigation_activity_name";
-    static final String MSG_KEY_FREE_NAVIGATION_ACTIVITY_VISIBLE =
-            "free_navigation_activity_visible";
-
-    private static final int NAVIGATION_ACTIVITY_MAX_RETRIES = 10;
-    private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
 
     private List<Messenger> mClients = new ArrayList<>();
     private ClusterDisplayProvider mDisplayProvider;
     private int mDisplayId = NO_DISPLAY;
-    private UserReceiver mUserReceiver;
-    private final Handler mHandler = new Handler();
     private final IBinder mLocalBinder = new Messenger(new MessageHandler(this)).getBinder();
-    private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
-    private int mNavigationDisplayId = NO_DISPLAY;
-    private ComponentName mFreeNavigationActivity;
-    private ActivityMonitor mActivityMonitor = new ActivityMonitor();
-    private boolean mFreeNavigationActivityVisible;
 
     private final DisplayListener mDisplayListener = new DisplayListener() {
         @Override
@@ -126,31 +99,6 @@
         }
     };
 
-    private static class UserReceiver extends BroadcastReceiver {
-        private WeakReference<ClusterRenderingServiceImpl> mService;
-
-        UserReceiver(ClusterRenderingServiceImpl service) {
-            mService = new WeakReference<>(service);
-        }
-
-        public void register(Context context) {
-            IntentFilter intentFilter =  new IntentFilter(ACTION_USER_UNLOCKED);
-            intentFilter.addAction(ACTION_USER_SWITCHED);
-            context.registerReceiver(this, intentFilter);
-        }
-
-        public void unregister(Context context) {
-            context.unregisterReceiver(this);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            ClusterRenderingServiceImpl service = mService.get();
-            Log.d(TAG, "Broadcast received: " + intent);
-            service.tryLaunchNavigationActivity();
-        }
-    }
-
     private static class MessageHandler extends Handler {
         private final WeakReference<ClusterRenderingServiceImpl> mService;
 
@@ -175,15 +123,10 @@
                                 category, options, options.getLaunchDisplayId()));
                         mService.get().setClusterActivityState(category, state);
                         Log.d(TAG, String.format("activity state set: %s = %s", category, state));
-
-                        // Starting a default navigation activity. This would take place until any
-                        // navigation app takes focus.
-                        mService.get().startNavigationActivity(displayId);
                         break;
                     }
                     case MSG_REGISTER_CLIENT:
                         mService.get().mClients.add(msg.replyTo);
-                        mService.get().notifyFreeNavigationActivityChange();
                         break;
                     case MSG_UNREGISTER_CLIENT:
                         mService.get().mClients.remove(msg.replyTo);
@@ -197,17 +140,6 @@
         }
     }
 
-    private ActivityMonitor.ActivityListener mNavigationActivityMonitor = (displayId, activity) -> {
-        if (displayId != mNavigationDisplayId) {
-            return;
-        }
-        boolean activityVisible = activity != null && activity.equals(mFreeNavigationActivity);
-        if (activityVisible != mFreeNavigationActivityVisible) {
-            mFreeNavigationActivityVisible = activityVisible;
-            notifyFreeNavigationActivityChange();
-        }
-    };
-
     @Override
     public IBinder onBind(Intent intent) {
         Log.d(TAG, "onBind, intent: " + intent);
@@ -221,9 +153,6 @@
         super.onCreate();
         Log.d(TAG, "onCreate");
         mDisplayProvider = new ClusterDisplayProvider(this, mDisplayListener);
-        mUserReceiver = new UserReceiver(this);
-        mUserReceiver.register(this);
-        mActivityMonitor.start();
     }
 
     private void launchMainActivity() {
@@ -265,14 +194,6 @@
     }
 
     @Override
-    public void onDestroy() {
-        super.onDestroy();
-        Log.w(TAG, "onDestroy");
-        mUserReceiver.unregister(this);
-        mActivityMonitor.stop();
-    }
-
-    @Override
     protected NavigationRenderer getNavigationRenderer() {
         NavigationRenderer navigationRenderer = new NavigationRenderer() {
             @Override
@@ -406,119 +327,4 @@
             }
         }
     }
-
-    private void startNavigationActivity(int displayId) {
-        mActivityMonitor.removeListener(mNavigationDisplayId, mNavigationActivityMonitor);
-        mActivityMonitor.addListener(displayId, mNavigationActivityMonitor);
-        mNavigationDisplayId = displayId;
-        tryLaunchNavigationActivity();
-    }
-
-    /**
-     * Tries to start a default navigation activity in the cluster. During system initialization
-     * launching user activities might fail due the system not being ready or {@link PackageManager}
-     * not being able to resolve the implicit intent. It is also possible that the system doesn't
-     * have a default navigation activity selected yet.
-     */
-    private void tryLaunchNavigationActivity() {
-        int userHandle = ActivityManager.getCurrentUser();
-        if (userHandle == UserHandle.USER_SYSTEM || mNavigationDisplayId == NO_DISPLAY) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, String.format("Launch activity ignored (user: %d, display: %d)",
-                        userHandle, mNavigationDisplayId));
-            }
-            // Not ready to launch yet.
-            return;
-        }
-        mHandler.removeCallbacks(mRetryLaunchNavigationActivity);
-
-        ComponentName navigationActivity = getNavigationActivity();
-        if (!Objects.equals(navigationActivity, mFreeNavigationActivity)) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Navigation activity change detected: " + navigationActivity);
-            }
-            mFreeNavigationActivity = navigationActivity;
-            notifyFreeNavigationActivityChange();
-        }
-
-        try {
-            if (navigationActivity == null) {
-                throw new ActivityNotFoundException();
-            }
-            Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION)
-                    .setPackage(navigationActivity.getPackageName())
-                    .setComponent(navigationActivity)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            Log.d(TAG, "Launching: " + intent + " on display: " + mNavigationDisplayId);
-            Bundle activityOptions = ActivityOptions.makeBasic()
-                    .setLaunchDisplayId(mNavigationDisplayId)
-                    .toBundle();
-
-            startActivityAsUser(intent, activityOptions, UserHandle.CURRENT);
-        } catch (ActivityNotFoundException ex) {
-            // Some activities might not be available right on startup. We will retry.
-            mHandler.postDelayed(mRetryLaunchNavigationActivity,
-                    NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS);
-        } catch (Exception ex) {
-            Log.e(TAG, "Unable to start navigation activity: " + navigationActivity, ex);
-        }
-    }
-
-    private void notifyFreeNavigationActivityChange() {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(MSG_KEY_FREE_NAVIGATION_ACTIVITY_NAME, mFreeNavigationActivity);
-        bundle.putBoolean(MSG_KEY_FREE_NAVIGATION_ACTIVITY_VISIBLE, mFreeNavigationActivityVisible);
-        broadcastClientMessage(MSG_ON_FREE_NAVIGATION_ACTIVITY_STATE_CHANGED, bundle);
-    }
-
-    /**
-     * 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
-     * selected from CarLauncher (see CarLauncher#getMapsIntent()).
-     * Alternatively, other implementations could:
-     * <ul>
-     * <li>Read this package from a resource (having a OEM default activity to show)
-     * <li>Let the user select one from settings.
-     * </ul>
-     */
-    private ComponentName getNavigationActivity() {
-        PackageManager pm = getPackageManager();
-        int userId = ActivityManager.getCurrentUser();
-
-        // Get currently selected navigation app.
-        Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
-                Intent.CATEGORY_APP_MAPS);
-        ResolveInfo navigationApp = pm.resolveActivityAsUser(intent,
-                PackageManager.MATCH_DEFAULT_ONLY, userId);
-
-        // Get all possible cluster activities
-        intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION);
-        List<ResolveInfo> candidates = pm.queryIntentActivitiesAsUser(intent, 0, userId);
-
-        // If there is a select navigation app, try finding a matching auxiliary navigation activity
-        if (navigationApp != null) {
-            Log.d(TAG, "Current navigation app: " + navigationApp);
-            for (ResolveInfo candidate : candidates) {
-                Log.d(TAG, "Candidate: " + candidate);
-                if (candidate.activityInfo.packageName.equals(navigationApp.activityInfo
-                        .packageName)) {
-                    Log.d(TAG, "Found activity: " + candidate);
-                    return new ComponentName(candidate.activityInfo.packageName,
-                            candidate.activityInfo.name);
-                }
-            }
-        } else {
-            Log.d(TAG, "NO CURRENT ACTIVITY");
-            for (ResolveInfo candidate : candidates) {
-                Log.d(TAG, "Candidate: " + candidate);
-            }
-        }
-
-        // During initialization implicit intents might not provided a result. We will just
-        // retry until we find one, or we exhaust the retries.
-        Log.d(TAG, "No default activity found (it might not be available yet).");
-        return null;
-    }
-
 }
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
index 55c9e54..8a67856 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
@@ -15,95 +15,235 @@
  */
 package android.car.cluster.sample;
 
+import android.annotation.Nullable;
 import android.app.Application;
+import android.car.Car;
+import android.car.CarAppFocusManager;
+import android.car.CarNotConnectedException;
+import android.car.VehicleAreaType;
+import android.car.cluster.sample.sensors.Sensor;
+import android.car.cluster.sample.sensors.Sensors;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
 import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
 import androidx.lifecycle.AndroidViewModel;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Transformations;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /**
  * {@link AndroidViewModel} for cluster information.
  */
 public class ClusterViewModel extends AndroidViewModel {
-    /**
-     * Reference to a component (e.g.: an activity) and whether such component is visible or not.
-     */
-    public static class ComponentVisibility {
-        /**
-         * Application component name
-         */
-        public final ComponentName mComponent;
-        /**
-         * Whether the component is currently visible to the user or not.
-         */
-        public final boolean mIsVisible;
+    private static final String TAG = "Cluster.ViewModel";
 
-        /**
-         * Creates a new component visibility reference
-         */
-        private ComponentVisibility(ComponentName component, boolean isVisible) {
-            mComponent = component;
-            mIsVisible = isVisible;
-        }
+    private static final int PROPERTIES_REFRESH_RATE_UI = 5;
 
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            ComponentVisibility that = (ComponentVisibility) o;
-            return mIsVisible == that.mIsVisible
-                    && Objects.equals(mComponent, that.mComponent);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mComponent, mIsVisible);
-        }
+    public enum NavigationActivityState {
+        /** No activity has been selected to be displayed on the navigation fragment yet */
+        NOT_SELECTED,
+        /** An activity has been selected, but it is not yet visible to the user */
+        LOADING,
+        /** Navigation activity is visible to the user */
+        VISIBLE,
     }
 
-    private final MutableLiveData<ComponentVisibility> mFreeNavigationActivity =
-            new MutableLiveData<>(new ComponentVisibility(null, false));
+    private ComponentName mFreeNavigationActivity;
+    private ComponentName mCurrentNavigationActivity;
+    private final MutableLiveData<NavigationActivityState> mNavigationActivityStateLiveData =
+            new MutableLiveData<>();
     private final MutableLiveData<Boolean> mNavigationFocus = new MutableLiveData<>(false);
+    private Car mCar;
+    private CarAppFocusManager mCarAppFocusManager;
+    private CarPropertyManager mCarPropertyManager;
+    private Map<Sensor<?>, MutableLiveData<?>> mSensorLiveDatas = new HashMap<>();
+
+    private ServiceConnection mCarServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                Log.i(TAG, "onServiceConnected, name: " + name + ", service: " + service);
+
+                // Listen navigation focus state
+                mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(
+                        Car.APP_FOCUS_SERVICE);
+                if (mCarAppFocusManager == null) {
+                    Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
+                    return;
+                }
+                mCarAppFocusManager.addFocusListener(
+                        (appType, active) -> setNavigationFocus(active),
+                        CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+
+                // Listen property value changes
+                mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+                for (Integer propertyId : Sensors.getInstance().getPropertyIds()) {
+                    mCarPropertyManager.registerListener(mCarPropertyEventListener,
+                            propertyId, PROPERTIES_REFRESH_RATE_UI);
+                }
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "onServiceConnected: error obtaining manager", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.i(TAG, "onServiceDisconnected, name: " + name);
+            mCarAppFocusManager = null;
+            mCarPropertyManager = null;
+        }
+    };
+
+    private CarPropertyManager.CarPropertyEventListener mCarPropertyEventListener =
+            new CarPropertyManager.CarPropertyEventListener() {
+        @Override
+        public void onChangeEvent(CarPropertyValue value) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "CarProperty change: property " + value.getPropertyId() + ", area"
+                        + value.getAreaId() + ", value: " + value.getValue());
+            }
+            for (Sensor<?> sensorId : Sensors.getInstance()
+                    .getSensorsForPropertyId(value.getPropertyId())) {
+                if (sensorId.mAreaId == Sensors.GLOBAL_AREA_ID
+                        || (sensorId.mAreaId & value.getAreaId()) != 0) {
+                    setSensorValue(sensorId, value);
+                }
+            }
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+            for (Sensor<?> sensorId : Sensors.getInstance().getSensorsForPropertyId(propId)) {
+                if (sensorId.mAreaId == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL
+                        || (sensorId.mAreaId & zone) != 0) {
+                    setSensorValue(sensorId, null);
+                }
+            }
+        }
+
+        private <T> void setSensorValue(Sensor<T> id, CarPropertyValue<?> value) {
+            T newValue = value != null ? id.mAdapter.apply(value) : null;
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Sensor " + id.mName + " = " + newValue);
+            }
+            getSensorMutableLiveData(id).setValue(newValue);
+        }
+    };
 
     /**
      * New {@link ClusterViewModel} instance
      */
     public ClusterViewModel(@NonNull Application application) {
         super(application);
+        mCar = Car.createCar(application, mCarServiceConnection);
+        mCar.connect();
+    }
+
+    @Override
+    protected void onCleared() {
+        super.onCleared();
+        mCar.disconnect();
+        mCar = null;
+        mCarAppFocusManager = null;
+        mCarPropertyManager = null;
     }
 
     /**
-     * Returns a {@link LiveData} providing the activity selected to be displayed on the cluster
-     * when navigation focus is not granted (a.k.a.: free navigation). It also indicates whether
-     * such activity is currently visible to the user or not.
+     * Returns a {@link LiveData} providing the current state of the activity displayed on the
+     * navigation fragment.
      */
-    public LiveData<ComponentVisibility> getFreeNavigationActivity() {
-        return mFreeNavigationActivity;
+    public LiveData<NavigationActivityState> getNavigationActivityState() {
+        return mNavigationActivityStateLiveData;
     }
 
     /**
      * Returns a {@link LiveData} indicating whether navigation focus is currently being granted
      * or not. This indicates whether a navigation application is currently providing driving
-     * directions. Instrument cluster can use this signal to show/hide turn-by-turn
-     * directions UI, and hide/show the free navigation activity
-     * (see {@link #getFreeNavigationActivity()}).
+     * directions.
      */
     public LiveData<Boolean> getNavigationFocus() {
         return mNavigationFocus;
     }
 
     /**
-     * Sets the activity selected to be displayed on the cluster when no driving directions are
-     * being provided, and whether such activity is currently visible to the user or not
+     * Returns a {@link LiveData} that tracks the value of a given car sensor. Each sensor has its
+     * own data type. The list of all supported sensors can be found at {@link Sensors}
+     *
+     * @param sensor sensor to observe
+     * @param <T> data type of such sensor
      */
-    public void setFreeNavigationActivity(ComponentName application, boolean isVisible) {
-        ComponentVisibility newValue = new ComponentVisibility(application, isVisible);
-        if (!Objects.equals(mFreeNavigationActivity.getValue(), newValue)) {
-            mFreeNavigationActivity.setValue(new ComponentVisibility(application, isVisible));
+    @SuppressWarnings("unchecked")
+    @NonNull
+    public <T> LiveData<T> getSensor(@NonNull Sensor<T> sensor) {
+        return getSensorMutableLiveData(Preconditions.checkNotNull(sensor));
+    }
+
+    /**
+     * Returns the current value of the sensor, directly from the VHAL.
+     *
+     * @param sensor sensor to read
+     * @param <V> VHAL data type
+     * @param <T> data type of such sensor
+     */
+    @Nullable
+    public <T> T getSensorValue(@NonNull Sensor<T> sensor) {
+        try {
+            CarPropertyValue<?> value = mCarPropertyManager
+                    .getProperty(sensor.mPropertyId, sensor.mAreaId);
+            return sensor.mAdapter.apply(value);
+        } catch (CarNotConnectedException ex) {
+            Log.e(TAG, "We got disconnected from Car Service", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the fuel level in a range from 0.0 to 1.0.
+     */
+    public LiveData<Float> getFuelLevel() {
+        return Transformations.map(getSensor(Sensors.SENSOR_FUEL), (fuelValue) -> {
+            Float fuelCapacityValue = getSensorValue(Sensors.SENSOR_FUEL_CAPACITY);
+            if (fuelValue == null || fuelCapacityValue == null || fuelCapacityValue == 0) {
+                return null;
+            }
+            if (fuelValue < 0.0f) {
+                return 0.0f;
+            }
+            if (fuelValue > fuelCapacityValue) {
+                return 1.0f;
+            }
+            return fuelValue / fuelCapacityValue;
+        });
+    }
+
+    /**
+     * Sets the activity selected to be displayed on the cluster when no driving directions are
+     * being provided.
+     */
+    public void setFreeNavigationActivity(ComponentName activity) {
+        if (!Objects.equals(activity, mFreeNavigationActivity)) {
+            mFreeNavigationActivity = activity;
+            updateNavigationActivityLiveData();
+        }
+    }
+
+    /**
+     * Sets the activity currently being displayed on the cluster.
+     */
+    public void setCurrentNavigationActivity(ComponentName activity) {
+        if (!Objects.equals(activity, mCurrentNavigationActivity)) {
+            mCurrentNavigationActivity = activity;
+            updateNavigationActivityLiveData();
         }
     }
 
@@ -113,6 +253,40 @@
     public void setNavigationFocus(boolean navigationFocus) {
         if (mNavigationFocus.getValue() == null || mNavigationFocus.getValue() != navigationFocus) {
             mNavigationFocus.setValue(navigationFocus);
+            updateNavigationActivityLiveData();
         }
     }
+
+    private void updateNavigationActivityLiveData() {
+        NavigationActivityState newState = calculateNavigationActivityState();
+        if (newState != mNavigationActivityStateLiveData.getValue()) {
+            mNavigationActivityStateLiveData.setValue(newState);
+        }
+    }
+
+    private NavigationActivityState calculateNavigationActivityState() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, String.format("Current state: current activity = '%s', free nav activity = "
+                            + "'%s', focus = %s", mCurrentNavigationActivity,
+                    mFreeNavigationActivity,
+                    mNavigationFocus.getValue()));
+        }
+        if (mNavigationFocus.getValue() != null && mNavigationFocus.getValue()) {
+            // Car service controls which activity is displayed while driving, so we assume this
+            // has already been taken care of.
+            return NavigationActivityState.VISIBLE;
+        } else if (mFreeNavigationActivity == null) {
+            return NavigationActivityState.NOT_SELECTED;
+        } else if (Objects.equals(mFreeNavigationActivity, mCurrentNavigationActivity)) {
+            return NavigationActivityState.VISIBLE;
+        } else {
+            return NavigationActivityState.LOADING;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> MutableLiveData<T> getSensorMutableLiveData(Sensor<T> sensor) {
+        return (MutableLiveData<T>) mSensorLiveDatas
+                .computeIfAbsent(sensor, x -> new MutableLiveData<>());
+    }
 }
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index 2a2ac73..e138a6b 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -20,12 +20,7 @@
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_DISPLAY_ID;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_STATE;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_CATEGORY;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_FREE_NAVIGATION_ACTIVITY_NAME;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl
-        .MSG_KEY_FREE_NAVIGATION_ACTIVITY_VISIBLE;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_KEY_EVENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl
-        .MSG_ON_FREE_NAVIGATION_ACTIVITY_STATE_CHANGED;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_ON_KEY_EVENT;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl
         .MSG_ON_NAVIGATION_STATE_CHANGED;
@@ -33,14 +28,23 @@
 import static android.car.cluster.sample.ClusterRenderingServiceImpl
         .MSG_SET_ACTIVITY_LAUNCH_OPTIONS;
 import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_UNREGISTER_CLIENT;
+import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
 
-import android.car.Car;
-import android.car.CarAppFocusManager;
-import android.car.CarNotConnectedException;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.car.cluster.CarInstrumentClusterManager;
 import android.car.cluster.ClusterActivityState;
+import android.car.cluster.sample.sensors.Sensors;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -48,6 +52,7 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
@@ -56,12 +61,14 @@
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
+import android.widget.TextView;
 
 import androidx.car.cluster.navigation.NavigationState;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.lifecycle.LiveData;
 import androidx.lifecycle.ViewModelProviders;
 import androidx.versionedparcelable.ParcelUtils;
 import androidx.viewpager.widget.ViewPager;
@@ -69,33 +76,65 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
+/**
+ * Main activity displayed on the instrument cluster. This activity contains fragments for each of
+ * the cluster "facets" (e.g.: navigation, communication, media and car state). Users can navigate
+ * to each facet by using the steering wheel buttons.
+ * <p>
+ * This activity runs on "system user" (see {@link UserHandle#USER_SYSTEM}) but it is visible on
+ * all users (the same activity remains active even during user switch).
+ * <p>
+ * This activity also launches a default navigation app inside a virtual display (which is located
+ * inside {@link NavigationFragment}). This navigation app is launched when:
+ * <ul>
+ * <li>Virtual display for navigation apps is ready.
+ * <li>After every user switch.
+ * </ul>
+ * This is necessary because the navigation app runs under a normal user, and different users will
+ * see different instances of the same application, with their own personalized data.
+ */
 public class MainClusterActivity extends FragmentActivity {
     private static final String TAG = "Cluster.MainActivity";
 
     private static final NavigationState NULL_NAV_STATE = new NavigationState.Builder().build();
+    private static final int NO_DISPLAY = -1;
 
     private ViewPager mPager;
     private NavStateController mNavStateController;
     private ClusterViewModel mClusterViewModel;
 
-    private HashMap<Button, Facet<?>> mButtonToFacet = new HashMap<>();
+    private Map<View, Facet<?>> mButtonToFacet = new HashMap<>();
     private SparseArray<Facet<?>> mOrderToFacet = new SparseArray<>();
 
+    private Map<Sensors.Gear, View> mGearsToIcon = new HashMap<>();
     private InputMethodManager mInputMethodManager;
     private Messenger mService;
     private Messenger mServiceCallbacks = new Messenger(new MessageHandler(this));
     private VirtualDisplay mPendingVirtualDisplay = null;
-    private Car mCar;
-    private CarAppFocusManager mCarAppFocusManager;
 
+    private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
+
+    private UserReceiver mUserReceiver;
+    private ActivityMonitor mActivityMonitor = new ActivityMonitor();
+    private final Handler mHandler = new Handler();
+    private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
+    private int mNavigationDisplayId = NO_DISPLAY;
+
+    /**
+     * Description of a virtual display
+     */
     public static class VirtualDisplay {
+        /** Identifier of the display */
         public final int mDisplayId;
-        public final Rect mBounds;
+        /** Rectangular area inside this display that can be viewed without obstructions */
+        public final Rect mUnobscuredBounds;
 
-        public VirtualDisplay(int displayId, Rect bounds) {
+        public VirtualDisplay(int displayId, Rect unobscuredBounds) {
             mDisplayId = displayId;
-            mBounds = bounds;
+            mUnobscuredBounds = unobscuredBounds;
         }
     }
 
@@ -130,32 +169,6 @@
         }
     };
 
-    private ServiceConnection mCarServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                Log.i(TAG, "onServiceConnected, name: " + name + ", service: " + service);
-                mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(
-                        Car.APP_FOCUS_SERVICE);
-                if (mCarAppFocusManager == null) {
-                    Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
-                    return;
-                }
-                mCarAppFocusManager.addFocusListener(
-                        (appType, active) -> mClusterViewModel.setNavigationFocus(active),
-                        CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
-            } catch (CarNotConnectedException e) {
-                Log.e(TAG, "onServiceConnected: error obtaining manager", e);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.i(TAG, "onServiceDisconnected, name: " + name);
-            mCarAppFocusManager = null;
-        }
-    };
-
     private static class MessageHandler extends Handler {
         private final WeakReference<MainClusterActivity> mActivity;
 
@@ -184,19 +197,46 @@
                         mActivity.get().onNavigationStateChange(navState);
                     }
                     break;
-                case MSG_ON_FREE_NAVIGATION_ACTIVITY_STATE_CHANGED:
-                    ComponentName activity = data.getParcelable(
-                            MSG_KEY_FREE_NAVIGATION_ACTIVITY_NAME);
-                    boolean isVisible = data.getBoolean(MSG_KEY_FREE_NAVIGATION_ACTIVITY_VISIBLE);
-                    mActivity.get().mClusterViewModel.setFreeNavigationActivity(activity,
-                            isVisible);
-                    break;
                 default:
                     super.handleMessage(msg);
             }
         }
     }
 
+    private ActivityMonitor.ActivityListener mNavigationActivityMonitor = (displayId, activity) -> {
+        if (displayId != mNavigationDisplayId) {
+            return;
+        }
+        mClusterViewModel.setCurrentNavigationActivity(activity);
+    };
+
+    private static class UserReceiver extends BroadcastReceiver {
+        private WeakReference<MainClusterActivity> mActivity;
+
+        UserReceiver(MainClusterActivity activity) {
+            mActivity = new WeakReference<>(activity);
+        }
+
+        public void register(Context context) {
+            IntentFilter intentFilter =  new IntentFilter(ACTION_USER_UNLOCKED);
+            intentFilter.addAction(ACTION_USER_SWITCHED);
+            context.registerReceiver(this, intentFilter);
+        }
+
+        public void unregister(Context context) {
+            context.unregisterReceiver(this);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MainClusterActivity activity = mActivity.get();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Broadcast received: " + intent);
+            }
+            activity.tryLaunchNavigationActivity();
+        }
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -209,11 +249,14 @@
         intent.setAction(LOCAL_BINDING_ACTION);
         bindService(intent, mClusterRenderingServiceConnection, 0);
 
-        registerFacets(
-                new Facet<>(findViewById(R.id.btn_nav), 0, NavigationFragment.class),
-                new Facet<>(findViewById(R.id.btn_phone), 1, PhoneFragment.class),
-                new Facet<>(findViewById(R.id.btn_music), 2, MusicFragment.class),
-                new Facet<>(findViewById(R.id.btn_car_info), 3, CarInfoFragment.class));
+        registerFacet(new Facet<>(findViewById(R.id.btn_nav), 0, NavigationFragment.class));
+        registerFacet(new Facet<>(findViewById(R.id.btn_phone), 1, PhoneFragment.class));
+        registerFacet(new Facet<>(findViewById(R.id.btn_music), 2, MusicFragment.class));
+        registerFacet(new Facet<>(findViewById(R.id.btn_car_info), 3, CarInfoFragment.class));
+        registerGear(findViewById(R.id.gear_parked), Sensors.Gear.PARK);
+        registerGear(findViewById(R.id.gear_reverse), Sensors.Gear.REVERSE);
+        registerGear(findViewById(R.id.gear_neutral), Sensors.Gear.NEUTRAL);
+        registerGear(findViewById(R.id.gear_drive), Sensors.Gear.DRIVE);
 
         mPager = findViewById(R.id.pager);
         mPager.setAdapter(new ClusterPageAdapter(getSupportFragmentManager()));
@@ -221,19 +264,42 @@
         mNavStateController = new NavStateController(findViewById(R.id.navigation_state));
 
         mClusterViewModel = ViewModelProviders.of(this).get(ClusterViewModel.class);
-        mClusterViewModel.getNavigationFocus().observe(this, active ->
-                mNavStateController.setActive(active));
+        mClusterViewModel.getNavigationFocus().observe(this, focus -> {
+            mNavStateController.setActive(focus);
+            // If focus is lost, we launch the default navigation activity again.
+            if (!focus) {
+                tryLaunchNavigationActivity();
+            }
+        });
 
-        mCar = Car.createCar(this, mCarServiceConnection);
-        mCar.connect();
+        mClusterViewModel.getSensor(Sensors.SENSOR_GEAR).observe(this, this::updateSelectedGear);
+
+        registerSensor(findViewById(R.id.info_fuel), mClusterViewModel.getFuelLevel());
+        registerSensor(findViewById(R.id.info_speed),
+                mClusterViewModel.getSensor(Sensors.SENSOR_SPEED));
+        registerSensor(findViewById(R.id.info_range),
+                mClusterViewModel.getSensor(Sensors.SENSOR_FUEL_RANGE));
+        registerSensor(findViewById(R.id.info_rpm),
+                mClusterViewModel.getSensor(Sensors.SENSOR_RPM));
+
+        mActivityMonitor.start();
+
+        mUserReceiver = new UserReceiver(this);
+        mUserReceiver.register(this);
+    }
+
+    private <V> void registerSensor(TextView textView, LiveData<V> source) {
+        String emptyValue = getString(R.string.info_value_empty);
+        source.observe(this, value -> textView.setText(value != null
+                ? value.toString() : emptyValue));
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
         Log.d(TAG, "onDestroy");
-        mCar.disconnect();
-        mCarAppFocusManager = null;
+        mUserReceiver.unregister(this);
+        mActivityMonitor.stop();
         if (mService != null) {
             sendServiceMessage(MSG_UNREGISTER_CLIENT, null, mServiceCallbacks);
             mService = null;
@@ -259,6 +325,10 @@
     }
 
     public void updateNavDisplay(VirtualDisplay virtualDisplay) {
+        // Starting the default navigation activity. This activity will be shown when navigation
+        // focus is not taken.
+        startNavigationActivity(virtualDisplay.mDisplayId);
+        // Notify the service (so it updates display properties on car service)
         if (mService == null) {
             // Service is not bound yet. Hold the information and notify when the service is bound.
             mPendingVirtualDisplay = virtualDisplay;
@@ -274,7 +344,7 @@
         data.putInt(MSG_KEY_ACTIVITY_DISPLAY_ID, virtualDisplay.mDisplayId);
         data.putBundle(MSG_KEY_ACTIVITY_STATE, ClusterActivityState
                 .create(virtualDisplay.mDisplayId != Display.INVALID_DISPLAY,
-                        virtualDisplay.mBounds)
+                        virtualDisplay.mUnobscuredBounds)
                 .toBundle());
         sendServiceMessage(MSG_SET_ACTIVITY_LAUNCH_OPTIONS, data, null);
     }
@@ -313,12 +383,6 @@
         }
     }
 
-    private void registerFacets(Facet<?>... facets) {
-        for (Facet<?> f : facets) {
-            registerFacet(f);
-        }
-    }
-
     private <T> void registerFacet(Facet<T> facet) {
         mOrderToFacet.append(facet.order, facet);
         mButtonToFacet.put(facet.button, facet);
@@ -350,4 +414,108 @@
             return mFragment;
         }
     }
+
+    private void startNavigationActivity(int displayId) {
+        mActivityMonitor.removeListener(mNavigationDisplayId, mNavigationActivityMonitor);
+        mActivityMonitor.addListener(displayId, mNavigationActivityMonitor);
+        mNavigationDisplayId = displayId;
+        tryLaunchNavigationActivity();
+    }
+
+    /**
+     * Tries to start a default navigation activity in the cluster. During system initialization
+     * launching user activities might fail due the system not being ready or {@link PackageManager}
+     * not being able to resolve the implicit intent. It is also possible that the system doesn't
+     * have a default navigation activity selected yet.
+     */
+    private void tryLaunchNavigationActivity() {
+        int userHandle = ActivityManager.getCurrentUser();
+        if (userHandle == UserHandle.USER_SYSTEM || mNavigationDisplayId == NO_DISPLAY) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, String.format("Launch activity ignored (user: %d, display: %d)",
+                        userHandle, mNavigationDisplayId));
+            }
+            // Not ready to launch yet.
+            return;
+        }
+        mHandler.removeCallbacks(mRetryLaunchNavigationActivity);
+
+        ComponentName navigationActivity = getNavigationActivity();
+        mClusterViewModel.setFreeNavigationActivity(navigationActivity);
+
+        try {
+            if (navigationActivity == null) {
+                throw new ActivityNotFoundException();
+            }
+            Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION)
+                    .setPackage(navigationActivity.getPackageName())
+                    .setComponent(navigationActivity)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            Log.d(TAG, "Launching: " + intent + " on display: " + mNavigationDisplayId);
+            Bundle activityOptions = ActivityOptions.makeBasic()
+                    .setLaunchDisplayId(mNavigationDisplayId)
+                    .toBundle();
+
+            startActivityAsUser(intent, activityOptions, UserHandle.CURRENT);
+        } catch (ActivityNotFoundException ex) {
+            // Some activities might not be available right on startup. We will retry.
+            mHandler.postDelayed(mRetryLaunchNavigationActivity,
+                    NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS);
+        } catch (Exception ex) {
+            Log.e(TAG, "Unable to start navigation activity: " + navigationActivity, ex);
+        }
+    }
+
+    /**
+     * 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
+     * selected from CarLauncher (see CarLauncher#getMapsIntent()).
+     * Alternatively, other implementations could:
+     * <ul>
+     * <li>Read this package from a resource (having a OEM default activity to show)
+     * <li>Let the user select one from settings.
+     * </ul>
+     */
+    private ComponentName getNavigationActivity() {
+        PackageManager pm = getPackageManager();
+        int userId = ActivityManager.getCurrentUser();
+
+        // Get currently selected navigation app.
+        Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+                Intent.CATEGORY_APP_MAPS);
+        ResolveInfo navigationApp = pm.resolveActivityAsUser(intent,
+                PackageManager.MATCH_DEFAULT_ONLY, userId);
+
+        // Get all possible cluster activities
+        intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION);
+        List<ResolveInfo> candidates = pm.queryIntentActivitiesAsUser(intent, 0, userId);
+
+        // If there is a select navigation app, try finding a matching auxiliary navigation activity
+        if (navigationApp != null) {
+            for (ResolveInfo candidate : candidates) {
+                if (candidate.activityInfo.packageName.equals(navigationApp.activityInfo
+                        .packageName)) {
+                    Log.d(TAG, "Found activity: " + candidate);
+                    return new ComponentName(candidate.activityInfo.packageName,
+                            candidate.activityInfo.name);
+                }
+            }
+        }
+
+        // During initialization implicit intents might not provided a result. We will just
+        // retry until we find one, or we exhaust the retries.
+        Log.d(TAG, "No default activity found (it might not be available yet).");
+        return null;
+    }
+
+    private void registerGear(View view, Sensors.Gear gear) {
+        mGearsToIcon.put(gear, view);
+    }
+
+    private void updateSelectedGear(Sensors.Gear gear) {
+        for (Map.Entry<Sensors.Gear, View> entry : mGearsToIcon.entrySet()) {
+            entry.getValue().setSelected(entry.getKey() == gear);
+        }
+    }
 }
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavigationFragment.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavigationFragment.java
index 407e13a..d35ba02 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavigationFragment.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavigationFragment.java
@@ -138,8 +138,17 @@
                 Log.i(TAG, "surfaceChanged, holder: " + holder + ", size:" + width + "x" + height
                         + ", format:" + format);
 
-                //Create dummy unobscured area to report to navigation activity.
-                mUnobscuredBounds = new Rect(40, 0, width - 80, height - 40);
+                // Create dummy unobscured area to report to navigation activity.
+                int obscuredWidth = (int) getResources()
+                        .getDimension(R.dimen.speedometer_overlap_width);
+                int obscuredHeight = (int) getResources()
+                        .getDimension(R.dimen.navigation_gradient_height);
+                mUnobscuredBounds = new Rect(
+                        obscuredWidth,          /* left: size of gauge */
+                        obscuredHeight,         /* top: gradient */
+                        width - obscuredWidth,  /* right: size of the display - size of gauge */
+                        height - obscuredHeight /* bottom: size of display - gradient */
+                );
 
                 if (mVirtualDisplay == null) {
                     mVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height);
@@ -159,10 +168,14 @@
         mProgressBar = root.findViewById(R.id.progress_bar);
         mMessage = root.findViewById(R.id.message);
 
-        mViewModel.getFreeNavigationActivity().observe(this, app -> {
-            mProgressBar.setVisibility(app.mComponent != null && !app.mIsVisible ? View.VISIBLE
-                    : View.GONE);
-            mMessage.setVisibility(app.mComponent == null ? View.VISIBLE : View.GONE);
+        mViewModel.getNavigationActivityState().observe(this, state -> {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "State: " + state);
+            }
+            mProgressBar.setVisibility(state == ClusterViewModel.NavigationActivityState.LOADING
+                    ? View.VISIBLE : View.INVISIBLE);
+            mMessage.setVisibility(state == ClusterViewModel.NavigationActivityState.NOT_SELECTED
+                    ? View.VISIBLE : View.INVISIBLE);
         });
 
         return root;
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java
index f5f535e..871d93f 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java
@@ -38,6 +38,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.RandomAccessFile;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.ByteBuffer;
@@ -46,10 +47,10 @@
 /**
  * This class encapsulates all work related to managing networked virtual display.
  * <p>
- * It opens server socket and listens on port {@code PORT} for incoming connections. Once connection
- * is established it creates virtual display and media encoder and starts streaming video to that
- * socket.  If the receiving part is disconnected, it will keep port open and virtual display won't
- * be destroyed.
+ * It opens a socket and listens on port {@code PORT} for connections, or the emulator pipe. Once
+ * connection is established it creates virtual display and media encoder and starts streaming video
+ * to that socket.  If the receiving part is disconnected, it will keep port open and virtual
+ * display won't be destroyed.
  */
 public class NetworkedVirtualDisplay {
     private static final String TAG = "Cluster." + NetworkedVirtualDisplay.class.getSimpleName();
@@ -61,21 +62,29 @@
     private final int mHeight;
     private final int mDpi;
 
-    private static final int PORT = 5151;
     private static final int FPS = 25;
     private static final int BITRATE = 6144000;
     private static final String MEDIA_FORMAT_MIMETYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
 
-    private static final int MSG_START = 0;
-    private static final int MSG_STOP = 1;
-    private static final int MSG_SEND_FRAME = 2;
+    public static final int MSG_START = 0;
+    public static final int MSG_STOP = 1;
+    public static final int MSG_SEND_FRAME = 2;
+
+    private static final String PIPE_NAME = "pipe:qemud:carCluster";
+    private static final String PIPE_DEVICE = "/dev/qemu_pipe";
+
+    // Constants shared with emulator in car-cluster-widget.cpp
+    public static final int PIPE_START = 1;
+    public static final int PIPE_STOP = 2;
+
+    private static final int PORT = 5151;
+
+    private SenderThread mActiveThread;
+    private HandlerThread mBroadcastThread = new HandlerThread("BroadcastThread");
 
     private VirtualDisplay mVirtualDisplay;
     private MediaCodec mVideoEncoder;
-    private HandlerThread mThread = new HandlerThread("NetworkThread");
     private Handler mHandler;
-    private ServerSocket mServerSocket;
-    private OutputStream mOutputStream;
     private byte[] mBuffer = null;
     private int mLastFrameLength = 0;
 
@@ -120,31 +129,31 @@
      * @throws IllegalStateException thrown if networked display already started
      */
     public String start() {
-        if (mThread.isAlive()) {
+        if (mBroadcastThread.isAlive()) {
             throw new IllegalStateException("Already started");
         }
-        mThread.start();
-        mHandler = new NetworkThreadHandler(mThread.getLooper());
-        mHandler.sendMessage(Message.obtain(mHandler, MSG_START));
 
+        mBroadcastThread.start();
+        mHandler = new BroadcastThreadHandler(mBroadcastThread.getLooper());
+        mHandler.sendMessage(Message.obtain(mHandler, MSG_START));
         return getDisplayName();
     }
 
     public void release() {
-        stopCasting();
+        mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP));
+        mBroadcastThread.quitSafely();
 
         if (mVirtualDisplay != null) {
+            mVirtualDisplay.setSurface(null);
             mVirtualDisplay.release();
             mVirtualDisplay = null;
         }
-        mThread.quit();
     }
 
     private String getDisplayName() {
         return "Cluster-" + mUniqueId;
     }
 
-
     private VirtualDisplay createVirtualDisplay() {
         Log.i(TAG, "createVirtualDisplay " + mWidth + "x" + mHeight +"@" + mDpi);
         return mDisplayManager.createVirtualDisplay(getDisplayName(), mWidth, mHeight, mDpi,
@@ -157,14 +166,18 @@
 
     private void startCasting(Handler handler) {
         Log.i(TAG, "Start casting...");
+        if (mVideoEncoder != null) {
+            Log.i(TAG, "Already started casting");
+            return;
+        }
         mVideoEncoder = createVideoStream(handler);
 
         if (mVirtualDisplay == null) {
             mVirtualDisplay = createVirtualDisplay();
         }
+
         mVirtualDisplay.setSurface(mVideoEncoder.createInputSurface());
         mVideoEncoder.start();
-
         Log.i(TAG, "Video encoder started");
     }
 
@@ -194,6 +207,8 @@
             public void onError(@NonNull MediaCodec codec, @NonNull CodecException e) {
                 Log.e(TAG, "onError, codec: " + codec, e);
                 mCounter.bufferErrors++;
+                stopCasting();
+                startCasting(handler);
             }
 
             @Override
@@ -242,43 +257,28 @@
     }
 
     private void sendFrame(byte[] buf, int len) {
-        try {
-            if (mOutputStream != null) {
-                mOutputStream.write(buf, 0, len);
-            }
-        } catch (IOException e) {
-            mCounter.clientsDisconnected++;
-            mOutputStream = null;
-            Log.e(TAG, "Failed to write data to socket, restart casting", e);
-            restart();
+        if (mActiveThread != null) {
+            mActiveThread.send(buf, len);
         }
     }
 
     private void stopCasting() {
         Log.i(TAG, "Stopping casting...");
-        if (mServerSocket != null) {
-            try {
-                mServerSocket.close();
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to close server socket, ignoring", e);
-            }
-            mServerSocket = null;
-        }
 
         if (mVirtualDisplay != null) {
-            // We do not want to destroy virtual display (as it will also destroy all the
-            // activities on that display, instead we will turn off the display by setting
-            // a null surface.
             Surface surface = mVirtualDisplay.getSurface();
             if (surface != null) surface.release();
-            mVirtualDisplay.setSurface(null);
         }
 
         if (mVideoEncoder != null) {
             // Releasing encoder as stop/start didn't work well (couldn't create or reuse input
             // surface).
-            mVideoEncoder.stop();
-            mVideoEncoder.release();
+            try {
+                mVideoEncoder.stop();
+                mVideoEncoder.release();
+            } catch (IllegalStateException e) {
+                // do nothing, already released
+            }
             mVideoEncoder = null;
         }
         Log.i(TAG, "Casting stopped");
@@ -287,14 +287,17 @@
     private synchronized void restart() {
         // This method could be called from different threads when receiver has disconnected.
         if (mHandler.hasMessages(MSG_START)) return;
-
         mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP));
         mHandler.sendMessage(Message.obtain(mHandler, MSG_START));
     }
 
-    private class NetworkThreadHandler extends Handler {
+    private class BroadcastThreadHandler extends Handler {
+        private static final int MAX_FAIL_COUNT = 10;
+        private int mFailConnectCounter;
+        private SocketThread mSocketThread;
+        private PipeThread mPipeThread;
 
-        NetworkThreadHandler(Looper looper) {
+        BroadcastThreadHandler(Looper looper) {
             super(looper);
         }
 
@@ -302,34 +305,102 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_START:
-                    if (mServerSocket == null) {
-                        mServerSocket = openServerSocket();
+                    Log.i(TAG, "Received start message");
+                    // Failure to connect to either pipe or network returns null
+                    if (mActiveThread == null) {
+                        mActiveThread = tryPipeConnect();
                     }
-                    Log.i(TAG, "Server socket opened");
-
-                    mOutputStream = waitForReceiver(mServerSocket);
-                    if (mOutputStream == null) {
-                        sendMessage(Message.obtain(this, MSG_START));
+                    if (mActiveThread == null) {
+                        mActiveThread = tryNetworkConnect();
+                    }
+                    if (mActiveThread == null) {
+                        // When failed attempt limit is reached, clean up and quit this thread.
+                        mFailConnectCounter++;
+                        if (mFailConnectCounter >= MAX_FAIL_COUNT) {
+                            Log.e(TAG, "Too many failed connection attempts; aborting");
+                            release();
+                            throw new RuntimeException("Abort after failed connection attempts");
+                        }
+                        mHandler.sendMessage(Message.obtain(mHandler, MSG_START));
                         break;
                     }
+                    mFailConnectCounter = 0;
                     mCounter.clientsConnected++;
-
+                    mActiveThread.start();
                     startCasting(this);
                     break;
 
                 case MSG_STOP:
+                    Log.i(TAG, "Received stop message");
                     stopCasting();
+                    mCounter.clientsDisconnected++;
+                    if (mActiveThread != null) {
+                        mActiveThread.close();
+                        try {
+                            mActiveThread.join();
+                        } catch (InterruptedException e) {
+                            Log.e(TAG, "Waiting for active thread to close failed", e);
+                        }
+                        mActiveThread = null;
+                    }
                     break;
 
                 case MSG_SEND_FRAME:
-                    if (mServerSocket != null && mOutputStream != null) {
-                        sendFrame(mBuffer, mLastFrameLength);
+                    if (mActiveThread == null) {
+                        // Stop the chaining signal if there's no client to send to
+                        break;
                     }
+                    sendFrame(mBuffer, mLastFrameLength);
                     // We will keep sending last frame every second as a heartbeat.
                     sendFrameAsync(1000L);
                     break;
             }
         }
+
+        // Returns null if can't establish pipe connection
+        // Otherwise returns the corresponding client thread
+        private PipeThread tryPipeConnect() {
+            try {
+                RandomAccessFile pipe = new RandomAccessFile(PIPE_DEVICE, "rw");
+                byte[] temp = new byte[PIPE_NAME.length() + 1];
+                temp[PIPE_NAME.length()] = 0;
+                System.arraycopy(PIPE_NAME.getBytes(), 0, temp, 0, PIPE_NAME.length());
+                pipe.write(temp);
+
+                // At this point, the pipe exists, so we will just wait for a start signal
+                // This is in case pipe still sends leftover stops from last instantiation
+                int signal = pipe.read();
+                while (signal != PIPE_START) {
+                    Log.i(TAG, "Received non-start signal: " + signal);
+                    signal = pipe.read();
+                }
+                return new PipeThread(mHandler, pipe);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to establish pipe connection", e);
+                return null;
+            }
+        }
+
+        // Returns null if can't establish network connection
+        // Otherwise returns the corresponding client thread
+        private SocketThread tryNetworkConnect() {
+            try {
+                ServerSocket serverSocket = new ServerSocket(PORT);
+                Log.i(TAG, "Server socket opened");
+                Socket socket = serverSocket.accept();
+                socket.setTcpNoDelay(true);
+                socket.setKeepAlive(true);
+                socket.setSoLinger(true, 0);
+
+                InputStream inputStream = socket.getInputStream();
+                OutputStream outputStream = socket.getOutputStream();
+
+                return new SocketThread(mHandler, serverSocket, inputStream, outputStream);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to establish network connection", e);
+                return null;
+            }
+        }
     }
 
     private static void configureVideoEncoder(MediaCodec codec, int width, int height) {
@@ -349,51 +420,12 @@
         codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
     }
 
-    private OutputStream waitForReceiver(ServerSocket serverSocket) {
-        try {
-            Log.i(TAG, "Listening for incoming connections on port: " + PORT);
-            Socket socket = serverSocket.accept();
-            socket.setTcpNoDelay(true);
-            socket.setKeepAlive(true);
-            socket.setSoLinger(true, 0);
-
-            Log.i(TAG, "Receiver connected: " + socket);
-            listenReceiverDisconnected(socket.getInputStream());
-
-            return socket.getOutputStream();
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to accept connection");
-            return null;
-        }
-    }
-
-    private void listenReceiverDisconnected(InputStream inputStream) {
-        new Thread(() -> {
-            try {
-                if (inputStream.read() == -1) throw new IOException();
-            } catch (IOException e) {
-                Log.w(TAG, "Receiver has disconnected", e);
-            }
-            restart();
-        }).start();
-    }
-
-    private static ServerSocket openServerSocket() {
-        try {
-            return new ServerSocket(PORT);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to create server socket", e);
-            throw new RuntimeException(e);
-        }
-    }
-
     @Override
     public String toString() {
         return getClass() + "{"
-                + mServerSocket
-                +", receiver connected: " + (mOutputStream != null)
-                +", encoder: " + mVideoEncoder
-                +", virtualDisplay" + mVirtualDisplay
+                + ", receiver connected: " + (mActiveThread != null)
+                + ", encoder: " + mVideoEncoder
+                + ", virtualDisplay" + mVirtualDisplay
                 + "}";
     }
 
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PipeThread.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PipeThread.java
new file mode 100644
index 0000000..c96f482
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PipeThread.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 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.
+ */
+package android.car.cluster.sample;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Thread that can send data to the emulator using a qemud service.
+ */
+public class PipeThread extends SenderThread {
+    private static final String TAG = "Cluster." + PipeThread.class.getSimpleName();
+
+    private RandomAccessFile mPipe;
+
+    /**
+     * Creates instance of pipe thread that can write to given pipe file.
+     *
+     * @param handler {@link Handler} used to message broadcaster.
+     * @param pipe {@link RandomAccessFile} file already connected to pipe.
+     */
+    PipeThread(Handler handler, RandomAccessFile pipe) {
+        super(handler);
+        mPipe = pipe;
+    }
+
+    public void run() {
+        try {
+            int signal = mPipe.read();
+            while (signal != NetworkedVirtualDisplay.PIPE_STOP) {
+                Log.i(TAG, "Received non-stop signal: " + signal);
+                signal = mPipe.read();
+            }
+            restart();
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read from pipe");
+            restart();
+        }
+    }
+
+    @Override
+    public void send(byte[] buf, int len) {
+        try {
+            // First sends the size prior to sending the data, since receiving side only sees
+            // the size of the buffer, which could be significant larger than the actual data.
+            mPipe.write(ByteBuffer.allocate(4)
+                          .order(ByteOrder.LITTLE_ENDIAN).putInt(len).array());
+            mPipe.write(buf);
+        } catch (IOException e) {
+            Log.e(TAG, "Write to pipe failed");
+            restart();
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            mPipe.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to close pipe", e);
+        }
+    }
+}
+
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SenderThread.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SenderThread.java
new file mode 100644
index 0000000..2c7f0d6
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SenderThread.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 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.
+ */
+package android.car.cluster.sample;
+import android.os.Handler;
+import android.os.Message;
+
+/**
+ * This class serves as a template for sending to specific clients of the broadcaster.
+ */
+public abstract class SenderThread extends Thread {
+    private Handler mHandler;
+
+    SenderThread(Handler handler) {
+        mHandler = handler;
+    }
+
+    abstract void send(byte[] buf, int len);
+    abstract void close();
+
+    /**
+     * Tells the broadcasting thread to stop and close everything in progress, and start over again.
+     * It will kill the current instance of this thread, and produce a new one.
+     */
+    void restart() {
+        if (mHandler.hasMessages(NetworkedVirtualDisplay.MSG_START)) return;
+        mHandler.sendMessage(Message.obtain(mHandler, NetworkedVirtualDisplay.MSG_STOP));
+        mHandler.sendMessage(Message.obtain(mHandler, NetworkedVirtualDisplay.MSG_START));
+    }
+}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SocketThread.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SocketThread.java
new file mode 100644
index 0000000..a5215b0
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SocketThread.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 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.
+ */
+package android.car.cluster.sample;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+
+/**
+ * The thread that will send data on an opened socket.
+ */
+public class SocketThread extends SenderThread {
+    private static final String TAG = "Cluster." + SocketThread.class.getSimpleName();
+    private ServerSocket mServerSocket;
+    private OutputStream mOutputStream;
+    private InputStream mInputStream;
+
+    /**
+     * Create instance of thread that can write to given open socket.
+     *
+     * @param handler {@link Handler} used to message the broadcaster.
+     * @param serverSocket {@link ServerSocket} should be already opened.
+     * @param inputStream {@link InputStream} corresponding to opened socket.
+     * @param outputStream {@link OutputStream} corresponding to opened socket.
+     */
+    SocketThread(Handler handler, ServerSocket serverSocket, InputStream inputStream,
+                    OutputStream outputStream) {
+        super(handler);
+        mServerSocket = serverSocket;
+        mInputStream = inputStream;
+        mOutputStream = outputStream;
+    }
+
+    public void run() {
+        try {
+            // This read should block until something disconnects (or something
+            // similar) which should cause an exception, in which case we should
+            // try to setup again and reconnect
+            mInputStream.read();
+        } catch (IOException e) {
+            Log.e(TAG, "Socket thread disconnected.");
+        }
+        restart();
+    }
+
+    @Override
+    public void send(byte[] buf, int len) {
+        try {
+            mOutputStream.write(buf, 0, len);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to write data to socket, retrying connection");
+            restart();
+        }
+    }
+
+    @Override
+    public void close() {
+        if (mServerSocket != null) {
+            try {
+                mServerSocket.close();
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to close server socket, ignoring");
+            }
+            mServerSocket = null;
+        }
+        mInputStream = null;
+        mOutputStream = null;
+    }
+}
+
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
new file mode 100644
index 0000000..54cc7f8
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 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.
+ */
+package android.car.cluster.sample.sensors;
+
+import android.car.VehiclePropertyType;
+import android.car.hardware.CarPropertyValue;
+
+import java.util.function.Function;
+
+/**
+ * Description of a car sensor. It can be used as identifier at
+ * {@link android.car.cluster.sample.ClusterViewModel#getSensor(Sensor)} to obtain a
+ * {@link androidx.lifecycle.LiveData} to track values of this sensor. The sensor description is
+ * used to process and decode the information reported by the car.
+ * <p>
+ * All instances of this class must be obtained from {@link Sensors}.
+ *
+ * @param <T> data type used by this sensor.
+ */
+public class Sensor<T> {
+    /** Name of the sensor (for debugging) */
+    public final String mName;
+    /** VHAL identifier of this sensor */
+    public final int mPropertyId;
+    /** VHAL area associated with this sensor (each area is reported as an independent sensor) */
+    public final int mAreaId;
+    /**
+     * Data type expected to be reported by the VHAL. If the values received don't match with the
+     * expected ones, we warn about it and ignore the value.
+     */
+    @VehiclePropertyType.Enum
+    public final int mExpectedPropertyType;
+    /** VHAL Area associated with this sensor. */
+    public final Function<CarPropertyValue<?>, T> mAdapter;
+
+    /**
+     * Creates a new sensor. Only {@link Sensors} should use this constructor.
+     */
+    Sensor(String name, int propertyId, int areaId, int expectedPropertyType,
+            Function<CarPropertyValue<?>, T> adapter) {
+        mName = name;
+        mPropertyId = propertyId;
+        mAreaId = areaId;
+        mExpectedPropertyType = expectedPropertyType;
+        mAdapter = adapter;
+    }
+}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
new file mode 100644
index 0000000..dd7566e
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensors.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 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.
+ */
+package android.car.cluster.sample.sensors;
+
+import android.car.VehiclePropertyIds;
+import android.car.VehiclePropertyType;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.CarSensorEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * The collection of all sensors supported by this application.
+ */
+public class Sensors {
+    /** Area identifier used for sensors corresponding to global VHAL properties */
+    public static final int GLOBAL_AREA_ID = -1;
+
+    private static Sensors sInstance;
+    private static List<Sensor<?>> sSensors = new ArrayList<>();
+    private Map<Integer, List<Sensor<?>>> mSensorsByPropertyId = new HashMap<>();
+
+    /** Possible values of the {@link #SENSOR_GEAR} sensor */
+    public enum Gear {
+        NEUTRAL,
+        REVERSE,
+        DRIVE,
+        PARK,
+    }
+
+    /** Fuel of the car, measured in millimeters */
+    public static final Sensor<Float> SENSOR_FUEL = registerSensor(
+            "Fuel", VehiclePropertyIds.FUEL_LEVEL, GLOBAL_AREA_ID, VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Fuel capacity of the car, measured in millimeters */
+    public static final Sensor<Float> SENSOR_FUEL_CAPACITY = registerSensor(
+            "Fuel Capacity", VehiclePropertyIds.INFO_FUEL_CAPACITY, GLOBAL_AREA_ID,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** RPMs */
+    public static final Sensor<Float> SENSOR_RPM = registerSensor(
+            "RPM", VehiclePropertyIds.ENGINE_RPM, GLOBAL_AREA_ID,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Fuel range in kilometers */
+    public static final Sensor<Float> SENSOR_FUEL_RANGE = registerSensor(
+            "Fuel Range", VehiclePropertyIds.RANGE_REMAINING, GLOBAL_AREA_ID,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Speed in kph */
+    public static final Sensor<Float> SENSOR_SPEED = registerSensor(
+            "Speed", VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Current gear of the car */
+    public static final Sensor<Gear> SENSOR_GEAR = registerSensor(
+            "Gear", VehiclePropertyIds.CURRENT_GEAR, GLOBAL_AREA_ID, VehiclePropertyType.INT32,
+            value -> {
+                if (value == null) {
+                    return null;
+                }
+                Integer gear = (Integer) value.getValue();
+                if ((gear & CarSensorEvent.GEAR_REVERSE) != 0) {
+                    return Gear.REVERSE;
+                } else if ((gear & CarSensorEvent.GEAR_NEUTRAL) != 0) {
+                    return Gear.NEUTRAL;
+                } else if ((gear & CarSensorEvent.GEAR_DRIVE) != 0) {
+                    return Gear.DRIVE;
+                } else if ((gear & CarSensorEvent.GEAR_PARK) != 0) {
+                    return Gear.PARK;
+                } else {
+                    return null;
+                }
+            });
+
+    private static <T> Sensor<T> registerSensor(String propertyName, int propertyId, int areaId,
+            int expectedPropertyType, Function<CarPropertyValue<?>, T> adapter) {
+        Sensor<T> sensor = new Sensor<>(propertyName, propertyId, areaId, expectedPropertyType,
+                adapter);
+        sSensors.add(sensor);
+        return sensor;
+    }
+
+    /**
+     * Obtains the singleton instance of this class
+     */
+    public static Sensors getInstance() {
+        if (sInstance == null) {
+            sInstance = new Sensors();
+        }
+        return sInstance;
+    }
+
+    private Sensors() {
+        initializeSensorsMap();
+    }
+
+    private void initializeSensorsMap() {
+        for (Sensor<?> sensorId : getSensors()) {
+            mSensorsByPropertyId
+                    .computeIfAbsent(sensorId.mPropertyId, (id) -> new ArrayList<>())
+                    .add(sensorId);
+        }
+    }
+
+    /**
+     * Returns all sensors.
+     */
+    public List<Sensor<?>> getSensors() {
+        return sSensors;
+    }
+
+    /**
+     * Returns all sensors associated to the given VHAL property id.
+     */
+    public List<Sensor<?>> getSensorsForPropertyId(int propertyId) {
+        return mSensorsByPropertyId.get(propertyId);
+    }
+
+    /**
+     * Returns all property ids we care about.
+     */
+    public Set<Integer> getPropertyIds() {
+        return mSensorsByPropertyId.keySet();
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index c400bd8..798e6b4 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
     <uses-permission android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"/>
     <uses-permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"/>
+    <uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
     <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
@@ -97,10 +98,9 @@
                   android:launchMode="singleInstance"
                   android:resizeableActivity="true"
                   android:allowEmbedded="true"
-                  android:permission="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL">
+                  android:permission="android.car.permission.CAR_DISPLAY_IN_CLUSTER">
             <intent-filter android:priority="-1">
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.DEFAULT"/>
                 <category android:name="android.car.cluster.NAVIGATION"/>
             </intent-filter>
         </activity>
@@ -108,7 +108,7 @@
         <activity android:name=".activityview.ActivityViewTestFragment"/>
 
         <!-- temporary solution until b/68882625 is fixed. -->
-        <receiver android:name=".touchsound.DisableTouchSoundOnBoot"  android:exported="true">
+        <receiver android:name=".touchsound.DisableTouchSoundOnBoot" android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
index 03a90e0..5818216 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
@@ -17,28 +17,30 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
-    <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:id="@+id/input_buttons">
-        <!-- Filled at runtime. -->
-    </LinearLayout>
     <ScrollView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:layout_marginLeft="15dp"
-        android:layout_marginRight="15dp"
-        android:layout_marginTop="20dp"
         android:fillViewport="true">
-        <TextView
-                android:layout_width="match_parent"
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <LinearLayout
+                android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:gravity="bottom"
-                android:scrollbars="vertical"
-                android:textSize="24px"
-                android:typeface="monospace"
-                android:id="@+id/events_list"/>
+                android:orientation="vertical"
+                android:id="@+id/input_buttons">
+                <!-- Filled at runtime. -->
+            </LinearLayout>
+            <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="bottom"
+                    android:scrollbars="vertical"
+                    android:textSize="24px"
+                    android:typeface="monospace"
+                    android:id="@+id/events_list"/>
+        </LinearLayout>
     </ScrollView>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
index f58af8f..d244904 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -13,40 +13,47 @@
      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:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout_marginTop="40dp"
-              android:layout_marginStart="40dp">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:layout_marginTop="40dp"
+    android:layout_marginStart="40dp"
+    android:layout_marginEnd="40dp">
 
     <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
             <Button
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:layout_weight="1"
-                    android:text="@string/cluster_start"
-                    android:id="@+id/cluster_start_button"/>
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:padding="20dp"
+                android:text="@string/cluster_start"
+                android:id="@+id/cluster_start_button"/>
             <Button
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:layout_weight="1"
-                    android:text="@string/cluster_turn_left"
-                    android:id="@+id/cluster_turn_left_button"/>
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:padding="20dp"
+                android:text="@string/cluster_start_guidance"
+                android:id="@+id/cluster_turn_left_button"/>
             <Button
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:layout_weight="1"
-                    android:text="@string/cluster_stop"
-                    android:id="@+id/cluster_stop_button"/>
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dp"
+                android:padding="20dp"
+                android:text="@string/cluster_stop"
+                android:id="@+id/cluster_stop_button"/>
             <Button
-                    android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:layout_weight="1"
-                    android:text="@string/cluster_start_activity"
-                    android:id="@+id/cluster_start_activity"/>
+                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/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 35d3ec2..15f2afc 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -126,11 +126,14 @@
     <string name="open_kb_button">Hide/Show Input</string>
 
     <!-- instrument cluster -->
-    <string name="cluster_start">Start metadata</string>
-    <string name="cluster_turn_left">Send turn-by-turn</string>
-    <string name="cluster_stop">Stop metadata</string>
+    <string name="cluster_start">Request focus</string>
+    <string name="cluster_start_guidance">Start turn-by-turn</string>
+    <string name="cluster_stop">Abandon focus</string>
+    <string name="cluster_stop_guidance">Stop turn-by-turn</string>
     <string name="cluster_nav_app_context_loss">Navigation app context lost!</string>
     <string name="cluster_start_activity">Start Nav Activity</string>
+    <string name="cluster_start_activity_failed">Failed to start activity in cluster</string>
+    <string name="cluster_not_started">Missing navigation focus</string>
 
     <!--  input test -->
     <string name="volume_up">Volume +</string>
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 ad4b93a..7e40b5b 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
@@ -15,7 +15,7 @@
  */
 package com.google.android.car.kitchensink.cluster;
 
-import android.app.AlertDialog;
+import android.annotation.Nullable;
 import android.car.Car;
 import android.car.CarAppFocusManager;
 import android.car.CarNotConnectedException;
@@ -31,14 +31,15 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 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.KitchenSinkActivity;
 import com.google.android.car.kitchensink.R;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -47,10 +48,8 @@
 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.
@@ -65,35 +64,61 @@
     private Car mCarApi;
     private Timer mTimer;
     private NavigationState[] mNavStateData;
+    private Button mTurnByTurnButton;
 
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                Log.d(TAG, "Connected to Car Service");
-                try {
-                    mCarNavigationStatusManager =
-                            (CarNavigationStatusManager) mCarApi.getCarManager(
-                                    Car.CAR_NAVIGATION_SERVICE);
-                    mCarAppFocusManager =
-                        (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
-                } catch (CarNotConnectedException e) {
-                    Log.e(TAG, "Car is not connected!", e);
-                }
+    private ServiceConnection mCarServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, "Connected to Car Service");
+            try {
+                mCarNavigationStatusManager = (CarNavigationStatusManager) mCarApi
+                        .getCarManager(Car.CAR_NAVIGATION_SERVICE);
+                mCarAppFocusManager = (CarAppFocusManager) mCarApi
+                        .getCarManager(Car.APP_FOCUS_SERVICE);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!", e);
             }
+        }
 
-            @Override
-            public void onServiceDisconnected(ComponentName name) {
-                Log.d(TAG, "Disconnect from Car Service");
-            }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, "Disconnect from Car Service");
+        }
     };
 
+    private final CarAppFocusManager.OnAppFocusOwnershipCallback mFocusCallback =
+            new CarAppFocusManager.OnAppFocusOwnershipCallback() {
+        @Override
+        public void onAppFocusOwnershipLost(@CarAppFocusManager.AppFocusType int appType) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onAppFocusOwnershipLost, appType: " + appType);
+            }
+            Toast.makeText(getContext(), getText(R.string.cluster_nav_app_context_loss),
+                    Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void onAppFocusOwnershipGranted(@CarAppFocusManager.AppFocusType int appType) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onAppFocusOwnershipGranted, appType: " + appType);
+            }
+        }
+    };
+    private CarAppFocusManager.OnAppFocusChangedListener mOnAppFocusChangedListener =
+            (appType, active) -> {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "onAppFocusChanged, appType: " + appType + " active: " + active);
+                }
+            };
+
+
     private void initCarApi() {
         if (mCarApi != null && mCarApi.isConnected()) {
             mCarApi.disconnect();
             mCarApi = null;
         }
 
-        mCarApi = Car.createCar(getContext(), mServiceConnection);
+        mCarApi = Car.createCar(getContext(), mCarServiceConnection);
         mCarApi.connect();
     }
 
@@ -122,7 +147,7 @@
         InputStream inputStream = getResources().openRawResource(resId);
         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
         StringBuilder builder = new StringBuilder();
-        for (String line = null; (line = reader.readLine()) != null; ) {
+        for (String line; (line = reader.readLine()) != null; ) {
             builder.append(line).append("\n");
         }
         return builder.toString();
@@ -135,9 +160,12 @@
         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 -> toogleSendTurn());
+        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());
+
         return view;
     }
 
@@ -159,14 +187,15 @@
             return;
         }
 
-        // Implicit intent
+        // 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(), "Failed to start activity in cluster",
+            Toast.makeText(getContext(), getText(R.string.cluster_start_activity_failed),
                     Toast.LENGTH_LONG).show();
             return;
         }
@@ -175,32 +204,48 @@
     /**
      * Enables/disables sending turn-by-turn data through the {@link CarNavigationStatusManager}
      */
-    private void toogleSendTurn() {
+    private void toggleSendTurn() {
         // 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);
+            startSendTurn();
         } else {
+            stopSendTurn();
+        }
+    }
+
+    private void startSendTurn() {
+        if (mTimer != null) {
+            stopSendTurn();
+        }
+        if (!hasFocus()) {
+            Toast.makeText(getContext(), getText(R.string.cluster_not_started), Toast.LENGTH_LONG)
+                    .show();
+            return;
+        }
+        mTimer = new Timer();
+        mTimer.schedule(new TimerTask() {
+            private int mPos;
+
+            @Override
+            public void run() {
+                sendTurn(mNavStateData[mPos]);
+                mPos = (mPos + 1) % mNavStateData.length;
+            }
+        }, 0, 1000);
+        mTurnByTurnButton.setText(R.string.cluster_stop_guidance);
+    }
+
+    private void stopSendTurn() {
+        if (mTimer != null) {
             mTimer.cancel();
             mTimer = null;
         }
+        mTurnByTurnButton.setText(R.string.cluster_start_guidance);
     }
 
     /**
@@ -218,55 +263,44 @@
     }
 
     private void initCluster() {
-        try {
-            mCarAppFocusManager
-                    .addFocusListener(new CarAppFocusManager.OnAppFocusChangedListener() {
-                        @Override
-                        public void onAppFocusChanged(int appType, boolean active) {
-                            Log.d(TAG, "onAppFocusChanged, appType: " + appType + " active: "
-                                    + active);
-                        }
-                    }, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to register focus listener", e);
+        if (hasFocus()) {
+            return;
         }
-
-        CarAppFocusManager.OnAppFocusOwnershipCallback
-                focusCallback = new CarAppFocusManager.OnAppFocusOwnershipCallback() {
-            @Override
-            public void onAppFocusOwnershipLost(int focus) {
-                Log.w(TAG, "onAppFocusOwnershipLost, focus: " + focus);
-                new AlertDialog.Builder(getContext())
-                        .setTitle(getContext().getApplicationInfo().name)
-                        .setMessage(R.string.cluster_nav_app_context_loss)
-                        .show();
-            }
-
-            @Override
-            public void onAppFocusOwnershipGranted(int focus) {
-                Log.w(TAG, "onAppFocusOwnershipGranted, focus: " + focus);
-            }
-
-        };
         try {
+            mCarAppFocusManager.addFocusListener(mOnAppFocusChangedListener,
+                    CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
             mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
-                    focusCallback);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to set active focus", e);
-        }
-
-        try {
-            boolean ownsFocus = mCarAppFocusManager.isOwningFocus(
-                    focusCallback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
-            Log.d(TAG, "Owns APP_FOCUS_TYPE_NAVIGATION: " + ownsFocus);
-            if (!ownsFocus) {
+                    mFocusCallback);
+            if (!hasFocus()) {
                 throw new RuntimeException("Focus was not acquired.");
             }
         } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to get owned focus", e);
+            Log.e(TAG, "Failed to set active focus", e);
         }
     }
 
+    private boolean hasFocus() {
+        try {
+            boolean ownsFocus = mCarAppFocusManager.isOwningFocus(mFocusCallback,
+                    CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Owns APP_FOCUS_TYPE_NAVIGATION: " + ownsFocus);
+            }
+            return ownsFocus;
+        } catch (CarNotConnectedException e) {
+            Log.e(TAG, "Failed to get owned focus", e);
+            return false;
+        }
+    }
+
+    private void stopCluster() {
+        stopSendTurn();
+        mCarAppFocusManager.removeFocusListener(mOnAppFocusChangedListener,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        mCarAppFocusManager.abandonAppFocus(mFocusCallback,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+    }
+
     @Override
     public void onResume() {
         super.onResume();