Merge Android 14
Bug: 298295554
Merged-In: Ie0ed2b21e8c7e45da904a111685829e5b862d41d
Change-Id: I6625d179c76d00750f6c36bd2c2a366a2f99af39
diff --git a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
index 2171e8a..2e656a2 100644
--- a/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
+++ b/ClusterHomeSample/src/com/android/car/cluster/home/ClusterHomeApplication.java
@@ -263,6 +263,10 @@
@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.
@@ -399,4 +403,21 @@
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/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 785f9d8..f86b001 100644
--- a/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
+++ b/ClusterOsDouble/src/com/android/car/cluster/osdouble/ClusterOsDoubleActivity.java
@@ -29,14 +29,17 @@
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;
@@ -171,7 +174,20 @@
if (sVirtualDisplay == null) {
sVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height);
} else {
- sVirtualDisplay.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);
+ }
}
}
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 @@
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 @@
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);
+ String displayUniqueId = context.getResources().getString(
+ R.string.config_clusterDisplayUniqueId);
+
+ if (displayPort <= 0 && TextUtils.isEmpty(displayUniqueId)) {
+ Log.e(TAG, "Cluster display isn't configured.");
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());
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ ClusterDisplayMonitor clusterDisplayMonitor = new ClusterDisplayMonitor(context,
+ displayManager, displayPort, displayUniqueId);
+ clusterDisplayMonitor.start(new Handler(Looper.myLooper()));
}
- 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();
- }
+ /**
+ * 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;
}
- return Display.INVALID_DISPLAY;
+
+ 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);
+ }
+
+ 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);
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ 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.");
+ }
+
+ 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;
+ }
}
+
}
diff --git a/DirectRenderingCluster/AndroidManifest.xml b/DirectRenderingCluster/AndroidManifest.xml
index f972e97..c4eaa87 100644
--- a/DirectRenderingCluster/AndroidManifest.xml
+++ b/DirectRenderingCluster/AndroidManifest.xml
@@ -59,9 +59,9 @@
<!-- Required to use PhoneStateListener.onCallStateChanged() -->
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <!-- Required to display media information. -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:directBootAware="true">
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 @@
}
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 @@
* 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/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 @@
* 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 @@
/** 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;