[automerger skipped] DO NOT MERGE - Merge RQ3A.210605.005 am: d6d0dc6f7d -s ours am: ae43483dcd -s ours

am skip reason: Merged-In I630bdfee09afa81be4490b961d66dde18de0b6b7 with SHA-1 9ac71a47fe is already in history

Original change: https://android-review.googlesource.com/c/platform/packages/apps/Car/Cluster/+/1741558

Change-Id: I02cf9ede2c4cdaef26803d37710c38584aae952b
diff --git a/ClusterHomeSample/Android.bp b/ClusterHomeSample/Android.bp
new file mode 100644
index 0000000..52003a5
--- /dev/null
+++ b/ClusterHomeSample/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "ClusterHomeSample",
+
+    // Only compile source java files in this apk.
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "android.car",
+    ],
+
+    required: ["allowed_privapp_com.android.car.cluster.home"],
+
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
diff --git a/ClusterHomeSample/AndroidManifest.xml b/ClusterHomeSample/AndroidManifest.xml
new file mode 100644
index 0000000..1142467
--- /dev/null
+++ b/ClusterHomeSample/AndroidManifest.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.cluster.home">
+
+    <!-- for ActivityManager.getCurrentUser() -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <!-- for IActivityManager.registerTaskStackListener() -->
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <!-- for ClusterHomeManager -->
+    <uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
+    <!-- for CarInputManager.requestInputEventCapture() -->
+    <uses-permission android:name="android.car.permission.CAR_MONITOR_INPUT"/>
+
+    <!--
+        android:directBootAware to start the application regardless of user-unlocking.
+    -->
+    <application android:name=".ClusterHomeApplication"
+            android:label="ClusterHomeSample"
+            android:directBootAware="true">
+        <!-- android:showForAllUsers to keep the Activity regardless of user-switching -->
+        <activity android:name=".ClusterHomeActivity"
+                android:exported="true"
+                android:showForAllUsers="true"
+                android:excludeFromRecents="true"
+                android:screenOrientation="nosensor"
+                android:launchMode="singleTask"
+                android:configChanges="uiMode|mcc|mnc"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".FakeFreeNavigationActivity"
+                android:exported="true"
+                android:showForAllUsers="true"
+                android:excludeFromRecents="true"
+                android:screenOrientation="nosensor"
+                android:launchMode="singleTask"
+                android:configChanges="uiMode|mcc|mnc"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".ClusterPhoneActivity"
+                android:exported="true"
+                android:showForAllUsers="true"
+                android:excludeFromRecents="true"
+                android:screenOrientation="nosensor"
+                android:launchMode="singleTask"
+                android:configChanges="uiMode|mcc|mnc"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".ClusterMusicActivity"
+                android:exported="true"
+                android:showForAllUsers="true"
+                android:excludeFromRecents="true"
+                android:screenOrientation="nosensor"
+                android:launchMode="singleTask"
+                android:configChanges="uiMode|mcc|mnc"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/ClusterHomeSample/res/layout/activity_fake_free_navigation.xml b/ClusterHomeSample/res/layout/activity_fake_free_navigation.xml
new file mode 100644
index 0000000..cfdf78e
--- /dev/null
+++ b/ClusterHomeSample/res/layout/activity_fake_free_navigation.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2021 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/unobscuredArea"
+        android:alpha="0.25"
+        android:background="@android:color/white"
+        android:layout_height="0dp"
+        android:layout_width="0dp"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Free Navigation PlaceHolder"
+            android:textSize="@dimen/title_text_size" />
+
+        <ProgressBar
+            android:id="@+id/indeterminateBar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/ClusterHomeSample/res/layout/cluster_home_activity.xml b/ClusterHomeSample/res/layout/cluster_home_activity.xml
new file mode 100644
index 0000000..e2fbdd0
--- /dev/null
+++ b/ClusterHomeSample/res/layout/cluster_home_activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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/text"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:textSize="18sp"
+    android:autoText="true"
+    android:capitalize="sentences"
+    android:gravity="center"
+    android:text="@string/cluster_home_text" />
diff --git a/ClusterHomeSample/res/values/config.xml b/ClusterHomeSample/res/values/config.xml
new file mode 100644
index 0000000..c54d0fd
--- /dev/null
+++ b/ClusterHomeSample/res/values/config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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 to configure based on each OEM's preference. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!--  Activity to present for free navigation -->
+    <string name="config_clusterMapActivity" translatable="false">
+        com.android.car.cluster.home/.FakeFreeNavigationActivity
+    </string>
+    <!--  Activity to present for Music application -->
+    <string name="config_clusterMusicActivity" translatable="false">
+        com.android.car.cluster.home/.ClusterMusicActivity
+    </string>
+    <!--  Activity to present for Phone application -->
+    <string name="config_clusterPhoneActivity" translatable="false">
+        com.android.car.cluster.home/.ClusterPhoneActivity
+    </string>
+</resources>
diff --git a/ClusterHomeSample/res/values/dimens.xml b/ClusterHomeSample/res/values/dimens.xml
new file mode 100644
index 0000000..eca0d19
--- /dev/null
+++ b/ClusterHomeSample/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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>
+    <!-- Navigation state components  -->
+    <dimen name="title_text_size">40sp</dimen>
+</resources>
diff --git a/ClusterHomeSample/res/values/overlayable.xml b/ClusterHomeSample/res/values/overlayable.xml
new file mode 100644
index 0000000..f976088
--- /dev/null
+++ b/ClusterHomeSample/res/values/overlayable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="ClusterHomeConfig">
+        <policy type="product|system|vendor">
+            <item type="string" name="config_clusterMapActivity" />
+            <item type="string" name="config_clusterMusicActivity" />
+            <item type="string" name="config_clusterPhoneActivity" />
+        </policy>
+    </overlayable>
+</resources>
diff --git a/ClusterHomeSample/res/values/strings.xml b/ClusterHomeSample/res/values/strings.xml
new file mode 100644
index 0000000..0b8eda9
--- /dev/null
+++ b/ClusterHomeSample/res/values/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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="cluster_home_text" translatable="false">
+        Welcome, ClusterHome.
+    </string>
+    <string name="cluster_phone_text" translatable="false">
+        Hello, ClusterPhone?
+    </string>
+    <string name="cluster_music_text" translatable="false">
+        Enjoy, ClusterMusic!
+    </string>
+</resources>
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeActivity.java b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeActivity.java
new file mode 100644
index 0000000..bc05beb
--- /dev/null
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.cluster.home;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.car.Car;
+import android.car.cluster.ClusterActivityState;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Skeleton Activity for Home UI in Cluster display.
+ */
+public class ClusterHomeActivity extends Activity {
+    private static final String TAG = ClusterHomeActivity.class.getSimpleName();
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        View view = getLayoutInflater().inflate(R.layout.cluster_home_activity, /* root= */ null);
+        setContentView(view);
+        logIntent(getIntent());
+   }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        logIntent(intent);
+    }
+
+    private static void logIntent(Intent intent) {
+        Log.d(TAG, "Got Intent=" + intent);
+        if (intent.hasExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE)) {
+            Bundle extra = intent.getBundleExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+            ClusterActivityState activityState = ClusterActivityState.fromBundle(extra);
+            Log.d(TAG, ">> ClusterActivityState=" + activityState);
+        }
+    }
+}
\ No newline at end of file
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
new file mode 100644
index 0000000..dc4fdc1
--- /dev/null
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2021 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.cluster.home;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.car.CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
+import static android.car.cluster.ClusterHomeManager.ClusterHomeCallback;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_HOME;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_NONE;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static android.content.Intent.ACTION_MAIN;
+import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.Application;
+import android.app.IActivityTaskManager;
+import android.app.TaskInfo;
+import android.app.TaskStackListener;
+import android.car.Car;
+import android.car.CarOccupantZoneManager;
+import android.car.cluster.ClusterActivityState;
+import android.car.cluster.ClusterHomeManager;
+import android.car.cluster.ClusterState;
+import android.car.input.CarInputManager;
+import android.car.input.CarInputManager.CarInputCaptureCallback;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleListener;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.Display;
+import android.view.KeyEvent;
+
+import java.util.List;
+
+public final class ClusterHomeApplication extends Application {
+    public static final String TAG = "ClusterHome";
+    private static final boolean DBG = false;
+    private static final int UI_TYPE_HOME = UI_TYPE_CLUSTER_HOME;
+    private static final int UI_TYPE_MAPS = UI_TYPE_HOME + 1;
+    private static final int UI_TYPE_MUSIC = UI_TYPE_HOME + 2;
+    private static final int UI_TYPE_PHONE = UI_TYPE_HOME + 3;
+
+    private static final byte HOME_AVAILABILITY = 1;
+    private static final byte MAPS_AVAILABILITY = 1;
+    private static final byte PHONE_AVAILABILITY = 1;
+    private static final byte MUSIC_AVAILABILITY = 1;
+
+    private IActivityTaskManager mAtm;
+    private InputManager mInputManager;
+    private ClusterHomeManager mHomeManager;
+    private CarUserManager mUserManager;
+    private CarInputManager mCarInputManager;
+    private ClusterState mClusterState;
+    private byte mUiAvailability[];
+    private int mUserLifeCycleEvent = USER_LIFECYCLE_EVENT_TYPE_STARTING;
+
+    private ComponentName[] mClusterActivities;
+
+    private int mLastLaunchedUiType = UI_TYPE_CLUSTER_NONE;
+    private int mLastReportedUiType = UI_TYPE_CLUSTER_NONE;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mClusterActivities = new ComponentName[] {
+                new ComponentName(getApplicationContext(), ClusterHomeActivity.class),
+                ComponentName.unflattenFromString(
+                        getString(R.string.config_clusterMapActivity)),
+                ComponentName.unflattenFromString(
+                        getString(R.string.config_clusterMusicActivity)),
+                ComponentName.unflattenFromString(
+                        getString(R.string.config_clusterPhoneActivity)),
+        };
+        mAtm = ActivityTaskManager.getService();
+        try {
+            mAtm.registerTaskStackListener(mTaskStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception from AM", e);
+        }
+        mInputManager = getApplicationContext().getSystemService(InputManager.class);
+
+        Car.createCar(getApplicationContext(), /* handler= */ null,
+                Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+                (car, ready) -> {
+                    if (!ready) return;
+                    mHomeManager = (ClusterHomeManager) car.getCarManager(Car.CLUSTER_HOME_SERVICE);
+                    mUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+                    mCarInputManager = (CarInputManager) car.getCarManager(Car.CAR_INPUT_SERVICE);
+                    initClusterHome();
+                });
+    }
+
+    private void initClusterHome() {
+        mHomeManager.registerClusterHomeCallback(getMainExecutor(),mClusterHomeCalback);
+        mClusterState = mHomeManager.getClusterState();
+        if (!mClusterState.on) {
+            mHomeManager.requestDisplay(UI_TYPE_HOME);
+        }
+        mUiAvailability = buildUiAvailability();
+        mHomeManager.reportState(mClusterState.uiType, UI_TYPE_CLUSTER_NONE, mUiAvailability);
+        mHomeManager.registerClusterHomeCallback(getMainExecutor(), mClusterHomeCalback);
+
+        mUserManager.addListener(getMainExecutor(), mUserLifecycleListener);
+
+        int r = mCarInputManager.requestInputEventCapture(
+                DISPLAY_TYPE_INSTRUMENT_CLUSTER,
+                new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
+                CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY,
+                mInputCaptureCallback);
+        if (r != CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED) {
+            Log.e(TAG, "Failed to capture InputEvent on Cluster: r=" + r);
+        }
+
+        if (mClusterState.uiType != UI_TYPE_HOME) {
+            startClusterActivity(mClusterState.uiType);
+        }
+    }
+
+    @Override
+    public void onTerminate() {
+        mCarInputManager.releaseInputEventCapture(DISPLAY_TYPE_INSTRUMENT_CLUSTER);
+        mUserManager.removeListener(mUserLifecycleListener);
+        mHomeManager.unregisterClusterHomeCallback(mClusterHomeCalback);
+        try {
+            mAtm.unregisterTaskStackListener(mTaskStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception from AM", e);
+        }
+        super.onTerminate();
+    }
+
+    private void startClusterActivity(int uiType) {
+        if (mUserLifeCycleEvent != USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
+            Log.i(TAG, "Ignore to start Activity(" + uiType + ") during user-switching");
+            return;
+        }
+        if (mClusterState == null || mClusterState.displayId == Display.INVALID_DISPLAY) {
+            Log.w(TAG, "Cluster display is not ready");
+            return;
+        }
+        mLastLaunchedUiType = uiType;
+        ComponentName activity = mClusterActivities[uiType];
+
+        Intent intent = new Intent(ACTION_MAIN).setComponent(activity);
+        if (mClusterState.bounds != null && mClusterState.insets != null) {
+            Rect unobscured = new Rect(mClusterState.bounds);
+            unobscured.inset(mClusterState.insets);
+            ClusterActivityState state = ClusterActivityState.create(mClusterState.on, unobscured);
+            intent.putExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE, state.toBundle());
+        }
+        ActivityOptions options = ActivityOptions.makeBasic();
+
+        // This sample assumes the Activities in this package are running as the system user,
+        // and the other Activities are running as a current user.
+        int userId = ActivityManager.getCurrentUser();
+        if (getApplicationContext().getPackageName().equals(activity.getPackageName())) {
+            userId = UserHandle.USER_SYSTEM;
+        }
+        mHomeManager.startFixedActivityModeAsUser(intent, options.toBundle(), userId);
+    }
+
+    private byte[] buildUiAvailability() {
+        // TODO(b/183115088): populate uiAvailability based on the package availability
+        return new byte[] {
+                HOME_AVAILABILITY, MAPS_AVAILABILITY, PHONE_AVAILABILITY, MUSIC_AVAILABILITY
+        };
+    }
+
+    private final ClusterHomeCallback mClusterHomeCalback = new ClusterHomeCallback() {
+        @Override
+        public void onClusterStateChanged(
+                ClusterState state, @ClusterHomeManager.Config int changes) {
+            mClusterState = state;
+            if ((changes & ClusterHomeManager.CONFIG_UI_TYPE) != 0
+                    && mLastLaunchedUiType != state.uiType) {
+                startClusterActivity(state.uiType);
+            }
+        }
+        @Override
+        public void onNavigationState(byte[] navigationState) {
+            // TODO(b/173454430): handle onNavigationState
+        }
+    };
+
+    private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+        // onTaskMovedToFront isn't called when Activity-change happens within the same task.
+        @Override
+        public void onTaskStackChanged()  {
+            getMainExecutor().execute(ClusterHomeApplication.this::handleTaskStackChanged);
+        }
+    };
+
+    private void handleTaskStackChanged() {
+        if (mClusterState.displayId == Display.INVALID_DISPLAY) {
+            return;
+        }
+        TaskInfo taskInfo;
+        try {
+             taskInfo = mAtm.getRootTaskInfoOnDisplay(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED, mClusterState.displayId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception from AM", e);
+            return;
+        }
+        if (taskInfo == null) {
+            return;
+        }
+        int uiType = identifyTopTask(taskInfo);
+        if (uiType == UI_TYPE_CLUSTER_NONE) {
+            Log.w(TAG, "Unexpected top Activity on Cluster: " + taskInfo.topActivity);
+            return;
+        }
+        if (mLastReportedUiType == uiType) {
+            // Don't report the same UI type repeatedly.
+            return;
+        }
+        mLastReportedUiType = uiType;
+        mHomeManager.reportState(uiType, UI_TYPE_CLUSTER_NONE, mUiAvailability);
+    }
+
+    private int identifyTopTask(TaskInfo taskInfo) {
+        for (int i = mClusterActivities.length - 1; i >=0; --i) {
+            if (mClusterActivities[i].equals(taskInfo.topActivity)) {
+                return i;
+            }
+        }
+        return UI_TYPE_CLUSTER_NONE;
+    }
+
+    private final UserLifecycleListener mUserLifecycleListener = (event) -> {
+        mUserLifeCycleEvent = event.getEventType();
+        if (mUserLifeCycleEvent == USER_LIFECYCLE_EVENT_TYPE_STARTING) {
+            startClusterActivity(UI_TYPE_HOME);
+        }
+    };
+
+    private final CarInputCaptureCallback mInputCaptureCallback = new CarInputCaptureCallback() {
+        @Override
+        public void onKeyEvents(@CarOccupantZoneManager.DisplayTypeEnum int targetDisplayType,
+                List<KeyEvent> keyEvents) {
+            keyEvents.forEach((keyEvent) -> onKeyEvent(keyEvent));
+        }
+    };
+
+    private void onKeyEvent(KeyEvent keyEvent) {
+        if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MENU) {
+            if (keyEvent.getAction() != KeyEvent.ACTION_DOWN) return;
+            int nextUiType = (mLastLaunchedUiType + 1) % mUiAvailability.length;
+            startClusterActivity(nextUiType);
+            return;
+        }
+        // Use Android InputManager to forward KeyEvent.
+        mInputManager.injectInputEvent(keyEvent, INJECT_INPUT_EVENT_MODE_ASYNC);
+    }
+}
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterMusicActivity.java b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterMusicActivity.java
new file mode 100644
index 0000000..83178bf
--- /dev/null
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterMusicActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.cluster.home;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+/**
+ * Skeleton Activity for Music UI in Cluster display.
+ */
+public final class ClusterMusicActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        TextView view = (TextView) getLayoutInflater().inflate(
+                R.layout.cluster_home_activity, /* root= */ null);
+        view.setText(R.string.cluster_music_text);
+        setContentView(view);
+    }
+}
\ No newline at end of file
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterPhoneActivity.java b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterPhoneActivity.java
new file mode 100644
index 0000000..c58a6f2
--- /dev/null
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterPhoneActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.cluster.home;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+/**
+ * Skeleton Activity for Phone UI in Cluster display.
+ */
+public final class ClusterPhoneActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        TextView view = (TextView) getLayoutInflater().inflate(
+                R.layout.cluster_home_activity, /* root= */ null);
+        view.setText(R.string.cluster_phone_text);
+        setContentView(view);
+    }
+}
\ No newline at end of file
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/FakeFreeNavigationActivity.java b/ClusterHomeSample/src/com/android/car/cluster/home/FakeFreeNavigationActivity.java
new file mode 100644
index 0000000..d8e5be0
--- /dev/null
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/FakeFreeNavigationActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.cluster.home;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.cluster.ClusterActivityState;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * Fake navigation activity used while no other application is providing turn-by-turn driving
+ * directions.
+ */
+public final class FakeFreeNavigationActivity extends Activity {
+    private final static String TAG = FakeFreeNavigationActivity.class.getSimpleName();
+
+    private ImageView mUnobscuredArea;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        setContentView(R.layout.activity_fake_free_navigation);
+        mUnobscuredArea = findViewById(R.id.unobscuredArea);
+
+        handleIntent(getIntent());
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleIntent(intent);
+    }
+
+    private void handleIntent(Intent intent) {
+        if (intent == null) {
+            Log.w(TAG, "Received a null intent");
+            return;
+        }
+        Bundle bundle = intent.getBundleExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+        if (bundle == null) {
+            Log.w(TAG, "Received an intent without " + Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+            return;
+        }
+        ClusterActivityState state = ClusterActivityState.fromBundle(bundle);
+        Log.i(TAG, "handling intent with state: " + state);
+
+        Rect unobscured = state.getUnobscuredBounds();
+        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+                unobscured.width(), unobscured.height());
+        lp.setMargins(unobscured.left, unobscured.top, 0, 0);
+        mUnobscuredArea.setLayoutParams(lp);
+    }
+}
\ No newline at end of file
diff --git a/Android.bp b/ClusterOsDouble/Android.bp
similarity index 60%
copy from Android.bp
copy to ClusterOsDouble/Android.bp
index f897aff..842c489 100644
--- a/Android.bp
+++ b/ClusterOsDouble/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2015 The Android Open Source Project
+//
+// Copyright (C) 2021 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.
@@ -11,49 +12,32 @@
 // 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 {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_app {
-    name: "DirectRenderingCluster",
+    name: "ClusterOsDouble",
 
     srcs: ["src/**/*.java"],
 
-    platform_apis: true,
-
-    // Each update should be signed by OEMs
-    certificate: "platform",
-    privileged: true,
-
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-        enabled: false,
-    },
-
-    resource_dirs: ["res"],
-
+    libs: [
+        "android.car",
+    ],
     static_libs: [
         "android.car.cluster.navigation",
-        "android.car.userlib",
-        "androidx.legacy_legacy-support-v4",
+        "androidx.annotation_annotation",
         "androidx-constraintlayout_constraintlayout",
-        "car-arch-common",
-        "car-media-common",
-        "car-telephony-common",
-        "car-apps-common",
+        "androidx.lifecycle_lifecycle-extensions",
     ],
 
-    libs: ["android.car"],
-
-    required: ["privapp_whitelist_android.car.cluster"],
-
-    product_variables: {
-        pdk: {
-            enabled: false,
-        },
+    optimize: {
+        enabled: false,
+    },
+    platform_apis: true,
+    certificate: "platform",
+    dex_preopt: {
+        enabled: false,
     },
 }
diff --git a/ClusterOsDouble/AndroidManifest.xml b/ClusterOsDouble/AndroidManifest.xml
new file mode 100644
index 0000000..c171524
--- /dev/null
+++ b/ClusterOsDouble/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.cluster.osdouble">
+
+    <!-- Required to show car sensor data -->
+    <uses-permission android:name="android.car.permission.CAR_SPEED"/>
+    <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_ENGINE_DETAILED"/>
+    <!-- to access the Vendor properties -->
+    <uses-permission android:name="android.car.permission.CAR_VENDOR_EXTENSION"/>
+    <!-- to create the trusted virtual display -->
+    <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY"/>
+    <!-- to get current user id -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <!--
+        android:directBootAware to start the application regardless of user-unlocking.
+        android:persistent not to be killed by OOM.
+    -->
+    <application android:label="ClusterOsDouble"
+                 android:name=".ClusterOsDoubleApplication"
+                 android:directBootAware="true"
+                 android:persistent="true">
+        <!-- android:showForAllUsers to keep the Activity regardless of user-switching -->
+        <activity android:name=".ClusterOsDoubleActivity"
+                android:exported="true"
+                android:showForAllUsers="true"
+                android:excludeFromRecents="true"
+                android:screenOrientation="nosensor"
+                android:configChanges="uiMode|mcc|mnc"
+                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/res/color/icon_color.xml b/ClusterOsDouble/res/color/icon_color.xml
similarity index 100%
rename from res/color/icon_color.xml
rename to ClusterOsDouble/res/color/icon_color.xml
diff --git a/res/drawable-hdpi/ic_car_info.png b/ClusterOsDouble/res/drawable-hdpi/ic_car_info.png
similarity index 100%
rename from res/drawable-hdpi/ic_car_info.png
rename to ClusterOsDouble/res/drawable-hdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_music.png b/ClusterOsDouble/res/drawable-hdpi/ic_music.png
similarity index 100%
rename from res/drawable-hdpi/ic_music.png
rename to ClusterOsDouble/res/drawable-hdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_nav.png b/ClusterOsDouble/res/drawable-hdpi/ic_nav.png
similarity index 100%
rename from res/drawable-hdpi/ic_nav.png
rename to ClusterOsDouble/res/drawable-hdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_phone.png b/ClusterOsDouble/res/drawable-hdpi/ic_phone.png
similarity index 100%
rename from res/drawable-hdpi/ic_phone.png
rename to ClusterOsDouble/res/drawable-hdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_car_info.png b/ClusterOsDouble/res/drawable-mdpi/ic_car_info.png
similarity index 100%
rename from res/drawable-mdpi/ic_car_info.png
rename to ClusterOsDouble/res/drawable-mdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_music.png b/ClusterOsDouble/res/drawable-mdpi/ic_music.png
similarity index 100%
rename from res/drawable-mdpi/ic_music.png
rename to ClusterOsDouble/res/drawable-mdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_nav.png b/ClusterOsDouble/res/drawable-mdpi/ic_nav.png
similarity index 100%
rename from res/drawable-mdpi/ic_nav.png
rename to ClusterOsDouble/res/drawable-mdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_phone.png b/ClusterOsDouble/res/drawable-mdpi/ic_phone.png
similarity index 100%
rename from res/drawable-mdpi/ic_phone.png
rename to ClusterOsDouble/res/drawable-mdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_car_info.png b/ClusterOsDouble/res/drawable-xhdpi/ic_car_info.png
similarity index 100%
rename from res/drawable-xhdpi/ic_car_info.png
rename to ClusterOsDouble/res/drawable-xhdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_music.png b/ClusterOsDouble/res/drawable-xhdpi/ic_music.png
similarity index 100%
rename from res/drawable-xhdpi/ic_music.png
rename to ClusterOsDouble/res/drawable-xhdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_nav.png b/ClusterOsDouble/res/drawable-xhdpi/ic_nav.png
similarity index 100%
rename from res/drawable-xhdpi/ic_nav.png
rename to ClusterOsDouble/res/drawable-xhdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_phone.png b/ClusterOsDouble/res/drawable-xhdpi/ic_phone.png
similarity index 100%
rename from res/drawable-xhdpi/ic_phone.png
rename to ClusterOsDouble/res/drawable-xhdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_car_info.png b/ClusterOsDouble/res/drawable-xxhdpi/ic_car_info.png
similarity index 100%
rename from res/drawable-xxhdpi/ic_car_info.png
rename to ClusterOsDouble/res/drawable-xxhdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_music.png b/ClusterOsDouble/res/drawable-xxhdpi/ic_music.png
similarity index 100%
rename from res/drawable-xxhdpi/ic_music.png
rename to ClusterOsDouble/res/drawable-xxhdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_nav.png b/ClusterOsDouble/res/drawable-xxhdpi/ic_nav.png
similarity index 100%
rename from res/drawable-xxhdpi/ic_nav.png
rename to ClusterOsDouble/res/drawable-xxhdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_phone.png b/ClusterOsDouble/res/drawable-xxhdpi/ic_phone.png
similarity index 100%
rename from res/drawable-xxhdpi/ic_phone.png
rename to ClusterOsDouble/res/drawable-xxhdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable/car_top_view.png b/ClusterOsDouble/res/drawable/car_top_view.png
similarity index 100%
rename from res/drawable/car_top_view.png
rename to ClusterOsDouble/res/drawable/car_top_view.png
Binary files differ
diff --git a/res/drawable/direction_arrive.xml b/ClusterOsDouble/res/drawable/direction_arrive.xml
similarity index 100%
rename from res/drawable/direction_arrive.xml
rename to ClusterOsDouble/res/drawable/direction_arrive.xml
diff --git a/res/drawable/direction_arrive_left.xml b/ClusterOsDouble/res/drawable/direction_arrive_left.xml
similarity index 100%
rename from res/drawable/direction_arrive_left.xml
rename to ClusterOsDouble/res/drawable/direction_arrive_left.xml
diff --git a/res/drawable/direction_arrive_right.xml b/ClusterOsDouble/res/drawable/direction_arrive_right.xml
similarity index 100%
rename from res/drawable/direction_arrive_right.xml
rename to ClusterOsDouble/res/drawable/direction_arrive_right.xml
diff --git a/res/drawable/direction_arrive_straight.xml b/ClusterOsDouble/res/drawable/direction_arrive_straight.xml
similarity index 100%
rename from res/drawable/direction_arrive_straight.xml
rename to ClusterOsDouble/res/drawable/direction_arrive_straight.xml
diff --git a/res/drawable/direction_close.xml b/ClusterOsDouble/res/drawable/direction_close.xml
similarity index 100%
rename from res/drawable/direction_close.xml
rename to ClusterOsDouble/res/drawable/direction_close.xml
diff --git a/res/drawable/direction_continue.xml b/ClusterOsDouble/res/drawable/direction_continue.xml
similarity index 100%
rename from res/drawable/direction_continue.xml
rename to ClusterOsDouble/res/drawable/direction_continue.xml
diff --git a/res/drawable/direction_continue_left.xml b/ClusterOsDouble/res/drawable/direction_continue_left.xml
similarity index 100%
rename from res/drawable/direction_continue_left.xml
rename to ClusterOsDouble/res/drawable/direction_continue_left.xml
diff --git a/res/drawable/direction_continue_right.xml b/ClusterOsDouble/res/drawable/direction_continue_right.xml
similarity index 100%
rename from res/drawable/direction_continue_right.xml
rename to ClusterOsDouble/res/drawable/direction_continue_right.xml
diff --git a/res/drawable/direction_depart.xml b/ClusterOsDouble/res/drawable/direction_depart.xml
similarity index 100%
rename from res/drawable/direction_depart.xml
rename to ClusterOsDouble/res/drawable/direction_depart.xml
diff --git a/res/drawable/direction_fork_left.xml b/ClusterOsDouble/res/drawable/direction_fork_left.xml
similarity index 100%
rename from res/drawable/direction_fork_left.xml
rename to ClusterOsDouble/res/drawable/direction_fork_left.xml
diff --git a/res/drawable/direction_fork_right.xml b/ClusterOsDouble/res/drawable/direction_fork_right.xml
similarity index 100%
rename from res/drawable/direction_fork_right.xml
rename to ClusterOsDouble/res/drawable/direction_fork_right.xml
diff --git a/res/drawable/direction_merge_left.xml b/ClusterOsDouble/res/drawable/direction_merge_left.xml
similarity index 100%
rename from res/drawable/direction_merge_left.xml
rename to ClusterOsDouble/res/drawable/direction_merge_left.xml
diff --git a/res/drawable/direction_merge_right.xml b/ClusterOsDouble/res/drawable/direction_merge_right.xml
similarity index 100%
rename from res/drawable/direction_merge_right.xml
rename to ClusterOsDouble/res/drawable/direction_merge_right.xml
diff --git a/res/drawable/direction_merge_unspecified.xml b/ClusterOsDouble/res/drawable/direction_merge_unspecified.xml
similarity index 100%
rename from res/drawable/direction_merge_unspecified.xml
rename to ClusterOsDouble/res/drawable/direction_merge_unspecified.xml
diff --git a/res/drawable/direction_new_name_straight.xml b/ClusterOsDouble/res/drawable/direction_new_name_straight.xml
similarity index 100%
rename from res/drawable/direction_new_name_straight.xml
rename to ClusterOsDouble/res/drawable/direction_new_name_straight.xml
diff --git a/res/drawable/direction_off_ramp_left.xml b/ClusterOsDouble/res/drawable/direction_off_ramp_left.xml
similarity index 100%
rename from res/drawable/direction_off_ramp_left.xml
rename to ClusterOsDouble/res/drawable/direction_off_ramp_left.xml
diff --git a/res/drawable/direction_off_ramp_right.xml b/ClusterOsDouble/res/drawable/direction_off_ramp_right.xml
similarity index 100%
rename from res/drawable/direction_off_ramp_right.xml
rename to ClusterOsDouble/res/drawable/direction_off_ramp_right.xml
diff --git a/res/drawable/direction_off_ramp_slight_left.xml b/ClusterOsDouble/res/drawable/direction_off_ramp_slight_left.xml
similarity index 100%
rename from res/drawable/direction_off_ramp_slight_left.xml
rename to ClusterOsDouble/res/drawable/direction_off_ramp_slight_left.xml
diff --git a/res/drawable/direction_off_ramp_slight_right.xml b/ClusterOsDouble/res/drawable/direction_off_ramp_slight_right.xml
similarity index 100%
rename from res/drawable/direction_off_ramp_slight_right.xml
rename to ClusterOsDouble/res/drawable/direction_off_ramp_slight_right.xml
diff --git a/res/drawable/direction_on_ramp_left.xml b/ClusterOsDouble/res/drawable/direction_on_ramp_left.xml
similarity index 100%
rename from res/drawable/direction_on_ramp_left.xml
rename to ClusterOsDouble/res/drawable/direction_on_ramp_left.xml
diff --git a/res/drawable/direction_on_ramp_right.xml b/ClusterOsDouble/res/drawable/direction_on_ramp_right.xml
similarity index 100%
rename from res/drawable/direction_on_ramp_right.xml
rename to ClusterOsDouble/res/drawable/direction_on_ramp_right.xml
diff --git a/res/drawable/direction_on_ramp_sharp_left.xml b/ClusterOsDouble/res/drawable/direction_on_ramp_sharp_left.xml
similarity index 100%
rename from res/drawable/direction_on_ramp_sharp_left.xml
rename to ClusterOsDouble/res/drawable/direction_on_ramp_sharp_left.xml
diff --git a/res/drawable/direction_on_ramp_sharp_right.xml b/ClusterOsDouble/res/drawable/direction_on_ramp_sharp_right.xml
similarity index 100%
rename from res/drawable/direction_on_ramp_sharp_right.xml
rename to ClusterOsDouble/res/drawable/direction_on_ramp_sharp_right.xml
diff --git a/res/drawable/direction_on_ramp_slight_left.xml b/ClusterOsDouble/res/drawable/direction_on_ramp_slight_left.xml
similarity index 100%
rename from res/drawable/direction_on_ramp_slight_left.xml
rename to ClusterOsDouble/res/drawable/direction_on_ramp_slight_left.xml
diff --git a/res/drawable/direction_on_ramp_slight_right.xml b/ClusterOsDouble/res/drawable/direction_on_ramp_slight_right.xml
similarity index 100%
rename from res/drawable/direction_on_ramp_slight_right.xml
rename to ClusterOsDouble/res/drawable/direction_on_ramp_slight_right.xml
diff --git a/res/drawable/direction_roundabout.xml b/ClusterOsDouble/res/drawable/direction_roundabout.xml
similarity index 100%
rename from res/drawable/direction_roundabout.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout.xml
diff --git a/res/drawable/direction_roundabout_ccw_left.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_left.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_left.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_left.xml
diff --git a/res/drawable/direction_roundabout_ccw_right.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_right.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_right.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_right.xml
diff --git a/res/drawable/direction_roundabout_ccw_sharp_left.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_sharp_left.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_sharp_left.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_sharp_left.xml
diff --git a/res/drawable/direction_roundabout_ccw_sharp_right.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_sharp_right.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_sharp_right.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_sharp_right.xml
diff --git a/res/drawable/direction_roundabout_ccw_slight_left.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_slight_left.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_slight_left.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_slight_left.xml
diff --git a/res/drawable/direction_roundabout_ccw_slight_right.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_slight_right.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_slight_right.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_slight_right.xml
diff --git a/res/drawable/direction_roundabout_ccw_straight.xml b/ClusterOsDouble/res/drawable/direction_roundabout_ccw_straight.xml
similarity index 100%
rename from res/drawable/direction_roundabout_ccw_straight.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_ccw_straight.xml
diff --git a/res/drawable/direction_roundabout_cw.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw.xml
diff --git a/res/drawable/direction_roundabout_cw_left.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_left.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_left.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_left.xml
diff --git a/res/drawable/direction_roundabout_cw_right.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_right.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_right.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_right.xml
diff --git a/res/drawable/direction_roundabout_cw_sharp_left.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_sharp_left.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_sharp_left.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_sharp_left.xml
diff --git a/res/drawable/direction_roundabout_cw_sharp_right.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_sharp_right.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_sharp_right.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_sharp_right.xml
diff --git a/res/drawable/direction_roundabout_cw_slight_left.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_slight_left.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_slight_left.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_slight_left.xml
diff --git a/res/drawable/direction_roundabout_cw_slight_right.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_slight_right.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_slight_right.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_slight_right.xml
diff --git a/res/drawable/direction_roundabout_cw_straight.xml b/ClusterOsDouble/res/drawable/direction_roundabout_cw_straight.xml
similarity index 100%
rename from res/drawable/direction_roundabout_cw_straight.xml
rename to ClusterOsDouble/res/drawable/direction_roundabout_cw_straight.xml
diff --git a/res/drawable/direction_turn_left.xml b/ClusterOsDouble/res/drawable/direction_turn_left.xml
similarity index 100%
rename from res/drawable/direction_turn_left.xml
rename to ClusterOsDouble/res/drawable/direction_turn_left.xml
diff --git a/res/drawable/direction_turn_right.xml b/ClusterOsDouble/res/drawable/direction_turn_right.xml
similarity index 100%
rename from res/drawable/direction_turn_right.xml
rename to ClusterOsDouble/res/drawable/direction_turn_right.xml
diff --git a/res/drawable/direction_turn_sharp_left.xml b/ClusterOsDouble/res/drawable/direction_turn_sharp_left.xml
similarity index 100%
rename from res/drawable/direction_turn_sharp_left.xml
rename to ClusterOsDouble/res/drawable/direction_turn_sharp_left.xml
diff --git a/res/drawable/direction_turn_sharp_right.xml b/ClusterOsDouble/res/drawable/direction_turn_sharp_right.xml
similarity index 100%
rename from res/drawable/direction_turn_sharp_right.xml
rename to ClusterOsDouble/res/drawable/direction_turn_sharp_right.xml
diff --git a/res/drawable/direction_turn_slight_left.xml b/ClusterOsDouble/res/drawable/direction_turn_slight_left.xml
similarity index 100%
rename from res/drawable/direction_turn_slight_left.xml
rename to ClusterOsDouble/res/drawable/direction_turn_slight_left.xml
diff --git a/res/drawable/direction_turn_slight_right.xml b/ClusterOsDouble/res/drawable/direction_turn_slight_right.xml
similarity index 100%
rename from res/drawable/direction_turn_slight_right.xml
rename to ClusterOsDouble/res/drawable/direction_turn_slight_right.xml
diff --git a/res/drawable/direction_uturn_left.xml b/ClusterOsDouble/res/drawable/direction_uturn_left.xml
similarity index 100%
rename from res/drawable/direction_uturn_left.xml
rename to ClusterOsDouble/res/drawable/direction_uturn_left.xml
diff --git a/res/drawable/direction_uturn_right.xml b/ClusterOsDouble/res/drawable/direction_uturn_right.xml
similarity index 100%
rename from res/drawable/direction_uturn_right.xml
rename to ClusterOsDouble/res/drawable/direction_uturn_right.xml
diff --git a/res/drawable/focused_button_shape.xml b/ClusterOsDouble/res/drawable/focused_button_shape.xml
similarity index 100%
rename from res/drawable/focused_button_shape.xml
rename to ClusterOsDouble/res/drawable/focused_button_shape.xml
diff --git a/res/drawable/gradient_bottom.xml b/ClusterOsDouble/res/drawable/gradient_bottom.xml
similarity index 100%
rename from res/drawable/gradient_bottom.xml
rename to ClusterOsDouble/res/drawable/gradient_bottom.xml
diff --git a/res/drawable/gradient_top.xml b/ClusterOsDouble/res/drawable/gradient_top.xml
similarity index 100%
rename from res/drawable/gradient_top.xml
rename to ClusterOsDouble/res/drawable/gradient_top.xml
diff --git a/ClusterOsDouble/res/drawable/seekbar_background.xml b/ClusterOsDouble/res/drawable/seekbar_background.xml
new file mode 100644
index 0000000..44e116a
--- /dev/null
+++ b/ClusterOsDouble/res/drawable/seekbar_background.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021, 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@android:id/background">
+        <shape android:shape="line">
+            <stroke android:width="@dimen/playback_seekbar_track_height"/>
+        </shape>
+    </item>
+
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape android:shape="line">
+                <stroke android:width="@dimen/playback_seekbar_track_height"/>
+            </shape>
+        </clip>
+    </item>
+
+</layer-list>
diff --git a/ClusterOsDouble/res/drawable/seekbar_thumb.xml b/ClusterOsDouble/res/drawable/seekbar_thumb.xml
new file mode 100644
index 0000000..774190f
--- /dev/null
+++ b/ClusterOsDouble/res/drawable/seekbar_thumb.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021, 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="@color/progress_bar_thumb_color"/>
+    <size
+        android:width="@dimen/playback_seekbar_thumb_width"
+        android:height="@dimen/playback_seekbar_thumb_height"/>
+</shape>
\ No newline at end of file
diff --git a/ClusterOsDouble/res/drawable/speedometer.xml b/ClusterOsDouble/res/drawable/speedometer.xml
new file mode 100644
index 0000000..7f8a537
--- /dev/null
+++ b/ClusterOsDouble/res/drawable/speedometer.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021, 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.
+-->
+<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/ClusterOsDouble/res/layout/cluster_os_double_activity.xml b/ClusterOsDouble/res/layout/cluster_os_double_activity.xml
new file mode 100644
index 0000000..06f241f
--- /dev/null
+++ b/ClusterOsDouble/res/layout/cluster_os_double_activity.xml
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<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"
+    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"/>
+
+    <SurfaceView
+        android:id="@+id/cluster_display"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/info"/>
+
+    <ImageView
+        android:layout_width="match_parent"
+        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="@dimen/navigation_gradient_height"
+        android:src="@drawable/gradient_bottom"
+        app:layout_constraintBottom_toBottomOf="@+id/info"/>
+
+    <LinearLayout
+        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_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="start">
+
+            <TextView
+                android:layout_height="wrap_content"
+                android:layout_width="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>
+
+    <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_car_info"
+            android:clickable="true"
+            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" />
+        <Button
+            android:id="@+id/btn_nav"
+            android:clickable="true"
+            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_music"
+            android:clickable="true"
+            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_phone"
+            android:clickable="true"
+            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" />
+    </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_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/ClusterOsDouble/res/layout/include_navigation_state.xml b/ClusterOsDouble/res/layout/include_navigation_state.xml
new file mode 100644
index 0000000..60d2f2d
--- /dev/null
+++ b/ClusterOsDouble/res/layout/include_navigation_state.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="horizontal">
+
+    <LinearLayout
+        android:id="@+id/section_maneuver"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/maneuver"
+            android:layout_width="@dimen/maneuver_width"
+            android:layout_height="@dimen/maneuver_height"
+            android:layout_marginLeft="@dimen/maneuver_margin"
+            android:layout_marginRight="@dimen/maneuver_margin"
+            android:tint="@android:color/white"/>
+        <ImageView
+            android:id="@+id/provided_maneuver"
+            android:layout_width="@dimen/provided_maneuver_width"
+            android:layout_height="@dimen/provided_maneuver_height"
+            android:layout_gravity="center"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/section_service_status"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:visibility="invisible">
+
+        <TextView
+            android:id="@+id/service_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Rerouting..."
+            android:textSize="@dimen/distance_text_size"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/section_navigation"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="1.0">
+
+            <TextView
+                android:id="@+id/distance"
+                android:layout_width="@dimen/distance_width"
+                android:layout_height="wrap_content"
+                android:maxLines="1"
+                android:textSize="@dimen/distance_text_size"/>
+
+            <com.android.car.cluster.view.LaneView
+                android:id="@+id/lane"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/laneview_height"
+                android:layout_alignParentRight="true"/>
+        </LinearLayout>
+
+        <com.android.car.cluster.view.CueView
+            android:id="@+id/cue"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:textSize="@dimen/cue_text_size"/>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/eta"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="@dimen/eta_margin_right"
+                android:textSize="@dimen/eta_text_size"/>
+
+            <TextView
+                android:id="@+id/segment"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:textSize="@dimen/segment_text_size"/>
+        </LinearLayout>
+
+        <com.android.car.cluster.view.LaneView
+            android:id="@+id/provided_lane"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/provided_laneview_height"
+            android:layout_marginLeft="@dimen/distance_width"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values-en-rUS/dimens.xml b/ClusterOsDouble/res/values-en-rUS/dimens.xml
similarity index 100%
rename from res/values-en-rUS/dimens.xml
rename to ClusterOsDouble/res/values-en-rUS/dimens.xml
diff --git a/res/values-w820dp/dimens.xml b/ClusterOsDouble/res/values-w820dp/dimens.xml
similarity index 100%
rename from res/values-w820dp/dimens.xml
rename to ClusterOsDouble/res/values-w820dp/dimens.xml
diff --git a/ClusterOsDouble/res/values/colors.xml b/ClusterOsDouble/res/values/colors.xml
new file mode 100644
index 0000000..b5a3512
--- /dev/null
+++ b/ClusterOsDouble/res/values/colors.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <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>
+
+    <!-- LaneView highlight colors -->
+    <color name="laneDirection">#888888</color>
+    <color name="laneDirectionHighlighted">#FFFFFF</color>
+
+    <!-- Traffic colors -->
+    <color name="low_traffic">#0F9D58</color>
+    <color name="medium_traffic">#F2B300</color>
+    <color name="high_traffic">#E06055</color>
+    <color name="unknown_traffic">#4285F4</color>
+
+    <color name="album_art_background">@*android:color/car_grey_100</color>
+    <!-- Color used on the placeholder album art -->
+    <color name="album_art_placeholder_color">@*android:color/car_grey_800</color>
+    <!-- Color used on the progress bar background -->
+    <color name="progress_bar_background">@*android:color/car_seekbar_track_background</color>
+    <!-- Color used on the progress bar -->
+    <color name="progress_bar_highlight">@*android:color/car_accent</color>
+    <!-- Color used on the thumb of the progress bar -->
+    <color name="progress_bar_thumb_color">@*android:color/car_accent</color>
+</resources>
diff --git a/ClusterOsDouble/res/values/config.xml b/ClusterOsDouble/res/values/config.xml
new file mode 100644
index 0000000..7f1cb52
--- /dev/null
+++ b/ClusterOsDouble/res/values/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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 to configure based on each OEM's preference. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!--  DisplayPort to launch ClusterOsDouble -->
+    <integer name="config_clusterDisplayPort">1</integer>
+</resources>
diff --git a/res/values/dimens.xml b/ClusterOsDouble/res/values/dimens.xml
similarity index 98%
rename from res/values/dimens.xml
rename to ClusterOsDouble/res/values/dimens.xml
index bff5c60..2072f57 100644
--- a/res/values/dimens.xml
+++ b/ClusterOsDouble/res/values/dimens.xml
@@ -49,6 +49,8 @@
 
     <dimen name="lane_icon_offset">12.5dp</dimen>
 
+    <item name="non_imminent_alpha" type="dimen" format="float">0.5</item>
+
     <!--                                   -->
     <!-- Sensor value conversion constants -->
     <!--                                   -->
diff --git a/ClusterOsDouble/res/values/overlayable.xml b/ClusterOsDouble/res/values/overlayable.xml
new file mode 100644
index 0000000..465c39f
--- /dev/null
+++ b/ClusterOsDouble/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="ClusterOsConfig">
+        <policy type="product|system|vendor">
+            <item type="integer" name="config_clusterDisplayPort" />
+        </policy>
+    </overlayable>
+</resources>
diff --git a/ClusterOsDouble/res/values/strings.xml b/ClusterOsDouble/res/values/strings.xml
new file mode 100644
index 0000000..f47ed2a
--- /dev/null
+++ b/ClusterOsDouble/res/values/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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>
+    <!-- Gears texts [CHAR LIMIT=1] -->
+    <string name="gear_parked" translatable="false">P</string>
+    <string name="gear_reverse" translatable="false">R</string>
+    <string name="gear_neutral" translatable="false">N</string>
+    <string name="gear_drive" translatable="false">D</string>
+
+    <!-- Information labels (shown next to driving directions) [CHAR LIMIT=30] -->
+    <string name="info_fuel_label" translatable="false">Fuel</string>
+    <string name="info_speed_label" translatable="false">Speed</string>
+    <string name="info_range_label" translatable="false">Range</string>
+    <string name="info_rpm_label" translatable="false">RPM</string>
+    <string name="info_value_empty" translatable="false">-</string>
+
+    <!-- Marker used to insert images inside rich-text strings. This string is never shown or spoken
+         to the user. Instead a image would be rendered in its place -->
+    <string name="span_image" translatable="false">[Image]</string>
+</resources>
diff --git a/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
new file mode 100644
index 0000000..bc40c02
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2021 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.cluster.osdouble;
+
+import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_HOME;
+import static android.car.cluster.ClusterHomeManager.UI_TYPE_CLUSTER_NONE;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+import static com.android.car.cluster.osdouble.ClusterOsDoubleApplication.TAG;
+
+import android.car.Car;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
+import android.car.VehiclePropertyIds;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Bundle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.activity.ComponentActivity;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.car.cluster.sensors.Sensors;
+import com.android.car.cluster.view.BitmapFetcher;
+import com.android.car.cluster.view.ClusterViewModel;
+import com.android.car.cluster.view.ImageResolver;
+import com.android.car.cluster.view.NavStateController;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * The Activity which plays the role of ClusterOs for the testing.
+ */
+public class ClusterOsDoubleActivity extends ComponentActivity {
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // VehiclePropertyGroup
+    private static final int SYSTEM = 0x10000000;
+    private static final int VENDOR = 0x20000000;
+    private static final int MASK = 0xf0000000;
+
+    private static final int VENDOR_CLUSTER_REPORT_STATE = toVendorId(
+            VehiclePropertyIds.CLUSTER_REPORT_STATE);
+    private static final int VENDOR_CLUSTER_SWITCH_UI = toVendorId(
+            VehiclePropertyIds.CLUSTER_SWITCH_UI);
+    private static final int VENDOR_CLUSTER_NAVIGATION_STATE = toVendorId(
+            VehiclePropertyIds.CLUSTER_NAVIGATION_STATE);
+    private static final int VENDOR_CLUSTER_REQUEST_DISPLAY = toVendorId(
+            VehiclePropertyIds.CLUSTER_REQUEST_DISPLAY);
+    private static final int VENDOR_CLUSTER_DISPLAY_STATE = toVendorId(
+            VehiclePropertyIds.CLUSTER_DISPLAY_STATE);
+
+    private DisplayManager mDisplayManager;
+    private CarPropertyManager mPropertyManager;
+
+    private SurfaceView mSurfaceView;
+    private Rect mBounds;
+    private Insets mInsets;
+    private VirtualDisplay mVirtualDisplay;
+
+    private ClusterViewModel mClusterViewModel;
+    private final ArrayMap<Sensors.Gear, View> mGearsToIcon = new ArrayMap<>();
+    private final ArrayList<View> mUiToButton = new ArrayList<>();
+    int mCurrentUi = UI_TYPE_CLUSTER_HOME;
+    int mTotalUiSize;
+
+    private NavStateController mNavStateController;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mDisplayManager = getSystemService(DisplayManager.class);
+
+        Car.createCar(getApplicationContext(), /* handler= */ null,
+                Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+                (car, ready) -> {
+                    if (!ready) return;
+                    mPropertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
+                    initClusterOsDouble();
+                });
+
+        View view = getLayoutInflater().inflate(R.layout.cluster_os_double_activity, null);
+        mSurfaceView = view.findViewById(R.id.cluster_display);
+        mSurfaceView.getHolder().addCallback(mSurfaceViewCallback);
+        setContentView(view);
+
+        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);
+
+        mClusterViewModel = new ViewModelProvider(this).get(ClusterViewModel.class);
+        mClusterViewModel.getSensor(Sensors.SENSOR_GEAR).observe(this, this::updateSelectedGear);
+
+        registerSensor(findViewById(R.id.info_fuel), mClusterViewModel.getFuelLevel());
+        registerSensor(findViewById(R.id.info_speed), mClusterViewModel.getSpeed());
+        registerSensor(findViewById(R.id.info_range), mClusterViewModel.getRange());
+        registerSensor(findViewById(R.id.info_rpm), mClusterViewModel.getRPM());
+
+        // The order should be matched with ClusterHomeApplication.
+        registerUi(findViewById(R.id.btn_car_info));
+        registerUi(findViewById(R.id.btn_nav));
+        registerUi(findViewById(R.id.btn_music));
+        registerUi(findViewById(R.id.btn_phone));
+
+        BitmapFetcher bitmapFetcher = new BitmapFetcher(this);
+        ImageResolver imageResolver = new ImageResolver(bitmapFetcher);
+        mNavStateController = new NavStateController(
+                findViewById(R.id.navigation_state), imageResolver);
+    }
+
+    @Override
+    protected void onDestroy() {
+        mVirtualDisplay.release();
+        mVirtualDisplay = null;
+        super.onDestroy();
+    }
+
+    private final SurfaceHolder.Callback mSurfaceViewCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            Log.i(TAG, "surfaceCreated, holder: " + holder);
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            Log.i(TAG, "surfaceChanged, holder: " + holder + ", size:" + width + "x" + height
+                    + ", format:" + format);
+
+            // Create mock 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);
+            mBounds = new Rect(/* left= */ 0, /* top= */ 0,
+                    /* right= */ width, /* bottom= */ height);
+            // Adds some empty space in the boundary of the display to verify if mBounds works.
+            mBounds.inset(/* dx= */ 12, /* dy= */ 12);
+            mInsets = Insets.of(obscuredWidth, obscuredHeight, obscuredWidth, obscuredHeight);
+            if (mVirtualDisplay == null) {
+                mVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height);
+            } else {
+                mVirtualDisplay.setSurface(holder.getSurface());
+            }
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            Log.i(TAG, "surfaceDestroyed, holder: " + holder + ", detaching surface from"
+                    + " display, surface: " + holder.getSurface());
+            // detaching surface is similar to turning off the display
+            mVirtualDisplay.setSurface(null);
+        }
+    };
+
+    private VirtualDisplay createVirtualDisplay(Surface surface, int width, int height) {
+        Log.i(TAG, "createVirtualDisplay, surface: " + surface + ", width: " + width
+                + "x" + height);
+        return mDisplayManager.createVirtualDisplay(/* projection= */ null, "ClusterOsDouble-VD",
+                width, height, 160, surface,
+                VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_TRUSTED,
+                /* callback= */ null, /* handler= */ null, "ClusterDisplay");
+    }
+
+    private void initClusterOsDouble() {
+        mPropertyManager.registerCallback(mPropertyEventCallback,
+                VENDOR_CLUSTER_REPORT_STATE, CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        mPropertyManager.registerCallback(mPropertyEventCallback,
+                VENDOR_CLUSTER_NAVIGATION_STATE, CarPropertyManager.SENSOR_RATE_ONCHANGE);
+        mPropertyManager.registerCallback(mPropertyEventCallback,
+                VENDOR_CLUSTER_REQUEST_DISPLAY, CarPropertyManager.SENSOR_RATE_ONCHANGE);
+    }
+
+    private final CarPropertyEventCallback mPropertyEventCallback = new CarPropertyEventCallback() {
+        @Override
+        public void onChangeEvent(CarPropertyValue carProp) {
+            int propertyId = carProp.getPropertyId();
+            if (propertyId == VENDOR_CLUSTER_REPORT_STATE) {
+                onClusterReportState((Object[]) carProp.getValue());
+            } else if (propertyId == VENDOR_CLUSTER_NAVIGATION_STATE) {
+                onClusterNavigationState((byte[]) carProp.getValue());
+            } else if (propertyId == VENDOR_CLUSTER_REQUEST_DISPLAY) {
+                onClusterRequestDisplay((Integer) carProp.getValue());
+            }
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+
+        }
+    };
+
+    private void onClusterReportState(Object[] values) {
+        if (DBG) Log.d(TAG, "onClusterReportState: " + Arrays.toString(values));
+        // CLUSTER_REPORT_STATE should have at least 11 elements, check vehicle/2.0/types.hal.
+        if (values.length < 11) {
+            throw new IllegalArgumentException("Insufficient size of CLUSTER_REPORT_STATE");
+        }
+        // mainUI is the 10th element, refer to vehicle/2.0/types.hal.
+        int mainUi = (Integer) values[9];
+        if (mainUi >= 0 && mainUi < mTotalUiSize) {
+            selectUiButton(mainUi);
+        }
+    }
+
+    private void selectUiButton(int mainUi) {
+        for (int i = 0; i < mTotalUiSize; ++i) {
+            View button = mUiToButton.get(i);
+            button.setSelected(i == mainUi);
+        }
+        mCurrentUi = mainUi;
+    }
+
+    private void onClusterNavigationState(byte[] protoBytes) {
+        if (DBG) Log.d(TAG, "onClusterNavigationState: " + Arrays.toString(protoBytes));
+        try {
+            NavigationStateProto navState = NavigationStateProto.parseFrom(protoBytes);
+            mNavStateController.update(navState);
+            if (DBG) Log.d(TAG, "onClusterNavigationState: " + navState);
+        } catch (InvalidProtocolBufferException e) {
+            Log.e(TAG, "Error parsing navigation state proto", e);
+        }
+    }
+
+    private void onClusterRequestDisplay(Integer mainUi) {
+        if (DBG) Log.d(TAG, "onClusterRequestDisplay: " + mainUi);
+        sendDisplayState();
+    }
+
+
+    private static int toVendorId(int propId) {
+        return (propId & ~MASK) | VENDOR;
+    }
+
+    private <V> void registerSensor(TextView textView, LiveData<V> source) {
+        String emptyValue = getString(R.string.info_value_empty);
+        source.observe(this, value -> {
+            // Need to check that the text is actually different, or else
+            // it will generate a bunch of CONTENT_CHANGE_TYPE_TEXT accessability
+            // actions. This will cause cts tests to fail when they waitForIdle(),
+            // and the system never idles because it's constantly updating these
+            // TextViews
+            if (value != null && !value.toString().contentEquals(textView.getText())) {
+                textView.setText(value.toString());
+            }
+            if (value == null && !emptyValue.contentEquals(textView.getText())) {
+                textView.setText(emptyValue);
+            }
+        });
+    }
+
+    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);
+        }
+    }
+
+    private void registerUi(View view) {
+        int currentUi = mUiToButton.size();
+        mUiToButton.add(view);
+        mTotalUiSize = mUiToButton.size();
+        view.setOnTouchListener((v, event) -> {
+            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                Log.d(TAG, "onTouch: " + currentUi);
+                switchUi(currentUi);
+            }
+            return true;
+        });
+    }
+
+    private void sendDisplayState() {
+        if (mBounds == null || mInsets == null) return;
+        mPropertyManager.setProperty(Integer[].class, VENDOR_CLUSTER_DISPLAY_STATE,
+                VEHICLE_AREA_TYPE_GLOBAL, new Integer[] {
+                        1  /* Display On */,
+                        mBounds.left, mBounds.top, mBounds.right, mBounds.bottom,
+                        mInsets.left, mInsets.top, mInsets.right, mInsets.bottom,
+                        UI_TYPE_CLUSTER_HOME, UI_TYPE_CLUSTER_NONE});
+    }
+
+    private void switchUi(int mainUi) {
+        mPropertyManager.setProperty(Integer[].class, VENDOR_CLUSTER_SWITCH_UI,
+                VEHICLE_AREA_TYPE_GLOBAL, new Integer[] {mainUi, UI_TYPE_CLUSTER_NONE});
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        Log.d(TAG, "onKeyDown: " + keyCode);
+        if (keyCode == KeyEvent.KEYCODE_MENU) {
+            switchUi((mCurrentUi + 1) % mTotalUiSize);
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+}
diff --git a/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleApplication.java b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleApplication.java
new file mode 100644
index 0000000..dd102c7
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleApplication.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.cluster.osdouble;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.ActivityOptions;
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayAddress;
+
+/**
+ * Application class to start ClusterOsDoubleActivity on the physical cluster display.
+ */
+public class ClusterOsDoubleApplication extends Application {
+    public static final String TAG = "ClusterOsDouble";
+
+    private DisplayManager mDisplayManager;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Context context = getApplicationContext();
+        mDisplayManager = context.getSystemService(DisplayManager.class);
+        int displayPort = context.getResources().getInteger(R.integer.config_clusterDisplayPort);
+        if (displayPort == 0) {
+            Log.e(TAG, "Invalid resource: config_clusterDisplayPort");
+            // Won't throw the exception, if so, it'll restart the application continuously,
+            // because this is the persistent application.
+            return;
+        }
+        int displayId = findDisplay(displayPort);
+        if (displayId == Display.INVALID_DISPLAY) {
+            Log.e(TAG, "Can't find the display with portId: " + displayPort);
+            return;
+        }
+
+        Intent intent = Intent.makeMainActivity(
+                ComponentName.createRelative(context, ClusterOsDoubleActivity.class.getName()));
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+
+        ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
+        context.startActivity(intent, options.toBundle());
+    }
+
+    private int findDisplay(int displayPort) {
+        for (Display display : mDisplayManager.getDisplays()) {
+            DisplayAddress address = display.getAddress();
+            if (!(address instanceof DisplayAddress.Physical)) {
+                continue;
+            }
+            DisplayAddress.Physical physical = (DisplayAddress.Physical) address;
+            if (physical.getPort() == displayPort) {
+                return display.getDisplayId();
+            }
+        }
+        return Display.INVALID_DISPLAY;
+    }
+}
diff --git a/ClusterOsDouble/src/com/android/car/cluster/sensors/Sensor.java b/ClusterOsDouble/src/com/android/car/cluster/sensors/Sensor.java
new file mode 100644
index 0000000..d2d89e4
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/sensors/Sensor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.cluster.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.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;
+    }
+
+    @Override
+    public String toString() {
+        return mName;
+    }
+}
diff --git a/ClusterOsDouble/src/com/android/car/cluster/sensors/Sensors.java b/ClusterOsDouble/src/com/android/car/cluster/sensors/Sensors.java
new file mode 100644
index 0000000..3ac3f7b
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/sensors/Sensors.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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.cluster.sensors;
+
+import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
+
+import android.car.VehiclePropertyIds;
+import android.car.VehiclePropertyType;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.CarSensorEvent;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * The collection of all sensors supported by this application.
+ */
+public class Sensors {
+    private static final List<Sensor<?>> sSensors = new ArrayList<>();
+    private final SparseArray<Sensor<?>> mSensorByPropertyId = new SparseArray<>();
+
+    /** 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, VEHICLE_AREA_TYPE_GLOBAL,
+            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, VEHICLE_AREA_TYPE_GLOBAL,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** RPMs */
+    public static final Sensor<Float> SENSOR_RPM = registerSensor(
+            "RPM", VehiclePropertyIds.ENGINE_RPM, VEHICLE_AREA_TYPE_GLOBAL,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Fuel range in meters */
+    public static final Sensor<Float> SENSOR_FUEL_RANGE = registerSensor(
+            "Fuel Range", VehiclePropertyIds.RANGE_REMAINING, VEHICLE_AREA_TYPE_GLOBAL,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Speed in meters per second */
+    public static final Sensor<Float> SENSOR_SPEED = registerSensor(
+            "Speed", VehiclePropertyIds.PERF_VEHICLE_SPEED, VEHICLE_AREA_TYPE_GLOBAL,
+            VehiclePropertyType.FLOAT,
+            value -> (Float) value.getValue());
+    /** Current gear of the car */
+    public static final Sensor<Gear> SENSOR_GEAR = registerSensor(
+            "Gear", VehiclePropertyIds.GEAR_SELECTION, VEHICLE_AREA_TYPE_GLOBAL,
+            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;
+    }
+
+    // Should be located after all registerSensor().
+    private static Sensors sInstance = new Sensors();
+
+    /**
+     * Obtains the singleton instance of this class
+     */
+    public static Sensors getInstance() {
+        return sInstance;
+    }
+
+    private Sensors() {
+        initializeSensorsMap();
+    }
+
+    private void initializeSensorsMap() {
+        for (Sensor<?> sensorId : getSensors()) {
+            mSensorByPropertyId.append(sensorId.mPropertyId, sensorId);
+        }
+    }
+
+    /**
+     * Returns all sensors.
+     */
+    public List<Sensor<?>> getSensors() {
+        return sSensors;
+    }
+
+    /**
+     * Returns all sensors associated to the given VHAL property id.
+     */
+    public Sensor<?> getSensorForPropertyId(int propertyId) {
+        return mSensorByPropertyId.get(propertyId);
+    }
+
+    /**
+     * Returns all property ids we care about.
+     */
+    public int[] getPropertyIds() {
+        int[] ids = new int[mSensorByPropertyId.size()];
+        for (int i = mSensorByPropertyId.size() - 1; i >= 0; --i) {
+            ids[i] = mSensorByPropertyId.keyAt(i);
+        }
+        return ids;
+    }
+}
diff --git a/ClusterOsDouble/src/com/android/car/cluster/view/BitmapFetcher.java b/ClusterOsDouble/src/com/android/car/cluster/view/BitmapFetcher.java
new file mode 100644
index 0000000..3ee825b
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/view/BitmapFetcher.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.cluster.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.util.LruCache;
+
+import java.io.IOException;
+
+/**
+ * Class to fetch a bitmap images from the ContentProvider.
+ */
+public class BitmapFetcher {
+    private static final String TAG = "Cluster.BitmapFetcher";
+
+    private static final int IMAGE_CACHE_SIZE_BYTES = 4 * 1024 * 1024; /* 4 mb */
+    private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(
+            IMAGE_CACHE_SIZE_BYTES) {
+        @Override
+        protected int sizeOf(String key, Bitmap value) {
+            return value.getByteCount();
+        }
+    };
+    private static final String BITMAP_QUERY_WIDTH = "w";
+    private static final String BITMAP_QUERY_HEIGHT = "h";
+    private static final String BITMAP_QUERY_OFFLANESALPHA = "offLanesAlpha";
+
+    private final Context mContext;
+
+    public BitmapFetcher (Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Fetches a bitmap from the ContentProvider with the given width and height
+     * and off lane opacity. The fetched bitmaps are cached.
+     * It returns null if:
+     * <ul>
+     * <li>there is no navigation context owner
+     * <li>or if the {@link Uri} is invalid
+     * <li>or if it references a process other than the current navigation context owner
+     * </ul>
+     * This is a costly operation. Returned bitmaps should be fetched on a secondary thread.
+     *
+     * @param uri           The URI of the bitmap
+     * @param width         Requested width
+     * @param height        Requested height
+     * @param offLanesAlpha Opacity value of the off-lane images. Only used for lane guidance images
+     * @throws IllegalArgumentException if width, height <= 0, or 0 > offLanesAlpha > 1
+     */
+    @Nullable
+    public Bitmap getBitmap(@NonNull Uri uri, int width, int height, float offLanesAlpha) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Width and height must be > 0");
+        }
+        if (offLanesAlpha < 0 || offLanesAlpha > 1) {
+            throw new IllegalArgumentException("offLanesAlpha must be between [0, 1]");
+        }
+
+        try {
+            String host = uri.getHost();
+
+            uri = uri.buildUpon()
+                    .appendQueryParameter(BITMAP_QUERY_WIDTH, String.valueOf(width))
+                    .appendQueryParameter(BITMAP_QUERY_HEIGHT, String.valueOf(height))
+                    .appendQueryParameter(BITMAP_QUERY_OFFLANESALPHA, String.valueOf(offLanesAlpha))
+                    .build();
+
+            // Add user to URI to make the request to the right instance of content provider
+            // (see ContentProvider#getUserIdFromAuthority()).
+            int userId = ActivityManager.getCurrentUser();
+            Uri filteredUid = uri.buildUpon().encodedAuthority(userId + "@" + host).build();
+
+            Bitmap bitmap = mCache.get(uri.toString());
+            if (bitmap == null) {
+                // Fetch the bitmap
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Requesting bitmap: " + uri);
+                }
+                try (ParcelFileDescriptor fileDesc = mContext.getContentResolver()
+                        .openFileDescriptor(filteredUid, "r")) {
+                    if (fileDesc != null) {
+                        bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
+                        return bitmap;
+                    } else {
+                        Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+                    }
+                }
+                if (bitmap.getWidth() != width || bitmap.getHeight() != height) {
+                    bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
+                }
+                mCache.put(uri.toString(), bitmap);
+            }
+            return bitmap;
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to fetch uri: " + uri, e);
+        }
+        return null;
+    }
+}
diff --git a/ClusterOsDouble/src/com/android/car/cluster/view/ClusterViewModel.java b/ClusterOsDouble/src/com/android/car/cluster/view/ClusterViewModel.java
new file mode 100644
index 0000000..f43d2be
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/view/ClusterViewModel.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2021 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.cluster.view;
+
+import static android.car.Car.CarServiceLifecycleListener;
+import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
+
+import android.annotation.Nullable;
+import android.app.Application;
+import android.car.Car;
+import android.car.CarAppFocusManager;
+import android.car.CarNotConnectedException;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.TypedValue;
+
+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 com.android.car.cluster.osdouble.R;
+import com.android.car.cluster.sensors.Sensor;
+import com.android.car.cluster.sensors.Sensors;
+
+import java.text.DecimalFormat;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * {@link AndroidViewModel} for cluster information.
+ */
+public class ClusterViewModel extends AndroidViewModel {
+    private static final String TAG = "Cluster.ViewModel";
+
+    private static final int PROPERTIES_REFRESH_RATE_UI = 5;
+
+    private float mSpeedFactor;
+    private float mDistanceFactor;
+
+    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 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 ArrayMap<>();
+
+    private final CarServiceLifecycleListener mCarListener = new CarServiceLifecycleListener() {
+        @Override
+        public void onLifecycleChanged(@NonNull Car car, boolean ready) {
+            if (ready) {
+                Log.i(TAG, "onCarServiceConnected");
+                mCar = car;
+                registerAppFocusListener();
+                registerCarPropertiesListener();
+            } else {
+                Log.i(TAG, "onCarServiceDisconnected");
+                mCarAppFocusManager = null;
+                mCarPropertyManager = null;
+            }
+        }
+    };
+
+    private void registerAppFocusListener() throws CarNotConnectedException {
+        mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(Car.APP_FOCUS_SERVICE);
+        if (mCarAppFocusManager != null) {
+            mCarAppFocusManager.addFocusListener(
+                    (appType, active) -> setNavigationFocus(active),
+                    CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        } else {
+            Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
+        }
+    }
+
+    private void registerCarPropertiesListener() throws CarNotConnectedException {
+        Sensors sensors = Sensors.getInstance();
+        mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+        for (int propertyId : sensors.getPropertyIds()) {
+            try {
+                mCarPropertyManager.registerCallback(mCarPropertyEventCallback,
+                        propertyId, PROPERTIES_REFRESH_RATE_UI);
+            } catch (SecurityException ex) {
+                Log.e(TAG, "onServiceConnected: Unable to listen to car property: " + propertyId
+                        + " sensors: " + sensors.getSensorForPropertyId(propertyId), ex);
+            }
+        }
+    }
+
+    private CarPropertyEventCallback mCarPropertyEventCallback = new CarPropertyEventCallback() {
+        @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());
+            }
+            Sensor<?> sensorId = Sensors.getInstance()
+                    .getSensorForPropertyId(value.getPropertyId());
+            if (sensorId != null && (sensorId.mAreaId == VEHICLE_AREA_TYPE_GLOBAL
+                    || sensorId.mAreaId == value.getAreaId())) {
+                setSensorValue(sensorId, value);
+            }
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+            Sensor<?> sensorId = Sensors.getInstance().getSensorForPropertyId(propId);
+            if (sensorId != null && (sensorId.mAreaId == VEHICLE_AREA_TYPE_GLOBAL
+                    || sensorId.mAreaId == zone)) {
+                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);
+        Car.createCar(application, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+                mCarListener);
+
+        TypedValue tv = new TypedValue();
+        getApplication().getResources().getValue(R.dimen.speed_factor, tv, true);
+        mSpeedFactor = tv.getFloat();
+
+        getApplication().getResources().getValue(R.dimen.distance_factor, tv, true);
+        mDistanceFactor = tv.getFloat();
+    }
+
+    @Override
+    protected void onCleared() {
+        super.onCleared();
+        mCar.disconnect();
+        mCar = null;
+        mCarAppFocusManager = null;
+        mCarPropertyManager = null;
+    }
+
+    /**
+     * Returns a {@link LiveData} providing the current state of the activity displayed on the
+     * navigation fragment.
+     */
+    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.
+     */
+    public LiveData<Boolean> getNavigationFocus() {
+        return mNavigationFocus;
+    }
+
+    /**
+     * 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
+     */
+    @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 to 100.
+     */
+    public LiveData<Integer> 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;
+            }
+            if (fuelValue > fuelCapacityValue) {
+                return 100;
+            }
+            return Math.round(fuelValue / fuelCapacityValue * 100f);
+        });
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the RPM x 1000
+     */
+    public LiveData<String> getRPM() {
+        return Transformations.map(getSensor(Sensors.SENSOR_RPM), (rpmValue) -> {
+            return new DecimalFormat("#0.0").format(rpmValue / 1000f);
+        });
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the speed in either mi/h or km/h depending on locale.
+     */
+    public LiveData<Integer> getSpeed() {
+        return Transformations.map(getSensor(Sensors.SENSOR_SPEED), (speedValue) -> {
+            return Math.round(speedValue * mSpeedFactor);
+        });
+    }
+
+    /**
+     * Returns a {@link LiveData} that tracks the range the vehicle has until it runs out of gas.
+     */
+    public LiveData<Integer> getRange() {
+        return Transformations.map(getSensor(Sensors.SENSOR_FUEL_RANGE), (rangeValue) -> {
+            return Math.round(rangeValue / mDistanceFactor);
+        });
+    }
+
+    /**
+     * 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();
+        }
+    }
+
+    /**
+     * Sets whether navigation focus is currently being granted or not.
+     */
+    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/src/android/car/cluster/CueView.java b/ClusterOsDouble/src/com/android/car/cluster/view/CueView.java
similarity index 74%
copy from src/android/car/cluster/CueView.java
copy to ClusterOsDouble/src/com/android/car/cluster/view/CueView.java
index fa08846..2bc8b89 100644
--- a/src/android/car/cluster/CueView.java
+++ b/ClusterOsDouble/src/com/android/car/cluster/view/CueView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -13,8 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.car.cluster;
+package com.android.car.cluster.view;
 
+import android.car.cluster.navigation.NavigationState.Cue;
+import android.car.cluster.navigation.NavigationState.Cue.CueElement;
+import android.car.cluster.navigation.NavigationState.ImageReference;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
@@ -25,9 +28,7 @@
 import android.util.Log;
 import android.widget.TextView;
 
-import android.car.cluster.navigation.NavigationState.ImageReference;
-import android.car.cluster.navigation.NavigationState.Cue;
-import android.car.cluster.navigation.NavigationState.Cue.CueElement;
+import com.android.car.cluster.osdouble.R;
 
 import java.util.Collections;
 import java.util.List;
@@ -66,7 +67,13 @@
         mImageSpanText = context.getString(R.string.span_image);
     }
 
-    public void setCue(Cue cue, ImageResolver imageResolver) {
+    /**
+     * Set the given {@link Cue} to the View.
+     * @param cue {@link Cue} to set in the view
+     * @param imageResolver {@link ImageResolver} to fetch the {@link Bitmap}
+     * @param alpha the opacity of the image
+     */
+    public void setCue(Cue cue, ImageResolver imageResolver, float alpha) {
         if (cue == null) {
             setText(null);
             return;
@@ -83,20 +90,21 @@
         mFuture = imageResolver
                 .getBitmaps(imageReferences, 0, getLineHeight())
                 .thenAccept(bitmaps -> {
-                    mHandler.post(() -> update(cue, bitmaps));
+                    mHandler.post(() -> update(cue, bitmaps, alpha));
                     mFuture = null;
                 })
                 .exceptionally(ex -> {
                     if (Log.isLoggable(TAG, Log.DEBUG)) {
                         Log.d(TAG, "Unable to fetch images for cue: " + cue);
                     }
-                    mHandler.post(() -> update(cue, Collections.emptyMap()));
+                    mHandler.post(
+                            () -> update(cue, Collections.emptyMap(), alpha));
                     return null;
                 });
         mContent = cue;
     }
 
-    private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps) {
+    private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps, float alpha) {
         SpannableStringBuilder builder = new SpannableStringBuilder();
 
         for (CueElement element : cue.getElementsList()) {
@@ -105,12 +113,7 @@
                 if (bitmap != null) {
                     String imageText = element.getText().isEmpty() ? mImageSpanText :
                             element.getText();
-                    int start = builder.length();
-                    int end = start + imageText.length();
-                    builder.append(imageText);
-                    BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
-                    drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
-                    builder.setSpan(new ImageSpan(drawable), start, end, 0);
+                    appendImage(builder, imageText, bitmap, alpha);
                 }
             } else if (!element.getText().isEmpty()) {
                 builder.append(element.getText());
@@ -118,5 +121,17 @@
         }
 
         setText(builder);
+        setAlpha(alpha);
+    }
+
+    private void appendImage(SpannableStringBuilder builder, String text, Bitmap bitmap,
+            float alpha) {
+        int start = builder.length();
+        int end = start + text.length();
+        builder.append(text);
+        BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
+        drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        drawable.setAlpha((int) (alpha * 255));
+        builder.setSpan(new ImageSpan(drawable), start, end, 0);
     }
 }
diff --git a/ClusterOsDouble/src/com/android/car/cluster/view/ImageResolver.java b/ClusterOsDouble/src/com/android/car/cluster/view/ImageResolver.java
new file mode 100644
index 0000000..2ceb4df
--- /dev/null
+++ b/ClusterOsDouble/src/com/android/car/cluster/view/ImageResolver.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.cluster.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.cluster.navigation.NavigationState.ImageReference;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+/**
+ * Class for retrieving bitmap images from a ContentProvider
+ *
+ * @hide
+ */
+public class ImageResolver {
+    private static final String TAG = "Cluster.ImageResolver";
+    private final BitmapFetcher mFetcher;
+
+    /**
+     * Creates a resolver that delegate the image retrieval to the given fetcher.
+     */
+    public ImageResolver(BitmapFetcher fetcher) {
+        mFetcher = fetcher;
+    }
+
+    /**
+     * Returns a {@link CompletableFuture} that provides a bitmap from a {@link ImageReference}.
+     * This image would fit inside the provided size. Either width, height or both should be greater
+     * than 0.
+     *
+     * @param width  required width, or 0 if width is flexible based on height.
+     * @param height required height, or 0 if height is flexible based on width.
+     */
+    @NonNull
+    public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height) {
+        return getBitmap(img, width, height, 1f);
+    }
+
+    /**
+     * Returns a {@link CompletableFuture} that provides a bitmap from a {@link ImageReference}.
+     * This image would fit inside the provided size. Either width, height or both should be greater
+     * than 0.
+     *
+     * @param width         required width, or 0 if width is flexible based on height.
+     * @param height        required height, or 0 if height is flexible based on width.
+     * @param offLanesAlpha opacity value for off lane guidance images. Only applies to lane
+     *                      guidance images. 0 (transparent) <= offLanesAlpha <= 1 (opaque).
+     */
+    @NonNull
+    public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height,
+            float offLanesAlpha) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, String.format("Requesting image %s (width: %d, height: %d)",
+                    img.getContentUri(), width, height));
+        }
+
+        return CompletableFuture.supplyAsync(() -> {
+            // Adjust the size to fit in the requested box.
+            Point adjusted = getAdjustedSize(img.getAspectRatio(), width, height);
+            if (adjusted == null) {
+                Log.e(TAG, "The provided image has no aspect ratio: " + img.getContentUri());
+                return null;
+            }
+
+            Uri uri = Uri.parse(img.getContentUri());
+            Bitmap bitmap = null;
+            try {
+                bitmap = mFetcher.getBitmap(uri, adjusted.x, adjusted.y, offLanesAlpha);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, e.getMessage());
+            }
+            if (bitmap == null) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Unable to fetch image: " + uri);
+                }
+                return null;
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, String.format("Returning image %s (width: %d, height: %d)",
+                        img.getContentUri(), width, height));
+            }
+            return bitmap;
+        });
+    }
+
+    /**
+     * Same as {@link #getBitmap(ImageReference, int, int)} but it works on a list of images. The
+     * returning {@link CompletableFuture} will contain a map from each {@link ImageReference} to
+     * its bitmap. If any image fails to be fetched, the whole future completes exceptionally.
+     *
+     * @param width  required width, or 0 if width is flexible based on height.
+     * @param height required height, or 0 if height is flexible based on width.
+     */
+    @NonNull
+    public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
+            @NonNull List<ImageReference> imgs, int width, int height) {
+        return getBitmaps(imgs, width, height, 1f);
+    }
+
+    /**
+     * Same as {@link #getBitmap(ImageReference, int, int)} but it works on a list of images. The
+     * returning {@link CompletableFuture} will contain a map from each {@link ImageReference} to
+     * its bitmap. If any image fails to be fetched, the whole future completes exceptionally.
+     *
+     * @param width         required width, or 0 if width is flexible based on height.
+     * @param height        required height, or 0 if height is flexible based on width.
+     * @param offLanesAlpha opacity value for off lane guidance images. Only applies to lane
+     *                      guidance images. 0 (transparent) <= offLanesAlpha <= 1 (opaque).
+     */
+    @NonNull
+    public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
+            @NonNull List<ImageReference> imgs, int width, int height, float offLanesAlpha) {
+        CompletableFuture<Map<ImageReference, Bitmap>> future = new CompletableFuture<>();
+
+        Map<ImageReference, CompletableFuture<Bitmap>> bitmapFutures = imgs.stream().collect(
+                Collectors.toMap(
+                        img -> img,
+                        img -> getBitmap(img, width, height, offLanesAlpha)));
+
+        CompletableFuture.allOf(bitmapFutures.values().toArray(new CompletableFuture[0]))
+                .thenAccept(v -> {
+                    Map<ImageReference, Bitmap> bitmaps = bitmapFutures.entrySet().stream()
+                            .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry
+                                    .getValue().join()));
+                    future.complete(bitmaps);
+                })
+                .exceptionally(ex -> {
+                    future.completeExceptionally(ex);
+                    return null;
+                });
+
+        return future;
+    }
+
+    /**
+     * Returns an image size that exactly fits inside a requested box, maintaining an original size
+     * aspect ratio.
+     *
+     * @param imageRatio      original aspect ratio (must be > 0)
+     * @param requestedWidth  required width, or 0 if width is flexible based on height.
+     * @param requestedHeight required height, or 0 if height is flexible based on width.
+     */
+    @Nullable
+    public Point getAdjustedSize(double imageRatio, int requestedWidth,
+            int requestedHeight) {
+        if (imageRatio <= 0) {
+            return null;
+        } else if (requestedWidth == 0 && requestedHeight == 0) {
+            throw new IllegalArgumentException("At least one of width or height must be != 0");
+        }
+        // If width is flexible or if both width and height are set and the original image is wider
+        // than the space provided, then scale the width.
+        float requiredRatio = requestedHeight > 0 ? ((float) requestedWidth) / requestedHeight : 0;
+        Point res = new Point(requestedWidth, requestedHeight);
+        if (requestedWidth == 0 || (requestedHeight != 0 && imageRatio < requiredRatio)) {
+            res.x = (int) (imageRatio * requestedHeight);
+        } else {
+            res.y = (int) (requestedWidth / imageRatio);
+        }
+        return res;
+    }
+}
diff --git a/src/android/car/cluster/LaneView.java b/ClusterOsDouble/src/com/android/car/cluster/view/LaneView.java
similarity index 96%
copy from src/android/car/cluster/LaneView.java
copy to ClusterOsDouble/src/com/android/car/cluster/view/LaneView.java
index 7d48e28..9072197 100644
--- a/src/android/car/cluster/LaneView.java
+++ b/ClusterOsDouble/src/com/android/car/cluster/view/LaneView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -13,9 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.car.cluster;
+package com.android.car.cluster.view;
 
 import android.annotation.Nullable;
+import android.car.cluster.navigation.NavigationState.ImageReference;
+import android.car.cluster.navigation.NavigationState.Lane;
+import android.car.cluster.navigation.NavigationState.Lane.LaneDirection;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -29,9 +32,7 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
-import android.car.cluster.navigation.NavigationState.ImageReference;
-import android.car.cluster.navigation.NavigationState.Lane;
-import android.car.cluster.navigation.NavigationState.Lane.LaneDirection;
+import com.android.car.cluster.osdouble.R;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -89,7 +90,7 @@
                 });
     }
 
-    public void setLanes(List<Lane> lanes) {
+    public void setLanes(List<Lane> lanes, float alpha) {
         mLanes = new ArrayList<>(lanes);
         removeAllViews();
 
@@ -99,6 +100,7 @@
             ImageView imgView = new ImageView(getContext());
             imgView.setImageBitmap(bitmap);
             imgView.setAdjustViewBounds(true);
+            imgView.setImageAlpha((int) (alpha * 255));
             addView(imgView);
         }
     }
diff --git a/src/android/car/cluster/NavStateController.java b/ClusterOsDouble/src/com/android/car/cluster/view/NavStateController.java
similarity index 90%
copy from src/android/car/cluster/NavStateController.java
copy to ClusterOsDouble/src/com/android/car/cluster/view/NavStateController.java
index fe4d724..37d7164 100644
--- a/src/android/car/cluster/NavStateController.java
+++ b/ClusterOsDouble/src/com/android/car/cluster/view/NavStateController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -13,9 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.car.cluster;
+package com.android.car.cluster.view;
 
-import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.NORMAL;
 import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.REROUTING;
 import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.SERVICE_STATUS_UNSPECIFIED;
 
@@ -33,11 +32,14 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.util.Log;
+import android.util.TypedValue;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.car.cluster.osdouble.R;
+
 import java.time.Instant;
 
 /**
@@ -46,23 +48,23 @@
 public class NavStateController {
     private static final String TAG = "Cluster.NavController";
 
-    private Handler mHandler = new Handler();
+    private final Handler mHandler = new Handler();
 
-    private View mNavigationState;
-    private LinearLayout mSectionManeuver;
-    private LinearLayout mSectionNavigation;
-    private LinearLayout mSectionServiceStatus;
+    private final View mNavigationState;
+    private final LinearLayout mSectionManeuver;
+    private final LinearLayout mSectionNavigation;
+    private final LinearLayout mSectionServiceStatus;
 
-    private ImageView mManeuver;
-    private ImageView mProvidedManeuver;
-    private LaneView mLane;
-    private LaneView mProvidedLane;
-    private TextView mDistance;
-    private TextView mSegment;
-    private TextView mEta;
-    private CueView mCue;
-    private Context mContext;
-    private ImageResolver mImageResolver;
+    private final ImageView mManeuver;
+    private final ImageView mProvidedManeuver;
+    private final LaneView mLane;
+    private final LaneView mProvidedLane;
+    private final TextView mDistance;
+    private final TextView mSegment;
+    private final TextView mEta;
+    private final CueView mCue;
+    private final Context mContext;
+    private final ImageResolver mImageResolver;
 
     /**
      * Creates a controller to coordinate updates to the views displaying navigation state
@@ -70,7 +72,7 @@
      *
      * @param container {@link View} containing the navigation state views
      */
-    public NavStateController(View container) {
+    public NavStateController(View container, ImageResolver imageResolver) {
         mNavigationState = container;
         mSectionManeuver = container.findViewById(R.id.section_maneuver);
         mSectionNavigation = container.findViewById(R.id.section_navigation);
@@ -86,6 +88,7 @@
         mCue = container.findViewById(R.id.cue);
 
         mContext = container.getContext();
+        mImageResolver = imageResolver;
     }
 
     public void hideNavigationStateInfo() {
@@ -96,10 +99,6 @@
         mNavigationState.setVisibility(View.VISIBLE);
     }
 
-    public void setImageResolver(@Nullable ImageResolver imageResolver) {
-        mImageResolver = imageResolver;
-    }
-
     /**
      * Updates views to reflect the provided navigation state
      */
@@ -130,6 +129,12 @@
         }
 
         Step step = state.getStepsCount() > 0 ? state.getSteps(0) : null;
+
+        // Get alpha based on is_imminent
+        float alpha = (step != null && !step.getIsImminent())
+                ? getAlphaFromResource(R.dimen.non_imminent_alpha)
+                : 1f;
+
         Destination destination = state.getDestinationsCount() > 0
                 ? state.getDestinations(0) : null;
         Traffic traffic = destination != null ? destination.getTraffic() : null;
@@ -144,9 +149,11 @@
         setProvidedManeuverIcon(mProvidedManeuver, step != null
                 ? step.getManeuver().hasIcon() ? step.getManeuver().getIcon() : null
                 : null);
+        mManeuver.setImageAlpha((int) (alpha * 255));
         mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
+        mDistance.setAlpha(alpha);
         mSegment.setText(getSegmentString(state.getCurrentRoad()));
-        mCue.setCue(step != null ? step.getCue() : null, mImageResolver);
+        mCue.setCue(step != null ? step.getCue() : null, mImageResolver, alpha);
 
         if (step != null && step.getLanesCount() > 0) {
             if (step.hasLanesImage()) {
@@ -154,12 +161,22 @@
                 mProvidedLane.setVisibility(View.VISIBLE);
             }
 
-            mLane.setLanes(step.getLanesList());
+            mLane.setLanes(step.getLanesList(), alpha);
             mLane.setVisibility(View.VISIBLE);
         } else {
             mLane.setVisibility(View.GONE);
             mProvidedLane.setVisibility(View.GONE);
         }
+
+    }
+
+    /**
+     * Get float value from dimens.xml, it only works for float format
+     */
+    private float getAlphaFromResource(int alphaId) {
+        TypedValue typedValue = new TypedValue();
+        mContext.getResources().getValue(alphaId, typedValue, true);
+        return typedValue.getFloat();
     }
 
     private int getTrafficColor(@Nullable Traffic traffic) {
diff --git a/Android.bp b/DirectRenderingCluster/Android.bp
similarity index 93%
rename from Android.bp
rename to DirectRenderingCluster/Android.bp
index f897aff..d3ba62a 100644
--- a/Android.bp
+++ b/DirectRenderingCluster/Android.bp
@@ -38,18 +38,18 @@
 
     static_libs: [
         "android.car.cluster.navigation",
-        "android.car.userlib",
         "androidx.legacy_legacy-support-v4",
         "androidx-constraintlayout_constraintlayout",
         "car-arch-common",
         "car-media-common",
         "car-telephony-common",
         "car-apps-common",
+        "com.android.car.internal.common",
     ],
 
     libs: ["android.car"],
 
-    required: ["privapp_whitelist_android.car.cluster"],
+    required: ["allowed_privapp_android.car.cluster"],
 
     product_variables: {
         pdk: {
diff --git a/AndroidManifest.xml b/DirectRenderingCluster/AndroidManifest.xml
similarity index 96%
rename from AndroidManifest.xml
rename to DirectRenderingCluster/AndroidManifest.xml
index 7c60d96..57c31c0 100644
--- a/AndroidManifest.xml
+++ b/DirectRenderingCluster/AndroidManifest.xml
@@ -48,9 +48,11 @@
     <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"/>
     <uses-permission android:name="android.car.permission.CAR_ENGINE_DETAILED"/>
 
+    <!-- Required to query packages in Android 11+ -->
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <application android:label="@string/app_name"
diff --git a/res/color/icon_color.xml b/DirectRenderingCluster/res/color/icon_color.xml
similarity index 100%
copy from res/color/icon_color.xml
copy to DirectRenderingCluster/res/color/icon_color.xml
diff --git a/res/drawable-hdpi/ic_car_info.png b/DirectRenderingCluster/res/drawable-hdpi/ic_car_info.png
similarity index 100%
copy from res/drawable-hdpi/ic_car_info.png
copy to DirectRenderingCluster/res/drawable-hdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_music.png b/DirectRenderingCluster/res/drawable-hdpi/ic_music.png
similarity index 100%
copy from res/drawable-hdpi/ic_music.png
copy to DirectRenderingCluster/res/drawable-hdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_nav.png b/DirectRenderingCluster/res/drawable-hdpi/ic_nav.png
similarity index 100%
copy from res/drawable-hdpi/ic_nav.png
copy to DirectRenderingCluster/res/drawable-hdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_phone.png b/DirectRenderingCluster/res/drawable-hdpi/ic_phone.png
similarity index 100%
copy from res/drawable-hdpi/ic_phone.png
copy to DirectRenderingCluster/res/drawable-hdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_car_info.png b/DirectRenderingCluster/res/drawable-mdpi/ic_car_info.png
similarity index 100%
copy from res/drawable-mdpi/ic_car_info.png
copy to DirectRenderingCluster/res/drawable-mdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_music.png b/DirectRenderingCluster/res/drawable-mdpi/ic_music.png
similarity index 100%
copy from res/drawable-mdpi/ic_music.png
copy to DirectRenderingCluster/res/drawable-mdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_nav.png b/DirectRenderingCluster/res/drawable-mdpi/ic_nav.png
similarity index 100%
copy from res/drawable-mdpi/ic_nav.png
copy to DirectRenderingCluster/res/drawable-mdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_phone.png b/DirectRenderingCluster/res/drawable-mdpi/ic_phone.png
similarity index 100%
copy from res/drawable-mdpi/ic_phone.png
copy to DirectRenderingCluster/res/drawable-mdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_car_info.png b/DirectRenderingCluster/res/drawable-xhdpi/ic_car_info.png
similarity index 100%
copy from res/drawable-xhdpi/ic_car_info.png
copy to DirectRenderingCluster/res/drawable-xhdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_music.png b/DirectRenderingCluster/res/drawable-xhdpi/ic_music.png
similarity index 100%
copy from res/drawable-xhdpi/ic_music.png
copy to DirectRenderingCluster/res/drawable-xhdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_nav.png b/DirectRenderingCluster/res/drawable-xhdpi/ic_nav.png
similarity index 100%
copy from res/drawable-xhdpi/ic_nav.png
copy to DirectRenderingCluster/res/drawable-xhdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_phone.png b/DirectRenderingCluster/res/drawable-xhdpi/ic_phone.png
similarity index 100%
copy from res/drawable-xhdpi/ic_phone.png
copy to DirectRenderingCluster/res/drawable-xhdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_car_info.png b/DirectRenderingCluster/res/drawable-xxhdpi/ic_car_info.png
similarity index 100%
copy from res/drawable-xxhdpi/ic_car_info.png
copy to DirectRenderingCluster/res/drawable-xxhdpi/ic_car_info.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_music.png b/DirectRenderingCluster/res/drawable-xxhdpi/ic_music.png
similarity index 100%
copy from res/drawable-xxhdpi/ic_music.png
copy to DirectRenderingCluster/res/drawable-xxhdpi/ic_music.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_nav.png b/DirectRenderingCluster/res/drawable-xxhdpi/ic_nav.png
similarity index 100%
copy from res/drawable-xxhdpi/ic_nav.png
copy to DirectRenderingCluster/res/drawable-xxhdpi/ic_nav.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_phone.png b/DirectRenderingCluster/res/drawable-xxhdpi/ic_phone.png
similarity index 100%
copy from res/drawable-xxhdpi/ic_phone.png
copy to DirectRenderingCluster/res/drawable-xxhdpi/ic_phone.png
Binary files differ
diff --git a/res/drawable/car_top_view.png b/DirectRenderingCluster/res/drawable/car_top_view.png
similarity index 100%
copy from res/drawable/car_top_view.png
copy to DirectRenderingCluster/res/drawable/car_top_view.png
Binary files differ
diff --git a/res/drawable/direction_arrive.xml b/DirectRenderingCluster/res/drawable/direction_arrive.xml
similarity index 100%
copy from res/drawable/direction_arrive.xml
copy to DirectRenderingCluster/res/drawable/direction_arrive.xml
diff --git a/res/drawable/direction_arrive_left.xml b/DirectRenderingCluster/res/drawable/direction_arrive_left.xml
similarity index 100%
copy from res/drawable/direction_arrive_left.xml
copy to DirectRenderingCluster/res/drawable/direction_arrive_left.xml
diff --git a/res/drawable/direction_arrive_right.xml b/DirectRenderingCluster/res/drawable/direction_arrive_right.xml
similarity index 100%
copy from res/drawable/direction_arrive_right.xml
copy to DirectRenderingCluster/res/drawable/direction_arrive_right.xml
diff --git a/res/drawable/direction_arrive_straight.xml b/DirectRenderingCluster/res/drawable/direction_arrive_straight.xml
similarity index 100%
copy from res/drawable/direction_arrive_straight.xml
copy to DirectRenderingCluster/res/drawable/direction_arrive_straight.xml
diff --git a/res/drawable/direction_close.xml b/DirectRenderingCluster/res/drawable/direction_close.xml
similarity index 100%
copy from res/drawable/direction_close.xml
copy to DirectRenderingCluster/res/drawable/direction_close.xml
diff --git a/res/drawable/direction_continue.xml b/DirectRenderingCluster/res/drawable/direction_continue.xml
similarity index 100%
copy from res/drawable/direction_continue.xml
copy to DirectRenderingCluster/res/drawable/direction_continue.xml
diff --git a/res/drawable/direction_continue_left.xml b/DirectRenderingCluster/res/drawable/direction_continue_left.xml
similarity index 100%
copy from res/drawable/direction_continue_left.xml
copy to DirectRenderingCluster/res/drawable/direction_continue_left.xml
diff --git a/res/drawable/direction_continue_right.xml b/DirectRenderingCluster/res/drawable/direction_continue_right.xml
similarity index 100%
copy from res/drawable/direction_continue_right.xml
copy to DirectRenderingCluster/res/drawable/direction_continue_right.xml
diff --git a/res/drawable/direction_depart.xml b/DirectRenderingCluster/res/drawable/direction_depart.xml
similarity index 100%
copy from res/drawable/direction_depart.xml
copy to DirectRenderingCluster/res/drawable/direction_depart.xml
diff --git a/res/drawable/direction_fork_left.xml b/DirectRenderingCluster/res/drawable/direction_fork_left.xml
similarity index 100%
copy from res/drawable/direction_fork_left.xml
copy to DirectRenderingCluster/res/drawable/direction_fork_left.xml
diff --git a/res/drawable/direction_fork_right.xml b/DirectRenderingCluster/res/drawable/direction_fork_right.xml
similarity index 100%
copy from res/drawable/direction_fork_right.xml
copy to DirectRenderingCluster/res/drawable/direction_fork_right.xml
diff --git a/res/drawable/direction_merge_left.xml b/DirectRenderingCluster/res/drawable/direction_merge_left.xml
similarity index 100%
copy from res/drawable/direction_merge_left.xml
copy to DirectRenderingCluster/res/drawable/direction_merge_left.xml
diff --git a/res/drawable/direction_merge_right.xml b/DirectRenderingCluster/res/drawable/direction_merge_right.xml
similarity index 100%
copy from res/drawable/direction_merge_right.xml
copy to DirectRenderingCluster/res/drawable/direction_merge_right.xml
diff --git a/res/drawable/direction_merge_unspecified.xml b/DirectRenderingCluster/res/drawable/direction_merge_unspecified.xml
similarity index 100%
copy from res/drawable/direction_merge_unspecified.xml
copy to DirectRenderingCluster/res/drawable/direction_merge_unspecified.xml
diff --git a/res/drawable/direction_new_name_straight.xml b/DirectRenderingCluster/res/drawable/direction_new_name_straight.xml
similarity index 100%
copy from res/drawable/direction_new_name_straight.xml
copy to DirectRenderingCluster/res/drawable/direction_new_name_straight.xml
diff --git a/res/drawable/direction_off_ramp_left.xml b/DirectRenderingCluster/res/drawable/direction_off_ramp_left.xml
similarity index 100%
copy from res/drawable/direction_off_ramp_left.xml
copy to DirectRenderingCluster/res/drawable/direction_off_ramp_left.xml
diff --git a/res/drawable/direction_off_ramp_right.xml b/DirectRenderingCluster/res/drawable/direction_off_ramp_right.xml
similarity index 100%
copy from res/drawable/direction_off_ramp_right.xml
copy to DirectRenderingCluster/res/drawable/direction_off_ramp_right.xml
diff --git a/res/drawable/direction_off_ramp_slight_left.xml b/DirectRenderingCluster/res/drawable/direction_off_ramp_slight_left.xml
similarity index 100%
copy from res/drawable/direction_off_ramp_slight_left.xml
copy to DirectRenderingCluster/res/drawable/direction_off_ramp_slight_left.xml
diff --git a/res/drawable/direction_off_ramp_slight_right.xml b/DirectRenderingCluster/res/drawable/direction_off_ramp_slight_right.xml
similarity index 100%
copy from res/drawable/direction_off_ramp_slight_right.xml
copy to DirectRenderingCluster/res/drawable/direction_off_ramp_slight_right.xml
diff --git a/res/drawable/direction_on_ramp_left.xml b/DirectRenderingCluster/res/drawable/direction_on_ramp_left.xml
similarity index 100%
copy from res/drawable/direction_on_ramp_left.xml
copy to DirectRenderingCluster/res/drawable/direction_on_ramp_left.xml
diff --git a/res/drawable/direction_on_ramp_right.xml b/DirectRenderingCluster/res/drawable/direction_on_ramp_right.xml
similarity index 100%
copy from res/drawable/direction_on_ramp_right.xml
copy to DirectRenderingCluster/res/drawable/direction_on_ramp_right.xml
diff --git a/res/drawable/direction_on_ramp_sharp_left.xml b/DirectRenderingCluster/res/drawable/direction_on_ramp_sharp_left.xml
similarity index 100%
copy from res/drawable/direction_on_ramp_sharp_left.xml
copy to DirectRenderingCluster/res/drawable/direction_on_ramp_sharp_left.xml
diff --git a/res/drawable/direction_on_ramp_sharp_right.xml b/DirectRenderingCluster/res/drawable/direction_on_ramp_sharp_right.xml
similarity index 100%
copy from res/drawable/direction_on_ramp_sharp_right.xml
copy to DirectRenderingCluster/res/drawable/direction_on_ramp_sharp_right.xml
diff --git a/res/drawable/direction_on_ramp_slight_left.xml b/DirectRenderingCluster/res/drawable/direction_on_ramp_slight_left.xml
similarity index 100%
copy from res/drawable/direction_on_ramp_slight_left.xml
copy to DirectRenderingCluster/res/drawable/direction_on_ramp_slight_left.xml
diff --git a/res/drawable/direction_on_ramp_slight_right.xml b/DirectRenderingCluster/res/drawable/direction_on_ramp_slight_right.xml
similarity index 100%
copy from res/drawable/direction_on_ramp_slight_right.xml
copy to DirectRenderingCluster/res/drawable/direction_on_ramp_slight_right.xml
diff --git a/res/drawable/direction_roundabout.xml b/DirectRenderingCluster/res/drawable/direction_roundabout.xml
similarity index 100%
copy from res/drawable/direction_roundabout.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout.xml
diff --git a/res/drawable/direction_roundabout_ccw_left.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_left.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_left.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_left.xml
diff --git a/res/drawable/direction_roundabout_ccw_right.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_right.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_right.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_right.xml
diff --git a/res/drawable/direction_roundabout_ccw_sharp_left.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_sharp_left.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_sharp_left.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_sharp_left.xml
diff --git a/res/drawable/direction_roundabout_ccw_sharp_right.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_sharp_right.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_sharp_right.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_sharp_right.xml
diff --git a/res/drawable/direction_roundabout_ccw_slight_left.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_slight_left.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_slight_left.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_slight_left.xml
diff --git a/res/drawable/direction_roundabout_ccw_slight_right.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_slight_right.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_slight_right.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_slight_right.xml
diff --git a/res/drawable/direction_roundabout_ccw_straight.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_ccw_straight.xml
similarity index 100%
copy from res/drawable/direction_roundabout_ccw_straight.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_ccw_straight.xml
diff --git a/res/drawable/direction_roundabout_cw.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw.xml
diff --git a/res/drawable/direction_roundabout_cw_left.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_left.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_left.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_left.xml
diff --git a/res/drawable/direction_roundabout_cw_right.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_right.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_right.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_right.xml
diff --git a/res/drawable/direction_roundabout_cw_sharp_left.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_sharp_left.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_sharp_left.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_sharp_left.xml
diff --git a/res/drawable/direction_roundabout_cw_sharp_right.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_sharp_right.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_sharp_right.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_sharp_right.xml
diff --git a/res/drawable/direction_roundabout_cw_slight_left.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_slight_left.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_slight_left.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_slight_left.xml
diff --git a/res/drawable/direction_roundabout_cw_slight_right.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_slight_right.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_slight_right.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_slight_right.xml
diff --git a/res/drawable/direction_roundabout_cw_straight.xml b/DirectRenderingCluster/res/drawable/direction_roundabout_cw_straight.xml
similarity index 100%
copy from res/drawable/direction_roundabout_cw_straight.xml
copy to DirectRenderingCluster/res/drawable/direction_roundabout_cw_straight.xml
diff --git a/res/drawable/direction_turn_left.xml b/DirectRenderingCluster/res/drawable/direction_turn_left.xml
similarity index 100%
copy from res/drawable/direction_turn_left.xml
copy to DirectRenderingCluster/res/drawable/direction_turn_left.xml
diff --git a/res/drawable/direction_turn_right.xml b/DirectRenderingCluster/res/drawable/direction_turn_right.xml
similarity index 100%
copy from res/drawable/direction_turn_right.xml
copy to DirectRenderingCluster/res/drawable/direction_turn_right.xml
diff --git a/res/drawable/direction_turn_sharp_left.xml b/DirectRenderingCluster/res/drawable/direction_turn_sharp_left.xml
similarity index 100%
copy from res/drawable/direction_turn_sharp_left.xml
copy to DirectRenderingCluster/res/drawable/direction_turn_sharp_left.xml
diff --git a/res/drawable/direction_turn_sharp_right.xml b/DirectRenderingCluster/res/drawable/direction_turn_sharp_right.xml
similarity index 100%
copy from res/drawable/direction_turn_sharp_right.xml
copy to DirectRenderingCluster/res/drawable/direction_turn_sharp_right.xml
diff --git a/res/drawable/direction_turn_slight_left.xml b/DirectRenderingCluster/res/drawable/direction_turn_slight_left.xml
similarity index 100%
copy from res/drawable/direction_turn_slight_left.xml
copy to DirectRenderingCluster/res/drawable/direction_turn_slight_left.xml
diff --git a/res/drawable/direction_turn_slight_right.xml b/DirectRenderingCluster/res/drawable/direction_turn_slight_right.xml
similarity index 100%
copy from res/drawable/direction_turn_slight_right.xml
copy to DirectRenderingCluster/res/drawable/direction_turn_slight_right.xml
diff --git a/res/drawable/direction_uturn_left.xml b/DirectRenderingCluster/res/drawable/direction_uturn_left.xml
similarity index 100%
copy from res/drawable/direction_uturn_left.xml
copy to DirectRenderingCluster/res/drawable/direction_uturn_left.xml
diff --git a/res/drawable/direction_uturn_right.xml b/DirectRenderingCluster/res/drawable/direction_uturn_right.xml
similarity index 100%
copy from res/drawable/direction_uturn_right.xml
copy to DirectRenderingCluster/res/drawable/direction_uturn_right.xml
diff --git a/res/drawable/focused_button_shape.xml b/DirectRenderingCluster/res/drawable/focused_button_shape.xml
similarity index 100%
copy from res/drawable/focused_button_shape.xml
copy to DirectRenderingCluster/res/drawable/focused_button_shape.xml
diff --git a/res/drawable/gradient_bottom.xml b/DirectRenderingCluster/res/drawable/gradient_bottom.xml
similarity index 100%
copy from res/drawable/gradient_bottom.xml
copy to DirectRenderingCluster/res/drawable/gradient_bottom.xml
diff --git a/res/drawable/gradient_top.xml b/DirectRenderingCluster/res/drawable/gradient_top.xml
similarity index 100%
copy from res/drawable/gradient_top.xml
copy to DirectRenderingCluster/res/drawable/gradient_top.xml
diff --git a/res/drawable/seekbar_background.xml b/DirectRenderingCluster/res/drawable/seekbar_background.xml
similarity index 100%
rename from res/drawable/seekbar_background.xml
rename to DirectRenderingCluster/res/drawable/seekbar_background.xml
diff --git a/res/drawable/seekbar_thumb.xml b/DirectRenderingCluster/res/drawable/seekbar_thumb.xml
similarity index 100%
rename from res/drawable/seekbar_thumb.xml
rename to DirectRenderingCluster/res/drawable/seekbar_thumb.xml
diff --git a/res/drawable/speedometer.xml b/DirectRenderingCluster/res/drawable/speedometer.xml
similarity index 100%
rename from res/drawable/speedometer.xml
rename to DirectRenderingCluster/res/drawable/speedometer.xml
diff --git a/res/layout/activity_fake_free_navigation.xml b/DirectRenderingCluster/res/layout/activity_fake_free_navigation.xml
similarity index 100%
rename from res/layout/activity_fake_free_navigation.xml
rename to DirectRenderingCluster/res/layout/activity_fake_free_navigation.xml
diff --git a/res/layout/activity_main.xml b/DirectRenderingCluster/res/layout/activity_main.xml
similarity index 100%
rename from res/layout/activity_main.xml
rename to DirectRenderingCluster/res/layout/activity_main.xml
diff --git a/res/layout/fragment_car_info.xml b/DirectRenderingCluster/res/layout/fragment_car_info.xml
similarity index 100%
rename from res/layout/fragment_car_info.xml
rename to DirectRenderingCluster/res/layout/fragment_car_info.xml
diff --git a/res/layout/fragment_music.xml b/DirectRenderingCluster/res/layout/fragment_music.xml
similarity index 100%
rename from res/layout/fragment_music.xml
rename to DirectRenderingCluster/res/layout/fragment_music.xml
diff --git a/res/layout/fragment_navigation.xml b/DirectRenderingCluster/res/layout/fragment_navigation.xml
similarity index 100%
rename from res/layout/fragment_navigation.xml
rename to DirectRenderingCluster/res/layout/fragment_navigation.xml
diff --git a/res/layout/fragment_phone.xml b/DirectRenderingCluster/res/layout/fragment_phone.xml
similarity index 100%
rename from res/layout/fragment_phone.xml
rename to DirectRenderingCluster/res/layout/fragment_phone.xml
diff --git a/res/layout/include_navigation_state.xml b/DirectRenderingCluster/res/layout/include_navigation_state.xml
similarity index 100%
rename from res/layout/include_navigation_state.xml
rename to DirectRenderingCluster/res/layout/include_navigation_state.xml
diff --git a/res/layout/metadata_normal.xml b/DirectRenderingCluster/res/layout/metadata_normal.xml
similarity index 100%
rename from res/layout/metadata_normal.xml
rename to DirectRenderingCluster/res/layout/metadata_normal.xml
diff --git a/res/mipmap-hdpi/ic_launcher.png b/DirectRenderingCluster/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from res/mipmap-hdpi/ic_launcher.png
rename to DirectRenderingCluster/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher.png b/DirectRenderingCluster/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from res/mipmap-mdpi/ic_launcher.png
rename to DirectRenderingCluster/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher.png b/DirectRenderingCluster/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from res/mipmap-xhdpi/ic_launcher.png
rename to DirectRenderingCluster/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher.png b/DirectRenderingCluster/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from res/mipmap-xxhdpi/ic_launcher.png
rename to DirectRenderingCluster/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher.png b/DirectRenderingCluster/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from res/mipmap-xxxhdpi/ic_launcher.png
rename to DirectRenderingCluster/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/values-en-rUS/dimens.xml b/DirectRenderingCluster/res/values-en-rUS/dimens.xml
similarity index 100%
copy from res/values-en-rUS/dimens.xml
copy to DirectRenderingCluster/res/values-en-rUS/dimens.xml
diff --git a/res/values-w820dp/dimens.xml b/DirectRenderingCluster/res/values-w820dp/dimens.xml
similarity index 100%
copy from res/values-w820dp/dimens.xml
copy to DirectRenderingCluster/res/values-w820dp/dimens.xml
diff --git a/res/values/colors.xml b/DirectRenderingCluster/res/values/colors.xml
similarity index 100%
rename from res/values/colors.xml
rename to DirectRenderingCluster/res/values/colors.xml
diff --git a/res/values/config.xml b/DirectRenderingCluster/res/values/config.xml
similarity index 100%
rename from res/values/config.xml
rename to DirectRenderingCluster/res/values/config.xml
diff --git a/res/values/dimens.xml b/DirectRenderingCluster/res/values/dimens.xml
similarity index 98%
copy from res/values/dimens.xml
copy to DirectRenderingCluster/res/values/dimens.xml
index bff5c60..2072f57 100644
--- a/res/values/dimens.xml
+++ b/DirectRenderingCluster/res/values/dimens.xml
@@ -49,6 +49,8 @@
 
     <dimen name="lane_icon_offset">12.5dp</dimen>
 
+    <item name="non_imminent_alpha" type="dimen" format="float">0.5</item>
+
     <!--                                   -->
     <!-- Sensor value conversion constants -->
     <!--                                   -->
diff --git a/DirectRenderingCluster/res/values/overlayable.xml b/DirectRenderingCluster/res/values/overlayable.xml
new file mode 100644
index 0000000..0f588da
--- /dev/null
+++ b/DirectRenderingCluster/res/values/overlayable.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="ClusterConfig">
+        <policy type="product|system|vendor">
+            <item type="string" name="freeNavigationIntent" />
+            <item type="bool" name="navigationOnly" />
+        </policy>
+    </overlayable>
+</resources>
diff --git a/res/values/strings.xml b/DirectRenderingCluster/res/values/strings.xml
similarity index 100%
rename from res/values/strings.xml
rename to DirectRenderingCluster/res/values/strings.xml
diff --git a/res/values/styles.xml b/DirectRenderingCluster/res/values/styles.xml
similarity index 100%
rename from res/values/styles.xml
rename to DirectRenderingCluster/res/values/styles.xml
diff --git a/res/values/themes.xml b/DirectRenderingCluster/res/values/themes.xml
similarity index 100%
rename from res/values/themes.xml
rename to DirectRenderingCluster/res/values/themes.xml
diff --git a/src/android/car/cluster/ActivityMonitor.java b/DirectRenderingCluster/src/android/car/cluster/ActivityMonitor.java
similarity index 96%
rename from src/android/car/cluster/ActivityMonitor.java
rename to DirectRenderingCluster/src/android/car/cluster/ActivityMonitor.java
index b33edda..b6e1ab0 100644
--- a/src/android/car/cluster/ActivityMonitor.java
+++ b/DirectRenderingCluster/src/android/car/cluster/ActivityMonitor.java
@@ -18,7 +18,7 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackInfo;
+import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.IActivityManager;
 import android.app.IProcessObserver;
 import android.app.TaskStackListener;
@@ -164,8 +164,8 @@
                 if (mActivityManager == null) {
                     return;
                 }
-                List<StackInfo> infos = mActivityManager.getAllStackInfos();
-                for (StackInfo info : infos) {
+                List<RootTaskInfo> infos = mActivityManager.getAllRootTaskInfos();
+                for (RootTaskInfo info : infos) {
                     if (!info.visible) {
                         continue;
                     }
diff --git a/src/android/car/cluster/CarInfoFragment.java b/DirectRenderingCluster/src/android/car/cluster/CarInfoFragment.java
similarity index 100%
rename from src/android/car/cluster/CarInfoFragment.java
rename to DirectRenderingCluster/src/android/car/cluster/CarInfoFragment.java
diff --git a/src/android/car/cluster/ClusterDisplayProvider.java b/DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java
similarity index 100%
rename from src/android/car/cluster/ClusterDisplayProvider.java
rename to DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java
diff --git a/src/android/car/cluster/ClusterRenderingService.java b/DirectRenderingCluster/src/android/car/cluster/ClusterRenderingService.java
similarity index 98%
rename from src/android/car/cluster/ClusterRenderingService.java
rename to DirectRenderingCluster/src/android/car/cluster/ClusterRenderingService.java
index 50d7b15..11bcfac 100644
--- a/src/android/car/cluster/ClusterRenderingService.java
+++ b/DirectRenderingCluster/src/android/car/cluster/ClusterRenderingService.java
@@ -28,7 +28,6 @@
 import android.car.cluster.renderer.InstrumentClusterRenderingService;
 import android.car.cluster.renderer.NavigationRenderer;
 import android.car.navigation.CarNavigationInstrumentCluster;
-import android.car.userlib.UserHelper;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -50,6 +49,8 @@
 import android.view.InputDevice;
 import android.view.KeyEvent;
 
+import com.android.car.internal.common.UserHelperLite;
+
 import com.google.protobuf.InvalidProtocolBufferException;
 
 import java.io.FileDescriptor;
@@ -190,7 +191,7 @@
                 return;
             }
             userId = ActivityManager.getCurrentUser();
-            if (UserHelper.isHeadlessSystemUser(userId)) {
+            if (UserHelperLite.isHeadlessSystemUser(userId)) {
                 Log.i(TAG, "Skipping the navigation activity for User 0");
                 return;
             }
diff --git a/src/android/car/cluster/ClusterViewModel.java b/DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java
similarity index 100%
rename from src/android/car/cluster/ClusterViewModel.java
rename to DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java
diff --git a/src/android/car/cluster/CueView.java b/DirectRenderingCluster/src/android/car/cluster/CueView.java
similarity index 91%
rename from src/android/car/cluster/CueView.java
rename to DirectRenderingCluster/src/android/car/cluster/CueView.java
index fa08846..95fabc7 100644
--- a/src/android/car/cluster/CueView.java
+++ b/DirectRenderingCluster/src/android/car/cluster/CueView.java
@@ -66,7 +66,7 @@
         mImageSpanText = context.getString(R.string.span_image);
     }
 
-    public void setCue(Cue cue, ImageResolver imageResolver) {
+    public void setCue(Cue cue, ImageResolver imageResolver, float alpha) {
         if (cue == null) {
             setText(null);
             return;
@@ -83,20 +83,21 @@
         mFuture = imageResolver
                 .getBitmaps(imageReferences, 0, getLineHeight())
                 .thenAccept(bitmaps -> {
-                    mHandler.post(() -> update(cue, bitmaps));
+                    mHandler.post(() -> update(cue, bitmaps, alpha));
                     mFuture = null;
                 })
                 .exceptionally(ex -> {
                     if (Log.isLoggable(TAG, Log.DEBUG)) {
                         Log.d(TAG, "Unable to fetch images for cue: " + cue);
                     }
-                    mHandler.post(() -> update(cue, Collections.emptyMap()));
+                    mHandler.post(
+                            () -> update(cue, Collections.emptyMap(), alpha));
                     return null;
                 });
         mContent = cue;
     }
 
-    private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps) {
+    private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps, float alpha) {
         SpannableStringBuilder builder = new SpannableStringBuilder();
 
         for (CueElement element : cue.getElementsList()) {
@@ -110,6 +111,7 @@
                     builder.append(imageText);
                     BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
                     drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+                    drawable.setAlpha((int) (alpha * 255));
                     builder.setSpan(new ImageSpan(drawable), start, end, 0);
                 }
             } else if (!element.getText().isEmpty()) {
@@ -118,5 +120,6 @@
         }
 
         setText(builder);
+        setAlpha(alpha);
     }
 }
diff --git a/src/android/car/cluster/FakeFreeNavigationActivity.java b/DirectRenderingCluster/src/android/car/cluster/FakeFreeNavigationActivity.java
similarity index 100%
rename from src/android/car/cluster/FakeFreeNavigationActivity.java
rename to DirectRenderingCluster/src/android/car/cluster/FakeFreeNavigationActivity.java
diff --git a/src/android/car/cluster/HeartBeatLiveData.java b/DirectRenderingCluster/src/android/car/cluster/HeartBeatLiveData.java
similarity index 100%
rename from src/android/car/cluster/HeartBeatLiveData.java
rename to DirectRenderingCluster/src/android/car/cluster/HeartBeatLiveData.java
diff --git a/src/android/car/cluster/ImageResolver.java b/DirectRenderingCluster/src/android/car/cluster/ImageResolver.java
similarity index 100%
rename from src/android/car/cluster/ImageResolver.java
rename to DirectRenderingCluster/src/android/car/cluster/ImageResolver.java
diff --git a/src/android/car/cluster/LaneView.java b/DirectRenderingCluster/src/android/car/cluster/LaneView.java
similarity index 97%
rename from src/android/car/cluster/LaneView.java
rename to DirectRenderingCluster/src/android/car/cluster/LaneView.java
index 7d48e28..1cab4b6 100644
--- a/src/android/car/cluster/LaneView.java
+++ b/DirectRenderingCluster/src/android/car/cluster/LaneView.java
@@ -89,7 +89,7 @@
                 });
     }
 
-    public void setLanes(List<Lane> lanes) {
+    public void setLanes(List<Lane> lanes, float alpha) {
         mLanes = new ArrayList<>(lanes);
         removeAllViews();
 
@@ -99,6 +99,7 @@
             ImageView imgView = new ImageView(getContext());
             imgView.setImageBitmap(bitmap);
             imgView.setAdjustViewBounds(true);
+            imgView.setImageAlpha((int) (alpha * 255));
             addView(imgView);
         }
     }
@@ -238,4 +239,10 @@
         }
         return null;
     }
+
+    @Override
+    public void setAlpha(float alpha) {
+        super.setAlpha(alpha);
+
+    }
 }
diff --git a/src/android/car/cluster/LoggingClusterRenderingService.java b/DirectRenderingCluster/src/android/car/cluster/LoggingClusterRenderingService.java
similarity index 100%
rename from src/android/car/cluster/LoggingClusterRenderingService.java
rename to DirectRenderingCluster/src/android/car/cluster/LoggingClusterRenderingService.java
diff --git a/src/android/car/cluster/MainClusterActivity.java b/DirectRenderingCluster/src/android/car/cluster/MainClusterActivity.java
similarity index 95%
rename from src/android/car/cluster/MainClusterActivity.java
rename to DirectRenderingCluster/src/android/car/cluster/MainClusterActivity.java
index 9c76d37..f306e94 100644
--- a/src/android/car/cluster/MainClusterActivity.java
+++ b/DirectRenderingCluster/src/android/car/cluster/MainClusterActivity.java
@@ -247,10 +247,8 @@
 
         mClusterViewModel = new ViewModelProvider(this).get(ClusterViewModel.class);
         mClusterViewModel.getNavigationFocus().observe(this, focus -> {
-            // If focus is lost, we launch the default navigation activity again.
             if (!focus) {
                 mNavStateController.update(null);
-                tryLaunchNavigationActivity();
             }
         });
         mClusterViewModel.getNavigationActivityState().observe(this, state -> {
@@ -300,8 +298,19 @@
 
     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));
+        source.observe(this, value -> {
+            // Need to check that the text is actually different, or else
+            // it will generate a bunch of CONTENT_CHANGE_TYPE_TEXT accessability
+            // actions. This will cause cts tests to fail when they waitForIdle(),
+            // and the system never idles because it's constantly updating these
+            // TextViews
+            if (value != null && !value.toString().contentEquals(textView.getText())) {
+                textView.setText(value.toString());
+            }
+            if (value == null && !emptyValue.contentEquals(textView.getText())) {
+                textView.setText(emptyValue);
+            }
+        });
     }
 
     @Override
@@ -444,11 +453,11 @@
                             activityState.toBundle());
 
             Log.d(TAG, "Launching: " + intent + " on display: " + mNavigationDisplay.mDisplayId);
-            Bundle activityOptions = ActivityOptions.makeBasic()
-                    .setLaunchDisplayId(mNavigationDisplay.mDisplayId)
-                    .toBundle();
+            ActivityOptions activityOptions = ActivityOptions.makeBasic()
+                    .setLaunchDisplayId(mNavigationDisplay.mDisplayId);
 
-            startActivityAsUser(intent, activityOptions, UserHandle.CURRENT);
+            mService.startFixedActivityModeForDisplayAndUser(
+                    intent, activityOptions, ActivityManager.getCurrentUser());
         } catch (ActivityNotFoundException ex) {
             // Some activities might not be available right on startup. We will retry.
             mHandler.postDelayed(mRetryLaunchNavigationActivity,
diff --git a/src/android/car/cluster/MusicFragment.java b/DirectRenderingCluster/src/android/car/cluster/MusicFragment.java
similarity index 100%
rename from src/android/car/cluster/MusicFragment.java
rename to DirectRenderingCluster/src/android/car/cluster/MusicFragment.java
diff --git a/src/android/car/cluster/MusicFragmentViewModel.java b/DirectRenderingCluster/src/android/car/cluster/MusicFragmentViewModel.java
similarity index 100%
rename from src/android/car/cluster/MusicFragmentViewModel.java
rename to DirectRenderingCluster/src/android/car/cluster/MusicFragmentViewModel.java
diff --git a/src/android/car/cluster/NavStateController.java b/DirectRenderingCluster/src/android/car/cluster/NavStateController.java
similarity index 95%
rename from src/android/car/cluster/NavStateController.java
rename to DirectRenderingCluster/src/android/car/cluster/NavStateController.java
index fe4d724..5655fde 100644
--- a/src/android/car/cluster/NavStateController.java
+++ b/DirectRenderingCluster/src/android/car/cluster/NavStateController.java
@@ -33,6 +33,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.util.Log;
+import android.util.TypedValue;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -130,6 +131,12 @@
         }
 
         Step step = state.getStepsCount() > 0 ? state.getSteps(0) : null;
+
+        // Get alpha based on is_imminent
+        float alpha = (step != null && !step.getIsImminent())
+                ? getAlphaFromResource(R.dimen.non_imminent_alpha)
+                : 1f;
+
         Destination destination = state.getDestinationsCount() > 0
                 ? state.getDestinations(0) : null;
         Traffic traffic = destination != null ? destination.getTraffic() : null;
@@ -144,9 +151,11 @@
         setProvidedManeuverIcon(mProvidedManeuver, step != null
                 ? step.getManeuver().hasIcon() ? step.getManeuver().getIcon() : null
                 : null);
+        mManeuver.setImageAlpha((int) (alpha * 255));
         mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
+        mDistance.setAlpha(alpha);
         mSegment.setText(getSegmentString(state.getCurrentRoad()));
-        mCue.setCue(step != null ? step.getCue() : null, mImageResolver);
+        mCue.setCue(step != null ? step.getCue() : null, mImageResolver, alpha);
 
         if (step != null && step.getLanesCount() > 0) {
             if (step.hasLanesImage()) {
@@ -154,12 +163,22 @@
                 mProvidedLane.setVisibility(View.VISIBLE);
             }
 
-            mLane.setLanes(step.getLanesList());
+            mLane.setLanes(step.getLanesList(), alpha);
             mLane.setVisibility(View.VISIBLE);
         } else {
             mLane.setVisibility(View.GONE);
             mProvidedLane.setVisibility(View.GONE);
         }
+
+    }
+
+    /**
+     * Get float value from dimens.xml, it only works for float format
+     */
+    private float getAlphaFromResource(int alphaId) {
+        TypedValue typedValue = new TypedValue();
+        mContext.getResources().getValue(alphaId, typedValue, true);
+        return typedValue.getFloat();
     }
 
     private int getTrafficColor(@Nullable Traffic traffic) {
diff --git a/src/android/car/cluster/NavigationFragment.java b/DirectRenderingCluster/src/android/car/cluster/NavigationFragment.java
similarity index 100%
rename from src/android/car/cluster/NavigationFragment.java
rename to DirectRenderingCluster/src/android/car/cluster/NavigationFragment.java
diff --git a/src/android/car/cluster/PhoneFragment.java b/DirectRenderingCluster/src/android/car/cluster/PhoneFragment.java
similarity index 100%
rename from src/android/car/cluster/PhoneFragment.java
rename to DirectRenderingCluster/src/android/car/cluster/PhoneFragment.java
diff --git a/src/android/car/cluster/PhoneFragmentViewModel.java b/DirectRenderingCluster/src/android/car/cluster/PhoneFragmentViewModel.java
similarity index 100%
rename from src/android/car/cluster/PhoneFragmentViewModel.java
rename to DirectRenderingCluster/src/android/car/cluster/PhoneFragmentViewModel.java
diff --git a/src/android/car/cluster/PhoneNumberInfoLiveData.java b/DirectRenderingCluster/src/android/car/cluster/PhoneNumberInfoLiveData.java
similarity index 100%
rename from src/android/car/cluster/PhoneNumberInfoLiveData.java
rename to DirectRenderingCluster/src/android/car/cluster/PhoneNumberInfoLiveData.java
diff --git a/src/android/car/cluster/SelfRefreshDescriptionLiveData.java b/DirectRenderingCluster/src/android/car/cluster/SelfRefreshDescriptionLiveData.java
similarity index 100%
rename from src/android/car/cluster/SelfRefreshDescriptionLiveData.java
rename to DirectRenderingCluster/src/android/car/cluster/SelfRefreshDescriptionLiveData.java
diff --git a/src/android/car/cluster/sensors/Sensor.java b/DirectRenderingCluster/src/android/car/cluster/sensors/Sensor.java
similarity index 100%
rename from src/android/car/cluster/sensors/Sensor.java
rename to DirectRenderingCluster/src/android/car/cluster/sensors/Sensor.java
diff --git a/src/android/car/cluster/sensors/Sensors.java b/DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java
similarity index 100%
rename from src/android/car/cluster/sensors/Sensors.java
rename to DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java
diff --git a/tests/robotests/Android.bp b/DirectRenderingCluster/tests/robotests/Android.bp
similarity index 100%
rename from tests/robotests/Android.bp
rename to DirectRenderingCluster/tests/robotests/Android.bp
diff --git a/tests/robotests/AndroidManifest.xml b/DirectRenderingCluster/tests/robotests/AndroidManifest.xml
similarity index 100%
rename from tests/robotests/AndroidManifest.xml
rename to DirectRenderingCluster/tests/robotests/AndroidManifest.xml
diff --git a/tests/robotests/config/robolectric.properties b/DirectRenderingCluster/tests/robotests/config/robolectric.properties
similarity index 100%
rename from tests/robotests/config/robolectric.properties
rename to DirectRenderingCluster/tests/robotests/config/robolectric.properties
diff --git a/tests/robotests/readme.md b/DirectRenderingCluster/tests/robotests/readme.md
similarity index 100%
rename from tests/robotests/readme.md
rename to DirectRenderingCluster/tests/robotests/readme.md
diff --git a/tests/robotests/src/android/car/cluster/ImageResolverTest.java b/DirectRenderingCluster/tests/robotests/src/android/car/cluster/ImageResolverTest.java
similarity index 100%
rename from tests/robotests/src/android/car/cluster/ImageResolverTest.java
rename to DirectRenderingCluster/tests/robotests/src/android/car/cluster/ImageResolverTest.java