summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:00:32 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:00:32 +0000
commit92473ff9d767db1718e05a3b884f5377d71871e9 (patch)
treef7872b14f3b3c12d604bad20ee49e0def4500ec3
parent955fa10612e88e9a673746ef7f38134602e3c18f (diff)
parent8f99a063b20dd5a18da30d1f1b09568bf5624117 (diff)
downloadCluster-android14-mainline-media-release.tar.gz
Change-Id: Ib1e5676148404b42d3b61eb8d0c4fb3da044d2a9
-rw-r--r--ClusterHomeSample/AndroidManifest.xml2
-rw-r--r--ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java182
-rw-r--r--ClusterOsDouble/AndroidManifest.xml5
-rw-r--r--ClusterOsDouble/res/values/config.xml5
-rw-r--r--ClusterOsDouble/res/values/overlayable.xml1
-rw-r--r--ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java66
-rw-r--r--ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleApplication.java137
-rw-r--r--DirectRenderingCluster/AndroidManifest.xml9
-rw-r--r--DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java54
-rw-r--r--DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java23
-rw-r--r--DirectRenderingCluster/src/android/car/cluster/MusicFragment.java4
-rw-r--r--DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java22
12 files changed, 371 insertions, 139 deletions
diff --git a/ClusterHomeSample/AndroidManifest.xml b/ClusterHomeSample/AndroidManifest.xml
index 1dc7eb5..b2ca15f 100644
--- a/ClusterHomeSample/AndroidManifest.xml
+++ b/ClusterHomeSample/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<!-- for IActivityManager.registerTaskStackListener() -->
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+ <!-- for PackageManager.queryIntentActivitiesAsUser() -->
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- for ClusterHomeManager -->
<uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
<!-- for CarInputManager.requestInputEventCapture() -->
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
index c85fa41..2e656a2 100644
--- a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
@@ -35,6 +35,8 @@ import android.app.IActivityTaskManager;
import android.app.TaskInfo;
import android.app.TaskStackListener;
import android.car.Car;
+import android.car.CarAppFocusManager;
+import android.car.CarAppFocusManager.OnAppFocusChangedListener;
import android.car.CarOccupantZoneManager;
import android.car.cluster.ClusterActivityState;
import android.car.cluster.ClusterHomeManager;
@@ -46,40 +48,47 @@ import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.UserLifecycleEventFilter;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
+import java.util.ArrayList;
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 boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
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 int UI_TYPE_START = UI_TYPE_MAPS;
- 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 static final byte UI_UNAVAILABLE = 0;
+ private static final byte UI_AVAILABLE = 1;
+ private PackageManager mPackageManager;
private IActivityTaskManager mAtm;
private InputManager mInputManager;
private ClusterHomeManager mHomeManager;
private CarUserManager mUserManager;
private CarInputManager mCarInputManager;
+ private CarAppFocusManager mAppFocusManager;
private ClusterState mClusterState;
private byte mUiAvailability[];
private int mUserLifeCycleEvent = USER_LIFECYCLE_EVENT_TYPE_STARTING;
- private ComponentName[] mClusterActivities;
+ private ArrayList<ComponentName> mClusterActivities = new ArrayList<>();
+ private int mDefaultClusterActivitySize = 0;
private int mLastLaunchedUiType = UI_TYPE_CLUSTER_NONE;
private int mLastReportedUiType = UI_TYPE_CLUSTER_NONE;
@@ -87,15 +96,16 @@ public final class ClusterHomeApplication extends Application {
@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)),
- };
+ mClusterActivities.add(UI_TYPE_HOME,
+ new ComponentName(getApplicationContext(), ClusterHomeActivity.class));
+ mClusterActivities.add(UI_TYPE_MAPS,
+ ComponentName.unflattenFromString(getString(R.string.config_clusterMapActivity)));
+ mClusterActivities.add(UI_TYPE_MUSIC,
+ ComponentName.unflattenFromString(getString(R.string.config_clusterMusicActivity)));
+ mClusterActivities.add(UI_TYPE_PHONE,
+ ComponentName.unflattenFromString(getString(R.string.config_clusterPhoneActivity)));
+ mDefaultClusterActivitySize = mClusterActivities.size();
+ mPackageManager = getApplicationContext().getPackageManager();
mAtm = ActivityTaskManager.getService();
try {
mAtm.registerTaskStackListener(mTaskStackListener);
@@ -111,6 +121,8 @@ public final class ClusterHomeApplication extends Application {
mHomeManager = (ClusterHomeManager) car.getCarManager(Car.CLUSTER_HOME_SERVICE);
mUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
mCarInputManager = (CarInputManager) car.getCarManager(Car.CAR_INPUT_SERVICE);
+ mAppFocusManager = (CarAppFocusManager) car.getCarManager(
+ Car.APP_FOCUS_SERVICE);
initClusterHome();
});
}
@@ -126,7 +138,7 @@ public final class ClusterHomeApplication extends Application {
if (!mClusterState.on) {
mHomeManager.requestDisplay(UI_TYPE_HOME);
}
- mUiAvailability = buildUiAvailability();
+ mUiAvailability = buildUiAvailability(ActivityManager.getCurrentUser());
mHomeManager.reportState(mClusterState.uiType, UI_TYPE_CLUSTER_NONE, mUiAvailability);
mHomeManager.registerClusterStateListener(getMainExecutor(), mClusterHomeCalback);
@@ -137,6 +149,9 @@ public final class ClusterHomeApplication extends Application {
.addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED).build();
mUserManager.addListener(getMainExecutor(), filter, mUserLifecycleListener);
+ mAppFocusManager.addFocusListener(mAppFocusChangedListener,
+ CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+
int r = mCarInputManager.requestInputEventCapture(
DISPLAY_TYPE_INSTRUMENT_CLUSTER,
new int[]{CarInputManager.INPUT_TYPE_ALL_INPUTS},
@@ -175,7 +190,7 @@ public final class ClusterHomeApplication extends Application {
return;
}
mLastLaunchedUiType = uiType;
- ComponentName activity = mClusterActivities[uiType];
+ ComponentName activity = mClusterActivities.get(uiType);
Intent intent = new Intent(ACTION_MAIN).setComponent(activity);
if (mClusterState.bounds != null && mClusterState.insets != null) {
@@ -195,17 +210,63 @@ public final class ClusterHomeApplication extends Application {
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 void add3PNavigationActivities(int currentUser) {
+ // Clean up the 3P Navigations from the previous user.
+ mClusterActivities.subList(mDefaultClusterActivitySize, mClusterActivities.size()).clear();
+
+ ArraySet<String> clusterPackages = new ArraySet<>();
+ for (int i = mDefaultClusterActivitySize - 1; i >= 0; --i) {
+ clusterPackages.add(mClusterActivities.get(i).getPackageName());
+ }
+ Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Car.CAR_CATEGORY_NAVIGATION);
+ List<ResolveInfo> resolveList = mPackageManager.queryIntentActivitiesAsUser(
+ intent, ResolveInfoFlags.of(PackageManager.GET_RESOLVED_FILTER),
+ UserHandle.of(currentUser));
+ for (int i = resolveList.size() - 1; i >= 0; --i) {
+ ActivityInfo activityInfo = resolveList.get(i).activityInfo;
+ if (DBG) Log.d(TAG, "Found: " + activityInfo.packageName + "/" + activityInfo.name);
+ // Some package can have multiple navigation Activities, we choose the default one only.
+ if (clusterPackages.contains(activityInfo.packageName)) {
+ if (DBG) {
+ Log.d(TAG, "Skip this, because another Activity in the package is registered.");
+ };
+ continue;
+ }
+ mClusterActivities.add(new ComponentName(activityInfo.packageName, activityInfo.name));
+ }
+ mUiAvailability = buildUiAvailability(currentUser);
+ }
+
+ private byte[] buildUiAvailability(int currentUser) {
+ byte[] availability = new byte[mClusterActivities.size()];
+ Intent intent = new Intent(ACTION_MAIN);
+ for (int i = mClusterActivities.size() - 1; i >= 0; --i) {
+ ComponentName clusterActivity = mClusterActivities.get(i);
+ if (clusterActivity.getPackageName().equals(getPackageName())) {
+ // Assume that all Activities in ClusterHome are available.
+ availability[i] = UI_AVAILABLE;
+ continue;
+ }
+ intent.setComponent(clusterActivity);
+ ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY, currentUser);
+ availability[i] = resolveInfo == null ? UI_UNAVAILABLE : UI_AVAILABLE;
+ if (DBG) {
+ Log.d(TAG, "availability=" + availability[i] + ", activity=" + clusterActivity
+ + ", userId=" + currentUser);
+ }
+ }
+ return availability;
}
private final ClusterStateListener mClusterHomeCalback = new ClusterStateListener() {
@Override
public void onClusterStateChanged(
ClusterState state, @ClusterHomeManager.Config int changes) {
+ if (DBG) {
+ Log.d(TAG, "onClusterStateChanged: changes=" + Integer.toHexString(changes) +
+ ", state=" + clusterStateToString(state));
+ }
mClusterState = state;
// We'll restart Activity when the display bounds or insets are changed, to let Activity
// redraw itself to fit the changed attributes.
@@ -255,8 +316,8 @@ public final class ClusterHomeApplication extends Application {
}
private int identifyTopTask(TaskInfo taskInfo) {
- for (int i = mClusterActivities.length - 1; i >=0; --i) {
- if (mClusterActivities[i].equals(taskInfo.topActivity)) {
+ for (int i = mClusterActivities.size() - 1; i >=0; --i) {
+ if (mClusterActivities.get(i).equals(taskInfo.topActivity)) {
return i;
}
}
@@ -269,9 +330,11 @@ public final class ClusterHomeApplication extends Application {
mUserLifeCycleEvent = event.getEventType();
if (mUserLifeCycleEvent == USER_LIFECYCLE_EVENT_TYPE_STARTING) {
startClusterActivity(UI_TYPE_HOME);
- } else if (UI_TYPE_HOME != UI_TYPE_START
- && mUserLifeCycleEvent == USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
- startClusterActivity(UI_TYPE_START);
+ } else if (mUserLifeCycleEvent == USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
+ add3PNavigationActivities(event.getUserId());
+ if (UI_TYPE_START != UI_TYPE_HOME) {
+ startClusterActivity(UI_TYPE_START);
+ }
}
};
@@ -287,11 +350,74 @@ public final class ClusterHomeApplication extends Application {
if (DBG) Log.d(TAG, "onKeyEvent: " + keyEvent);
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MENU) {
if (keyEvent.getAction() != KeyEvent.ACTION_DOWN) return;
- int nextUiType = (mLastLaunchedUiType + 1) % mUiAvailability.length;
+ int nextUiType;
+ do {
+ // Select the Cluster Activity within the preinstalled ones.
+ nextUiType = mLastLaunchedUiType + 1;
+ if (nextUiType >= mDefaultClusterActivitySize) nextUiType = 0;
+ } while (mUiAvailability[nextUiType] == UI_UNAVAILABLE);
startClusterActivity(nextUiType);
return;
}
// Use Android InputManager to forward KeyEvent.
mInputManager.injectInputEvent(keyEvent, INJECT_INPUT_EVENT_MODE_ASYNC);
}
+
+ private OnAppFocusChangedListener mAppFocusChangedListener = new OnAppFocusChangedListener() {
+ @Override
+ public void onAppFocusChanged(int appType, boolean active) {
+ if (!active || appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
+ return;
+ }
+ int navigationUi = getFocusedNavigationUi();
+ if (navigationUi != UI_TYPE_CLUSTER_NONE) {
+ startClusterActivity(navigationUi);
+ }
+ }
+ };
+
+ private int getFocusedNavigationUi() {
+ List<String> focusOwnerPackageNames = mAppFocusManager.getAppTypeOwner(
+ CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+ if (focusOwnerPackageNames == null || focusOwnerPackageNames.isEmpty()) {
+ Log.e(TAG, "Can't find the navigation owner");
+ return UI_TYPE_CLUSTER_NONE;
+ }
+ for (int i = 0; i < focusOwnerPackageNames.size(); ++i) {
+ String focusOwnerPackage = focusOwnerPackageNames.get(i);
+ for (int j = mClusterActivities.size() - 1; j >= 0; --j) {
+ if (mUiAvailability[j] == UI_UNAVAILABLE) {
+ continue;
+ }
+ if (mClusterActivities.get(j).getPackageName().equals(focusOwnerPackage)) {
+ if (DBG) {
+ Log.d(TAG, "Found focused NavigationUI: " + j
+ + ", package=" + focusOwnerPackage);
+ }
+ return j;
+ }
+ }
+ }
+ Log.e(TAG, "Can't find the navigation UI for "
+ + String.join(", ", focusOwnerPackageNames) + ".");
+ return UI_TYPE_CLUSTER_NONE;
+ }
+
+ private static String clusterStateToString(ClusterState state) {
+ StringBuilder sb = new StringBuilder("ClusterState[");
+ sb.append("on="); sb.append(state.on);
+ if (state.bounds != null) {
+ sb.append(", bounds="); sb.append(state.bounds);
+ }
+ if (state.insets != null) {
+ sb.append(", insets="); sb.append(state.insets);
+ }
+ if (state.insets != null) {
+ sb.append(", insets="); sb.append(state.insets);
+ }
+ sb.append(", uiType="); sb.append(state.uiType);
+ sb.append(", displayId="); sb.append(state.displayId);
+ sb.append(']');
+ return sb.toString();
+ }
}
diff --git a/ClusterOsDouble/AndroidManifest.xml b/ClusterOsDouble/AndroidManifest.xml
index 5e8d334..e57b260 100644
--- a/ClusterOsDouble/AndroidManifest.xml
+++ b/ClusterOsDouble/AndroidManifest.xml
@@ -13,9 +13,10 @@
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">
+ package="com.android.car.cluster.osdouble"
+ android:sharedUserId="android.uid.system">
+ <!-- system uid is required to access the local private display -->
<!-- Required to show car sensor data -->
<uses-permission android:name="android.car.permission.CAR_SPEED"/>
diff --git a/ClusterOsDouble/res/values/config.xml b/ClusterOsDouble/res/values/config.xml
index 7f1cb52..e85c206 100644
--- a/ClusterOsDouble/res/values/config.xml
+++ b/ClusterOsDouble/res/values/config.xml
@@ -18,5 +18,8 @@
<!-- 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>
+ <integer name="config_clusterDisplayPort">-1</integer>
+ <!-- Display uniqueId to launch ClusterOsDouble. -->
+ <!-- To use this values, set config_clusterDisplayPort to -1. -->
+ <string name="config_clusterDisplayUniqueId" />
</resources>
diff --git a/ClusterOsDouble/res/values/overlayable.xml b/ClusterOsDouble/res/values/overlayable.xml
index 465c39f..329ffee 100644
--- a/ClusterOsDouble/res/values/overlayable.xml
+++ b/ClusterOsDouble/res/values/overlayable.xml
@@ -19,6 +19,7 @@
<overlayable name="ClusterOsConfig">
<policy type="product|system|vendor">
<item type="integer" name="config_clusterDisplayPort" />
+ <item type="string" name="config_clusterDisplayUniqueId" />
</policy>
</overlayable>
</resources>
diff --git a/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
index 8ba7eb3..f86b001 100644
--- a/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
+++ b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
@@ -20,23 +20,26 @@ 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.cluster.navigation.NavigationState.NavigationStateProto;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
+import android.content.res.CompatibilityInfo;
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.DisplayMetrics;
+import android.util.IntArray;
import android.util.Log;
+import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
@@ -83,19 +86,23 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
private static final int VENDOR_CLUSTER_DISPLAY_STATE = toVendorId(
VehiclePropertyIds.CLUSTER_DISPLAY_STATE);
+ // For the detail, please refer to vehicle/2.0/types.hal.
+ private static final int REPORT_STATE_MAIN_UI_INDEX = 9;
+ private static final int REPORT_STATE_UI_AVAILABILITY_INDEX = 11;
+
private DisplayManager mDisplayManager;
private CarPropertyManager mPropertyManager;
private SurfaceView mSurfaceView;
private Rect mBounds;
private Insets mInsets;
- private VirtualDisplay mVirtualDisplay;
+ private static VirtualDisplay sVirtualDisplay;
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 final IntArray mUiAvailability = new IntArray();
private NavStateController mNavStateController;
@@ -143,15 +150,6 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
findViewById(R.id.navigation_state), imageResolver);
}
- @Override
- protected void onDestroy() {
- if (mVirtualDisplay != null) {
- mVirtualDisplay.release();
- mVirtualDisplay = null;
- }
- super.onDestroy();
- }
-
private final SurfaceHolder.Callback mSurfaceViewCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
@@ -173,10 +171,23 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
// 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);
+ if (sVirtualDisplay == null) {
+ sVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height);
} else {
- mVirtualDisplay.setSurface(holder.getSurface());
+ DisplayInfo displayInfo = new DisplayInfo();
+ DisplayMetrics boundsMetrics = new DisplayMetrics();
+ boolean isDisplayValid = sVirtualDisplay.getDisplay().getDisplayInfo(displayInfo);
+ displayInfo.getLogicalMetrics(boundsMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, /* configuration= */ null);
+ if (isDisplayValid && boundsMetrics.widthPixels == width
+ && boundsMetrics.heightPixels == height) {
+ sVirtualDisplay.setSurface(holder.getSurface());
+ } else {
+ // Display was resized, delete existing and create new display.
+ // TODO(b/254931119): Resize the display instead of replacing it.
+ sVirtualDisplay.release();
+ sVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height);
+ }
}
}
@@ -185,7 +196,7 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
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);
+ sVirtualDisplay.setSurface(null);
}
};
@@ -234,15 +245,17 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
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);
+ int mainUi = (Integer) values[REPORT_STATE_MAIN_UI_INDEX];
+ int totalUiSize = values.length - REPORT_STATE_UI_AVAILABILITY_INDEX;
+ mUiAvailability.resize(totalUiSize);
+ for (int i = 0; i < totalUiSize; ++i) {
+ mUiAvailability.set(i, (Byte) values[i + REPORT_STATE_UI_AVAILABILITY_INDEX]);
}
+ selectUiButton(mainUi);
}
private void selectUiButton(int mainUi) {
- for (int i = 0; i < mTotalUiSize; ++i) {
+ for (int i = mUiToButton.size() - 1; i >= 0; --i) {
View button = mUiToButton.get(i);
button.setSelected(i == mainUi);
}
@@ -265,7 +278,6 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
sendDisplayState();
}
-
private static int toVendorId(int propId) {
return (propId & ~MASK) | VENDOR;
}
@@ -300,7 +312,6 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
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);
@@ -329,7 +340,12 @@ public class ClusterOsDoubleActivity extends ComponentActivity {
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d(TAG, "onKeyDown: " + keyCode);
if (keyCode == KeyEvent.KEYCODE_MENU) {
- switchUi((mCurrentUi + 1) % mTotalUiSize);
+ int nextUi = mCurrentUi;
+ do {
+ nextUi = nextUi + 1;
+ if (nextUi >= mUiToButton.size()) nextUi = 0;
+ } while (mUiAvailability.get(nextUi) == 0);
+ switchUi(nextUi);
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
index dd102c7..4b63a9a 100644
--- a/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleApplication.java
+++ b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleApplication.java
@@ -18,12 +18,16 @@ package com.android.car.cluster.osdouble;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import android.annotation.NonNull;
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.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
import android.view.DisplayAddress;
@@ -34,45 +38,126 @@ import android.view.DisplayAddress;
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.
+ String displayUniqueId = context.getResources().getString(
+ R.string.config_clusterDisplayUniqueId);
+
+ if (displayPort <= 0 && TextUtils.isEmpty(displayUniqueId)) {
+ Log.e(TAG, "Cluster display isn't configured.");
return;
}
- int displayId = findDisplay(displayPort);
- if (displayId == Display.INVALID_DISPLAY) {
- Log.e(TAG, "Can't find the display with portId: " + displayPort);
- return;
+
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ ClusterDisplayMonitor clusterDisplayMonitor = new ClusterDisplayMonitor(context,
+ displayManager, displayPort, displayUniqueId);
+ clusterDisplayMonitor.start(new Handler(Looper.myLooper()));
+ }
+
+ /**
+ * Monitors displays and starts the cluster activity when the correct display becomes available.
+ */
+ private static class ClusterDisplayMonitor {
+ private final Context mContext;
+ private final DisplayManager mDisplayManager;
+ private final int mDisplayPort;
+ private final String mDisplayUniqueId;
+
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ int clusterDisplayId = findClusterDisplayId();
+ if (clusterDisplayId == displayId) {
+ Log.d(TAG, "Display " + displayId + " was added. Starting cluster.");
+ onDisplayReadyForCluster(displayId);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ // No-op
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ // No-op
+ }
+ };
+
+ public ClusterDisplayMonitor(Context context, DisplayManager displayManager,
+ int displayPort, String displayUniqueId) {
+ mContext = context;
+ mDisplayManager = displayManager;
+ mDisplayPort = displayPort;
+ mDisplayUniqueId = displayUniqueId;
+ }
+
+ public void start(Handler handler) {
+ int clusterDisplayId = findClusterDisplayId();
+ if (clusterDisplayId != Display.INVALID_DISPLAY) {
+ onDisplayReadyForCluster(clusterDisplayId);
+ }
+ // This listener will never get unregistered. This is only ok as long as this is a
+ // persistent app that is not expected to stop.
+ mDisplayManager.registerDisplayListener(mDisplayListener, handler);
}
- Intent intent = Intent.makeMainActivity(
- ComponentName.createRelative(context, ClusterOsDoubleActivity.class.getName()));
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+ private void onDisplayReadyForCluster(int displayId) {
+ Intent intent = Intent.makeMainActivity(
+ ComponentName.createRelative(mContext,
+ ClusterOsDoubleActivity.class.getName()));
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
- context.startActivity(intent, options.toBundle());
- }
+ ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
+ mContext.startActivity(intent, options.toBundle());
+ }
- private int findDisplay(int displayPort) {
- for (Display display : mDisplayManager.getDisplays()) {
- DisplayAddress address = display.getAddress();
- if (!(address instanceof DisplayAddress.Physical)) {
- continue;
+ private int findClusterDisplayId() {
+ int displayId = Display.INVALID_DISPLAY;
+ if (mDisplayPort > 0) {
+ displayId = findDisplayByPort(mDisplayPort);
+ if (displayId == Display.INVALID_DISPLAY) {
+ Log.e(TAG, "Can't find the display with portId: " + mDisplayPort);
+ }
+ } else if (!TextUtils.isEmpty(mDisplayUniqueId)) {
+ displayId = findDisplayIdByUniqueId(mDisplayUniqueId);
+ if (displayId == Display.INVALID_DISPLAY) {
+ Log.e(TAG, "Can't find the display with uniqueId: " + mDisplayUniqueId);
+ }
+ } else {
+ // This should not ever happen.
+ Log.wtf(TAG, "No valid cluster display configs found.");
}
- DisplayAddress.Physical physical = (DisplayAddress.Physical) address;
- if (physical.getPort() == displayPort) {
- return display.getDisplayId();
+
+ return displayId;
+ }
+
+ private int findDisplayIdByUniqueId(@NonNull String displayUniqueId) {
+ for (Display display : mDisplayManager.getDisplays()) {
+ if (displayUniqueId.equals(display.getUniqueId())) {
+ return display.getDisplayId();
+ }
}
+ return Display.INVALID_DISPLAY;
+ }
+
+ private int findDisplayByPort(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;
}
- return Display.INVALID_DISPLAY;
}
+
}
diff --git a/DirectRenderingCluster/AndroidManifest.xml b/DirectRenderingCluster/AndroidManifest.xml
index 6f1ce2b..c4eaa87 100644
--- a/DirectRenderingCluster/AndroidManifest.xml
+++ b/DirectRenderingCluster/AndroidManifest.xml
@@ -20,7 +20,7 @@
android:process="android.car.cluster"
android:sharedUserId="android.uid.cluster">
- <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="29"/>
+ <uses-sdk android:targetSdkVersion="30"/>
<protected-broadcast android:name="android.car.cluster.NAVIGATION_STATE_UPDATE"/>
@@ -42,8 +42,11 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<!-- Required to launch navigation apps -->
<uses-permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
+ <!-- Required to use CarOccupantZoneManager.getDisplayIdForDriver() -->
+ <uses-permission android:name="android.car.permission.ACCESS_PRIVATE_DISPLAY_ID"/>
<!-- Required to watch activities running on the cluster -->
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
+
<!-- Required to show car sensor data -->
<uses-permission android:name="android.car.permission.CAR_ENERGY"/>
<uses-permission android:name="android.car.permission.CAR_POWERTRAIN"/>
@@ -53,7 +56,11 @@
<!-- Required to query packages in Android 11+ -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <!-- Required to use PhoneStateListener.onCallStateChanged() -->
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
diff --git a/DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java b/DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java
index 4050099..5949a50 100644
--- a/DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java
+++ b/DirectRenderingCluster/src/android/car/cluster/ClusterDisplayProvider.java
@@ -18,7 +18,6 @@ package android.car.cluster;
import android.car.Car;
import android.car.CarOccupantZoneManager;
-import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.content.Context;
import android.hardware.display.DisplayManager.DisplayListener;
import android.util.Log;
@@ -26,8 +25,6 @@ import android.view.Display;
import com.android.internal.util.Preconditions;
-import java.util.List;
-
/**
* This class provides a display for instrument cluster renderer.
* <p>
@@ -37,7 +34,7 @@ import java.util.List;
*/
public class ClusterDisplayProvider {
private static final String TAG = "Cluster.DisplayProvider";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final DisplayListener mListener;
private final Car mCar;
@@ -66,35 +63,30 @@ public class ClusterDisplayProvider {
Preconditions.checkArgument(
occupantZoneManager != null,"Can't get CarOccupantZoneManager");
mOccupantZoneManager = occupantZoneManager;
- checkClusterDisplayAdded();
+ checkClusterDisplayChanged();
mOccupantZoneManager.registerOccupantZoneConfigChangeListener(
new ClusterDisplayChangeListener());
}
- private void checkClusterDisplayAdded() {
- Display clusterDisplay = getClusterDisplay();
- if (clusterDisplay != null) {
- Log.i(TAG, String.format("Found display: %s (id: %d, owner: %s)",
- clusterDisplay.getName(), clusterDisplay.getDisplayId(),
- clusterDisplay.getOwnerPackageName()));
- mClusterDisplayId = clusterDisplay.getDisplayId();
- mListener.onDisplayAdded(clusterDisplay.getDisplayId());
+ private void checkClusterDisplayChanged() {
+ int clusterDisplayId = getClusterDisplayId();
+ if (clusterDisplayId == mClusterDisplayId) {
+ return;
+ }
+ if (mClusterDisplayId != Display.INVALID_DISPLAY) {
+ Log.i(TAG, "Cluster display is removed");
+ mListener.onDisplayRemoved(mClusterDisplayId);
+ }
+ mClusterDisplayId = clusterDisplayId;
+ if (clusterDisplayId != Display.INVALID_DISPLAY) {
+ Log.i(TAG, "Found cluster displayId=" + clusterDisplayId);
+ mListener.onDisplayAdded(clusterDisplayId);
}
}
- private Display getClusterDisplay() {
- List<OccupantZoneInfo> zones = mOccupantZoneManager.getAllOccupantZones();
- int zones_size = zones.size();
- for (int i = 0; i < zones_size; ++i) {
- OccupantZoneInfo zone = zones.get(i);
- // Assumes that a Car has only one driver.
- if (zone.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
- return mOccupantZoneManager.getDisplayForOccupant(
- zone, CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
- }
- }
- Log.e(TAG, "Can't find the OccupantZoneInfo for driver");
- return null;
+ private int getClusterDisplayId() {
+ return mOccupantZoneManager.getDisplayIdForDriver(
+ CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
}
private final class ClusterDisplayChangeListener implements
@@ -105,15 +97,7 @@ public class ClusterDisplayProvider {
if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) == 0) {
return;
}
- if (mClusterDisplayId == Display.INVALID_DISPLAY) {
- checkClusterDisplayAdded();
- } else {
- Display clusterDisplay = getClusterDisplay();
- if (clusterDisplay == null) {
- mListener.onDisplayRemoved(mClusterDisplayId);
- mClusterDisplayId = Display.INVALID_DISPLAY;
- }
- }
+ checkClusterDisplayChanged();
}
}
diff --git a/DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java b/DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java
index 7412ac6..d02bab3 100644
--- a/DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java
+++ b/DirectRenderingCluster/src/android/car/cluster/ClusterViewModel.java
@@ -15,12 +15,15 @@
*/
package android.car.cluster;
+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.VehicleAreaType;
+import android.car.VehiclePropertyIds;
import android.car.cluster.sensors.Sensor;
import android.car.cluster.sensors.Sensors;
import android.car.hardware.CarPropertyValue;
@@ -131,7 +134,7 @@ public class ClusterViewModel extends AndroidViewModel {
}
for (Sensor<?> sensorId : Sensors.getInstance()
.getSensorsForPropertyId(value.getPropertyId())) {
- if (sensorId.mAreaId == Sensors.GLOBAL_AREA_ID
+ if (sensorId.mAreaId == VEHICLE_AREA_TYPE_GLOBAL
|| (sensorId.mAreaId & value.getAreaId()) != 0) {
setSensorValue(sensorId, value);
}
@@ -217,19 +220,23 @@ public class ClusterViewModel extends AndroidViewModel {
* 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);
+ if (mCarPropertyManager == null) {
+ Log.e(TAG, "CarPropertyManager reference is null, car service is disconnected.");
+ return null;
+ }
+ CarPropertyValue<?> carPropertyValue = mCarPropertyManager.getProperty(sensor.mPropertyId,
+ sensor.mAreaId);
+ if (carPropertyValue == null) {
+ Log.w(TAG, "Property ID: " + VehiclePropertyIds.toString(sensor.mPropertyId)
+ + " Area ID: 0x" + Integer.toHexString(sensor.mAreaId)
+ + " returned null from CarPropertyManager#getProperty()");
return null;
}
+ return sensor.mAdapter.apply(carPropertyValue);
}
/**
diff --git a/DirectRenderingCluster/src/android/car/cluster/MusicFragment.java b/DirectRenderingCluster/src/android/car/cluster/MusicFragment.java
index 873fef3..ac17371 100644
--- a/DirectRenderingCluster/src/android/car/cluster/MusicFragment.java
+++ b/DirectRenderingCluster/src/android/car/cluster/MusicFragment.java
@@ -85,6 +85,7 @@ public class MusicFragment extends Fragment {
new MetadataController(
getViewLifecycleOwner(),
playbackViewModel,
+ null,
title,
subtitle,
null,
@@ -95,7 +96,8 @@ public class MusicFragment extends Fragment {
seekBar,
albumIcon,
null,
- new Size(artSize, artSize)
+ new Size(artSize, artSize),
+ null
);
return view;
diff --git a/DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java b/DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java
index 25f6572..65dea7e 100644
--- a/DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java
+++ b/DirectRenderingCluster/src/android/car/cluster/sensors/Sensors.java
@@ -15,6 +15,8 @@
*/
package 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;
@@ -31,9 +33,6 @@ import java.util.function.Function;
* The collection of all sensors supported by this application.
*/
public class Sensors {
- /** Area identifier used for sensors corresponding to global VHAL properties */
- public static final int GLOBAL_AREA_ID = -1;
-
private static Sensors sInstance;
private static List<Sensor<?>> sSensors = new ArrayList<>();
private Map<Integer, List<Sensor<?>>> mSensorsByPropertyId = new HashMap<>();
@@ -48,35 +47,34 @@ public class Sensors {
/** Fuel of the car, measured in millimeters */
public static final Sensor<Float> SENSOR_FUEL = registerSensor(
- "Fuel", VehiclePropertyIds.FUEL_LEVEL, GLOBAL_AREA_ID, VehiclePropertyType.FLOAT,
+ "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, GLOBAL_AREA_ID,
+ "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, GLOBAL_AREA_ID,
+ "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, GLOBAL_AREA_ID,
+ "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, GLOBAL_AREA_ID,
+ "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, GLOBAL_AREA_ID, VehiclePropertyType.INT32,
+ "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;