[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