Merge "Add CPU and memory load monitoring to SystemMonitor." into sc-v2-dev
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index cc0d10b..7a6d1f3 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -17,6 +17,7 @@
package android.car;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.IBinder;
import android.os.RemoteException;
@@ -28,6 +29,7 @@
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -221,6 +223,22 @@
}
/**
+ * Returns the package names of the current owner of a given application type, or {@code null}
+ * if there is no owner. This method might return more than one package name if the current
+ * owner uses the "android:sharedUserId" feature.
+ *
+ * @hide
+ */
+ @Nullable
+ public List<String> getAppTypeOwner(@AppFocusType int appType) {
+ try {
+ return mService.getAppTypeOwner(appType);
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, null);
+ }
+ }
+
+ /**
* Checks if listener is associated with active a focus
* @param callback
* @param appType
diff --git a/car-lib/src/android/car/IAppFocus.aidl b/car-lib/src/android/car/IAppFocus.aidl
index f3d6d1f..118ad3b 100644
--- a/car-lib/src/android/car/IAppFocus.aidl
+++ b/car-lib/src/android/car/IAppFocus.aidl
@@ -30,4 +30,5 @@
int requestAppFocus(IAppFocusOwnershipCallback callback, int appType) = 4;
/** callback used as a token */
void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) = 5;
+ List<String> getAppTypeOwner(int appType) = 6;
}
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index 0e5695c..f229e39 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -228,10 +228,12 @@
/**
* Speed of the vehicle in meters per second.
*
- * <p>PERF_VEHICLE_SPEED property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}, and returns
- * a Float type value.
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Float} property type
+ * </ul>
*
* <p>When the vehicle is moving forward, PERF_VEHICLE_SPEED is positive and negative when the
* vehicle is moving backward. Also, this value is independent of gear value (CURRENT_GEAR or
@@ -246,9 +248,17 @@
/**
* Speed of the vehicle in meters per second for displays.
*
- * Some cars display a slightly slower speed than the actual speed. This is
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Float} property type
+ * </ul>
+ *
+ * <p>Some cars display a slightly slower speed than the actual speed. This is
* usually displayed on the speedometer.
- * Requires permission: {@link Car#PERMISSION_SPEED}.
+ *
+ * <p>Requires permission: {@link Car#PERMISSION_SPEED}.
*/
@RequiresPermission(Car.PERMISSION_SPEED)
public static final int PERF_VEHICLE_SPEED_DISPLAY = 291504648;
@@ -299,10 +309,12 @@
/**
* Reports wheel ticks.
*
- * <p>WHEEL_TICK property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS} and returns a
- * Long[] type value.
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Long[]} property type
+ * </ul>
*
* <p>The first element in the array is a reset count. A reset indicates
* previous tick counts are not comparable with this and future ones. Some
@@ -326,7 +338,7 @@
* which wheels are supported. configArray is set as follows:
*
* <ul>
- * <li>configArray[0], bits [0:3] = supported wheels. Uses enum Wheel.
+ * <li>configArray[0], bits [0:3] = supported wheels. Uses {@link VehicleAreaWheel}.
* <li>configArray[1] = micrometers per front left wheel tick
* <li>configArray[2] = micrometers per front right wheel tick
* <li>configArray[3] = micrometers per rear right wheel tick
@@ -340,56 +352,145 @@
@RequiresPermission(Car.PERMISSION_SPEED)
public static final int WHEEL_TICK = 290521862;
/**
- * Fuel remaining in the the vehicle, in milliliters
- * Requires permission: {@link Car#PERMISSION_ENERGY}.
+ * Fuel remaining in the vehicle in milliliters.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Float} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY} to read property.
+ * <li>Property is not writable.
+ * </ul>
*/
@RequiresPermission(Car.PERMISSION_ENERGY)
public static final int FUEL_LEVEL = 291504903;
/**
- * Fuel door open
- * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS} to read the property.
- * Requires the signature permission: android.car.permission.CONTROL_CAR_ENERGY_PORTS to write
- * the property.
+ * Fuel door open.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY_PORTS} to read property.
+ * <li>Signature permission, android.car.permission.CONTROL_CAR_ENERGY_PORTS, to write
+ * property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY_PORTS))
@RequiresPermission.Write(@RequiresPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS))
public static final int FUEL_DOOR_OPEN = 287310600;
/**
* EV battery level in watt-hours (Wh), if EV or hybrid.
- * Requires permission: {@link Car#PERMISSION_ENERGY}.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Float} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY} to read property.
+ * <li>Property is not writable.
+ * </ul>
*/
@RequiresPermission(Car.PERMISSION_ENERGY)
public static final int EV_BATTERY_LEVEL = 291504905;
/**
- * EV charge port open
- * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS} to read the property.
- * Requires the signature permission: android.car.permission.CONTROL_CAR_ENERGY_PORTS to write
- * the property.
+ * EV charge port open.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY_PORTS} to read property.
+ * <li>Signature permission, android.car.permission.CONTROL_CAR_ENERGY_PORTS, to write
+ * property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY_PORTS))
@RequiresPermission.Write(@RequiresPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS))
public static final int EV_CHARGE_PORT_OPEN = 287310602;
/**
- * EV charge port connected
- * Requires permission: {@link Car#PERMISSION_ENERGY_PORTS}.
+ * EV charge port connected.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY_PORTS} to read property.
+ * <li>Property is not writable.
+ * </ul>
*/
@RequiresPermission(Car.PERMISSION_ENERGY_PORTS)
public static final int EV_CHARGE_PORT_CONNECTED = 287310603;
/**
- * EV instantaneous charge rate in milliwatts
- * Requires permission: {@link Car#PERMISSION_ENERGY}.
+ * EV instantaneous charge rate in milliwatts.
+ *
+ * <p>Positive rate indicates battery is being charged, and Negative rate indicates battery
+ * being discharged.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Float} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY} to read property.
+ * <li>Property is not writable.
+ * </ul>
*/
@RequiresPermission(Car.PERMISSION_ENERGY)
public static final int EV_BATTERY_INSTANTANEOUS_CHARGE_RATE = 291504908;
/**
- * Range remaining
+ * Range remaining in meters.
*
- * Meters remaining of fuel and charge. Range remaining shall account for
- * all energy sources in a vehicle. For example, a hybrid car's range will
- * be the sum of the ranges based on fuel and battery.
- * Requires permission: {@link Car#PERMISSION_ENERGY} to read the property.
- * Requires the signature permission: android.car.permission.ADJUST_RANGE_REMAINING to write
- * the property.
+ * <p>Range remaining accounts for all energy sources in a vehicle. For example, a hybrid car's
+ * range will be the sum of the ranges based on fuel and battery.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Float} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY} to read property.
+ * <li>Signature permission, android.car.permission.ADJUST_RANGE_REMAINING, to write property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY))
@RequiresPermission.Write(@RequiresPermission(Car.PERMISSION_ADJUST_RANGE_REMAINING))
@@ -417,10 +518,12 @@
/**
* Currently selected gear by user.
*
- * <p>GEAR_SELECTION property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}, and returns a
- * Integer type value.
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
*
* <p> See {@link VehicleGear} for gear value enum.
*
@@ -513,10 +616,12 @@
/**
* Parking brake state.
*
- * <p>PARKING_BRAKE_ON property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}, and returns a
- * Boolean type value.
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
*
* <p>Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
*/
@@ -538,17 +643,42 @@
public static final int PARKING_BRAKE_AUTO_APPLY = 287310851;
/**
* Warning for fuel low level.
- * Requires permission: {@link Car#PERMISSION_ENERGY}.
+ *
+ * <p>{@code FUEL_LEVEL_LOW} corresponds to the low fuel warning on the dashboard. Once {@code
+ * FUEL_LEVEL_LOW} is set, it should not be cleared until more fuel is added to the vehicle.
+ * This property may take into account all fuel sources for a vehicle - for example:
+ * <ul>
+ * <li>For a gas powered vehicle, this property is based solely on gas level.
+ * <li>For a battery powered vehicle, this property is based solely on battery level.
+ * <li>For a hybrid vehicle, this property may be based on the combination of gas and
+ * battery levels, at the OEM's discretion.
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_ENERGY} to read property.
+ * <li>Property is not writable.
+ * </ul>
*/
@RequiresPermission(Car.PERMISSION_ENERGY)
public static final int FUEL_LEVEL_LOW = 287310853;
/**
* Night mode.
*
- * <p>NIGHT_MODE property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
- * android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}, and returns a
- * Boolean type value.
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
*
* <p>True indicates that the night mode sensor has detected that the car cabin environment has
* low light.
@@ -565,8 +695,32 @@
@RequiresPermission(Car.PERMISSION_EXTERIOR_LIGHTS)
public static final int TURN_SIGNAL_STATE = 289408008;
/**
- * Represents ignition state
- * Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
+ * Vehicle's ignition state.
+ *
+ * <p>The property value can be one of:
+ * <ul>
+ * <li>{@code 0}: Steering wheel is locked.
+ * <li>{@code 1}: Steering wheel is not locked, engine and all accessories are OFF.
+ * <li>{@code 2}: Typically in this state accessories become available (e.g. radio).
+ * Instrument cluster and engine are turned off
+ * <li>{@code 3}: Ignition is in state ON. Accessories and instrument cluster available,
+ * engine might be running or ready to be started.
+ * <li>{@code 4}: Typically in this state engine is starting (cranking).
+ * </>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_POWERTRAIN} to read property.
+ * <li>Property is not writable.
+ * </ul>
*/
@RequiresPermission(Car.PERMISSION_POWERTRAIN)
public static final int IGNITION_STATE = 289408009;
@@ -806,50 +960,159 @@
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
public static final int HVAC_ELECTRIC_DEFROSTER_ON = 320865556;
/**
- * Distance units for display
- * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
- * Requires two signature permissions: android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
- * android.car.permission.CAR_VENDOR_EXTENSION to write the property.
+ * Distance units for display.
+ *
+ * <p>Indicates which units the car is using to display distances to the user.
+ *
+ * <p>configArray represents the list of supported units for {@code
+ * DISTANCE_DISPLAY_UNITS}. Here is an example configArray:
+ * <ul>
+ * <li>configArray[0] = {@code 33 //VehicleUnit#METER}
+ * <li>configArray[1] = {@code 35 //VehicleUnit#KILOMETER}
+ * <li>configArray[1] = {@code 36 //VehicleUnit#MILE}
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_READ_DISPLAY_UNITS} to read property.
+ * <li>Signature permissions, android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
+ * android.car.permission.CAR_VENDOR_EXTENSION, to write property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
@RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
Car.PERMISSION_VENDOR_EXTENSION}))
public static final int DISTANCE_DISPLAY_UNITS = 289408512;
/**
- * Fuel volume units for display
- * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
- * Requires two signature permissions: android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
- * android.car.permission.CAR_VENDOR_EXTENSION to write the property.
+ * Fuel volume units for display.
+ *
+ * <p>Indicates which units the car is using to display fuel volume to the user.
+ *
+ * <p>configArray represents the list of supported units for {@code
+ * FUEL_VOLUME_DISPLAY_UNITS}. Here is an example configArray:
+ * <ul>
+ * <li>configArray[0] = {@code 65 //VehicleUnit#LITER}
+ * <li>configArray[1] = {@code 66 //VehicleUnit#US_GALLON}
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_READ_DISPLAY_UNITS} to read property.
+ * <li>Signature permissions, android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
+ * android.car.permission.CAR_VENDOR_EXTENSION, to write property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
@RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
Car.PERMISSION_VENDOR_EXTENSION}))
public static final int FUEL_VOLUME_DISPLAY_UNITS = 289408513;
/**
- * Tire pressure units for display
- * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
- * Requires two signature permissions: android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
- * android.car.permission.CAR_VENDOR_EXTENSION to write the property.
+ * Tire pressure units for display.
+ *
+ * <p>Indicates which units the car is using to display tire pressure to the user.
+ *
+ * <p>configArray represents the list of supported units for {@code
+ * TIRE_PRESSURE_DISPLAY_UNITS}. Here is an example configArray:
+ * <ul>
+ * <li>configArray[0] = {@code 112 //VehicleUnit#KILOPASCAL}
+ * <li>configArray[1] = {@code 113 //VehicleUnit#PSI}
+ * <li>configArray[2] = {@code 114 //VehicleUnit#BAR}
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_READ_DISPLAY_UNITS} to read property.
+ * <li>Signature permissions, android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
+ * android.car.permission.CAR_VENDOR_EXTENSION, to write property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
@RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
Car.PERMISSION_VENDOR_EXTENSION}))
public static final int TIRE_PRESSURE_DISPLAY_UNITS = 289408514;
/**
- * EV battery units for display
- * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
- * Requires two signature permissions: android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
- * android.car.permission.CAR_VENDOR_EXTENSION to write the property.
+ * EV battery units for display.
+ *
+ * <p>Indicates which units the vehicle is using to display EV battery information to the user.
+ *
+ * <p>configArray represents the list of supported units for {@code
+ * EV_BATTERY_DISPLAY_UNITS}. Here is an example configArray:
+ * <ul>
+ * <li>configArray[0] = {@code 96 //VehicleUnit#WATT_HOUR}
+ * <li>configArray[1] = {@code 100 //VehicleUnit#AMPERE_HOURS}
+ * <li>configArray[2] = {@code 101 //VehicleUnit#KILOWATT_HOUR}
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_READ_DISPLAY_UNITS} to read property.
+ * <li>Signature permissions, android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
+ * android.car.permission.CAR_VENDOR_EXTENSION, to write property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
@RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
Car.PERMISSION_VENDOR_EXTENSION}))
public static final int EV_BATTERY_DISPLAY_UNITS = 289408515;
/**
- * Speed Units for display
- * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
- * Requires two signature permissions: android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
- * android.car.permission.CAR_VENDOR_EXTENSION to write the property.
+ * Speed units for display.
+ *
+ * <p>Indicates type of units the vehicle is using to display speed to user.
+ *
+ * <p>configArray represents the list of supported units for {@code
+ * VEHICLE_SPEED_DISPLAY_UNITS}. Here is an example configArray:
+ * <ul>
+ * <li>configArray[0] = {@code 1 //VehicleUnit#METER_PER_SEC}
+ * <li>configArray[1] = {@code 114 //VehicleUnit#MILES_PER_HOUR}
+ * <li>configArray[2] = {@code 115 //VehicleUnit#KILOMETERS_PER_HOUR}
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_READ_DISPLAY_UNITS} to read property.
+ * <li>Signature permissions, android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
+ * android.car.permission.CAR_VENDOR_EXTENSION, to write property.
+ * </ul>
*
* @hide
*/
@@ -858,10 +1121,28 @@
Car.PERMISSION_VENDOR_EXTENSION}))
public static final int VEHICLE_SPEED_DISPLAY_UNITS = 289408516;
/**
- * Fuel consumption units for display
- * Requires permission {@link Car#PERMISSION_READ_DISPLAY_UNITS} to read the property.
- * Requires two signature permissions: android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
- * android.car.permission.CAR_VENDOR_EXTENSION to write the property.
+ * Fuel consumption units for display.
+ *
+ * <p>Indicates type of units the car is using to display fuel consumption information to user.
+ *
+ * <p>{@code true} indicates units are distance over volume such as MPG.
+ *
+ * <p>{@code false} indicates units are volume over distance such as L/100KM.
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Boolean} property type
+ * </ul>
+ *
+ * <p>Required Permissions:
+ * <ul>
+ * <li>{@link Car#PERMISSION_READ_DISPLAY_UNITS} to read property.
+ * <li>Signature permissions, android.car.permission.CONTROL_CAR_DISPLAY_UNITS and
+ * android.car.permission.CAR_VENDOR_EXTENSION, to write property.
+ * </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_READ_DISPLAY_UNITS))
@RequiresPermission.Write(@RequiresPermission(allOf = {Car.PERMISSION_CONTROL_DISPLAY_UNITS,
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index fe697b1..008fc4b 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Service;
import android.car.Car;
@@ -152,7 +153,7 @@
String packageName) {
try {
ProviderInfo[] providers = packageManager.getPackageInfo(packageName,
- PackageManager.GET_PROVIDERS).providers;
+ PackageManager.GET_PROVIDERS | PackageManager.MATCH_ANY_USER).providers;
if (providers == null) {
return Collections.emptyList();
}
@@ -370,8 +371,16 @@
}
}
+ /**
+ * Returns the cluster activity from the application given by its package name.
+ *
+ * @return the {@link ComponentName} of the cluster activity, or null if the given application
+ * doesn't have a cluster activity.
+ *
+ * @hide
+ */
@Nullable
- private ComponentName getComponentFromPackage(@NonNull String packageName) {
+ public ComponentName getComponentFromPackage(@NonNull String packageName) {
PackageManager packageManager = getPackageManager();
// Check package permission.
@@ -385,8 +394,8 @@
Intent intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Car.CAR_CATEGORY_NAVIGATION)
.setPackage(packageName);
- List<ResolveInfo> resolveList = packageManager.queryIntentActivities(intent,
- PackageManager.GET_RESOLVED_FILTER);
+ List<ResolveInfo> resolveList = packageManager.queryIntentActivitiesAsUser(intent,
+ PackageManager.GET_RESOLVED_FILTER, ActivityManager.getCurrentUser());
if (resolveList == null || resolveList.isEmpty()
|| resolveList.get(0).getComponentInfo() == null) {
Log.i(TAG, "Failed to resolve an intent: " + intent);
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index d78c167..76967a8 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -63,13 +63,15 @@
carbugreportd \
vehicle_binding_util \
+# ENABLE_CAMERA_SERVICE must be set as true from the product's makefile if it wants to support
+# Android Camera service.
+ifneq ($(ENABLE_CAMERA_SERVICE), true)
+PRODUCT_PROPERTY_OVERRIDES += config.disable_cameraservice=true
+endif
+
# EVS service
include packages/services/Car/cpp/evs/manager/evsmanager.mk
-# EVS manager overrides cameraserver on automotive implementations so
-# we need to configure Camera API to not connect to it
-PRODUCT_PROPERTY_OVERRIDES += config.disable_cameraservice=true
-
ifeq ($(ENABLE_EVS_SAMPLE), true)
# ENABLE_EVS_SAMPLE should set be true or their vendor specific equivalents should be included in
# the device.mk with the corresponding selinux policies
@@ -108,6 +110,10 @@
packages/services/Car/car_product/init/init.bootstat.rc:system/etc/init/init.bootstat.car.rc \
packages/services/Car/car_product/init/init.car.rc:system/etc/init/init.car.rc
+# Device policy management support
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.software.device_admin.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_admin.xml
+
# Enable car watchdog
include packages/services/Car/cpp/watchdog/product/carwatchdog.mk
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index 41194f4..b241c89 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -46,6 +46,15 @@
<install-in-user-type package="android.car.cluster">
<install-in user-type="SYSTEM" />
</install-in-user-type>
+
+ <!-- These two packages should only support one proxy device-wide, and that
+ the proxy depends on the network, which are independent of users. -->
+ <install-in-user-type package="com.android.pacprocessor">
+ <install-in user-type="SYSTEM"/>
+ </install-in-user-type>
+ <install-in-user-type package="com.android.proxyhandler">
+ <install-in user-type="SYSTEM"/>
+ </install-in-user-type>
<!--
Apps that need to run on SYSTEM and evaluated by package owner.
Here the apps will have FULL and SYSTEM.
@@ -262,6 +271,11 @@
<install-in-user-type package="com.android.bluetoothmidiservice">
<install-in user-type="FULL" />
</install-in-user-type>
+ <!-- ManagedProvisioning app is used for provisioning the device. It
+ requires UX for the provisioning flow. -->
+ <install-in-user-type package="com.android.managedprovisioning">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
<install-in-user-type package="com.android.statementservice">
<install-in user-type="FULL" />
</install-in-user-type>
@@ -304,14 +318,6 @@
<install-in-user-type package="com.android.certinstaller">
<install-in user-type="FULL" />
</install-in-user-type>
- <install-in-user-type package="com.android.pacprocessor">
- <install-in user-type="FULL" />
- <install-in user-type="SYSTEM" />
- </install-in-user-type>
- <install-in-user-type package="com.android.proxyhandler">
- <install-in user-type="FULL" />
- <install-in user-type="SYSTEM" />
- </install-in-user-type>
<install-in-user-type package="com.android.vpndialogs">
<install-in user-type="FULL" />
</install-in-user-type>
diff --git a/car_product/car_ui_portrait/Android.mk b/car_product/car_ui_portrait/Android.mk
index c75d51a..2345d92 100644
--- a/car_product/car_ui_portrait/Android.mk
+++ b/car_product/car_ui_portrait/Android.mk
@@ -14,6 +14,7 @@
#
car_ui_portrait_modules := \
+ rro/alert-dialog-customizations \
rro/car-ui-customizations \
apps/HideApps
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/exit_icon_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/exit_icon_background.xml
deleted file mode 100644
index cac886c..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/exit_icon_background.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
- -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <shape android:shape="oval">
- <size android:width="@dimen/rear_view_camera_exit_button_width"
- android:height="@dimen/rear_view_camera_exit_button_height"/>
- <solid android:color="@color/rear_view_camera_button_background"/>
- </shape>
- </item>
- <item>
- <ripple android:color="@color/car_ui_ripple_color">
- <item android:id="@android:id/mask">
- <shape android:shape="oval">
- <solid android:color="?android:colorAccent"/>
- </shape>
- </item>
- </ripple>
- </item>
-</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_decrease_button.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_decrease_button.xml
new file mode 100644
index 0000000..ea3a853
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_decrease_button.xml
@@ -0,0 +1,50 @@
+<?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.
+ -->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <item>
+ <aapt:attr name="android:drawable">
+ <vector android:width="@dimen/hvac_temperature_button_size"
+ android:height="@dimen/hvac_temperature_button_size"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+ <path
+ android:pathData="M32,0L32,0A32,32 0,0 1,64 32L64,32A32,32 0,0 1,32 64L32,64A32,32 0,0 1,0 32L0,32A32,32 0,0 1,32 0z"
+ android:fillColor="@color/hvac_temperature_adjust_button_color"/>
+ </vector>
+ </aapt:attr>
+ </item>
+ <item android:gravity="center">
+ <aapt:attr name="android:drawable">
+ <vector android:width="24dp"
+ android:height="3dp"
+ android:viewportWidth="24"
+ android:viewportHeight="3">
+ <path
+ android:fillColor="@color/hvac_temperature_decrease_arrow_color"
+ android:pathData="M24,3.5H0V0.5H24V3.5Z"/>
+ </vector>
+ </aapt:attr>
+ </item>
+ <item>
+ <aapt:attr name="android:drawable">
+ <ripple android:color="?android:attr/colorControlHighlight"/>
+ </aapt:attr>
+ </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_increase_button.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_increase_button.xml
new file mode 100644
index 0000000..630727d
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/hvac_increase_button.xml
@@ -0,0 +1,53 @@
+<?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.
+ -->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <item>
+ <aapt:attr name="android:drawable">
+ <vector android:width="@dimen/hvac_temperature_button_size"
+ android:height="@dimen/hvac_temperature_button_size"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+ <path
+ android:pathData="M32,0L32,0A32,32 0,0 1,64 32L64,32A32,32 0,0 1,32 64L32,64A32,32 0,0 1,0 32L0,32A32,32 0,0 1,32 0z"
+ android:fillColor="@color/hvac_temperature_adjust_button_color"/>
+ </vector>
+ </aapt:attr>
+ </item>
+ <item
+ android:gravity="center"
+ android:width="24dp"
+ android:height="24dp">
+ <aapt:attr name="android:drawable">
+ <vector android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/hvac_temperature_increase_arrow_color"
+ android:pathData="M24,13.5H13.5V24H10.5V13.5H0V10.5H10.5V0H13.5V10.5H24V13.5Z"/>
+ </vector>
+ </aapt:attr>
+ </item>
+ <item>
+ <aapt:attr name="android:drawable">
+ <ripple android:color="?android:attr/colorControlHighlight"/>
+ </aapt:attr>
+ </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_close.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_close.xml
deleted file mode 100644
index 4df0f11..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/ic_close.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/rear_view_camera_exit_icon_width"
- android:height="@dimen/rear_view_camera_exit_icon_height"
- android:viewportWidth="26"
- android:viewportHeight="26">
- <path
- android:pathData="M25.8327 2.75199L23.2477 0.166992L12.9994 10.4153L2.75102 0.166992L0.166016 2.75199L10.4144 13.0003L0.166016 23.2487L2.75102 25.8337L12.9994 15.5853L23.2477 25.8337L25.8327 23.2487L15.5844 13.0003L25.8327 2.75199Z"
- android:fillColor="@color/rear_view_camera_exit_icon_color" />
-</vector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
index 3f7700b..de76f55 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
@@ -13,34 +13,61 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
<item android:state_activated="true">
<layer-list>
<item>
- <shape android:shape="oval">
+ <shape android:shape="rectangle">
<size android:width="@dimen/system_bar_button_size"
android:height="@dimen/system_bar_button_size"/>
- <solid android:color="@color/car_nav_icon_fill_color"/>
+ <corners
+ android:radius="@dimen/system_bar_button_corner_radius"/>
+ <solid
+ android:color="@color/car_nav_icon_fill_color"/>
</shape>
</item>
<item>
- <ripple android:color="@color/car_ui_ripple_color"
- android:radius="@dimen/system_bar_icon_drawing_size"/>
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item>
+ <shape android:shape="rectangle">
+ <size android:width="@dimen/system_bar_button_size"
+ android:height="@dimen/system_bar_button_size"/>
+ <corners
+ android:radius="@dimen/system_bar_button_corner_radius"/>
+ <solid
+ android:color="@color/car_nav_icon_fill_color"/>
+ </shape>
+ </item>
+ </ripple>
</item>
</layer-list>
</item>
<item>
<layer-list>
<item>
- <shape android:shape="oval">
+ <shape android:shape="rectangle">
<size android:width="@dimen/system_bar_button_size"
android:height="@dimen/system_bar_button_size"/>
- <solid android:color="@color/car_nav_icon_background_color"/>
+ <corners
+ android:radius="@dimen/system_bar_button_corner_radius"/>
+ <solid
+ android:color="@color/car_nav_icon_background_color"/>
</shape>
</item>
<item>
- <ripple android:color="@color/car_ui_ripple_color"
- android:radius="@dimen/system_bar_icon_drawing_size"/>
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item>
+ <shape android:shape="rectangle">
+ <size android:width="@dimen/system_bar_button_size"
+ android:height="@dimen/system_bar_button_size"/>
+ <corners
+ android:radius="@dimen/system_bar_button_corner_radius"/>
+ <solid
+ android:color="@color/car_nav_icon_background_color"/>
+ </shape>
+ </item>
+ </ripple>
</item>
</layer-list>
</item>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
index cfdeade..974cec2 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
@@ -58,7 +58,7 @@
systemui:clearBackStack="true"/>
<com.android.systemui.car.systembar.CarSystemBarButton
- android:id="@+id/notifications"
+ android:id="@+id/standalone_notifications"
style="@style/SystemBarButton"
systemui:componentNames="com.android.car.notification/.CarNotificationCenterActivity"
systemui:highlightWhenSelected="true"
@@ -78,7 +78,7 @@
style="@style/SystemBarButton"
systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_mic"
- systemui:useDefaultAppIconForRole="false"/>
+ systemui:useDefaultAppIconForRole="true"/>
</LinearLayout>
<com.android.systemui.car.hvac.TemperatureControlView
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/rear_view_camera.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/rear_view_camera.xml
deleted file mode 100644
index a7631d7..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/rear_view_camera.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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.
- -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/transparent">
- <LinearLayout
- android:id="@+id/rear_view_camera_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"/>
- <ImageButton
- android:id="@+id/close_button"
- android:layout_width="@dimen/rear_view_camera_exit_button_width"
- android:layout_height="@dimen/rear_view_camera_exit_button_height"
- android:layout_marginStart="@dimen/rear_view_camera_exit_button_margin"
- android:layout_marginTop="@dimen/rear_view_camera_exit_button_margin"
- android:scaleType="center"
- android:src="@drawable/ic_close"
- android:background="@drawable/exit_icon_background"/>
-</FrameLayout>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/text_toast.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..87eb12c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/text_toast.xml
@@ -0,0 +1,45 @@
+<?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="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:maxWidth="@*android:dimen/toast_width"
+ android:background="@android:drawable/toast_frame"
+ android:elevation="@*android:dimen/toast_elevation"
+ android:paddingEnd="@dimen/toast_margin"
+ android:paddingTop="@dimen/toast_margin"
+ android:paddingBottom="@dimen/toast_margin"
+ android:paddingStart="@dimen/toast_margin"
+ android:layout_marginBottom="@dimen/toast_bottom_margin">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/toast_icon_dimen"
+ android:layout_height="@dimen/toast_icon_dimen"
+ android:layout_marginEnd="@dimen/toast_margin"/>
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:textAppearance="@*android:style/TextAppearance.Toast"/>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
index 248a2c8..6f685b3 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/colors.xml
@@ -19,9 +19,13 @@
<color name="rear_view_camera_exit_icon_color">@android:color/black</color>
<color name="car_nav_icon_fill_color">#FFFFFF</color>
- <color name="car_nav_icon_background_color">#3C4043</color>
+ <color name="car_nav_icon_background_color">#282A2D</color>
<drawable name="system_bar_background">#000000</drawable>
+ <color name="system_bar_text_color">#E8EAED</color>
+ <color name="hvac_temperature_adjust_button_color">#282A2D</color>
+ <color name="hvac_temperature_decrease_arrow_color">#E8EAED</color>
+ <color name="hvac_temperature_increase_arrow_color">#E8EAED</color>
<color name="status_bar_background_color">#00000000</color>
<drawable name="status_bar_background">@color/status_bar_background_color</drawable>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
index 6a961fd..3064fd9 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
@@ -26,16 +26,18 @@
<dimen name="rear_view_camera_exit_icon_width">26dp</dimen>
<dimen name="rear_view_camera_exit_icon_height">26dp</dimen>
- <dimen name="hvac_container_padding">16dp</dimen>
- <dimen name="hvac_temperature_text_size">48sp</dimen>
- <dimen name="hvac_temperature_text_padding">0dp</dimen>
+ <dimen name="hvac_container_padding">24dp</dimen>
+ <dimen name="hvac_temperature_text_size">56sp</dimen>
+ <dimen name="hvac_temperature_text_padding">12dp</dimen>
+ <dimen name="hvac_temperature_button_size">64dp</dimen>
<dimen name="system_bar_icon_drawing_size">56dp</dimen>
<dimen name="system_bar_button_size">88dp</dimen>
<!-- Margin between the system bar buttons -->
- <dimen name="system_bar_button_margin">32dp</dimen>
+ <dimen name="system_bar_button_margin">40dp</dimen>
<!-- Padding between the system bar button and the icon within it -->
<dimen name="system_bar_button_padding">16dp</dimen>
+ <dimen name="system_bar_button_corner_radius">24dp</dimen>
<dimen name="system_bar_user_icon_drawing_size">44dp</dimen>
<dimen name="status_bar_system_icon_spacing">32dp</dimen>
@@ -65,4 +67,9 @@
<dimen name="hvac_panel_button_external_margin">24dp</dimen>
<dimen name="hvac_panel_button_internal_margin">16dp</dimen>
<item name="hvac_heat_or_cool_off_alpha" format="float" type="dimen">0.3</item>
+
+ <dimen name="toast_margin">24dp</dimen>
+ <dimen name="toast_icon_dimen">48dp</dimen>
+ <dimen name="toast_bottom_margin">32dp</dimen>
+
</resources>
diff --git a/car_product/car_ui_portrait/bootanimation/README b/car_product/car_ui_portrait/bootanimation/README
new file mode 100644
index 0000000..2ce687f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/README
@@ -0,0 +1,68 @@
+# Boot Animation
+
+The boot animation format is described in full by [FORMAT.md](https://android.googlesource.com/platform/frameworks/base/+/master/cmds/bootanimation/FORMAT.md)
+
+## Command to create the zip:
+zip -0qry -i \*.txt \*.png \*.wav @ ../bootanimation.zip *.txt part*
+
+## zipfile layout
+
+The `bootanimation.zip` archive file includes:
+
+ desc.txt - a text file
+ part0 \
+ part1 \ directories full of PNG frames
+ ... /
+ partN /
+
+## desc.txt format
+
+The first line defines the general parameters of the animation:
+
+ WIDTH HEIGHT FPS [PROGRESS]
+
+ * **WIDTH:** animation width (pixels)
+ * **HEIGHT:** animation height (pixels)
+ * **FPS:** frames per second, e.g. 60
+ * **PROGRESS:** whether to show a progress percentage on the last part
+ + The percentage will be displayed with an x-coordinate of 'c', and a
+ y-coordinate set to 1/3 of the animation height.
+
+It is followed by a number of rows of the form:
+
+ TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]
+
+ * **TYPE:** a single char indicating what type of animation segment this is:
+ + `p` -- this part will play unless interrupted by the end of the boot
+ + `c` -- this part will play to completion, no matter what
+ + `f` -- same as `p` but in addition the specified number of frames is being faded out while
+ continue playing. Only the first interrupted `f` part is faded out, other subsequent `f`
+ parts are skipped
+ * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete
+ * **PAUSE:** number of FRAMES to delay after this part ends
+ * **PATH:** directory in which to find the frames for this part (e.g. `part0`)
+ * **FADE:** _(ONLY FOR `f` TYPE)_ number of frames to fade out when interrupted where `0` means
+ _immediately_ which makes `f ... 0` behave like `p` and doesn't count it as a fading
+ part
+ * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`
+ * **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches):
+ + If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate
+ defaults to `c`
+ + If both `CLOCK1` and `CLOCK2` are provided then `CLOCK1` is the x-coordinate and `CLOCK2` is
+ the y-coodinate
+ + Values can be either a positive integer, a negative integer, or `c`
+ - `c` -- will centre the text
+ - `n` -- will position the text n pixels from the start; left edge for x-axis, bottom edge
+ for y-axis
+ - `-n` -- will position the text n pixels from the end; right edge for x-axis, top edge
+ for y-axis
+ - Examples:
+ * `-24` or `c -24` will position the text 24 pixels from the top of the screen,
+ centred horizontally
+ * `16 c` will position the text 16 pixels from the left of the screen, centred
+ vertically
+ * `-32 32` will position the text such that the bottom right corner is 32 pixels above
+ and 32 pixels left of the edges of the screen
+
+There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip`
+and plays that.
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/bootanimation/bootanimation.zip b/car_product/car_ui_portrait/bootanimation/bootanimation.zip
new file mode 100644
index 0000000..cc40a4b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/bootanimation.zip
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/desc.txt b/car_product/car_ui_portrait/bootanimation/parts/desc.txt
new file mode 100644
index 0000000..483f8e2
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/desc.txt
@@ -0,0 +1,2 @@
+986 1000 60
+p 0 0 part0
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png
new file mode 100644
index 0000000..3a12b7e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00060.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png
new file mode 100644
index 0000000..3a12b7e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00061.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png
new file mode 100644
index 0000000..455560c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00062.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png
new file mode 100644
index 0000000..7450c5f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00063.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png
new file mode 100644
index 0000000..7795a77
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00064.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png
new file mode 100644
index 0000000..fae1212
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00065.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png
new file mode 100644
index 0000000..df8f1cd
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00066.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png
new file mode 100644
index 0000000..f936d27
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00067.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png
new file mode 100644
index 0000000..2a6bb73
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00068.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png
new file mode 100644
index 0000000..e565ea5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00069.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png
new file mode 100644
index 0000000..41cbf3c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00070.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png
new file mode 100644
index 0000000..956259a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00071.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png
new file mode 100644
index 0000000..e86e314
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00072.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png
new file mode 100644
index 0000000..4cbd91c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00073.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png
new file mode 100644
index 0000000..a00c625
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00074.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png
new file mode 100644
index 0000000..9deae48
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00075.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png
new file mode 100644
index 0000000..3fad600
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00076.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png
new file mode 100644
index 0000000..17f8728
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00077.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png
new file mode 100644
index 0000000..caaad59
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00078.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png
new file mode 100644
index 0000000..0b106e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00079.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png
new file mode 100644
index 0000000..fb9e49a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00080.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png
new file mode 100644
index 0000000..ac81644
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00081.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png
new file mode 100644
index 0000000..f25cec5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00082.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png
new file mode 100644
index 0000000..3a4e680
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00083.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png
new file mode 100644
index 0000000..7a9c203
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00084.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png
new file mode 100644
index 0000000..765afd9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00085.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png
new file mode 100644
index 0000000..a3a233c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00086.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png
new file mode 100644
index 0000000..f2aa138
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00087.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png
new file mode 100644
index 0000000..a1e35ec
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00088.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png
new file mode 100644
index 0000000..e49f6ee
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00089.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png
new file mode 100644
index 0000000..6934ac3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00090.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png
new file mode 100644
index 0000000..0dc6b52
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00091.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png
new file mode 100644
index 0000000..d0cc426
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00092.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png
new file mode 100644
index 0000000..5e5a8da
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00093.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png
new file mode 100644
index 0000000..608e17f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00094.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png
new file mode 100644
index 0000000..a802840
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00095.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png
new file mode 100644
index 0000000..5f600f1
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00096.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png
new file mode 100644
index 0000000..76448c2
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00097.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png
new file mode 100644
index 0000000..fbbb879
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00098.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png
new file mode 100644
index 0000000..35f6feb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00099.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png
new file mode 100644
index 0000000..b980f13
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00100.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png
new file mode 100644
index 0000000..11617e7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00101.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png
new file mode 100644
index 0000000..77f660c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00102.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png
new file mode 100644
index 0000000..7653dd8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00103.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png
new file mode 100644
index 0000000..5afc5f3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00104.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png
new file mode 100644
index 0000000..7331f7b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00105.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png
new file mode 100644
index 0000000..45b3473
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00106.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png
new file mode 100644
index 0000000..8f48b1d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00107.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png
new file mode 100644
index 0000000..7c1dfbb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00108.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png
new file mode 100644
index 0000000..60240eb
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00109.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png
new file mode 100644
index 0000000..7f7821f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00110.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png
new file mode 100644
index 0000000..1d4fc02
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00111.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png
new file mode 100644
index 0000000..4272e3b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00112.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png
new file mode 100644
index 0000000..74aa68b
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00113.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png
new file mode 100644
index 0000000..9f9962a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00114.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png
new file mode 100644
index 0000000..1afd28a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00115.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png
new file mode 100644
index 0000000..5f6eab0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00116.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png
new file mode 100644
index 0000000..7640188
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00117.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png
new file mode 100644
index 0000000..b8691e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00118.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png
new file mode 100644
index 0000000..8c75267
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00119.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png
new file mode 100644
index 0000000..38a22cc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00120.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png
new file mode 100644
index 0000000..52b1a3d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00121.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png
new file mode 100644
index 0000000..1e56cea
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00122.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png
new file mode 100644
index 0000000..a5c7458
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00123.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png
new file mode 100644
index 0000000..89f4952
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00124.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png
new file mode 100644
index 0000000..3044353
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00125.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png
new file mode 100644
index 0000000..d9e42d7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00126.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png
new file mode 100644
index 0000000..ea03d6a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00127.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png
new file mode 100644
index 0000000..39b7176
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00128.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png
new file mode 100644
index 0000000..39cd88f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00129.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png
new file mode 100644
index 0000000..1fb3509
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00130.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png
new file mode 100644
index 0000000..3f35990
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00131.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png
new file mode 100644
index 0000000..aa6ad9a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00132.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png
new file mode 100644
index 0000000..8075ac8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00133.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png
new file mode 100644
index 0000000..7b9c3b0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00134.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png
new file mode 100644
index 0000000..ec203ec
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00135.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png
new file mode 100644
index 0000000..cbd7182
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00136.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png
new file mode 100644
index 0000000..8319819
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00137.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png
new file mode 100644
index 0000000..3729ade
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00138.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png
new file mode 100644
index 0000000..b41c045
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00139.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png
new file mode 100644
index 0000000..f2a840a
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00140.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png
new file mode 100644
index 0000000..9bdd6db
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00141.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png
new file mode 100644
index 0000000..63dd4d0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00142.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png
new file mode 100644
index 0000000..de44f52
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00143.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png
new file mode 100644
index 0000000..20e5242
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00144.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png
new file mode 100644
index 0000000..9db1d26
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00145.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png
new file mode 100644
index 0000000..aa55e1e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00146.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png
new file mode 100644
index 0000000..077c3b0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00147.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png
new file mode 100644
index 0000000..46cf822
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00148.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png
new file mode 100644
index 0000000..179870f
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00149.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png
new file mode 100644
index 0000000..55cc405
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00150.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png
new file mode 100644
index 0000000..6adcea3
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00151.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png
new file mode 100644
index 0000000..b11dc75
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00152.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png
new file mode 100644
index 0000000..cb87e84
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00153.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png
new file mode 100644
index 0000000..af1acad
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00154.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png
new file mode 100644
index 0000000..0abfa3d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00155.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png
new file mode 100644
index 0000000..b2cf2ae
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00156.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png
new file mode 100644
index 0000000..2e4648d
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00157.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png
new file mode 100644
index 0000000..bc05bf9
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00158.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png
new file mode 100644
index 0000000..ff331f7
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00159.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png
new file mode 100644
index 0000000..fe111e6
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00160.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png
new file mode 100644
index 0000000..9e81d38
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00161.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png
new file mode 100644
index 0000000..1cf960c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00162.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png
new file mode 100644
index 0000000..f5e4543
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00163.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png
new file mode 100644
index 0000000..cac88e2
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00164.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png
new file mode 100644
index 0000000..c843493
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00165.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png
new file mode 100644
index 0000000..761eeba
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00166.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png
new file mode 100644
index 0000000..0f3402e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00167.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png
new file mode 100644
index 0000000..bb74e3c
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00168.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png
new file mode 100644
index 0000000..23c31e0
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00169.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png
new file mode 100644
index 0000000..989f4a8
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00170.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png
new file mode 100644
index 0000000..91f2130
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00171.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png
new file mode 100644
index 0000000..90bfe15
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00172.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png
new file mode 100644
index 0000000..cdf1e30
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00173.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png
new file mode 100644
index 0000000..e00a7ce
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00174.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png
new file mode 100644
index 0000000..5f7cf40
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00175.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png
new file mode 100644
index 0000000..a4d9bdf
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00176.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png
new file mode 100644
index 0000000..e044dbc
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00177.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png
new file mode 100644
index 0000000..52b0244
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00178.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png
new file mode 100644
index 0000000..8873cde
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00179.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png
new file mode 100644
index 0000000..81385e5
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00180.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png
new file mode 100644
index 0000000..104cd1e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00181.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png
new file mode 100644
index 0000000..0ea0bd1
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00182.png
Binary files differ
diff --git a/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png b/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png
new file mode 100644
index 0000000..c70211e
--- /dev/null
+++ b/car_product/car_ui_portrait/bootanimation/parts/part0/00183.png
Binary files differ
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
new file mode 100644
index 0000000..fecf167
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+android_app {
+ name: "CarUiPortraitDialerRRO",
+ resource_dirs: ["res"],
+ platform_apis: true,
+ aaptflags: [
+ "--no-resource-deduping",
+ "--no-resource-removal"
+ ],
+ static_libs: [
+ "androidx-constraintlayout_constraintlayout",
+ "androidx-constraintlayout_constraintlayout-solver",
+ ],
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/AndroidManifest.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/AndroidManifest.xml
new file mode 100644
index 0000000..fea65f9
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/AndroidManifest.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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.dialer.caruiportrait.rro">
+ <application android:hasCode="false"/>
+ <overlay android:priority="20"
+ android:targetName="CarDialerApp"
+ android:targetPackage="com.android.car.dialer"
+ android:resourcesMap="@xml/overlays"
+ android:isStatic="true" />
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_phone.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_phone.xml
new file mode 100644
index 0000000..b659da1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/ic_phone.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#fffafafa"
+ android:pathData="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27 .67 -.36 1.02-.24 1.12
+.37 2.33 .57 3.57 .57 .55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17
+0-.55 .45 -1 1-1h3.5c.55 0 1 .45 1 1 0 1.25 .2 2.45 .57 3.57 .11 .35 .03 .74-.25
+1.02l-2.2 2.2z" />
+</vector>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/icon_call_button.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/icon_call_button.xml
new file mode 100644
index 0000000..748a1d0
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/icon_call_button.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="rectangle">
+ <solid
+ android:color="#52CCB0" />
+ <corners android:radius="100dp"/>
+ <size
+ android:width="424dp"
+ android:height="120dp"/>
+ </shape>
+ </item>
+ <item android:drawable="@drawable/ic_phone" android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/keypad_default_background.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/keypad_default_background.xml
new file mode 100644
index 0000000..bf49159
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/drawable/keypad_default_background.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <solid
+ android:color="#282A2D" />
+ <size
+ android:width="120dp"
+ android:height="120dp"/>
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml
new file mode 100644
index 0000000..678cf0c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/values/styles.xml
@@ -0,0 +1,37 @@
+<?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">
+ <!-- Phone -->
+ <style name="KeypadButtonStyle">
+ <item name="android:clickable">true</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginRight">16dp</item>
+ <item name="android:layout_marginBottom">16dp</item>
+ <item name="android:layout_marginLeft">16dp</item>
+ <item name="android:background">@drawable/keypad_default_background</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="DialpadPrimaryButton">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">150dp</item>
+ <item name="android:layout_marginBottom">180dp</item>
+ <item name="android:scaleType">center</item>
+ </style>
+</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
new file mode 100644
index 0000000..a404157
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+<overlay>
+ <item target="style/KeypadButtonStyle" value="@style/KeypadButtonStyle"/>
+ <item target="style/DialpadPrimaryButton" value="@style/DialpadPrimaryButton"/>
+
+ <item target="drawable/ic_phone" value="@drawable/ic_phone"/>
+ <item target="drawable/icon_call_button" value="@drawable/icon_call_button"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml
index 0f6711d..efa3b57 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/layout/message_headsup_notification_template.xml
@@ -55,6 +55,8 @@
android:id="@+id/notification_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_touch_target_size"
+ android:gravity="center_vertical"
android:layout_toEndOf="@id/notification_body_icon"
android:layout_alignParentTop="true"
android:layout_toStartOf="@id/message_count"
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
index aaf0d3e..cc5397d 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/dimens.xml
@@ -18,24 +18,26 @@
<resources>
<!-- Horizontal margin for HUNs -->
<dimen name="notification_headsup_card_margin_horizontal">0dp</dimen>
- <dimen name="car_notification_card_inner_top_margin">24dp</dimen>
+ <dimen name="car_notification_card_inner_top_margin">8dp</dimen>
<!-- Card View -->
- <dimen name="card_start_margin">24dp</dimen>
- <dimen name="card_end_margin">24dp</dimen>
- <dimen name="card_body_margin_bottom">36dp</dimen>
- <dimen name="card_body_margin_start">24dp</dimen>
- <dimen name="card_header_margin_bottom">36dp</dimen>
+ <dimen name="card_start_margin">16dp</dimen>
+ <dimen name="card_end_margin">8dp</dimen>
+ <dimen name="card_body_margin_bottom">8dp</dimen>
+ <dimen name="card_body_margin_start">16dp</dimen>
+ <dimen name="card_header_margin_bottom">8dp</dimen>
<dimen name="notification_card_radius">24dp</dimen>
- <dimen name="headsup_notification_bottom_margin">136dp</dimen>
+ <dimen name="headsup_notification_bottom_margin">296dp</dimen>
<!-- Icons -->
- <dimen name="notification_touch_target_size">80dp</dimen>
+ <dimen name="notification_touch_target_size">120dp</dimen>
<!-- Action View -->
<dimen name="action_button_height">88dp</dimen>
<dimen name="action_button_radius">24dp</dimen>
- <dimen name="action_view_left_margin">24dp</dimen>
- <dimen name="action_view_right_margin">24dp</dimen>
+ <dimen name="action_view_left_margin">8dp</dimen>
+ <dimen name="action_view_right_margin">8dp</dimen>
+ <dimen name="card_min_bottom_padding">8dp</dimen>
+ <dimen name="card_min_top_padding">8dp</dimen>
</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
index 40abd04..d588c34 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/values/styles.xml
@@ -24,7 +24,7 @@
</style>
<style name="NotificationBodyContentText">
- <item name="android:textSize">24sp</item>
+ <item name="android:textSize">28sp</item>
<item name="android:ellipsize">end</item>
<item name="android:textColor">@color/secondary_text_color</item>
<item name="android:textAlignment">viewStart</item>
@@ -32,11 +32,10 @@
<style name="NotificationActionViewLayout">
<item name="android:layout_gravity">center</item>
- <item name="android:minHeight">88dp</item>
- <item name="android:layout_marginLeft">24dp</item>
- <item name="android:layout_marginRight">24dp</item>
- <item name="android:layout_marginTop">36dp</item>
- <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:layout_marginLeft">8dp</item>
+ <item name="android:layout_marginRight">8dp</item>
+ <item name="android:layout_marginTop">8dp</item>
+ <item name="android:layout_marginBottom">8dp</item>
</style>
<style name="NotificationActionButtonBase" parent="@android:Widget.DeviceDefault.Button.Borderless.Colored">
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
index 23db804..9e2ca19 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitNotificationRRO/res/xml/overlays.xml
@@ -61,4 +61,7 @@
<item target="style/NotificationBodContentText" value="@style/NotificationBodyContentText" />
<item target="style/NotificationBodyTitleText" value="@style/NotificationBodyTitleText" />
+
+ <item target="dimen/card_min_bottom_padding" value="@dimen/card_min_bottom_padding"/>
+ <item target="dimen/card_min_top_padding" value="@dimen/card_min_top_padding"/>
</overlay>
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/Android.mk b/car_product/car_ui_portrait/rro/alert-dialog-customizations/Android.mk
new file mode 100644
index 0000000..7ae49f5
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/Android.mk
@@ -0,0 +1,57 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+CAR_UI_RRO_SET_NAME := generated_caruiportrait_alertdialog
+CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml
+CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
+CAR_UI_RRO_TARGETS := \
+ com.android.car.ui.paintbooth \
+ com.google.android.car.ui.paintbooth \
+ com.google.android.carui.ats \
+ com.android.car.rotaryplayground \
+ com.android.car.themeplayground \
+ com.android.car.carlauncher \
+ com.android.car.home \
+ com.android.car.media \
+ com.android.car.radio \
+ com.android.car.calendar \
+ com.android.car.companiondevicesupport \
+ com.android.car.systemupdater \
+ com.android.car.dialer \
+ com.android.car.linkviewer \
+ com.android.car.settings \
+ com.android.car.voicecontrol \
+ com.android.car.faceenroll \
+ com.android.car.developeroptions \
+ com.android.managedprovisioning \
+ com.android.settings.intelligence \
+ com.google.android.apps.automotive.inputmethod \
+ com.google.android.apps.automotive.inputmethod.dev \
+ com.google.android.apps.automotive.templates.host \
+ com.google.android.embedded.projection \
+ com.google.android.gms \
+ com.google.android.gsf \
+ com.google.android.packageinstaller \
+ com.google.android.permissioncontroller \
+ com.google.android.carassistant \
+ com.google.android.tts \
+ com.android.htmlviewer \
+ com.android.vending \
+
+include packages/apps/Car/libs/car-ui-lib/generate_rros.mk
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/AndroidManifest.xml b/car_product/car_ui_portrait/rro/alert-dialog-customizations/AndroidManifest.xml
new file mode 100644
index 0000000..9d3a1a4
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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="{{RRO_PACKAGE_NAME}}">
+ <application android:hasCode="false"/>
+ <overlay android:priority="10"
+ android:targetName="car-ui-lib"
+ android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+ android:resourcesMap="@xml/overlays"
+ android:isStatic="true"
+ android:requiredSystemPropertyName="ro.build.characteristics"
+ android:requiredSystemPropertyValue="automotive"/>
+</manifest>
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/product.mk b/car_product/car_ui_portrait/rro/alert-dialog-customizations/product.mk
new file mode 100644
index 0000000..8a3a73c
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/product.mk
@@ -0,0 +1,51 @@
+#
+# 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.
+#
+
+# Inherit from this product to include the "Car Ui Portrait" RROs for CarUi
+# Include generated RROs
+PRODUCT_PACKAGES += \
+ generated_caruiportrait_alertdialog-com-android-car-ui-paintbooth \
+ generated_caruiportrait_alertdialog-com-google-android-car-ui-paintbooth \
+ generated_caruiportrait_alertdialog-com-google-android-carui-ats \
+ generated_caruiportrait_alertdialog-com-android-car-rotaryplayground \
+ generated_caruiportrait_alertdialog-com-android-car-themeplayground \
+ generated_caruiportrait_alertdialog-com-android-car-carlauncher \
+ generated_caruiportrait_alertdialog-com-android-car-home \
+ generated_caruiportrait_alertdialog-com-android-car-media \
+ generated_caruiportrait_alertdialog-com-android-car-radio \
+ generated_caruiportrait_alertdialog-com-android-car-calendar \
+ generated_caruiportrait_alertdialog-com-android-car-companiondevicesupport \
+ generated_caruiportrait_alertdialog-com-android-car-systemupdater \
+ generated_caruiportrait_alertdialog-com-android-car-dialer \
+ generated_caruiportrait_alertdialog-com-android-car-linkviewer \
+ generated_caruiportrait_alertdialog-com-android-car-settings \
+ generated_caruiportrait_alertdialog-com-android-car-voicecontrol \
+ generated_caruiportrait_alertdialog-com-android-car-faceenroll \
+ generated_caruiportrait_alertdialog-com-android-car-developeroptions \
+ generated_caruiportrait_alertdialog-com-android-managedprovisioning \
+ generated_caruiportrait_alertdialog-com-android-settings-intelligence \
+ generated_caruiportrait_alertdialog-com-google-android-apps-automotive-inputmethod \
+ generated_caruiportrait_alertdialog-com-google-android-apps-automotive-inputmethod-dev \
+ generated_caruiportrait_alertdialog-com-google-android-apps-automotive-templates-host \
+ generated_caruiportrait_alertdialog-com-google-android-embedded-projection \
+ generated_caruiportrait_alertdialog-com-google-android-gms \
+ generated_caruiportrait_alertdialog-com-google-android-gsf \
+ generated_caruiportrait_alertdialog-com-google-android-packageinstaller \
+ generated_caruiportrait_alertdialog-com-google-android-permissioncontroller \
+ generated_caruiportrait_alertdialog-com-google-android-carassistant \
+ generated_caruiportrait_alertdialog-com-google-android-tts \
+ generated_caruiportrait_alertdialog-com-android-htmlviewer \
+ generated_caruiportrait_alertdialog-com-android-vending \
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/layout/car_ui_alert_dialog_title_with_subtitle.xml b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
new file mode 100644
index 0000000..faf9eeb
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
@@ -0,0 +1,60 @@
+<?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:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <!-- Leave this view here so that we don't get any null pointer errors in the alert dialog
+ class. -->
+ <ImageView
+ android:id="@+id/car_ui_alert_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="fitCenter"
+ android:visibility="gone"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/alert_dialog_margin"
+ android:layout_marginEnd="@dimen/alert_dialog_margin"
+ android:layout_marginTop="@dimen/alert_dialog_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/car_ui_alert_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textAppearance="@style/TextAppearance_CarUi_AlertDialog_Title" />
+
+ <TextView
+ android:id="@+id/car_ui_alert_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textAppearance="@style/TextAppearance_CarUi_AlertDialog_Subtitle"/>
+ </LinearLayout>
+
+ <View
+ android:id="@+id/empty_space"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"/>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/values/dimens.xml b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/values/dimens.xml
new file mode 100644
index 0000000..a3ce819
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?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>
+ <dimen name="car_ui_body1_size">32sp</dimen>
+ <dimen name="car_ui_body3_size">24sp</dimen>
+
+ <dimen name="alert_dialog_margin">36dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/values/styles.xml b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/values/styles.xml
new file mode 100644
index 0000000..cf229bd
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/values/styles.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">
+
+ <style name="TextAppearance_CarUi_AlertDialog_Title" parent="android:TextAppearance.DeviceDefault">
+ <item name="android:textSize">@dimen/car_ui_body1_size</item>
+ </style>
+ <style name="TextAppearance_CarUi_AlertDialog_Subtitle" parent="android:TextAppearance.DeviceDefault">
+ <item name="android:textSize">@dimen/car_ui_body3_size</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/xml/overlays.xml
new file mode 100644
index 0000000..46bf533
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/alert-dialog-customizations/res/xml/overlays.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.
+ -->
+<overlay>
+ <item target="layout/car_ui_alert_dialog_title_with_subtitle" value="@layout/car_ui_alert_dialog_title_with_subtitle"/>
+
+ <item target="id/car_ui_alert_icon" value="@id/car_ui_alert_icon"/>
+ <item target="id/car_ui_alert_title" value="@id/car_ui_alert_title"/>
+ <item target="id/car_ui_alert_subtitle" value="@id/car_ui_alert_subtitle"/>
+
+ <item target="dimen/car_ui_body1_size" value="@dimen/car_ui_body1_size"/>
+ <item target="dimen/car_ui_body3_size" value="@dimen/car_ui_body3_size"/>
+</overlay>
diff --git a/car_product/car_ui_portrait/rro/android/res/drawable/toast_frame.xml b/car_product/car_ui_portrait/rro/android/res/drawable/toast_frame.xml
new file mode 100644
index 0000000..8b829db
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/drawable/toast_frame.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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#2E3134" />
+ <corners android:radius="@dimen/toast_corner_radius" />
+</shape>
diff --git a/car_product/car_ui_portrait/rro/android/res/layout/transient_notification.xml b/car_product/car_ui_portrait/rro/android/res/layout/transient_notification.xml
new file mode 100644
index 0000000..2eeaa7a
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/android/res/layout/transient_notification.xml
@@ -0,0 +1,40 @@
+<?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="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:maxWidth="@dimen/toast_width"
+ android:background="@drawable/toast_frame"
+ android:elevation="@dimen/toast_elevation"
+ android:paddingEnd="@dimen/toast_margin"
+ android:paddingTop="@dimen/toast_margin"
+ android:paddingBottom="@dimen/toast_margin"
+ android:paddingStart="@dimen/toast_margin"
+ android:layout_marginBottom="@dimen/toast_bottom_margin">
+
+ <TextView
+ android:id="@android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance_Toast"/>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/config.xml b/car_product/car_ui_portrait/rro/android/res/values/config.xml
index 55cac9f..8ab71a7 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/config.xml
@@ -23,4 +23,7 @@
<string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider">
com.android.server.wm.CarDisplayAreaPolicyProvider
</string>
+
+ <!-- Colon separated list of package names that should be granted Notification Listener access -->
+ <string name="config_defaultListenerAccessPackages" translatable="false">com.android.car.notification</string>
</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/dimens.xml b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
index 4d1cd24..de98f91 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
@@ -35,4 +35,18 @@
<dimen name="car_alert_dialog_margin">24dp</dimen>
<!-- Dialog button layout height -->
<dimen name="button_layout_height">88dp</dimen>
+
+ <!-- ****** Toast dimens ***** -->
+
+ <!-- Toast corner radius -->
+ <dimen name="toast_corner_radius">24dp</dimen>
+ <!-- Toast margin -->
+ <dimen name="toast_margin">24dp</dimen>
+ <!-- Toast elevation -->
+ <dimen name="toast_elevation">2dp</dimen>
+ <!-- Toast max width -->
+ <dimen name="toast_width">760dp</dimen>
+ <!-- Toast y offset (should be the same as the height of the audio bar -->
+ <dimen name="toast_y_offset">136dp</dimen>
+ <dimen name="toast_bottom_margin">32dp</dimen>
</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/styles.xml b/car_product/car_ui_portrait/rro/android/res/values/styles.xml
index 32bb407..5149b83 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/styles.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/styles.xml
@@ -23,4 +23,15 @@
<item name="android:textStyle">normal</item>
<item name="android:background">@drawable/car_dialog_button_background</item>
</style>
+
+ <style name="TextAppearance_Toast">
+ <item name="android:textColorHighlight">?android:textColorHighlight</item>
+ <item name="android:textColorHint">?android:textColorHint</item>
+ <item name="android:textColorLink">?android:textColorLink</item>
+ <item name="android:textStyle">normal</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:textSize">28sp</item>
+ <item name="android:lineHeight">36sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/android/res/xml/overlays.xml
index 7466ace..f71b648 100644
--- a/car_product/car_ui_portrait/rro/android/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/android/res/xml/overlays.xml
@@ -15,14 +15,24 @@
-->
<overlay>
<item target="string/config_deviceSpecificDisplayAreaPolicyProvider" value="@string/config_deviceSpecificDisplayAreaPolicyProvider"/>
+ <item target="string/config_defaultListenerAccessPackages" value="@string/config_defaultListenerAccessPackages"/>
<item target="dimen/button_layout_height" value="@dimen/button_layout_height"/>
<item target="dimen/car_card_header_height" value="@dimen/car_card_header_height"/>
<item target="dimen/navigation_bar_height" value="@dimen/navigation_bar_height"/>
<item target="dimen/navigation_bar_height_landscape" value="@dimen/navigation_bar_height_landscape"/>
<item target="dimen/status_bar_height" value="@dimen/status_bar_height"/>
+ <item target="dimen/toast_elevation" value="@dimen/toast_elevation"/>
+ <item target="dimen/toast_width" value="@dimen/toast_width"/>
+ <item target="dimen/toast_y_offset" value="@dimen/toast_y_offset"/>
- <item target="drawable/car_dialog_button_background" value="@drawable/car_dialog_button_background" />
+
+ <item target="drawable/car_dialog_button_background" value="@drawable/car_dialog_button_background"/>
+ <item target="drawable/toast_frame" value="@drawable/toast_frame"/>
+
+ <item target="layout/toast_frame" value="@drawable/toast_frame"/>
<item target="bool/config_automotiveHideNavBarForKeyboard" value="@bool/config_automotiveHideNavBarForKeyboard"/>
+
+ <item target="style/TextAppearance.Toast" value="@style/TextAppearance_Toast"/>
</overlay>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/tab_side_indicator_color.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/tab_side_indicator_color.xml
index 4412c36..0cf2a1a 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/tab_side_indicator_color.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/color/tab_side_indicator_color.xml
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="#4285F4" android:state_activated="true"/>
+ <item android:color="#52CCB0" android:state_activated="true"/>
<item android:color="@android:color/transparent"/>
</selector>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_background.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
new file mode 100644
index 0000000..57ac917
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="40dp"
+ android:height="40dp"/>
+ <solid android:color="@android:color/transparent"/>
+</shape>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/tab_background.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/tab_background.xml
new file mode 100644
index 0000000..0162e5b
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/drawable/tab_background.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true">
+ <shape android:shape="rectangle">
+ <solid android:color="#2E3134"/>
+ </shape>
+ </item>
+ <item android:state_activated="false">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/transparent"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_toolbar_tab_item.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_toolbar_tab_item.xml
index 7386e2c..54f7241 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_toolbar_tab_item.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/layout/car_ui_toolbar_tab_item.xml
@@ -19,10 +19,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="260dp"
android:layout_height="96dp"
- android:background="?android:attr/selectableItemBackground">
+ android:background="@drawable/tab_background">
<View
- android:layout_width="6dp"
+ android:layout_width="8dp"
android:layout_height="match_parent"
android:background="@color/tab_side_indicator_color"
app:layout_constraintBottom_toBottomOf="parent"
@@ -53,12 +53,4 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="2dp"
- android:background="#F1F3F4"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
index 9df6001..204321c 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
@@ -42,6 +42,7 @@
<item target="drawable/car_ui_recyclerview_ic_down" value="@drawable/car_ui_recyclerview_ic_down" />
<item target="drawable/car_ui_recyclerview_scrollbar_thumb" value="@drawable/car_ui_recyclerview_scrollbar_thumb" />
<item target="drawable/car_ui_activity_background" value="@drawable/car_ui_activity_background" />
+ <item target="drawable/car_ui_toolbar_menu_item_icon_background" value="@drawable/car_ui_toolbar_menu_item_icon_background" />
<item target="attr/state_ux_restricted" value="@attr/state_ux_restricted"/>
<item target="attr/layout_constraintGuide_begin" value="@attr/layout_constraintGuide_begin"/>
diff --git a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
index 7fc8ee6..8eb667c 100644
--- a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
+++ b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
@@ -14,12 +14,18 @@
# limitations under the License.
#
+$(call inherit-product, packages/services/Car/car_product/car_ui_portrait/rro/alert-dialog-customizations/product.mk)
$(call inherit-product, packages/services/Car/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk)
# All RROs to be included in car_ui_portrait builds.
PRODUCT_PACKAGES += \
- CarUiPortraitSettingsProviderRRO \
+ CarUiPortraitDialerRRO \
CarUiPortraitMediaRRO \
CarUiPortraitLauncherRRO \
CarUiPortraitNotificationRRO \
CarUiPortraitFrameworkResRRO \
+
+ifneq ($(INCLUDE_SEAHAWK_ONLY_RROS),)
+PRODUCT_PACKAGES += \
+ CarUiPortraitSettingsProviderRRO
+endif
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index d7c566c..a1b0cd2 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -151,4 +151,9 @@
<!-- Whether this device is supporting the microphone toggle -->
<bool name="config_supportsMicToggle">true</bool>
+ <!-- Whether the airplane mode should be reset when device boots in non-safemode after exiting
+ from safemode.
+ This flag should be enabled only when the product does not have any UI to toggle airplane
+ mode like automotive devices.-->
+ <bool name="config_autoResetAirplaneMode">true</bool>
</resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml b/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml
index 9ab0ed3..27986a4 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/policy_exempt_apps.xml
@@ -20,7 +20,6 @@
device policies or APIs.
-->
<string-array translatable="false" name="policy_exempt_apps">
- <item>com.android.car.carlauncher</item>
<item>com.android.car.cluster.home</item>
<item>com.android.car.hvac</item>
<item>com.android.car.media</item>
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.cpp b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
index 96dd6b3..2ff118a 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.cpp
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
@@ -83,12 +83,6 @@
return output;
}
-bool isZeroValueThresholds(const PerStateIoOveruseThreshold& thresholds) {
- return thresholds.perStateWriteBytes.foregroundBytes == 0 &&
- thresholds.perStateWriteBytes.backgroundBytes == 0 &&
- thresholds.perStateWriteBytes.garageModeBytes == 0;
-}
-
std::string toString(const PerStateIoOveruseThreshold& thresholds) {
return StringPrintf("name=%s, foregroundBytes=%" PRId64 ", backgroundBytes=%" PRId64
", garageModeBytes=%" PRId64,
@@ -102,23 +96,20 @@
return Error() << "Doesn't contain threshold name";
}
- if (isZeroValueThresholds(thresholds)) {
- return Error() << "Zero value thresholds for " << thresholds.name;
- }
-
- if (thresholds.perStateWriteBytes.foregroundBytes == 0 ||
- thresholds.perStateWriteBytes.backgroundBytes == 0 ||
- thresholds.perStateWriteBytes.garageModeBytes == 0) {
- return Error() << "Some thresholds are zero: " << toString(thresholds);
+ if (thresholds.perStateWriteBytes.foregroundBytes <= 0 ||
+ thresholds.perStateWriteBytes.backgroundBytes <= 0 ||
+ thresholds.perStateWriteBytes.garageModeBytes <= 0) {
+ return Error() << "Some thresholds are less than or equal to zero: "
+ << toString(thresholds);
}
return {};
}
Result<void> containsValidThreshold(const IoOveruseAlertThreshold& threshold) {
- if (threshold.durationInSeconds == 0) {
+ if (threshold.durationInSeconds <= 0) {
return Error() << "Duration must be greater than zero";
}
- if (threshold.writtenBytesPerSecond == 0) {
+ if (threshold.writtenBytesPerSecond <= 0) {
return Error() << "Written bytes/second must be greater than zero";
}
return {};
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index fabe8e2..3db6a93 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -939,6 +939,18 @@
<service android:name=".telemetry.ScriptExecutor"
android:exported="false"
android:isolatedProcess="true"/>
+ <service
+ android:name="com.android.car.pm.CarSafetyAccessibilityService"
+ android:singleUser="true"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ </intent-filter>
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/car_safety_accessibility_service_config" />
+ </service>
<activity android:name="com.android.car.pm.ActivityBlockingActivity"
android:documentLaunchMode="always"
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 6703b61..8146cc2 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -17,8 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="car_permission_label" msgid="2215078736675564541">"કારની માહિતી"</string>
- <!-- no translation found for car_permission_desc (3584369074931334964) -->
- <skip />
+ <string name="car_permission_desc" msgid="3584369074931334964">"તમારી કારની માહિતીને ઍક્સેસ કરો"</string>
<string name="car_permission_label_camera" msgid="3725702064841827180">"કારનો કૅમેરા ઍક્સેસ કરો"</string>
<string name="car_permission_desc_camera" msgid="917024932164501426">"તમારી કારના કૅમેરાને ઍક્સેસ કરવાની મંજૂરી આપો."</string>
<string name="car_permission_label_energy" msgid="7409144323527821558">"કારની ઊર્જાની માહિતીને ઍક્સેસ કરો"</string>
diff --git a/service/res/values-sq/strings.xml b/service/res/values-sq/strings.xml
index 632f0f3..01bc06e 100644
--- a/service/res/values-sq/strings.xml
+++ b/service/res/values-sq/strings.xml
@@ -62,10 +62,8 @@
<string name="car_permission_desc_bind_instrument_cluster_rendering" msgid="5073596870485006783">"Merr të dhënat e grupimit të instrumenteve"</string>
<string name="car_permission_label_car_ux_restrictions_configuration" msgid="6801393970411049725">"Konfigurimi i kufizimeve për eksperiencën e përdoruesit"</string>
<string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"Konfiguro kufizimet për eksperiencën e përdoruesit"</string>
- <!-- no translation found for car_permission_label_access_private_display_id (6712116114341634316) -->
- <skip />
- <!-- no translation found for car_permission_desc_access_private_display_id (8535974477610944721) -->
- <skip />
+ <string name="car_permission_label_access_private_display_id" msgid="6712116114341634316">"Qasja për lexim për ID-në e ekranit privat"</string>
+ <string name="car_permission_desc_access_private_display_id" msgid="8535974477610944721">"Lejon qasjen për lexim për ID-në e ekranit privat"</string>
<string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"Komuniko me pajisjen USB në modalitetin AOAP"</string>
<string name="car_permission_desc_car_handle_usb_aoap_device" msgid="273505990971317034">"Lejon që një aplikacion të komunikojë me një pajisje në modalitetin AOAP"</string>
<string name="car_permission_label_read_car_occupant_awareness_state" msgid="125517953575032758">"Qasja për leximin e Occupant Awareness System"</string>
diff --git a/service/res/values-te/strings.xml b/service/res/values-te/strings.xml
index 893d756..4e23131 100644
--- a/service/res/values-te/strings.xml
+++ b/service/res/values-te/strings.xml
@@ -61,7 +61,7 @@
<string name="car_permission_label_bind_instrument_cluster_rendering" msgid="8627480897198377418">"పరికర గుంపు రెండరింగ్"</string>
<string name="car_permission_desc_bind_instrument_cluster_rendering" msgid="5073596870485006783">"పరికర గుంపు డేటాని పొందండి"</string>
<string name="car_permission_label_car_ux_restrictions_configuration" msgid="6801393970411049725">"UX పరిమితుల కాన్ఫిగరేషన్"</string>
- <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX పరిమితులను కాన్ఫిగర్ చెయ్యండి"</string>
+ <string name="car_permission_desc_car_ux_restrictions_configuration" msgid="5711926927484813777">"UX పరిమితులను కాన్ఫిగర్ చేయండి"</string>
<string name="car_permission_label_access_private_display_id" msgid="6712116114341634316">"ప్రైవేట్ డిస్ప్లే idకి చదివే యాక్సెస్"</string>
<string name="car_permission_desc_access_private_display_id" msgid="8535974477610944721">"ప్రైవేట్ డిస్ప్లే idకి చదివే యాక్సెస్ను అనుమతిస్తుంది"</string>
<string name="car_permission_label_car_handle_usb_aoap_device" msgid="72783989504378036">"AOAP మోడ్లో USB పరికరాన్ని కమ్యూనికేట్ చేయండి"</string>
@@ -84,9 +84,9 @@
<string name="car_permission_label_diag_clear" msgid="4783070510879698157">"సమస్య విశ్లేషణ డేటాను క్లియర్ చేయగలవు"</string>
<string name="car_permission_desc_diag_clear" msgid="7453222114866042786">"కారు నుండి సమస్య విశ్లేషణ డేటాను క్లియర్ చేయగలవు."</string>
<string name="car_permission_label_vms_publisher" msgid="3049934078926106641">"VMS ప్రచురణకర్త"</string>
- <string name="car_permission_desc_vms_publisher" msgid="5589489298597386828">"VMS సందేశాలను ప్రచురించండి"</string>
+ <string name="car_permission_desc_vms_publisher" msgid="5589489298597386828">"VMS మెసేజ్లను ప్రచురించండి"</string>
<string name="car_permission_label_vms_subscriber" msgid="5648841182059222299">"VMS సభ్యులు"</string>
- <string name="car_permission_desc_vms_subscriber" msgid="7551009457847673620">"VMS సందేశాలను పొందడానికి సభ్యత్వం తీసుకోండి"</string>
+ <string name="car_permission_desc_vms_subscriber" msgid="7551009457847673620">"VMS మెసేజ్లను పొందడానికి సభ్యత్వం తీసుకోండి"</string>
<string name="car_permission_label_bind_vms_client" msgid="4889732900973280313">"VMS క్లయింట్ సేవ"</string>
<string name="car_permission_desc_bind_vms_client" msgid="4062835325264330564">"VMS క్లయింట్లను ఆచరించండి"</string>
<string name="car_permission_label_storage_monitoring" msgid="2327639346522530549">"ఫ్లాష్ నిల్వ పర్యవేక్షణ"</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index f0c611c..ae487da 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -413,4 +413,14 @@
<!-- A configuration flag to adjust Wifi for suspend. -->
<bool name="config_wifiAdjustmentForSuspend">false</bool>
+ <!-- A configuration flag to prevent the templated apps from showing dialogs. This is done in
+ the view of driver safety as templated apps can potentially show a dialog with custom UI
+ which can be a distraction hazard for the driver. -->
+ <bool name="config_preventTemplatedAppsFromShowingDialog">true</bool>
+
+ <!-- The class name of the templated activities. This is used to detect currently running
+ templated activity.-->
+ <string name="config_template_activity_class_name">
+ androidx.car.app.activity.CarAppActivity
+ </string>
</resources>
diff --git a/service/res/xml/car_safety_accessibility_service_config.xml b/service/res/xml/car_safety_accessibility_service_config.xml
new file mode 100644
index 0000000..9029ec2
--- /dev/null
+++ b/service/res/xml/car_safety_accessibility_service_config.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeWindowStateChanged"
+ android:accessibilityFlags="flagDefault"
+ android:accessibilityFeedbackType="feedbackAllMask"/>
\ No newline at end of file
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index 477529d..6b1c7b4 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -15,11 +15,14 @@
*/
package com.android.car;
+import android.car.Car;
import android.car.CarAppFocusManager;
import android.car.IAppFocus;
import android.car.IAppFocusListener;
import android.car.IAppFocusOwnershipCallback;
import android.content.Context;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -36,6 +39,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -77,9 +81,11 @@
getClass().getSimpleName());
private final DispatchHandler mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper(),
this);
+ private final Context mContext;
public AppFocusService(Context context,
SystemActivityMonitoringService systemActivityMonitoringService) {
+ mContext = context;
mSystemActivityMonitoringService = systemActivityMonitoringService;
mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
mAllOwnershipClients = new OwnershipClientHolder(this);
@@ -121,6 +127,22 @@
}
@Override
+ public List<String> getAppTypeOwner(@CarAppFocusManager.AppFocusType int appType) {
+ OwnershipClientInfo owner;
+ synchronized (mLock) {
+ owner = mFocusOwners.get(appType);
+ }
+ if (owner == null) {
+ return null;
+ }
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(owner.getUid());
+ if (packageNames == null) {
+ return null;
+ }
+ return Arrays.asList(packageNames);
+ }
+
+ @Override
public boolean isOwningFocus(IAppFocusOwnershipCallback callback, int appType) {
OwnershipClientInfo info;
synchronized (mLock) {
@@ -146,10 +168,10 @@
if (!alreadyOwnedAppTypes.contains(appType)) {
OwnershipClientInfo ownerInfo = mFocusOwners.get(appType);
if (ownerInfo != null && ownerInfo != info) {
- if (mSystemActivityMonitoringService.isInForeground(
- ownerInfo.getPid(), ownerInfo.getUid())
- && !mSystemActivityMonitoringService.isInForeground(
- info.getPid(), info.getUid())) {
+ // Allow receiving focus if the requester has a foreground activity OR if the
+ // requester is privileged service.
+ if (isInForeground(ownerInfo) && !isInForeground(info)
+ && !hasPrivilegedPermission()) {
Slog.w(CarLog.TAG_APP_FOCUS, "Focus request failed for non-foreground app("
+ "pid=" + info.getPid() + ", uid=" + info.getUid() + ")."
+ "Foreground app (pid=" + ownerInfo.getPid() + ", uid="
@@ -190,6 +212,15 @@
return CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED;
}
+ private boolean isInForeground(OwnershipClientInfo info) {
+ return mSystemActivityMonitoringService.isInForeground(info.getPid(), info.getUid());
+ }
+
+ private boolean hasPrivilegedPermission() {
+ return mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER)
+ == PermissionChecker.PERMISSION_GRANTED;
+ }
+
@Override
public void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) {
synchronized (mLock) {
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 5870b32..d4d8e35 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -60,6 +60,7 @@
import com.android.car.hal.InputHalService;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.common.UserHelperLite;
+import com.android.car.pm.CarSafetyAccessibilityService;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -69,6 +70,7 @@
import com.android.server.utils.Slogf;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
@@ -81,7 +83,8 @@
*/
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
-
+ public static final String ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ":";
+ private static final int MAX_RETRIES_FOR_ENABLING_ACCESSIBILITY_SERVICES = 5;
private static final String TAG = CarLog.TAG_INPUT;
/** An interface to receive {@link KeyEvent}s as they occur. */
@@ -232,7 +235,7 @@
private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
Slogf.d(TAG, "CarInputService.onEvent(%s)", event);
if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
- updateRotaryServiceSettings(event.getUserId());
+ updateCarAccessibilityServicesSettings(event.getUserId());
}
};
@@ -327,9 +330,7 @@
mBluetoothProfileServiceListener, BluetoothProfile.HEADSET_CLIENT);
});
}
- if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
- mUserService.addUserLifecycleListener(mUserLifecycleListener);
- }
+ mUserService.addUserLifecycleListener(mUserLifecycleListener);
}
@Override
@@ -344,9 +345,7 @@
mBluetoothHeadsetClient = null;
}
}
- if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
- mUserService.removeUserLifecycleListener(mUserLifecycleListener);
- }
+ mUserService.removeUserLifecycleListener(mUserLifecycleListener);
}
@Override
@@ -691,6 +690,26 @@
return true;
}
+ private List<String> getAccessibilityServicesToBeEnabled() {
+ String carSafetyAccessibilityServiceComponentName = mContext.getPackageName()
+ + "/"
+ + CarSafetyAccessibilityService.class.getName();
+ ArrayList<String> accessibilityServicesToBeEnabled = new ArrayList<>();
+ accessibilityServicesToBeEnabled.add(carSafetyAccessibilityServiceComponentName);
+ if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+ accessibilityServicesToBeEnabled.add(mRotaryServiceComponentName);
+ }
+ return accessibilityServicesToBeEnabled;
+ }
+
+ private static List<String> createServiceListFromSettingsString(
+ String accessibilityServicesString) {
+ return TextUtils.isEmpty(accessibilityServicesString)
+ ? new ArrayList<>()
+ : Arrays.asList(accessibilityServicesString.split(
+ ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR));
+ }
+
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(IndentingPrintWriter writer) {
@@ -701,15 +720,44 @@
mCaptureController.dump(writer);
}
- private void updateRotaryServiceSettings(@UserIdInt int userId) {
+ private void updateCarAccessibilityServicesSettings(@UserIdInt int userId) {
if (UserHelperLite.isHeadlessSystemUser(userId)) {
return;
}
+ List<String> accessibilityServicesToBeEnabled = getAccessibilityServicesToBeEnabled();
ContentResolver contentResolver = mContext.getContentResolver();
- Settings.Secure.putStringForUser(contentResolver,
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- mRotaryServiceComponentName,
- userId);
+ List<String> alreadyEnabledServices = createServiceListFromSettingsString(
+ Settings.Secure.getStringForUser(contentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId));
+
+ int retry = 0;
+ while (!alreadyEnabledServices.containsAll(accessibilityServicesToBeEnabled)
+ && retry <= MAX_RETRIES_FOR_ENABLING_ACCESSIBILITY_SERVICES) {
+ ArrayList<String> enabledServicesList = new ArrayList<>(alreadyEnabledServices);
+ int numAccessibilityServicesToBeEnabled = accessibilityServicesToBeEnabled.size();
+ for (int i = 0; i < numAccessibilityServicesToBeEnabled; i++) {
+ String serviceToBeEnabled = accessibilityServicesToBeEnabled.get(i);
+ if (!enabledServicesList.contains(serviceToBeEnabled)) {
+ enabledServicesList.add(serviceToBeEnabled);
+ }
+ }
+ Settings.Secure.putStringForUser(contentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ String.join(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR, enabledServicesList),
+ userId);
+ // Read again to account for any race condition with other parts of the code that might
+ // be enabling other accessibility services.
+ alreadyEnabledServices = createServiceListFromSettingsString(
+ Settings.Secure.getStringForUser(contentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId));
+ retry++;
+ }
+ if (!alreadyEnabledServices.containsAll(accessibilityServicesToBeEnabled)) {
+ Slogf.e(TAG, "Failed to enable accessibility services");
+ }
+
Settings.Secure.putStringForUser(contentResolver,
Settings.Secure.ACCESSIBILITY_ENABLED,
"1",
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 576aad2..2e16d10 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -172,6 +172,10 @@
private static final String COMMAND_APPLY_POWER_POLICY = "apply-power-policy";
private static final String COMMAND_DEFINE_POWER_POLICY_GROUP = "define-power-policy-group";
private static final String COMMAND_SET_POWER_POLICY_GROUP = "set-power-policy-group";
+ private static final String COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY =
+ "apply-cts-verifier-power-off-policy";
+ private static final String COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY =
+ "apply-cts-verifier-power-on-policy";
private static final String COMMAND_POWER_OFF = "power-off";
private static final String POWER_OFF_SKIP_GARAGEMODE = "--skip-garagemode";
private static final String POWER_OFF_SHUTDOWN = "--shutdown";
@@ -252,6 +256,10 @@
android.Manifest.permission.DEVICE_POWER);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_POWER_POLICY_GROUP,
android.Manifest.permission.DEVICE_POWER);
+ USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY,
+ android.Manifest.permission.DEVICE_POWER);
+ USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY,
+ android.Manifest.permission.DEVICE_POWER);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SILENT_MODE,
PERMISSION_CAR_POWER);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_INITIAL_USER,
@@ -599,6 +607,14 @@
pw.println("\t Sets power policy group which is defined in /vendor/etc/power_policy.xml ");
pw.printf("\t or by %s command\n", COMMAND_DEFINE_POWER_POLICY_GROUP);
+ pw.printf("\t%s\n", COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY);
+ pw.println("\t Define and apply the cts_verifier_off power policy with "
+ + "--disable WIFI,LOCATION,BLUETOOTH");
+
+ pw.printf("\t%s\n", COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY);
+ pw.println("\t Define and apply the cts_verifier_on power policy with "
+ + "--enable WIFI,LOCATION,BLUETOOTH");
+
pw.printf("\t%s [%s] [%s]\n", COMMAND_POWER_OFF, POWER_OFF_SKIP_GARAGEMODE,
POWER_OFF_SHUTDOWN);
pw.println("\t Powers off the car.");
@@ -618,7 +634,7 @@
pw.printf("\t%s\n", COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES);
pw.println("\t Gets third-party apps foreground I/O overuse threshold");
- pw.printf("\t%s true|false\n", COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK);
+ pw.printf("\t%s enable|disable\n", COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK);
pw.println("\t Enables/disables car watchdog process health check.");
pw.println("\t Set to true to disable the process health check.");
}
@@ -934,6 +950,10 @@
return definePowerPolicyGroup(args, writer);
case COMMAND_SET_POWER_POLICY_GROUP:
return setPowerPolicyGroup(args, writer);
+ case COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY:
+ return applyCtsVerifierPowerOffPolicy(args, writer);
+ case COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY:
+ return applyCtsVerifierPowerOnPolicy(args, writer);
case COMMAND_POWER_OFF:
powerOff(args, writer);
break;
@@ -1995,6 +2015,29 @@
return RESULT_ERROR;
}
+ private int applyCtsVerifierPowerPolicy(String policyId, String ops, String cmdName,
+ IndentingPrintWriter writer) {
+ String[] defArgs = {"define-power-policy", policyId, ops, "WIFI,BLUETOOTH,LOCATION"};
+ mCarPowerManagementService.definePowerPolicyFromCommand(defArgs, writer);
+
+ String[] appArgs = {"apply-power-policy", policyId};
+ boolean result = mCarPowerManagementService.applyPowerPolicyFromCommand(appArgs, writer);
+ if (result) return RESULT_OK;
+
+ writer.printf("\nUsage: cmd car_service %s\n", cmdName);
+ return RESULT_ERROR;
+ }
+
+ private int applyCtsVerifierPowerOffPolicy(String[] unusedArgs, IndentingPrintWriter writer) {
+ return applyCtsVerifierPowerPolicy("cts_verifier_off", "--disable",
+ COMMAND_APPLY_CTS_VERIFIER_POWER_OFF_POLICY, writer);
+ }
+
+ private int applyCtsVerifierPowerOnPolicy(String[] unusedArgs, IndentingPrintWriter writer) {
+ return applyCtsVerifierPowerPolicy("cts_verifier_on", "--enable",
+ COMMAND_APPLY_CTS_VERIFIER_POWER_ON_POLICY, writer);
+ }
+
private void powerOff(String[] args, IndentingPrintWriter writer) {
int index = 1;
boolean skipGarageMode = false;
@@ -2216,12 +2259,12 @@
showInvalidArguments(writer);
return;
}
- boolean newState = Boolean.parseBoolean(args[1]);
- if (!newState && !args[1].equalsIgnoreCase("false")) {
- writer.println("Failed to parse argument. Valid arguments: true | false");
+ if (!args[1].equals("enable") && !args[1].equals("disable")) {
+ writer.println("Failed to parse argument. Valid arguments: enable | disable");
return;
}
- mCarWatchdogService.controlProcessHealthCheck(newState);
+ mCarWatchdogService.controlProcessHealthCheck(args[1].equals("disable"));
+ writer.printf("Watchdog health checking is now %sd \n", args[1]);
}
// Check if the given property is global
diff --git a/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java b/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
index 6fccbf9..14b1a6f 100644
--- a/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
+++ b/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
@@ -31,6 +31,8 @@
import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
+
import java.util.Objects;
@@ -67,25 +69,32 @@
int zoneId = PRIMARY_AUDIO_ZONE;
int groupId = mCarAudioService.getVolumeGroupIdForAudioContext(zoneId, suggestedContext);
+ boolean isMuted = isMuted(zoneId, groupId);
- if (Log.isLoggable(TAG_AUDIO, VERBOSE)) {
- Slog.v(TAG_AUDIO, "onVolumeAdjustment: "
+ if (Slogf.isLoggable(TAG_AUDIO, VERBOSE)) {
+ Slogf.v(TAG_AUDIO, "onVolumeAdjustment: "
+ AudioManager.adjustToString(adjustment) + " suggested audio context: "
+ CarAudioContext.toString(suggestedContext) + " suggested volume group: "
- + groupId);
+ + groupId + " is muted " + isMuted);
}
final int currentVolume = mCarAudioService.getGroupVolume(zoneId, groupId);
final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
+ int minGain = mCarAudioService.getGroupMinVolume(zoneId, groupId);
switch (adjustment) {
case AudioManager.ADJUST_LOWER:
- int minValue = Math.max(currentVolume - 1,
- mCarAudioService.getGroupMinVolume(zoneId, groupId));
+ int minValue = Math.max(currentVolume - 1, minGain);
+ if (isMuted) {
+ minValue = minGain;
+ }
mCarAudioService.setGroupVolume(zoneId, groupId, minValue, flags);
break;
case AudioManager.ADJUST_RAISE:
int maxValue = Math.min(currentVolume + 1,
mCarAudioService.getGroupMaxVolume(zoneId, groupId));
+ if (isMuted) {
+ maxValue = minGain;
+ }
mCarAudioService.setGroupVolume(zoneId, groupId, maxValue, flags);
break;
case AudioManager.ADJUST_MUTE:
@@ -93,7 +102,7 @@
setMute(adjustment == AudioManager.ADJUST_MUTE, groupId, flags);
break;
case AudioManager.ADJUST_TOGGLE_MUTE:
- toggleMute(groupId, flags);
+ setMute(!isMuted, groupId, flags);
break;
case AudioManager.ADJUST_SAME:
default:
@@ -101,13 +110,11 @@
}
}
- private void toggleMute(int groupId, int flags) {
+ private boolean isMuted(int zoneId, int groupId) {
if (mUseCarVolumeGroupMuting) {
- setMute(!mCarAudioService.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId), groupId,
- flags);
- return;
+ return mCarAudioService.isVolumeGroupMuted(zoneId, groupId);
}
- setMute(!mAudioManager.isMasterMute(), groupId, flags);
+ return mAudioManager.isMasterMute();
}
private void setMute(boolean mute, int groupId, int flags) {
diff --git a/service/src/com/android/car/audio/CarDuckingUtils.java b/service/src/com/android/car/audio/CarDuckingUtils.java
index f386d62..e0998fc 100644
--- a/service/src/com/android/car/audio/CarDuckingUtils.java
+++ b/service/src/com/android/car/audio/CarDuckingUtils.java
@@ -126,6 +126,7 @@
static List<String> getAddressesToDuck(int[] usages, CarAudioZone zone) {
Set<Integer> uniqueContexts = CarAudioContext.getUniqueContextsForUsages(usages);
+ uniqueContexts.remove(INVALID);
Set<Integer> contextsToDuck = getContextsToDuck(uniqueContexts);
Set<String> addressesToDuck = getAddressesForContexts(contextsToDuck, zone);
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 9c40725..8e98fea 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -150,30 +150,43 @@
int getCurrentGainIndex() {
synchronized (mLock) {
- return mCurrentGainIndex;
+ if (mIsMuted) {
+ return getIndexForGain(mMinGain);
+ }
+ return getCurrentGainIndexLocked();
}
}
+ private int getCurrentGainIndexLocked() {
+ return mCurrentGainIndex;
+ }
+
/**
* Sets the gain on this group, gain will be set on all devices within volume group.
*/
void setCurrentGainIndex(int gainIndex) {
- int gainInMillibels = getGainForIndex(gainIndex);
Preconditions.checkArgument(isValidGainIndex(gainIndex),
- "Gain out of range (%d:%d) %d index %d", mMinGain, mMaxGain,
- gainInMillibels, gainIndex);
+ "Gain out of range (%d:%d) index %d", mMinGain, mMaxGain, gainIndex);
synchronized (mLock) {
- for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
- CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
- info.setCurrentGain(gainInMillibels);
+ if (mIsMuted) {
+ setMuteLocked(false);
}
-
- mCurrentGainIndex = gainIndex;
-
- storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
+ setCurrentGainIndexLocked(gainIndex);
}
}
+ private void setCurrentGainIndexLocked(int gainIndex) {
+ int gainInMillibels = getGainForIndex(gainIndex);
+ for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
+ CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
+ info.setCurrentGain(gainInMillibels);
+ }
+
+ mCurrentGainIndex = gainIndex;
+
+ storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
+ }
+
@Nullable
AudioDevicePort getAudioDevicePortForContext(int carAudioContext) {
final String address = mContextToAddress.get(carAudioContext);
@@ -233,18 +246,22 @@
updateUserIdLocked(userId);
//Update the current gain index
updateCurrentGainIndexLocked();
+ setCurrentGainIndexLocked(getCurrentGainIndexLocked());
//Reset devices with current gain index
updateGroupMuteLocked();
}
- setCurrentGainIndex(getCurrentGainIndex());
}
void setMute(boolean mute) {
synchronized (mLock) {
- mIsMuted = mute;
- if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
- mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute);
- }
+ setMuteLocked(mute);
+ }
+ }
+
+ void setMuteLocked(boolean mute) {
+ mIsMuted = mute;
+ if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
+ mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute);
}
}
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 8c8a105..10288b1 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -50,10 +50,15 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
@@ -73,9 +78,13 @@
import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
import com.google.android.collect.Sets;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -96,12 +105,14 @@
private static final String PACKAGE_DELIMITER = ",";
private static final String PACKAGE_ACTIVITY_DELIMITER = "/";
private static final int LOG_SIZE = 20;
+ private static final String[] WINDOW_DUMP_ARGUMENTS = new String[]{"windows"};
private final Context mContext;
private final SystemActivityMonitoringService mSystemActivityMonitoringService;
private final PackageManager mPackageManager;
private final ActivityManager mActivityManager;
private final DisplayManager mDisplayManager;
+ private final IBinder mWindowManagerBinder;
private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
@@ -122,6 +133,9 @@
private final List<String> mAllowedAppInstallSources;
+ @GuardedBy("mLock")
+ private final SparseArray<ComponentName> mTopActivityWithDialogPerDisplay = new SparseArray<>();
+
/**
* Hold policy set from policy service or client.
* Key: packageName of policy service
@@ -142,6 +156,8 @@
private final CarUxRestrictionsManagerService mCarUxRestrictionsService;
private boolean mEnableActivityBlocking;
private final ComponentName mActivityBlockingActivity;
+ private final boolean mPreventTemplatedAppsFromShowingDialog;
+ private final String mTemplateActivityClassName;
private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener();
@@ -204,6 +220,7 @@
mPackageManager = mContext.getPackageManager();
mActivityManager = mContext.getSystemService(ActivityManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mWindowManagerBinder = ServiceManager.getService(Context.WINDOW_SERVICE);
Resources res = context.getResources();
mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety);
String blockingActivity = res.getString(R.string.activityBlockingActivity);
@@ -212,6 +229,9 @@
res.getStringArray(R.array.allowedAppInstallSources));
mVendorServiceController = new VendorServiceController(
mContext, mHandler.getLooper());
+ mPreventTemplatedAppsFromShowingDialog =
+ res.getBoolean(R.bool.config_preventTemplatedAppsFromShowingDialog);
+ mTemplateActivityClassName = res.getString(R.string.config_template_activity_class_name);
}
@@ -282,6 +302,15 @@
Slog.d(TAG, "isActivityDistractionOptimized" + dumpPoliciesLocked(false));
}
+ for (int i = mTopActivityWithDialogPerDisplay.size() - 1; i >= 0; i--) {
+ ComponentName activityWithDialog = mTopActivityWithDialogPerDisplay.get(
+ mTopActivityWithDialogPerDisplay.keyAt(i));
+ if (activityWithDialog.getClassName().equals(className)
+ && activityWithDialog.getPackageName().equals(packageName)) {
+ return false;
+ }
+ }
+
if (searchFromClientPolicyBlocklistsLocked(packageName)) {
return false;
}
@@ -997,6 +1026,9 @@
synchronized (mLock) {
writer.println("*CarPackageManagerService*");
writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
+ writer.println("mPreventTemplatedAppsFromShowingDialog"
+ + mPreventTemplatedAppsFromShowingDialog);
+ writer.println("mTemplateActivityClassName" + mTemplateActivityClassName);
List<String> restrictions = new ArrayList<>(mUxRestrictionsListeners.size());
for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
int displayId = mUxRestrictionsListeners.keyAt(i);
@@ -1102,6 +1134,14 @@
}
private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) {
+ synchronized (mLock) {
+ if (mTopActivityWithDialogPerDisplay.contains(topTask.displayId)
+ && !topTask.topActivity.equals(
+ mTopActivityWithDialogPerDisplay.get(topTask.displayId))) {
+ // Clear top activity-with-dialog if the activity has changed on this display.
+ mTopActivityWithDialogPerDisplay.remove(topTask.displayId);
+ }
+ }
if (isUxRestrictedOnDisplay(topTask.displayId)) {
doBlockTopActivityIfNotAllowed(topTask);
}
@@ -1111,10 +1151,7 @@
if (topTask.topActivity == null) {
return;
}
-
- boolean allowed = isActivityDistractionOptimized(
- topTask.topActivity.getPackageName(),
- topTask.topActivity.getClassName());
+ boolean allowed = isActivityAllowed(topTask);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Slog.d(TAG, "new activity:" + topTask.toString() + " allowed:" + allowed);
}
@@ -1149,9 +1186,10 @@
boolean isRootDO = false;
if (taskRootActivity != null) {
- ComponentName componentName = ComponentName.unflattenFromString(taskRootActivity);
+ ComponentName taskRootComponentName =
+ ComponentName.unflattenFromString(taskRootActivity);
isRootDO = isActivityDistractionOptimized(
- componentName.getPackageName(), componentName.getClassName());
+ taskRootComponentName.getPackageName(), taskRootComponentName.getClassName());
}
Intent newActivityIntent = createBlockingActivityIntent(
@@ -1168,6 +1206,82 @@
mSystemActivityMonitoringService.blockActivity(topTask, newActivityIntent);
}
+ private boolean isActivityAllowed(TopTaskInfoContainer topTaskInfoContainer) {
+ ComponentName activityName = topTaskInfoContainer.topActivity;
+ boolean isDistractionOptimized = isActivityDistractionOptimized(
+ activityName.getPackageName(),
+ activityName.getClassName());
+ if (!isDistractionOptimized) {
+ return false;
+ }
+ return !(mPreventTemplatedAppsFromShowingDialog
+ && isTemplateActivity(activityName)
+ && isActivityShowingADialogOnDisplay(activityName, topTaskInfoContainer.displayId));
+ }
+
+ private boolean isTemplateActivity(ComponentName activityName) {
+ // TODO(b/191263486): Finalise on how to detect the templated activities.
+ return activityName.getClassName().equals(mTemplateActivityClassName);
+ }
+
+ private boolean isActivityShowingADialogOnDisplay(ComponentName activityName, int displayId) {
+ String output = dumpWindows();
+ List<WindowDumpParser.Window> appWindows =
+ WindowDumpParser.getParsedAppWindows(output, activityName.getPackageName());
+ // TODO(b/192354699): Handle case where an activity can have multiple instances on the same
+ // display.
+ int totalAppWindows = appWindows.size();
+ String firstActivityRecord = null;
+ int numTopActivityAppWindowsOnDisplay = 0;
+ for (int i = 0; i < totalAppWindows; i++) {
+ WindowDumpParser.Window appWindow = appWindows.get(i);
+ if (appWindow.getDisplayId() != displayId) {
+ continue;
+ }
+ if (TextUtils.isEmpty(appWindow.getActivityRecord())) {
+ continue;
+ }
+ if (firstActivityRecord == null) {
+ firstActivityRecord = appWindow.getActivityRecord();
+ }
+ if (firstActivityRecord.equals(appWindow.getActivityRecord())) {
+ numTopActivityAppWindowsOnDisplay++;
+ }
+ }
+ Slogf.d(TAG, "Top activity = " + activityName);
+ Slogf.d(TAG, "Number of app widows of top activity = " + numTopActivityAppWindowsOnDisplay);
+ boolean isShowingADialog = numTopActivityAppWindowsOnDisplay > 1;
+ synchronized (mLock) {
+ if (isShowingADialog) {
+ mTopActivityWithDialogPerDisplay.put(displayId, activityName);
+ } else {
+ mTopActivityWithDialogPerDisplay.remove(displayId);
+ }
+ }
+ return isShowingADialog;
+ }
+
+ private String dumpWindows() {
+ try {
+ ParcelFileDescriptor[] fileDescriptors = ParcelFileDescriptor.createSocketPair();
+ mWindowManagerBinder.dump(
+ fileDescriptors[0].getFileDescriptor(), WINDOW_DUMP_ARGUMENTS);
+ fileDescriptors[0].close();
+ StringBuilder outputBuilder = new StringBuilder();
+ BufferedReader reader = new BufferedReader(
+ new FileReader(fileDescriptors[1].getFileDescriptor()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ outputBuilder.append(line).append("\n");
+ }
+ reader.close();
+ fileDescriptors[1].close();
+ return outputBuilder.toString();
+ } catch (IOException | RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Creates an intent to start blocking activity.
*
@@ -1175,6 +1289,7 @@
* @param blockedActivity the activity being blocked
* @param blockedTaskId the blocked task id, which contains the blocked activity
* @param taskRootActivity root activity of the blocked task
+ * @param isRootDo denotes if the root activity is distraction optimised
* @return an intent to launch the blocking activity.
*/
private static Intent createBlockingActivityIntent(ComponentName blockingActivity,
@@ -1463,6 +1578,15 @@
}
/**
+ * Called when a window change event is received by the {@link CarSafetyAccessibilityService}.
+ */
+ @VisibleForTesting
+ void onWindowChangeEvent() {
+ Slogf.d(TAG, "onWindowChange event received");
+ mHandlerThread.getThreadHandler().post(() -> blockTopActivitiesIfNecessary());
+ }
+
+ /**
* Listens to the package install/uninstall events to know when to initiate parsing
* installed packages.
*/
diff --git a/service/src/com/android/car/pm/CarSafetyAccessibilityService.java b/service/src/com/android/car/pm/CarSafetyAccessibilityService.java
new file mode 100644
index 0000000..787a0f8
--- /dev/null
+++ b/service/src/com/android/car/pm/CarSafetyAccessibilityService.java
@@ -0,0 +1,39 @@
+/*
+ * 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.pm;
+
+import android.accessibilityservice.AccessibilityService;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.car.CarLocalServices;
+
+/**
+ * An accessibility service to notify the Car Service of any change in the window state. The car
+ * safety related code can read the events sent from this service and take the necessary actions.
+ */
+public class CarSafetyAccessibilityService extends AccessibilityService {
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ CarPackageManagerService carPackageManagerService =
+ CarLocalServices.getService(CarPackageManagerService.class);
+ carPackageManagerService.onWindowChangeEvent();
+ }
+
+ @Override
+ public void onInterrupt() {
+ }
+}
diff --git a/service/src/com/android/car/pm/TEST_MAPPING b/service/src/com/android/car/pm/TEST_MAPPING
index d37fe3d..1c32824 100644
--- a/service/src/com/android/car/pm/TEST_MAPPING
+++ b/service/src/com/android/car/pm/TEST_MAPPING
@@ -19,6 +19,12 @@
},
{
"include-filter": "com.android.car.pm.VendorServiceInfoTest"
+ },
+ {
+ "include-filter": "com.android.car.pm.CarSafetyAccessibilityServiceTest"
+ },
+ {
+ "include-filter": "com.android.car.pm.WindowDumpParserTest"
}
]
},
diff --git a/service/src/com/android/car/pm/WindowDumpParser.java b/service/src/com/android/car/pm/WindowDumpParser.java
new file mode 100644
index 0000000..8387ce7
--- /dev/null
+++ b/service/src/com/android/car/pm/WindowDumpParser.java
@@ -0,0 +1,124 @@
+/*
+ * 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.pm;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A utility class to parse the window dump.
+ */
+class WindowDumpParser {
+ private static final String WINDOW_TYPE_APPLICATION_STARTING = "APPLICATION_STARTING";
+
+ /**
+ * Parses the provided window dump and returns the list of windows only for a particular app.
+ *
+ * @param dump the window dump in string format as returned from `dumpsys window
+ * windows`.
+ * @param appPackageName the package name of the app, which the windows are being
+ * requested for.
+ * @return a list of parsed {@link Window} objects.
+ */
+ public static List<Window> getParsedAppWindows(String dump, String appPackageName) {
+ Pattern dumpSplitter = Pattern.compile("(Window #)|\\n\\n");
+ // \\n\\n to separate out the Global dump from the windows list.
+
+ Pattern windowDetailsPattern = Pattern.compile("\\d*.*\\n"
+ + ".*mDisplayId=(\\S*).*\\n"
+ + ".*package=(\\S*).*\\n"
+ + ".*ty=(\\S*)"
+ + "((.*ActivityRecord\\{(.*?)\\}.*\\n)|(.*\\n))*"
+ // (.*\\n) is required for skipping the lines before the line containing
+ // ActivityRecord{}.
+ );
+ List<Window> windows = new ArrayList<>();
+
+ String[] windowDumps = dumpSplitter.split(dump);
+ for (int i = 1; i < windowDumps.length - 1; i++) {
+ Matcher m = windowDetailsPattern.matcher(windowDumps[i]);
+ if (m.find()) {
+ // Only consider windows for the given appPackageName which are not the splash
+ // screen windows.
+ // TODO(b/192355798): Revisit this logic as window type can be changed.
+ if (Objects.equals(m.group(2), appPackageName)
+ && !Objects.equals(m.group(3), WINDOW_TYPE_APPLICATION_STARTING)) {
+ windows.add(new Window(
+ /* packageName = */ m.group(2),
+ /* displayId = */ Integer.parseInt(m.group(1)),
+ /* activityRecord = */ m.group(6)
+ ));
+ }
+ }
+ }
+ return windows;
+ }
+
+ /**
+ * A holder class that represents an app's window.
+ */
+ static class Window {
+ private final String mPackageName;
+ private final int mDisplayId;
+ private final String mActivityRecord;
+
+ Window(String packageName, int displayId, String activityRecord) {
+ mPackageName = packageName;
+ mDisplayId = displayId;
+ mActivityRecord = activityRecord;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ public String getActivityRecord() {
+ return mActivityRecord;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Window)) return false;
+ Window window = (Window) o;
+ return mDisplayId == window.mDisplayId
+ && mPackageName.equals(window.mPackageName)
+ && Objects.equals(mActivityRecord, window.mActivityRecord);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mDisplayId, mActivityRecord);
+ }
+
+ @Override
+ public String toString() {
+ return "Window{"
+ + "mPackageName=" + mPackageName
+ + ", mDisplayId=" + mDisplayId
+ + ", mActivityRecord={" + mActivityRecord + "}"
+ + "}";
+ }
+ }
+}
diff --git a/service/src/com/android/car/power/PowerComponentHandler.java b/service/src/com/android/car/power/PowerComponentHandler.java
index dcf22e8..f6f1b3b 100644
--- a/service/src/com/android/car/power/PowerComponentHandler.java
+++ b/service/src/com/android/car/power/PowerComponentHandler.java
@@ -113,13 +113,8 @@
mComponentStates.put(component, false);
PowerComponentMediator mediator = factory.createPowerComponent(component);
String componentName = powerComponentToString(component);
- if (mediator == null) {
- Slogf.w(TAG, "Power component(%s) is not valid or doesn't need a mediator",
- componentName);
- continue;
- }
- if (!mediator.isComponentAvailable()) {
- Slogf.w(TAG, "Power component(%s) is not available", componentName);
+ if (mediator == null || !mediator.isComponentAvailable()) {
+ // We don't not associate a mediator with the component.
continue;
}
mPowerComponentMediators.put(component, mediator);
@@ -237,7 +232,6 @@
PowerComponentMediator mediator = mPowerComponentMediators.get(component);
if (mediator == null) {
- Slogf.w(TAG, "%s doesn't have a mediator", powerComponentToString(component));
return true;
}
diff --git a/service/src/com/android/car/telemetry/ResultStore.java b/service/src/com/android/car/telemetry/ResultStore.java
new file mode 100644
index 0000000..058e7f2
--- /dev/null
+++ b/service/src/com/android/car/telemetry/ResultStore.java
@@ -0,0 +1,204 @@
+/*
+ * 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.telemetry;
+
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Map;
+
+/**
+ * Disk storage for interim and final metrics statistics.
+ */
+class ResultStore {
+
+ @VisibleForTesting
+ static final String INTERIM_RESULT_DIR = "interim";
+ @VisibleForTesting
+ static final String FINAL_RESULT_DIR = "final";
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<String, PersistableBundle> mInterimResultCache = new ArrayMap<>();
+
+ private final File mInterimResultDirectory;
+ private final File mFinalResultDirectory;
+ private final Handler mWorkerHandler; // for all non I/O operations
+ private final Handler mIoHandler; // for all I/O operations
+
+ ResultStore(Handler handler, Handler ioHandler, File rootDirectory) {
+ mWorkerHandler = handler;
+ mIoHandler = ioHandler;
+ mInterimResultDirectory = new File(rootDirectory, INTERIM_RESULT_DIR);
+ mFinalResultDirectory = new File(rootDirectory, FINAL_RESULT_DIR);
+ mInterimResultDirectory.mkdirs();
+ mFinalResultDirectory.mkdirs();
+ // load results into memory to reduce the frequency of disk access
+ synchronized (mLock) {
+ loadInterimResultsIntoMemoryLocked();
+ }
+ }
+
+ /** Reads interim results into memory for faster access. */
+ @GuardedBy("mLock")
+ private void loadInterimResultsIntoMemoryLocked() {
+ for (File file : mInterimResultDirectory.listFiles()) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ mInterimResultCache.put(
+ file.getName(),
+ PersistableBundle.readFromStream(fis));
+ } catch (IOException e) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Failed to read result from disk.", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves interim metrics for the given
+ * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+ */
+ PersistableBundle getInterimResult(String metricsConfigName) {
+ synchronized (mLock) {
+ return mInterimResultCache.get(metricsConfigName);
+ }
+ }
+
+ /**
+ * Stores interim metrics results in memory for the given
+ * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+ */
+ void putInterimResult(String metricsConfigName, PersistableBundle result) {
+ synchronized (mLock) {
+ mInterimResultCache.put(metricsConfigName, result);
+ }
+ }
+
+ /**
+ * Retrieves final metrics for the given
+ * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+ *
+ * @param metricsConfigName name of the MetricsConfig.
+ * @param deleteResult if true, the final result will be deleted from disk.
+ * @param callback for receiving the metrics output. If result does not exist, it will
+ * receive a null value.
+ */
+ void getFinalResult(
+ String metricsConfigName, boolean deleteResult, FinalResultCallback callback) {
+ // I/O operations should happen on I/O thread
+ mIoHandler.post(() -> {
+ synchronized (mLock) {
+ loadFinalResultLockedOnIoThread(metricsConfigName, deleteResult, callback);
+ }
+ });
+ }
+
+ @GuardedBy("mLock")
+ private void loadFinalResultLockedOnIoThread(
+ String metricsConfigName, boolean deleteResult, FinalResultCallback callback) {
+ File file = new File(mFinalResultDirectory, metricsConfigName);
+ // if no final result exists for this metrics config, return immediately
+ if (!file.exists()) {
+ mWorkerHandler.post(() -> callback.onFinalResult(metricsConfigName, null));
+ return;
+ }
+ try (FileInputStream fis = new FileInputStream(file)) {
+ PersistableBundle bundle = PersistableBundle.readFromStream(fis);
+ // invoke callback on worker thread
+ mWorkerHandler.post(() -> callback.onFinalResult(metricsConfigName, bundle));
+ } catch (IOException e) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Failed to get final result from disk.", e);
+ mWorkerHandler.post(() -> callback.onFinalResult(metricsConfigName, null));
+ }
+ if (deleteResult) {
+ file.delete();
+ }
+ }
+
+ /**
+ * Stores final metrics in memory for the given
+ * {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
+ */
+ void putFinalResult(String metricsConfigName, PersistableBundle result) {
+ synchronized (mLock) {
+ mIoHandler.post(() -> {
+ writeSingleResultToFileOnIoThread(mFinalResultDirectory, metricsConfigName, result);
+ deleteSingleFileOnIoThread(mInterimResultDirectory, metricsConfigName);
+ });
+ mInterimResultCache.remove(metricsConfigName);
+ }
+ }
+
+ /** Persist data to disk. */
+ void shutdown() {
+ mIoHandler.post(() -> {
+ synchronized (mLock) {
+ writeInterimResultsToFileLockedOnIoThread();
+ }
+ });
+ }
+
+ /** Writes interim results to disk. */
+ @GuardedBy("mLock")
+ private void writeInterimResultsToFileLockedOnIoThread() {
+ mInterimResultCache.forEach((metricsConfigName, persistableBundle) ->
+ writeSingleResultToFileOnIoThread(
+ mInterimResultDirectory, metricsConfigName, persistableBundle));
+ }
+
+ /**
+ * Converts a {@link PersistableBundle} into byte array and saves the results to a file.
+ */
+ private void writeSingleResultToFileOnIoThread(
+ File dir, String metricsConfigName, PersistableBundle result) {
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ result.writeToStream(outputStream);
+ Files.write(
+ new File(dir, metricsConfigName).toPath(),
+ outputStream.toByteArray());
+ } catch (IOException e) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Failed to write result to file", e);
+ }
+ }
+
+ /** Deletes a the given file in the given directory if it exists. */
+ private void deleteSingleFileOnIoThread(File interimResultDirectory, String metricsConfigName) {
+ File file = new File(interimResultDirectory, metricsConfigName);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ /** Callback for receiving final metrics output. */
+ interface FinalResultCallback {
+ void onFinalResult(String metricsConfigName, PersistableBundle result);
+ }
+
+ // TODO(b/195422227): Implement deletion of stale data based on system time
+ // TODO(b/195422227): Implement deletion of interim results after MetricsConfig is removed
+}
diff --git a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
index 655e9c9..b5d4d7b 100644
--- a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
@@ -37,13 +37,14 @@
*
* @param subscriber a subscriber to receive data
* @throws IllegalArgumentException if the subscriber is invalid.
+ * @throws IllegalStateException if there are internal errors.
*/
public abstract void addDataSubscriber(DataSubscriber subscriber);
/**
* Removes the subscriber from the publisher. Publisher stops if necessary.
*
- * @throws IllegalArgumentException if the subscriber was not found.
+ * @throws IllegalArgumentException if invalid subscriber was provided.
*/
public abstract void removeDataSubscriber(DataSubscriber subscriber);
diff --git a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
index 8507b51..feeeada 100644
--- a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
@@ -82,7 +82,7 @@
}
/**
- * Returns true if connected, false if not connected.
+ * Connects to ICarTelemetryInternal service and starts listening for CarData.
*
* @throws IllegalStateException if it cannot connect to ICarTelemetryInternal service.
*/
@@ -114,6 +114,25 @@
}
}
+ /**
+ * Disconnects from ICarTelemetryInternal service.
+ *
+ * @throws IllegalStateException if fails to clear the listener.
+ */
+ @GuardedBy("mLock")
+ private void disconnectFromCarTelemetrydLocked() {
+ if (mCarTelemetryInternal == null) {
+ return; // already disconnected
+ }
+ try {
+ mCarTelemetryInternal.clearListener();
+ } catch (RemoteException e) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove ICarTelemetryInternal listener", e);
+ }
+ mCarTelemetryInternal.asBinder().unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
+ mCarTelemetryInternal = null;
+ }
+
@VisibleForTesting
boolean isConnectedToCarTelemetryd() {
synchronized (mLock) {
@@ -152,12 +171,20 @@
@Override
public void removeDataSubscriber(DataSubscriber subscriber) {
- // TODO(b/189142577): implement and disconnect from cartelemetryd if necessary
+ synchronized (mLock) {
+ mSubscribers.remove(subscriber);
+ if (mSubscribers.isEmpty()) {
+ disconnectFromCarTelemetrydLocked();
+ }
+ }
}
@Override
public void removeAllDataSubscribers() {
- // TODO(b/189142577): implement and disconnect from cartelemetryd
+ synchronized (mLock) {
+ mSubscribers.clear();
+ disconnectFromCarTelemetrydLocked();
+ }
}
@Override
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index 52722c8..8ab9a50 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -174,7 +174,7 @@
.addAllowedLogSource("AID_SYSTEM")
.build();
} else {
- throw new IllegalArgumentException("Unsupported metric " + metric);
+ throw new IllegalArgumentException("Unsupported metric " + metric.name());
}
}
}
diff --git a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
index bc0148d..146d995 100644
--- a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
@@ -124,16 +124,20 @@
@Override
public void removeDataSubscriber(DataSubscriber subscriber) {
TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
- Preconditions.checkArgument(
- publisherParam.getPublisherCase() == PublisherCase.VEHICLE_PROPERTY,
- "Subscribers only with VehicleProperty publisher are supported by this class.");
+ if (publisherParam.getPublisherCase() != PublisherCase.VEHICLE_PROPERTY) {
+ Slog.w(CarLog.TAG_TELEMETRY,
+ "Expected VEHICLE_PROPERTY publisher, but received "
+ + publisherParam.getPublisherCase().name());
+ return;
+ }
int propertyId = publisherParam.getVehicleProperty().getVehiclePropertyId();
synchronized (mLock) {
ArraySet<DataSubscriber> subscribers = mCarPropertyToSubscribers.get(propertyId);
- if (subscribers == null || !subscribers.remove(subscriber)) {
- throw new IllegalArgumentException("DataSubscriber was not found");
+ if (subscribers == null) {
+ return;
}
+ subscribers.remove(subscriber);
if (subscribers.isEmpty()) {
mCarPropertyToSubscribers.remove(propertyId);
// Doesn't throw exception as listener is not null. mCarPropertyService and
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 4bbb40a..5ecefcf 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -475,6 +475,11 @@
mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
}
+ @VisibleForTesting
+ void setResourceOveruseKillingDelay(long millis) {
+ mWatchdogPerfHandler.setResourceOveruseKillingDelay(millis);
+ }
+
private static final class ICarWatchdogServiceForSystemImpl
extends ICarWatchdogServiceForSystem.Stub {
private final WeakReference<CarWatchdogService> mService;
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index afbb0bb..7f4a48e 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -75,6 +75,7 @@
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +101,8 @@
public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
+ static final long RESOURCE_OVERUSE_KILLING_DELAY_MILLS = 10_000;
+
private static final long MAX_WAIT_TIME_MILLS = 3_000;
private final Context mContext;
@@ -123,7 +126,9 @@
mOveruseSystemListenerInfosByUid = new SparseArray<>();
/* Set of safe-to-kill system and vendor packages. */
@GuardedBy("mLock")
- public final ArraySet<String> mSafeToKillPackages = new ArraySet<>();
+ public final ArraySet<String> mSafeToKillSystemPackages = new ArraySet<>();
+ @GuardedBy("mLock")
+ public final ArraySet<String> mSafeToKillVendorPackages = new ArraySet<>();
/* Default killable state for packages when not updated by the user. */
@GuardedBy("mLock")
public final ArraySet<String> mDefaultNotKillableGenericPackages = new ArraySet<>();
@@ -134,6 +139,8 @@
mPendingSetResourceOveruseConfigurationsRequest = null;
@GuardedBy("mLock")
boolean mIsConnectedToDaemon;
+ @GuardedBy("mLock")
+ long mResourceOveruseKillingDelayMills;
public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
PackageInfoHandler packageInfoHandler) {
@@ -142,6 +149,7 @@
mPackageInfoHandler = packageInfoHandler;
mMainHandler = new Handler(Looper.getMainLooper());
mLastStatsReportUTC = ZonedDateTime.now(ZoneOffset.UTC);
+ mResourceOveruseKillingDelayMills = RESOURCE_OVERUSE_KILLING_DELAY_MILLS;
}
/** Initializes the handler. */
@@ -151,12 +159,13 @@
* state changes.
* TODO(b/192294393): Persist in-memory data: Read the current day's I/O overuse stats from
* database.
- * TODO(b/192665269): Fetch the safe-to-kill from daemon on initialization and update
- * mSafeToKillPackages.
*/
synchronized (mLock) {
checkAndHandleDateChangeLocked();
}
+ mMainHandler.sendMessage(
+ obtainMessage(WatchdogPerfHandler::fetchAndSyncResourceOveruseConfigurations,
+ this));
if (DEBUG) {
Slogf.d(TAG, "WatchdogPerfHandler is initialized");
}
@@ -451,10 +460,11 @@
PackageInfo packageInfo = packageInfos.get(i);
String genericPackageName = mPackageInfoHandler.getNameForPackage(packageInfo);
if (packageInfo.sharedUserId == null) {
+ int componentType = mPackageInfoHandler.getComponentType(
+ packageInfo.applicationInfo);
int killableState = getPackageKillableStateForUserPackageLocked(
- userId, genericPackageName,
- mPackageInfoHandler.getComponentType(packageInfo.applicationInfo),
- mSafeToKillPackages.contains(genericPackageName));
+ userId, genericPackageName, componentType,
+ isSafeToKillLocked(genericPackageName, componentType, null));
states.add(new PackageKillableState(packageInfo.packageName, userId,
killableState));
continue;
@@ -473,13 +483,13 @@
List<ApplicationInfo> applicationInfos = entry.getValue();
int componentType = mPackageInfoHandler.getSharedComponentType(
applicationInfos, genericPackageName);
- boolean isSafeToKill = mSafeToKillPackages.contains(genericPackageName);
+ List<String> packageNames = new ArrayList<>(applicationInfos.size());
for (int i = 0; i < applicationInfos.size(); ++i) {
- isSafeToKill = isSafeToKill
- || mSafeToKillPackages.contains(applicationInfos.get(i).packageName);
+ packageNames.add(applicationInfos.get(i).packageName);
}
int killableState = getPackageKillableStateForUserPackageLocked(
- userId, genericPackageName, componentType, isSafeToKill);
+ userId, genericPackageName, componentType,
+ isSafeToKillLocked(genericPackageName, componentType, packageNames));
for (int i = 0; i < applicationInfos.size(); ++i) {
states.add(new PackageKillableState(
applicationInfos.get(i).packageName, userId, killableState));
@@ -503,29 +513,11 @@
"Must provide at least one configuration");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
- ArraySet<Integer> seenComponentTypes = new ArraySet<>();
+ checkResourceOveruseConfigs(configurations, resourceOveruseFlag);
List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
new ArrayList<>();
for (int i = 0; i < configurations.size(); ++i) {
- ResourceOveruseConfiguration config = configurations.get(i);
- /*
- * TODO(b/186119640): Make sure the validation done here matches the validation done in
- * the daemon so set requests retried at a later time will complete successfully.
- */
- int componentType = config.getComponentType();
- if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
- throw new IllegalArgumentException("Invalid component type in the configuration");
- }
- if (seenComponentTypes.contains(componentType)) {
- throw new IllegalArgumentException(
- "Cannot provide duplicate configurations for the same component type");
- }
- if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
- && config.getIoOveruseConfiguration() == null) {
- throw new IllegalArgumentException("Must provide I/O overuse configuration");
- }
- seenComponentTypes.add(config.getComponentType());
- internalConfigs.add(toInternalResourceOveruseConfiguration(config,
+ internalConfigs.add(toInternalResourceOveruseConfiguration(configurations.get(i),
resourceOveruseFlag));
}
synchronized (mLock) {
@@ -574,6 +566,7 @@
/** Processes the latest I/O overuse stats */
public void latestIoOveruseStats(List<PackageIoOveruseStats> packageIoOveruseStats) {
+ SparseBooleanArray recurringIoOverusesByUid = new SparseBooleanArray();
int[] uids = new int[packageIoOveruseStats.size()];
for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
uids[i] = packageIoOveruseStats.get(i).uid;
@@ -628,45 +621,19 @@
mOveruseActionsByUserPackage.add(overuseAction);
continue;
}
- IPackageManager packageManager = ActivityThread.getPackageManager();
- List<String> packages = Collections.singletonList(genericPackageName);
- if (usage.isSharedPackage()) {
- packages = mPackageInfoHandler.getPackagesForUid(
- stats.uid, genericPackageName);
- }
- for (int pkgIdx = 0; pkgIdx < packages.size(); ++pkgIdx) {
- String pkg = packages.get(pkgIdx);
- try {
- int oldEnabledState = -1;
- if (!hasRecurringOveruse) {
- oldEnabledState = packageManager.getApplicationEnabledSetting(
- pkg, userId);
- if (oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED
- || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
- || oldEnabledState
- == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
- continue;
- }
- }
- packageManager.setApplicationEnabledSetting(pkg,
- COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, /* flags= */ 0, userId,
- mContext.getPackageName());
- overuseAction.resourceOveruseActionType = hasRecurringOveruse
- ? KILLED_RECURRING_OVERUSE : KILLED;
- if (oldEnabledState != -1) {
- usage.oldEnabledStateByPackage.put(pkg, oldEnabledState);
- }
- } catch (RemoteException e) {
- Slogf.e(TAG, "Failed to disable application for user %d, package '%s'",
- userId, pkg);
- }
- }
- mOveruseActionsByUserPackage.add(overuseAction);
+
+ recurringIoOverusesByUid.put(stats.uid, hasRecurringOveruse);
}
if (!mOveruseActionsByUserPackage.isEmpty()) {
mMainHandler.sendMessage(obtainMessage(
WatchdogPerfHandler::notifyActionsTakenOnOveruse, this));
}
+ if (recurringIoOverusesByUid.size() > 0) {
+ mMainHandler.sendMessageDelayed(
+ obtainMessage(WatchdogPerfHandler::handleIoOveruseKilling,
+ this, recurringIoOverusesByUid, genericPackageNamesByUid),
+ mResourceOveruseKillingDelayMills);
+ }
}
if (DEBUG) {
Slogf.d(TAG, "Processed latest I/O overuse stats");
@@ -694,6 +661,65 @@
}
}
+ /** Handle packages that exceed resource overuse thresholds */
+ private void handleIoOveruseKilling(SparseBooleanArray recurringIoOverusesByUid,
+ SparseArray<String> genericPackageNamesByUid) {
+ IPackageManager packageManager = ActivityThread.getPackageManager();
+ synchronized (mLock) {
+ for (int i = 0; i < recurringIoOverusesByUid.size(); i++) {
+ int uid = recurringIoOverusesByUid.keyAt(i);
+ boolean hasRecurringOveruse = recurringIoOverusesByUid.valueAt(i);
+ String genericPackageName = genericPackageNamesByUid.get(uid);
+ int userId = UserHandle.getUserId(uid);
+ String key = getUserPackageUniqueId(userId, genericPackageName);
+ PackageResourceUsage usage = mUsageByUserPackage.get(key);
+
+ PackageResourceOveruseAction overuseAction = new PackageResourceOveruseAction();
+ overuseAction.packageIdentifier = new PackageIdentifier();
+ overuseAction.packageIdentifier.name = genericPackageName;
+ overuseAction.packageIdentifier.uid = uid;
+ overuseAction.resourceTypes = new int[]{ ResourceType.IO };
+ overuseAction.resourceOveruseActionType = NOT_KILLED;
+
+ List<String> packages = Collections.singletonList(genericPackageName);
+ if (usage.isSharedPackage()) {
+ packages = mPackageInfoHandler.getPackagesForUid(uid, genericPackageName);
+ }
+ for (int pkgIdx = 0; pkgIdx < packages.size(); pkgIdx++) {
+ String packageName = packages.get(pkgIdx);
+ try {
+ int oldEnabledState = -1;
+ if (!hasRecurringOveruse) {
+ oldEnabledState = packageManager.getApplicationEnabledSetting(
+ packageName, userId);
+ if (oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED
+ || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
+ || oldEnabledState
+ == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ continue;
+ }
+ }
+ packageManager.setApplicationEnabledSetting(packageName,
+ COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, /* flags= */ 0, userId,
+ mContext.getPackageName());
+ overuseAction.resourceOveruseActionType = hasRecurringOveruse
+ ? KILLED_RECURRING_OVERUSE : KILLED;
+ if (oldEnabledState != -1) {
+ usage.oldEnabledStateByPackage.put(packageName, oldEnabledState);
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "Failed to disable application for user %d, package '%s'",
+ userId, packageName);
+ }
+ }
+ mOveruseActionsByUserPackage.add(overuseAction);
+ }
+ if (!mOveruseActionsByUserPackage.isEmpty()) {
+ notifyActionsTakenOnOveruse();
+ }
+ }
+ }
+
/** Resets the resource overuse stats for the given generic package names. */
public void resetResourceOveruseStats(Set<String> genericPackageNames) {
synchronized (mLock) {
@@ -710,6 +736,48 @@
}
}
+ /** Set the delay to kill a package after the package is notified of resource overuse. */
+ public void setResourceOveruseKillingDelay(long millis) {
+ synchronized (mLock) {
+ mResourceOveruseKillingDelayMills = millis;
+ }
+ }
+
+ /** Fetches and syncs the resource overuse configurations from watchdog daemon. */
+ private void fetchAndSyncResourceOveruseConfigurations() {
+ synchronized (mLock) {
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs;
+ try {
+ internalConfigs = mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(TAG, e, "Failed to fetch resource overuse configurations");
+ return;
+ }
+ if (internalConfigs.isEmpty()) {
+ Slogf.e(TAG, "Failed to fetch resource overuse configurations");
+ return;
+ }
+ mSafeToKillSystemPackages.clear();
+ mSafeToKillVendorPackages.clear();
+ for (int i = 0; i < internalConfigs.size(); i++) {
+ switch (internalConfigs.get(i).componentType) {
+ case ComponentType.SYSTEM:
+ mSafeToKillSystemPackages.addAll(internalConfigs.get(i).safeToKillPackages);
+ break;
+ case ComponentType.VENDOR:
+ mSafeToKillVendorPackages.addAll(internalConfigs.get(i).safeToKillPackages);
+ break;
+ default:
+ // All third-party apps are killable.
+ break;
+ }
+ }
+ if (DEBUG) {
+ Slogf.d(TAG, "Fetched and synced safe to kill packages.");
+ }
+ }
+ }
+
@GuardedBy("mLock")
private int getPackageKillableStateForUserPackageLocked(
int userId, String genericPackageName, int componentType, boolean isSafeToKill) {
@@ -946,6 +1014,9 @@
boolean doClearPendingRequest = isPendingRequest;
try {
mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(configs);
+ mMainHandler.sendMessage(
+ obtainMessage(WatchdogPerfHandler::fetchAndSyncResourceOveruseConfigurations,
+ this));
} catch (RemoteException e) {
if (e instanceof TransactionTooLargeException) {
throw e;
@@ -963,7 +1034,6 @@
}
}
}
- /* TODO(b/192665269): Fetch safe-to-kill list from daemon and update mSafeToKillPackages. */
if (DEBUG) {
Slogf.d(TAG, "Set the resource overuse configuration successfully");
}
@@ -989,6 +1059,45 @@
}
}
+ @GuardedBy("mLock")
+ private boolean isSafeToKillLocked(String genericPackageName, int componentType,
+ List<String> sharedPackages) {
+ BiFunction<List<String>, Set<String>, Boolean> isSafeToKillAnyPackage =
+ (packages, safeToKillPackages) -> {
+ if (packages == null) {
+ return false;
+ }
+ for (int i = 0; i < packages.size(); i++) {
+ if (safeToKillPackages.contains(packages.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ switch (componentType) {
+ case ComponentType.SYSTEM:
+ if (mSafeToKillSystemPackages.contains(genericPackageName)) {
+ return true;
+ }
+ return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
+ case ComponentType.VENDOR:
+ if (mSafeToKillVendorPackages.contains(genericPackageName)) {
+ return true;
+ }
+ /*
+ * Packages under the vendor shared UID may contain system packages because when
+ * CarWatchdogService derives the shared component type it attributes system
+ * packages as vendor packages when there is at least one vendor package.
+ */
+ return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
+ || isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillVendorPackages);
+ default:
+ // Third-party apps are always killable
+ return true;
+ }
+ }
+
private static String getUserPackageUniqueId(int userId, String genericPackageName) {
return String.valueOf(userId) + ":" + genericPackageName;
}
@@ -1060,6 +1169,8 @@
metadata.appCategoryType = ApplicationCategoryType.MEDIA;
break;
default:
+ Slogf.i(TAG, "Invalid application category type: %s skipping package: %s",
+ entry.getValue(), metadata.packageName);
continue;
}
internalConfig.packageMetadata.add(metadata);
@@ -1238,6 +1349,72 @@
return thresholds;
}
+ private static void checkResourceOveruseConfigs(
+ List<ResourceOveruseConfiguration> configurations,
+ @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+ ArraySet<Integer> seenComponentTypes = new ArraySet<>();
+ for (int i = 0; i < configurations.size(); ++i) {
+ ResourceOveruseConfiguration config = configurations.get(i);
+ if (seenComponentTypes.contains(config.getComponentType())) {
+ throw new IllegalArgumentException(
+ "Cannot provide duplicate configurations for the same component type");
+ }
+ checkResourceOveruseConfig(config, resourceOveruseFlag);
+ seenComponentTypes.add(config.getComponentType());
+ }
+ }
+
+ private static void checkResourceOveruseConfig(ResourceOveruseConfiguration config,
+ @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+ int componentType = config.getComponentType();
+ if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
+ throw new IllegalArgumentException(
+ "Invalid component type in the configuration: " + componentType);
+ }
+ if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
+ && config.getIoOveruseConfiguration() == null) {
+ throw new IllegalArgumentException("Must provide I/O overuse configuration");
+ }
+ checkIoOveruseConfig(config.getIoOveruseConfiguration(), componentType);
+ }
+
+ private static void checkIoOveruseConfig(IoOveruseConfiguration config, int componentType) {
+ if (config.getComponentLevelThresholds().getBackgroundModeBytes() <= 0
+ || config.getComponentLevelThresholds().getForegroundModeBytes() <= 0
+ || config.getComponentLevelThresholds().getGarageModeBytes() <= 0) {
+ throw new IllegalArgumentException(
+ "For component: " + toComponentTypeStr(componentType)
+ + " some thresholds are zero for: "
+ + config.getComponentLevelThresholds().toString());
+ }
+ if (componentType == ComponentType.SYSTEM) {
+ List<IoOveruseAlertThreshold> systemThresholds = config.getSystemWideThresholds();
+ if (systemThresholds.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Empty system-wide alert thresholds provided in "
+ + toComponentTypeStr(componentType)
+ + " config.");
+ }
+ for (int i = 0; i < systemThresholds.size(); i++) {
+ checkIoOveruseAlertThreshold(systemThresholds.get(i));
+ }
+ }
+ }
+
+ private static void checkIoOveruseAlertThreshold(
+ IoOveruseAlertThreshold ioOveruseAlertThreshold) {
+ if (ioOveruseAlertThreshold.getDurationInSeconds() <= 0) {
+ throw new IllegalArgumentException(
+ "System wide threshold duration must be greater than zero for: "
+ + ioOveruseAlertThreshold);
+ }
+ if (ioOveruseAlertThreshold.getWrittenBytesPerSecond() <= 0) {
+ throw new IllegalArgumentException(
+ "System wide threshold written bytes per second must be greater than zero for: "
+ + ioOveruseAlertThreshold);
+ }
+ }
+
private static void replaceKey(Map<String, PerStateBytes> map, String oldKey, String newKey) {
PerStateBytes perStateBytes = map.get(oldKey);
if (perStateBytes != null) {
diff --git a/tests/BugReportApp/res/values-te/strings.xml b/tests/BugReportApp/res/values-te/strings.xml
index 0a9b835..1a8d6c5 100644
--- a/tests/BugReportApp/res/values-te/strings.xml
+++ b/tests/BugReportApp/res/values-te/strings.xml
@@ -17,34 +17,34 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2596316479611335185">"బగ్ నివేదిక"</string>
+ <string name="app_name" msgid="2596316479611335185">"బగ్ రిపోర్ట్"</string>
<string name="bugreport_info_quit" msgid="5590138890181142473">"మూసివేయి"</string>
- <string name="bugreport_info_start" msgid="667324824650830832">"బగ్ నివేదికను ప్రారంభించండి"</string>
+ <string name="bugreport_info_start" msgid="667324824650830832">"బగ్ రిపోర్ట్ను ప్రారంభించండి"</string>
<string name="bugreport_info_status" msgid="7211044508792815451">"స్థితి:"</string>
- <string name="bugreport_info_expires_soon_notice" msgid="927139313070992675">"ఈ బగ్ నివేదిక గడువు త్వరలో ముగుస్తుంది"</string>
+ <string name="bugreport_info_expires_soon_notice" msgid="927139313070992675">"ఈ బగ్ రిపోర్ట్ గడువు త్వరలో ముగుస్తుంది"</string>
<string name="bugreport_dialog_submit" msgid="2789636252713280633">"సమర్పించండి"</string>
<string name="bugreport_dialog_cancel" msgid="4741928791364757040">"రద్దు చేయండి"</string>
<string name="bugreport_dialog_upload" msgid="2517386929450370781">"అప్లోడ్ చేయి"</string>
<string name="bugreport_dialog_save" msgid="3291363266190644226">"సేవ్ చేయి"</string>
- <string name="bugreport_dialog_show_bugreports" msgid="6964385141627170297">"బగ్ నివేదికలను చూపండి"</string>
+ <string name="bugreport_dialog_show_bugreports" msgid="6964385141627170297">"బగ్ రిపోర్ట్లను చూపండి"</string>
<string name="bugreport_dialog_close" msgid="289925437277364266">"మూసివేయి"</string>
<string name="bugreport_dialog_title" msgid="3315160684205929910">"మాట్లాడండి & సమస్యను వివరించండి"</string>
- <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"బగ్ నివేదిక కోసం %s వద్ద ఆడియో సందేశం"</string>
+ <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"బగ్ రిపోర్ట్ కోసం %s వద్ద ఆడియో మెసేజ్"</string>
<string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"రికార్డ్ చేయడం పూర్తయింది"</string>
- <string name="bugreport_dialog_in_progress_title" msgid="3782308141532622394">"బగ్ నివేదిక ఇప్పటికే సేకరించబడుతోంది"</string>
- <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"బగ్ నివేదిక సేకరించబడింది"</string>
+ <string name="bugreport_dialog_in_progress_title" msgid="3782308141532622394">"బగ్ రిపోర్ట్ ఇప్పటికే సేకరించబడుతోంది"</string>
+ <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"బగ్ రిపోర్ట్ సేకరించబడింది"</string>
<string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"ఆడియోను జోడించు"</string>
<string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"ఆడియోను జోడించు & అప్లోడ్ చేయి"</string>
<string name="bugreport_move_button_text" msgid="1245698439228323880">"USBకి తరలించు"</string>
<string name="bugreport_upload_button_text" msgid="4136749466634820848">"అప్లోడ్ చేయండి"</string>
<string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"GCSకు అప్లోడ్ చేయండి"</string>
<string name="toast_permissions_denied" msgid="7054832711916992770">"దయచేసి అనుమతులను మంజూరు చేయండి"</string>
- <string name="toast_bug_report_in_progress" msgid="8319601113129121579">"బగ్ నివేదిక ఇప్పటికే సేకరించబడుతోంది"</string>
+ <string name="toast_bug_report_in_progress" msgid="8319601113129121579">"బగ్ రిపోర్ట్ ఇప్పటికే సేకరించబడుతోంది"</string>
<string name="toast_bug_report_started" msgid="7154589593986557754">"బగ్ను నివేదించడం మొదలైంది"</string>
- <string name="toast_status_failed" msgid="6365384202315043395">"బగ్ నివేదిక విఫలమైంది"</string>
+ <string name="toast_status_failed" msgid="6365384202315043395">"బగ్ రిపోర్ట్ విఫలమైంది"</string>
<string name="toast_status_screencap_failed" msgid="2187083897594745149">"స్క్రీన్ క్యాప్చర్ విఫలమైంది"</string>
<string name="toast_status_dump_state_failed" msgid="8072469535227541761">"డంప్ స్థితి విఫలమైంది"</string>
- <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"బగ్ నివేదిక ప్రోగ్రెస్లో ఉంది"</string>
- <string name="notification_bugreport_finished_title" msgid="3970195939909624320">"బగ్ నివేదిక సేకరించబడింది"</string>
- <string name="notification_bugreport_channel_name" msgid="776902295433824255">"బగ్ నివేదిక స్థితి ఛానెల్"</string>
+ <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"బగ్ రిపోర్ట్ ప్రోగ్రెస్లో ఉంది"</string>
+ <string name="notification_bugreport_finished_title" msgid="3970195939909624320">"బగ్ రిపోర్ట్ సేకరించబడింది"</string>
+ <string name="notification_bugreport_channel_name" msgid="776902295433824255">"బగ్ రిపోర్ట్ స్థితి ఛానెల్"</string>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
index a29296c..82c2c30 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -48,5 +48,40 @@
android:padding="20dp"
android:text="@string/cluster_stop"
android:id="@+id/cluster_stop_button"/>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:padding="20dp">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_margin="5dp"
+ android:text="@string/cluster_activity_state"/>
+ <RadioGroup
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:id="@+id/cluster_activity_state_default"
+ android:text="@string/cluster_activity_state_default"/>
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:id="@+id/cluster_activity_state_enabled"
+ android:text="@string/cluster_activity_state_enabled"/>
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp"
+ android:id="@+id/cluster_activity_state_disabled"
+ android:text="@string/cluster_activity_state_disabled"/>
+ </RadioGroup>
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index f1d7d81..ed3f100 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -44,6 +44,58 @@
android:background="#334666"
android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_gravity="center_vertical"
+ android:text="Number of messages:"/>
+
+ <NumberPicker
+ android:id="@+id/number_messages"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#1da9ff"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:textSize="30sp"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_gravity="center_vertical"
+ android:text="Number of people:"/>
+
+ <NumberPicker
+ android:id="@+id/number_people"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#1da9ff"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:textSize="30sp"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/customizable_message_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="50dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:background="#1da9ff"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:text="Customizable message notification builder"
+ android:textSize="30sp"/>
+
<Button
android:id="@+id/category_call_button"
android:layout_width="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 0613b45..2fb2de5 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -167,6 +167,10 @@
<string name="cluster_start_activity" translatable="false">Start Nav Activity</string>
<string name="cluster_start_activity_failed" translatable="false">Failed to start activity in cluster</string>
<string name="cluster_not_started" translatable="false">Missing navigation focus</string>
+ <string name="cluster_activity_state" translatable="false">Cluster activity state</string>
+ <string name="cluster_activity_state_default" translatable="false">Default</string>
+ <string name="cluster_activity_state_enabled" translatable="false">On</string>
+ <string name="cluster_activity_state_disabled" translatable="false">Off</string>
<!-- input test -->
<string name="volume_up" translatable="false">Volume +</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index ab4e2d9..5599465 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -34,6 +34,7 @@
import android.car.cluster.navigation.NavigationState.Step;
import android.car.cluster.navigation.NavigationState.Timestamp;
import android.car.navigation.CarNavigationStatusManager;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
@@ -41,6 +42,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.RadioButton;
import android.widget.Toast;
import androidx.annotation.IdRes;
@@ -124,6 +126,7 @@
NavigationStateProto[] navigationStateArray = new NavigationStateProto[1];
navigationStateArray[0] = NavigationStateProto.newBuilder()
+ .setServiceStatus(NavigationStateProto.ServiceStatus.NORMAL)
.addSteps(Step.newBuilder()
.setManeuver(Maneuver.newBuilder()
.setType(Maneuver.Type.DEPART)
@@ -205,6 +208,13 @@
view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
view.findViewById(R.id.cluster_stop_button).setOnClickListener(v -> stopCluster());
+ view.findViewById(R.id.cluster_activity_state_default).setOnClickListener(v ->
+ changeClusterActivityState(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT));
+ view.findViewById(R.id.cluster_activity_state_enabled).setOnClickListener(v ->
+ changeClusterActivityState(PackageManager.COMPONENT_ENABLED_STATE_ENABLED));
+ view.findViewById(R.id.cluster_activity_state_disabled).setOnClickListener(v ->
+ changeClusterActivityState(PackageManager.COMPONENT_ENABLED_STATE_DISABLED));
+ updateInitialClusterActivityState(view);
mTurnByTurnButton = view.findViewById(R.id.cluster_turn_left_button);
mTurnByTurnButton.setOnClickListener(v -> toggleSendTurn());
@@ -212,6 +222,36 @@
return view;
}
+ private void updateInitialClusterActivityState(View view) {
+ PackageManager pm = getContext().getPackageManager();
+ ComponentName clusterActivity =
+ new ComponentName(getContext(), FakeClusterNavigationActivity.class);
+ int currentComponentState = pm.getComponentEnabledSetting(clusterActivity);
+ RadioButton button = view.findViewById(
+ convertClusterActivityStateToViewId(currentComponentState));
+ button.setChecked(true);
+ }
+
+ private int convertClusterActivityStateToViewId(int componentState) {
+ switch (componentState) {
+ case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+ return R.id.cluster_activity_state_default;
+ case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+ return R.id.cluster_activity_state_enabled;
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+ return R.id.cluster_activity_state_disabled;
+ }
+ throw new IllegalStateException("Unknown component state: " + componentState);
+ }
+
+ private void changeClusterActivityState(int newComponentState) {
+ PackageManager pm = getContext().getPackageManager();
+ ComponentName clusterActivity =
+ new ComponentName(getContext(), FakeClusterNavigationActivity.class);
+ pm.setComponentEnabledSetting(clusterActivity, newComponentState,
+ PackageManager.DONT_KILL_APP);
+ }
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
initCarApi();
@@ -271,6 +311,7 @@
mTimer.cancel();
mTimer = null;
}
+ sendTurn(NavigationStateProto.newBuilder().build());
mTurnByTurnButton.setText(R.string.cluster_start_guidance);
}
@@ -278,13 +319,11 @@
* Sends one update of the navigation state through the {@link CarNavigationStatusManager}
*/
private void sendTurn(@NonNull NavigationStateProto state) {
- try {
+ if (hasFocus()) {
Bundle bundle = new Bundle();
bundle.putByteArray("navstate2", state.toByteArray());
- mCarNavigationStatusManager.sendEvent(1, bundle);
+ mCarNavigationStatusManager.sendNavigationStateChange(bundle);
Log.i(TAG, "Sending nav state: " + state);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Failed to send turn information.", e);
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
index 3d6d76d..9dbef65 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
@@ -14,6 +14,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.NumberPicker;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Action;
@@ -26,7 +27,9 @@
import com.google.android.car.kitchensink.KitchenSinkActivity;
import com.google.android.car.kitchensink.R;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* Test fragment that can send all sorts of notifications.
@@ -100,6 +103,7 @@
initCallButton(view);
initCustomGroupSummaryButton(view);
initGroupWithoutSummaryButton(view);
+ initCustomizableMessageButton(view);
return view;
}
@@ -244,6 +248,83 @@
});
}
+ private void initCustomizableMessageButton(View view) {
+ NumberPicker messagesPicker = view.findViewById(R.id.number_messages);
+ messagesPicker.setMinValue(1);
+ messagesPicker.setMaxValue(25);
+ messagesPicker.setWrapSelectorWheel(true);
+ NumberPicker peoplePicker = view.findViewById(R.id.number_people);
+ peoplePicker.setMinValue(1);
+ peoplePicker.setMaxValue(25);
+ peoplePicker.setWrapSelectorWheel(true);
+
+ view.findViewById(R.id.customizable_message_button).setOnClickListener(v -> {
+ int id = mCurrentNotificationId++;
+
+ int numPeople = peoplePicker.getValue();
+ int numMessages = messagesPicker.getValue();
+
+ PendingIntent replyIntent = createServiceIntent(id, "reply");
+ PendingIntent markAsReadIntent = createServiceIntent(id, "read");
+
+ List<Person> personList = new ArrayList<>();
+
+ for (int i = 1; i <= numPeople; i++) {
+ personList.add(new Person.Builder()
+ .setName("Person " + i)
+ .setIcon(IconCompat.createWithResource(v.getContext(),
+ i % 2 == 1 ? R.drawable.avatar1 : R.drawable.avatar2))
+ .build());
+ }
+
+ MessagingStyle messagingStyle =
+ new MessagingStyle(personList.get(0))
+ .setConversationTitle("Customizable Group chat");
+ if (personList.size() > 1) {
+ messagingStyle.setGroupConversation(true);
+ }
+
+ int messageNumber = 1;
+ for (int i = 0; i < numMessages; i++) {
+ int personNum = i % numPeople;
+ if (personNum == numPeople - 1) {
+ messageNumber++;
+ }
+ Person person = personList.get(personNum);
+ String messageText = person.getName() + "'s " + messageNumber + " message";
+ messagingStyle.addMessage(
+ new MessagingStyle.Message(
+ messageText,
+ System.currentTimeMillis(),
+ person));
+ }
+
+ NotificationCompat.Builder notification = new NotificationCompat
+ .Builder(mContext, IMPORTANCE_HIGH_ID)
+ .setContentTitle("Customizable Group chat (Title)")
+ .setContentText("Customizable Group chat (Text)")
+ .setShowWhen(true)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setSmallIcon(R.drawable.car_ic_mode)
+ .setStyle(messagingStyle)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(android.R.color.holo_green_light))
+ .addAction(
+ new Action.Builder(R.drawable.ic_check_box, "read", markAsReadIntent)
+ .setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
+ .setShowsUserInterface(false)
+ .build())
+ .addAction(
+ new Action.Builder(R.drawable.ic_check_box, "reply", replyIntent)
+ .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)
+ .setShowsUserInterface(false)
+ .addRemoteInput(new RemoteInput.Builder("input").build())
+ .build());
+
+ mManager.notify(id, notification.build());
+ });
+ }
+
private void initMessagingStyleButtonForDiffPerson(View view) {
view.findViewById(R.id.category_message_diff_person_button).setOnClickListener(v -> {
int id = mCurrentNotificationId++;
diff --git a/tests/NetworkPreferenceApp/AndroidManifest.xml b/tests/NetworkPreferenceApp/AndroidManifest.xml
index dd96509..537885a 100644
--- a/tests/NetworkPreferenceApp/AndroidManifest.xml
+++ b/tests/NetworkPreferenceApp/AndroidManifest.xml
@@ -39,7 +39,7 @@
<application android:label="NetworkPreferenceApp">
<receiver
- android:name=".ApplyPansOnBootCompleteReceiver" >
+ android:name=".RunOnBootCompleteTasksReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
diff --git a/tests/NetworkPreferenceApp/res/layout/manager.xml b/tests/NetworkPreferenceApp/res/layout/manager.xml
index 5cfcdb3..0f826ee 100644
--- a/tests/NetworkPreferenceApp/res/layout/manager.xml
+++ b/tests/NetworkPreferenceApp/res/layout/manager.xml
@@ -47,14 +47,34 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:weightSum="2">
- <TextView
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/label_apply_latest_policy_on_boot"/>
- <Switch
- android:id="@+id/reapplyPANSOnBootSwitch"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_weight="1"
+ android:weightSum="2">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_apply_latest_policy_on_boot"/>
+ <Switch
+ android:id="@+id/reapplyPANSOnBootSwitch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:weightSum="2">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_apply_wifi_policy_on_boot"/>
+ <Switch
+ android:id="@+id/reapplyWifiSuggestionsOnBootSwitch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
diff --git a/tests/NetworkPreferenceApp/res/values/strings.xml b/tests/NetworkPreferenceApp/res/values/strings.xml
index f6eb590..b70d475 100644
--- a/tests/NetworkPreferenceApp/res/values/strings.xml
+++ b/tests/NetworkPreferenceApp/res/values/strings.xml
@@ -27,7 +27,8 @@
<string name="button_apply_configuration" translatable="false">Apply Configuration</string>
<string name="button_reset_network_preference" translatable="false">Reset Network Preference</string>
<string name="label_is_pans_overridden" translatable="false">Is PANS overridden?</string>
- <string name="label_apply_latest_policy_on_boot" translatable="false">Apply latest policy on device boot?</string>
+ <string name="label_apply_latest_policy_on_boot" translatable="false">Apply current PANS policy on boot?</string>
+ <string name="label_apply_wifi_policy_on_boot" translatable="false">Apply current Wifi policy on boot?</string>
<!-- Metrics table string -->
<string name="label_metric_oem_paid" translatable="false">OEM Paid</string>
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/ApplyPansOnBootCompleteReceiver.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/ApplyPansOnBootCompleteReceiver.java
deleted file mode 100644
index d6209c5..0000000
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/ApplyPansOnBootCompleteReceiver.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.google.android.car.networking.preferenceupdater;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import com.google.android.car.networking.preferenceupdater.components.OemNetworkPreferencesAdapter;
-import com.google.android.car.networking.preferenceupdater.components.PersonalStorage;
-
-/**
- * This class is responsible to apply PANS Policy if it was requested to be persisted within in the
- * application. If it was requested, PersonalStorage should have information about it.
- */
-public final class ApplyPansOnBootCompleteReceiver extends BroadcastReceiver {
- private static final String TAG = ApplyPansOnBootCompleteReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // Since we are listening to "android.intent.action.BOOT_COMPLETED" only, we don't need
- // to validate nor check if we are triggered on the right intent. Though let's log it.
- Log.v(TAG, "Action intent received. User: " + context.getUserId() + ", Intent:" + intent);
-
- PersonalStorage storage = new PersonalStorage(context);
- if (storage.getReapplyPansOnBootCompleteState()) {
- // This means we have PANS reapply logic on boot_complete enabled. Applying.
- Log.v(TAG, "PANS Reapply logic enabled. Applying logic from PersonalStorage");
-
- // Get all preferences from PersonalStorage
- new OemNetworkPreferencesAdapter(context).applyPreference(storage.getAllPrefApps());
- } else {
- // This means PANS reapply logic is disabled
- Log.v(TAG, "NetworkPreferencesApp reapply PANS logic is disabled.");
- }
- }
-}
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/RunOnBootCompleteTasksReceiver.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/RunOnBootCompleteTasksReceiver.java
new file mode 100644
index 0000000..c5cb2b4
--- /dev/null
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/RunOnBootCompleteTasksReceiver.java
@@ -0,0 +1,93 @@
+/*
+ * 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.google.android.car.networking.preferenceupdater;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.util.Log;
+
+import com.google.android.car.networking.preferenceupdater.components.OemNetworkPreferencesAdapter;
+import com.google.android.car.networking.preferenceupdater.components.PersonalStorage;
+import com.google.android.car.networking.preferenceupdater.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * This class is responsible to apply PANS Policy if it was requested to be persisted within in the
+ * application. If it was requested, PersonalStorage should have information about it.
+ */
+public final class RunOnBootCompleteTasksReceiver extends BroadcastReceiver {
+ private static final String TAG = RunOnBootCompleteTasksReceiver.class.getSimpleName();
+ PersonalStorage mStorage;
+ WifiManager mWifiManager;
+ OemNetworkPreferencesAdapter mOemNetworkPreferencesAdapter;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Since we are listening to "android.intent.action.BOOT_COMPLETED" only, we don't need
+ // to validate nor check if we are triggered on the right intent. Though let's log it.
+ Log.v(TAG, "Action intent received. User: " + context.getUserId() + ", Intent:" + intent);
+ mStorage = new PersonalStorage(context);
+ mWifiManager = context.getSystemService(WifiManager.class);
+ mOemNetworkPreferencesAdapter = new OemNetworkPreferencesAdapter(context);
+
+ reapplyPANSOnBoot();
+ reapplyWifiOnBoot();
+ }
+
+ public void reapplyPANSOnBoot() {
+ if (mStorage.getReapplyPansOnBootCompleteState()) {
+ // This means we have PANS reapply logic on boot_complete enabled. Applying.
+ Log.v(TAG, "PANS Reapply logic enabled. Applying logic from PersonalStorage");
+
+ // Get all preferences from PersonalStorage
+ mOemNetworkPreferencesAdapter.applyPreference(mStorage.getAllPrefApps());
+ } else {
+ // This means PANS reapply logic is disabled
+ Log.v(TAG, "NetworkPreferencesApp reapply PANS logic is disabled.");
+ }
+ }
+
+ public void reapplyWifiOnBoot() {
+ if (mStorage.getReapplyWifiOnBootCompleteState()) {
+ // Need to reapply Wifi policy
+ Log.v(TAG, "WIFI Reapply logic is triggered. Applying...");
+
+ Set<String> ssidsWithOemPaid = mStorage.getOemPaidWifiSsids();
+ Set<String> ssidsWithOemPrivate = mStorage.getOemPrivateWifiSsids();
+ try {
+ ArrayList<WifiNetworkSuggestion> list = new ArrayList<>();
+ for (String ssid : ssidsWithOemPaid) {
+ list.add(Utils.buildWifiSuggestion(ssid, true));
+ }
+
+ for (String ssid : ssidsWithOemPrivate) {
+ list.add(Utils.buildWifiSuggestion(ssid, false));
+ }
+
+ mWifiManager.removeNetworkSuggestions(new ArrayList<>());
+ mWifiManager.addNetworkSuggestions(list);
+ } catch (Exception e) {
+ // Could not set Wifi capabilities, pop the toast and do nothing
+ Log.e(TAG, "Failed to set Wifi capabilities", e);
+ }
+ }
+ }
+}
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/PersonalStorage.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/PersonalStorage.java
index a836190..36aaa26 100644
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/PersonalStorage.java
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/components/PersonalStorage.java
@@ -36,6 +36,8 @@
private static final String KEY_REAPPLY_PANS_ON_BOOT_COMPLETE =
"key_reapply_pans_on_boot_complete";
+ private static final String KEY_REAPPLY_WIFI_ON_BOOT_COMPLETE =
+ "key_reapply_wifi_on_boot_complete";
private static final String KEY_OEM_PAID_WIFI_SSIDS = "key_oem_paid_wifi_ssids";
private static final String KEY_OEM_PRIVATE_WIFI_SSIDS = "key_oem_private_wifi_ssids";
@@ -80,23 +82,21 @@
}
public Set<String> getOemPaidWifiSsids() {
- return mSharedPrefs.getStringSet(KEY_OEM_PAID_WIFI_SSIDS, new HashSet<String>());
+ return mSharedPrefs.getStringSet(KEY_OEM_PAID_WIFI_SSIDS, new HashSet<>());
}
public Set<String> getOemPrivateWifiSsids() {
- return mSharedPrefs.getStringSet(KEY_OEM_PRIVATE_WIFI_SSIDS, new HashSet<String>());
- }
-
- public void saveReapplyPansOnBootCompleteState(boolean checked) {
- SharedPreferences.Editor editor = mSharedPrefs.edit();
- editor.putBoolean(KEY_REAPPLY_PANS_ON_BOOT_COMPLETE, checked);
- editor.apply();
+ return mSharedPrefs.getStringSet(KEY_OEM_PRIVATE_WIFI_SSIDS, new HashSet<>());
}
public boolean getReapplyPansOnBootCompleteState() {
return mSharedPrefs.getBoolean(KEY_REAPPLY_PANS_ON_BOOT_COMPLETE, false);
}
+ public boolean getReapplyWifiOnBootCompleteState() {
+ return mSharedPrefs.getBoolean(KEY_REAPPLY_WIFI_ON_BOOT_COMPLETE, false);
+ }
+
public SparseArray<Set<String>> getAllPrefApps() {
SparseArray<Set<String>> prefs = new SparseArray<>();
for (int type : OEM_NETWORK_PREFERENCE_ARRAY) {
@@ -137,4 +137,16 @@
return Integer.toHexString(value);
}
}
+
+ public void saveReapplyPansOnBootCompleteState(boolean isChecked) {
+ SharedPreferences.Editor editor = mSharedPrefs.edit();
+ editor.putBoolean(KEY_REAPPLY_PANS_ON_BOOT_COMPLETE, isChecked);
+ editor.apply();
+ }
+
+ public void saveReapplyWifiOnBootCompleteState(boolean isChecked) {
+ SharedPreferences.Editor editor = mSharedPrefs.edit();
+ editor.putBoolean(KEY_REAPPLY_WIFI_ON_BOOT_COMPLETE, isChecked);
+ editor.apply();
+ }
}
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
index a6f6e6b..2fc6cfb 100644
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
@@ -29,7 +29,6 @@
import android.net.NetworkIdentity;
import android.net.NetworkRequest;
import android.net.NetworkTemplate;
-import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSuggestion;
import android.os.Bundle;
@@ -98,6 +97,7 @@
private EditText mOEMPrivateOnlyAppsEditText;
private TextView mCurrentPANSStatusTextView;
private Switch mReapplyPANSOnBootSwitch;
+ private Switch mReapplyWifiSuggestionsOnBootSwitch;
private Button mApplyConfigurationBtn;
private Button mResetNetworkPreferencesBtn;
private Button mApplyWifiCapabilitiesBtn;
@@ -156,6 +156,8 @@
mOEMPrivateWifiSSIDsEditText = v.findViewById(R.id.OEMPrivateWifiSSIDsEditText);
mCurrentPANSStatusTextView = v.findViewById(R.id.currentPANSStatusTextView);
mReapplyPANSOnBootSwitch = v.findViewById(R.id.reapplyPANSOnBootSwitch);
+ mReapplyWifiSuggestionsOnBootSwitch = v.findViewById(
+ R.id.reapplyWifiSuggestionsOnBootSwitch);
mApplyConfigurationBtn = v.findViewById(R.id.applyConfigurationBtn);
mResetNetworkPreferencesBtn = v.findViewById(R.id.resetNetworkPreferencesBtn);
mApplyWifiCapabilitiesBtn = v.findViewById(R.id.applyWifiCapabilitiesButton);
@@ -176,16 +178,16 @@
private void updateMetricIndicatorByType(int type, long rx, long tx) {
switch (type) {
case NetworkIdentity.OEM_PAID:
- mOemPaidRxBytesTextView.setText("RX: " + String.valueOf(rx));
- mOemPaidTxBytesTextView.setText("TX: " + String.valueOf(tx));
+ mOemPaidRxBytesTextView.setText("RX: " + rx);
+ mOemPaidTxBytesTextView.setText("TX: " + tx);
break;
case NetworkIdentity.OEM_PRIVATE:
- mOemPrivateRxBytesTextView.setText("RX: " + String.valueOf(rx));
- mOemPrivateTxBytesTextView.setText("TX: " + String.valueOf(tx));
+ mOemPrivateRxBytesTextView.setText("RX: " + rx);
+ mOemPrivateTxBytesTextView.setText("TX: " + tx);
break;
case NetworkTemplate.OEM_MANAGED_YES:
- mOemTotalRxBytesTextView.setText("RX: " + String.valueOf(rx));
- mOemTotalTxBytesTextView.setText("TX: " + String.valueOf(tx));
+ mOemTotalRxBytesTextView.setText("RX: " + rx);
+ mOemTotalTxBytesTextView.setText("TX: " + tx);
break;
default:
Log.e(TAG, "Unknown NetworkIdentity " + type);
@@ -199,7 +201,10 @@
mResetWifiCapabilitiesBtn.setOnClickListener(view -> onResetWifiCapabilitiesBtnClick());
mReapplyPANSOnBootSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) ->
- mPersonalStorage.saveReapplyPansOnBootCompleteState(true));
+ mPersonalStorage.saveReapplyPansOnBootCompleteState(isChecked));
+ mReapplyWifiSuggestionsOnBootSwitch.setOnCheckedChangeListener(
+ (buttonView, isChecked) ->
+ mPersonalStorage.saveReapplyWifiOnBootCompleteState(isChecked));
mResetNetworkPreferencesBtn.setOnClickListener(view -> resetNetworkPreferences());
mConnectToOemPaidWifiSwitch.setOnCheckedChangeListener(
@@ -270,7 +275,7 @@
mOEMPaidWifiSSIDsEditText.setText(Utils.toString(mPersonalStorage.getOemPaidWifiSsids()));
mOEMPrivateWifiSSIDsEditText.setText(
Utils.toString(mPersonalStorage.getOemPrivateWifiSsids()));
- updatePansPolicyInEffectStatus(false);
+ updatePansPolicyInEffectStatus(mPersonalStorage.getReapplyPansOnBootCompleteState());
}
private String getFromStorage(int type) {
@@ -284,21 +289,6 @@
mCurrentPANSStatusTextView.setText(status ? "Yes" : "No");
}
- private WifiNetworkSuggestion buildWifiSuggestion(String ssid, boolean isOemPaid) {
- WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder();
- String[] elements = ssid.split(":");
- builder.setSsid(WifiInfo.sanitizeSsid(elements[0]));
- if (elements.length > 1) {
- builder.setWpa2Passphrase(elements[1]);
- }
- if (isOemPaid) {
- builder.setOemPaid(true);
- } else {
- builder.setOemPrivate(true);
- }
- return builder.build();
- }
-
private void onApplyWifiCapabilitiesBtnClick() {
Log.d(TAG, "Applying WiFi settings");
Set<String> ssidsWithOemPaid = Utils.toSet(mOEMPaidWifiSSIDsEditText.getText().toString());
@@ -307,11 +297,11 @@
try {
ArrayList<WifiNetworkSuggestion> list = new ArrayList<>();
for (String ssid : ssidsWithOemPaid) {
- list.add(buildWifiSuggestion(ssid, true));
+ list.add(Utils.buildWifiSuggestion(ssid, true));
}
for (String ssid : ssidsWithOemPrivate) {
- list.add(buildWifiSuggestion(ssid, false));
+ list.add(Utils.buildWifiSuggestion(ssid, false));
}
mWifiManager.removeNetworkSuggestions(new ArrayList<>());
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/utils/Utils.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/utils/Utils.java
index 53d8524..b67b2fb 100644
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/utils/Utils.java
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/utils/Utils.java
@@ -15,6 +15,8 @@
*/
package com.google.android.car.networking.preferenceupdater.utils;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkSuggestion;
import android.text.TextUtils;
import java.util.Set;
@@ -32,4 +34,20 @@
public static Set<String> toSet(String st) {
return Stream.of(TextUtils.split(st, ",")).collect(Collectors.toSet());
}
+
+ /** Builds wifi suggestions based on provided ssid and whether it is oem paid */
+ public static WifiNetworkSuggestion buildWifiSuggestion(String ssid, boolean isOemPaid) {
+ WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder();
+ String[] elements = ssid.split(":");
+ builder.setSsid(WifiInfo.sanitizeSsid(elements[0]));
+ if (elements.length > 1) {
+ builder.setWpa2Passphrase(elements[1]);
+ }
+ if (isOemPaid) {
+ builder.setOemPaid(true);
+ } else {
+ builder.setOemPrivate(true);
+ }
+ return builder.build();
+ }
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index af81c61..276e829 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -36,6 +36,8 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -246,6 +248,24 @@
APP_FOCUS_TYPE_NAVIGATION, false)).isTrue();
}
+ @Test
+ public void testGetAppTypeOwner() throws Exception {
+ CarAppFocusManager manager = createManager(getContext(), mEventThread);
+
+ assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)).isNull();
+
+ FocusOwnershipCallback owner = new FocusOwnershipCallback();
+ assertThat(manager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner))
+ .isEqualTo(APP_FOCUS_REQUEST_SUCCEEDED);
+
+ assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION))
+ .containsExactly("android.car.apitest", "com.google.android.car.kitchensink");
+
+ manager.abandonAppFocus(owner, APP_FOCUS_TYPE_NAVIGATION);
+
+ assertThat(manager.getAppTypeOwner(APP_FOCUS_TYPE_NAVIGATION)).isNull();
+ }
+
private CarAppFocusManager createManager() throws InterruptedException {
return createManager(getContext(), mEventThread);
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java
index 9fb2781..cab66f9 100644
--- a/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/media/CarAudioManagerTest.java
@@ -16,14 +16,18 @@
package android.car.apitest.media;
+import static android.car.Car.AUDIO_SERVICE;
+import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING;
+import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
+import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
-import android.car.Car;
import android.car.apitest.CarApiTestBase;
import android.car.media.CarAudioManager;
-import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.os.Process;
@@ -38,11 +42,13 @@
@RunWith(AndroidJUnit4.class)
public class CarAudioManagerTest extends CarApiTestBase {
+ private static final int TEST_FLAGS = 0;
+
private CarAudioManager mCarAudioManager;
@Before
public void setUp() throws Exception {
- mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+ mCarAudioManager = (CarAudioManager) getCar().getCarManager(AUDIO_SERVICE);
assertThat(mCarAudioManager).isNotNull();
}
@@ -52,21 +58,21 @@
List<Integer> zoneIds = mCarAudioManager.getAudioZoneIds();
assertThat(zoneIds).isNotEmpty();
- assertThat(zoneIds).contains(CarAudioManager.PRIMARY_AUDIO_ZONE);
+ assertThat(zoneIds).contains(PRIMARY_AUDIO_ZONE);
}
@Test
public void test_isAudioFeatureEnabled() throws Exception {
// nothing to assert. Just call the API.
- mCarAudioManager.isAudioFeatureEnabled(CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING);
- mCarAudioManager.isAudioFeatureEnabled(CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING);
+ mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING);
+ mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING);
}
@Test
public void test_getVolumeGroupCount() throws Exception {
int primaryZoneCount = mCarAudioManager.getVolumeGroupCount();
assertThat(
- mCarAudioManager.getVolumeGroupCount(CarAudioManager.PRIMARY_AUDIO_ZONE)).isEqualTo(
+ mCarAudioManager.getVolumeGroupCount(PRIMARY_AUDIO_ZONE)).isEqualTo(
primaryZoneCount);
}
@@ -74,14 +80,14 @@
public void test_getGroupVolume() throws Exception {
int groudId = 0;
int volume = mCarAudioManager.getGroupVolume(groudId);
- assertThat(mCarAudioManager.getGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
- groudId)).isEqualTo(volume);
+ assertThat(mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groudId))
+ .isEqualTo(volume);
int maxVolume = mCarAudioManager.getGroupMaxVolume(groudId);
- assertThat(mCarAudioManager.getGroupMaxVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
- groudId)).isEqualTo(maxVolume);
+ assertThat(mCarAudioManager.getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groudId))
+ .isEqualTo(maxVolume);
int minVolume = mCarAudioManager.getGroupMinVolume(groudId);
- assertThat(mCarAudioManager.getGroupMinVolume(CarAudioManager.PRIMARY_AUDIO_ZONE,
- groudId)).isEqualTo(minVolume);
+ assertThat(mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groudId))
+ .isEqualTo(minVolume);
assertThat(volume).isAtLeast(minVolume);
assertThat(volume).isAtMost(maxVolume);
}
@@ -90,8 +96,8 @@
public void test_setGroupVolume() throws Exception {
int groudId = 0;
int volume = mCarAudioManager.getGroupVolume(groudId);
- mCarAudioManager.setGroupVolume(groudId, volume, 0);
- mCarAudioManager.setGroupVolume(CarAudioManager.PRIMARY_AUDIO_ZONE, groudId, volume, 0);
+ mCarAudioManager.setGroupVolume(groudId, volume, TEST_FLAGS);
+ mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groudId, volume, TEST_FLAGS);
assertThat(mCarAudioManager.getGroupVolume(groudId)).isEqualTo(volume);
}
@@ -99,8 +105,7 @@
public void test_getInputDevicesForZoneId() throws Exception {
assumeDynamicRoutingIsEnabled();
- List<AudioDeviceInfo> info = mCarAudioManager.getInputDevicesForZoneId(
- CarAudioManager.PRIMARY_AUDIO_ZONE);
+ List<AudioDeviceInfo> info = mCarAudioManager.getInputDevicesForZoneId(PRIMARY_AUDIO_ZONE);
assertThat(info).isNotEmpty();
}
@@ -109,15 +114,15 @@
assumeDynamicRoutingIsEnabled();
AudioDeviceInfo device = mCarAudioManager.getOutputDeviceForUsage(
- CarAudioManager.PRIMARY_AUDIO_ZONE, AudioAttributes.USAGE_MEDIA);
+ PRIMARY_AUDIO_ZONE, USAGE_MEDIA);
assertThat(device).isNotNull();
}
@Test
public void test_getVolumeGroupIdForUsage() throws Exception {
- int groupId = mCarAudioManager.getVolumeGroupIdForUsage(AudioAttributes.USAGE_MEDIA);
- assertThat(mCarAudioManager.getVolumeGroupIdForUsage(CarAudioManager.PRIMARY_AUDIO_ZONE,
- AudioAttributes.USAGE_MEDIA)).isEqualTo(groupId);
+ int groupId = mCarAudioManager.getVolumeGroupIdForUsage(USAGE_MEDIA);
+ assertThat(mCarAudioManager.getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, USAGE_MEDIA))
+ .isEqualTo(groupId);
int primaryZoneCount = mCarAudioManager.getVolumeGroupCount();
assertThat(groupId).isLessThan(primaryZoneCount);
}
@@ -126,8 +131,99 @@
public void test_getZoneIdForUid() throws Exception {
assumeDynamicRoutingIsEnabled();
- assertThat(mCarAudioManager.getZoneIdForUid(Process.myUid())).isEqualTo(
- CarAudioManager.PRIMARY_AUDIO_ZONE);
+ assertThat(mCarAudioManager.getZoneIdForUid(Process.myUid())).isEqualTo(PRIMARY_AUDIO_ZONE);
+ }
+
+ @Test
+ public void setVolumeGroupMute_toMute_mutesVolumeGroup() throws Exception {
+ assumeVolumeGroupMutingIsEnabled();
+ int groupId = 0;
+ boolean muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+
+ try {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+ assertThat(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId))
+ .isEqualTo(true);
+ } finally {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+ }
+ }
+
+ @Test
+ public void setVolumeGroupMute_toUnMute_unMutesVolumeGroup() throws Exception {
+ assumeVolumeGroupMutingIsEnabled();
+ int groupId = 0;
+ boolean muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+
+ try {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, false, TEST_FLAGS);
+ assertThat(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId))
+ .isEqualTo(false);
+ } finally {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+ }
+ }
+
+ @Test
+ public void setGroupVolume_whileMuted_unMutesVolumeGroup() throws Exception {
+ assumeVolumeGroupMutingIsEnabled();
+ int groupId = 0;
+ boolean muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+ int volume = mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
+ int minVolume = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
+
+ try {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+
+ mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, minVolume, TEST_FLAGS);
+ assertThat(mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId))
+ .isEqualTo(false);
+ } finally {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+ mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, volume, TEST_FLAGS);
+ }
+ }
+
+ @Test
+ public void getGroupVolume_whileMuted_returnsMinVolume() throws Exception {
+ assumeVolumeGroupMutingIsEnabled();
+ int groupId = 0;
+ boolean muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+ int minVolume = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
+
+ try {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+
+ assertThat(mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId))
+ .isEqualTo(minVolume);
+ } finally {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+ }
+ }
+
+ @Test
+ public void getGroupVolume_whileUnMuted_returnLastSetValue() throws Exception {
+ assumeVolumeGroupMutingIsEnabled();
+ int groupId = 0;
+ boolean muteState = mCarAudioManager.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, groupId);
+ int volume = mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
+ int minVolume = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
+ int maxVolume = mCarAudioManager.getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
+ int testVolume = (minVolume + maxVolume) / 2;
+
+ try {
+ mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, testVolume, TEST_FLAGS);
+
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, true, TEST_FLAGS);
+
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, false, TEST_FLAGS);
+
+ assertThat(mCarAudioManager.getGroupVolume(PRIMARY_AUDIO_ZONE, groupId))
+ .isEqualTo(testVolume);
+ } finally {
+ mCarAudioManager.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, groupId, muteState, TEST_FLAGS);
+ mCarAudioManager.setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, volume, TEST_FLAGS);
+ }
}
@Test
@@ -136,12 +232,14 @@
// TODO(b/191660867): Better to change this to play something and asert true.
assertThat(
- mCarAudioManager.isPlaybackOnVolumeGroupActive(CarAudioManager.PRIMARY_AUDIO_ZONE,
+ mCarAudioManager.isPlaybackOnVolumeGroupActive(PRIMARY_AUDIO_ZONE,
0)).isFalse();
}
private void assumeDynamicRoutingIsEnabled() {
- assumeTrue(mCarAudioManager.isAudioFeatureEnabled(
- CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING));
+ assumeTrue(mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING));
+ }
+ private void assumeVolumeGroupMutingIsEnabled() {
+ assumeTrue(mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING));
}
}
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 7bf749e..47dbb26 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -59,6 +59,11 @@
<meta-data android:name="distractionOptimized"
android:value="true"/>
</activity>
+ <activity android:name="androidx.car.app.activity.CarAppActivity"
+ android:label="CarAppActivity">
+ <meta-data android:name="distractionOptimized"
+ android:value="true"/>
+ </activity>
<receiver android:name="com.android.car.CarStorageMonitoringBroadcastReceiver"
android:exported="true"
diff --git a/tests/carservice_test/src/androidx/car/app/activity/CarAppActivity.java b/tests/carservice_test/src/androidx/car/app/activity/CarAppActivity.java
new file mode 100644
index 0000000..b42b7cc
--- /dev/null
+++ b/tests/carservice_test/src/androidx/car/app/activity/CarAppActivity.java
@@ -0,0 +1,90 @@
+/*
+ * 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 androidx.car.app.activity;
+
+
+import static com.android.car.pm.ActivityBlockingActivityTest.DoActivity.DIALOG_TITLE;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+/**
+ * An activity to represent a template activity in tests.
+ */
+public class CarAppActivity extends Activity {
+ public static final String ACTION_SHOW_DIALOG = "SHOW_DIALOG";
+ public static final String ACTION_START_SECOND_INSTANCE = "START_SECOND_INSTANCE";
+ public static final String SECOND_INSTANCE_TITLE = "Second Instance";
+ private static final String BUNDLE_KEY_IS_SECOND_INSTANCE = "is_second_instance";
+
+ private final ShowDialogReceiver mShowDialogReceiver = new ShowDialogReceiver();
+ private final StartSecondInstanceReceiver
+ mStartSecondInstanceReceiver = new StartSecondInstanceReceiver();
+
+ private class ShowDialogReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ showDialog();
+ }
+ }
+
+ private class StartSecondInstanceReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ startSecondInstance();
+ }
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getBooleanExtra(BUNDLE_KEY_IS_SECOND_INSTANCE, false)) {
+ getActionBar().setTitle(SECOND_INSTANCE_TITLE);
+ }
+ this.registerReceiver(mShowDialogReceiver, new IntentFilter(ACTION_SHOW_DIALOG));
+ this.registerReceiver(mStartSecondInstanceReceiver,
+ new IntentFilter(ACTION_START_SECOND_INSTANCE));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ this.unregisterReceiver(mShowDialogReceiver);
+ this.unregisterReceiver(mStartSecondInstanceReceiver);
+ }
+
+ private void startSecondInstance() {
+ Intent intent = new Intent(CarAppActivity.this, CarAppActivity.class);
+ intent.putExtra(BUNDLE_KEY_IS_SECOND_INSTANCE, true);
+ startActivity(intent);
+ }
+
+ private void showDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setTitle(DIALOG_TITLE)
+ .setMessage("Message")
+ .create();
+ dialog.show();
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/AppFocusTest.java b/tests/carservice_test/src/com/android/car/AppFocusTest.java
index 390acf2..1afb52f 100644
--- a/tests/carservice_test/src/com/android/car/AppFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/AppFocusTest.java
@@ -16,6 +16,9 @@
package com.android.car;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import static com.google.common.truth.Truth.assertThat;
import android.car.Car;
import android.car.CarAppFocusManager;
@@ -46,10 +49,13 @@
manager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, ownershipListener);
listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true);
+ assertThat(manager.getAppTypeOwner(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION))
+ .containsExactly("com.android.car.test", "com.google.android.car.kitchensink");
listener.resetWait();
manager.abandonAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false);
+ assertNull(manager.getAppTypeOwner(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
manager.removeFocusListener(listener);
}
diff --git a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
index 69632f0..b8c4eab 100644
--- a/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
+++ b/tests/carservice_test/src/com/android/car/pm/ActivityBlockingActivityTest.java
@@ -16,12 +16,18 @@
package com.android.car.pm;
+import static androidx.car.app.activity.CarAppActivity.ACTION_SHOW_DIALOG;
+import static androidx.car.app.activity.CarAppActivity.ACTION_START_SECOND_INSTANCE;
+import static androidx.car.app.activity.CarAppActivity.SECOND_INSTANCE_TITLE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.AlertDialog;
+import android.app.UiAutomation;
import android.car.Car;
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarDrivingStateManager;
@@ -30,10 +36,12 @@
import android.content.Intent;
import android.os.Bundle;
import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
import android.view.Display;
+import androidx.car.app.activity.CarAppActivity;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
@@ -72,6 +80,8 @@
car.getCarManager(Car.CAR_DRIVING_STATE_SERVICE);
assertNotNull(mCarDrivingStateManager);
+ Configurator.getInstance()
+ .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
setDrivingStateMoving();
@@ -98,6 +108,58 @@
}
@Test
+ public void testBlockingActivity_doActivity_showingDialog_isNotBlocked() throws Exception {
+ Intent intent = new Intent();
+ intent.putExtra(DoActivity.INTENT_EXTRA_SHOW_DIALOG, true);
+ intent.setComponent(toComponentName(getTestContext(), DoActivity.class));
+ startActivity(intent);
+
+ assertThat(mDevice.wait(Until.findObject(By.text(
+ DoActivity.DIALOG_TITLE)),
+ UI_TIMEOUT_MS)).isNotNull();
+ assertBlockingActivityNotFound();
+ }
+
+ @Test
+ public void testBlockingActivity_doTemplateActivity_isNotBlocked() throws Exception {
+ startActivity(toComponentName(getTestContext(), CarAppActivity.class));
+
+ assertThat(mDevice.wait(Until.findObject(By.text(
+ CarAppActivity.class.getSimpleName())),
+ UI_TIMEOUT_MS)).isNotNull();
+ assertBlockingActivityNotFound();
+ }
+
+ @Test
+ public void testBlockingActivity_multipleDoTemplateActivity_notBlocked() throws Exception {
+ startActivity(toComponentName(getTestContext(), CarAppActivity.class));
+ assertThat(mDevice.wait(Until.findObject(By.text(
+ CarAppActivity.class.getSimpleName())),
+ UI_TIMEOUT_MS)).isNotNull();
+ getContext().sendBroadcast(new Intent().setAction(ACTION_START_SECOND_INSTANCE));
+ assertThat(mDevice.wait(Until.findObject(By.text(
+ SECOND_INSTANCE_TITLE)),
+ UI_TIMEOUT_MS)).isNotNull();
+ assertBlockingActivityNotFound();
+ }
+
+ @Test
+ public void testBlockingActivity_doTemplateActivity_showingDialog_isBlocked() throws Exception {
+ startActivity(toComponentName(getTestContext(), CarAppActivity.class));
+ assertThat(mDevice.wait(Until.findObject(By.text(
+ CarAppActivity.class.getSimpleName())),
+ UI_TIMEOUT_MS)).isNotNull();
+ assertBlockingActivityNotFound();
+
+ getContext().sendBroadcast(new Intent().setAction(ACTION_SHOW_DIALOG));
+ assertThat(mDevice.wait(Until.findObject(By.text(DoActivity.DIALOG_TITLE)),
+ UI_TIMEOUT_MS)).isNotNull();
+
+ assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
+ UI_TIMEOUT_MS)).isNotNull();
+ }
+
+ @Test
public void testBlockingActivity_nonDoActivity_isBlocked() throws Exception {
startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING);
@@ -153,11 +215,13 @@
private void startActivity(ComponentName name) {
Intent intent = new Intent();
intent.setComponent(name);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ private void startActivity(Intent intent) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
-
getContext().startActivity(intent, options.toBundle());
}
@@ -240,6 +304,20 @@
}
public static class DoActivity extends TempActivity {
+ public static final String INTENT_EXTRA_SHOW_DIALOG = "SHOW_DIALOG";
+ public static final String DIALOG_TITLE = "Title";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getBooleanExtra(INTENT_EXTRA_SHOW_DIALOG, false)) {
+ AlertDialog dialog = new AlertDialog.Builder(DoActivity.this)
+ .setTitle(DIALOG_TITLE)
+ .setMessage("Message")
+ .create();
+ dialog.show();
+ }
+ }
}
/** Activity that closes itself after some timeout to clean up the screen. */
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
index b832462..acfa606 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
@@ -16,6 +16,8 @@
package com.android.car;
+import static com.android.car.CarInputService.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -48,6 +50,7 @@
import com.android.car.hal.InputHalService;
import com.android.car.hal.UserHalService;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.car.pm.CarSafetyAccessibilityService;
import com.android.car.user.CarUserService;
import com.android.internal.app.AssistUtils;
import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -140,12 +143,21 @@
}
@Test
- public void rotaryServiceSettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+ public void accessibilitySettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+ final String existingService = "com.android.temp/com.android.car.TempService";
final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+ final String carSafetyAccessibilityService = mContext.getPackageName()
+ + "/"
+ + CarSafetyAccessibilityService.class.getName();
init(rotaryService);
assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
-
final int userId = 11;
+ Settings.Secure.putStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ existingService,
+ userId);
+
// By default RotaryService is not enabled.
String enabledServices = Settings.Secure.getStringForUser(
@@ -167,7 +179,12 @@
mMockContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userId);
- assertThat(enabledServices).contains(rotaryService);
+ assertThat(enabledServices).isEqualTo(
+ existingService
+ + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+ + carSafetyAccessibilityService
+ + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+ + rotaryService);
enabled = Settings.Secure.getStringForUser(
mMockContext.getContentResolver(),
@@ -177,7 +194,11 @@
}
@Test
- public void rotaryServiceSettingsNotUpdated_whenRotaryServiceIsEmpty() throws Exception {
+ public void accessibilitySettingsUpdated_withoutRotaryService_whenRotaryServiceIsEmpty()
+ throws Exception {
+ final String carSafetyAccessibilityService = mContext.getPackageName()
+ + "/"
+ + CarSafetyAccessibilityService.class.getName();
final String rotaryService = "";
init(rotaryService);
assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
@@ -199,7 +220,55 @@
mMockContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED,
userId);
+ assertThat(enabled).isEqualTo("1");
+ String enabledServices = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ assertThat(enabledServices).isEqualTo(carSafetyAccessibilityService);
+ }
+
+ @Test
+ public void accessibilitySettingsUpdated_accessibilityServicesAlreadyEnabled()
+ throws Exception {
+ final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+ final String carSafetyAccessibilityService = mContext.getPackageName()
+ + "/"
+ + CarSafetyAccessibilityService.class.getName();
+ init(rotaryService);
+ assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+ final int userId = 11;
+ Settings.Secure.putStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ carSafetyAccessibilityService
+ + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+ + rotaryService,
+ userId);
+
+ String enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
assertThat(enabled).isNull();
+
+ // Enable RotaryService by sending user switch event.
+ sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+ String enabledServices = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ assertThat(enabledServices).isEqualTo(
+ carSafetyAccessibilityService
+ + ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
+ + rotaryService);
+
+ enabled = Settings.Secure.getStringForUser(
+ mMockContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ userId);
+ assertThat(enabled).isEqualTo("1");
}
@After
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
index fc78159..b070bd8 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
@@ -153,6 +153,35 @@
}
@Test
+ public void onVolumeAdjustment_withAdjustRaise_whileMuted_setsGroupVolumeToMin() {
+ setGroupVolume(TEST_MAX_VOLUME);
+ setGroupVolumeMute(true);
+
+ CarAudioPolicyVolumeCallback callback =
+ new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
+
+
+ callback.onVolumeAdjustment(ADJUST_RAISE);
+
+ verify(mMockCarAudioService).setGroupVolume(PRIMARY_AUDIO_ZONE,
+ TEST_VOLUME_GROUP, TEST_MIN_VOLUME, TEST_EXPECTED_FLAGS);
+ }
+
+ @Test
+ public void onVolumeAdjustment_withAdjustLower_whileMuted_setsGroupVolumeToMin() {
+ setGroupVolume(TEST_MAX_VOLUME);
+ setGroupVolumeMute(true);
+
+ CarAudioPolicyVolumeCallback callback =
+ new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
+
+ callback.onVolumeAdjustment(ADJUST_LOWER);
+
+ verify(mMockCarAudioService).setGroupVolume(PRIMARY_AUDIO_ZONE,
+ TEST_VOLUME_GROUP, TEST_MIN_VOLUME, TEST_EXPECTED_FLAGS);
+ }
+
+ @Test
public void onVolumeAdjustment_withAdjustSame_doesNothing() {
setGroupVolume(TEST_VOLUME);
@@ -207,8 +236,8 @@
@Test
public void onVolumeAdjustment_forGroupMute_withAdjustToggleMute_togglesMutesVolumeGroup() {
- when(mMockCarAudioService.isVolumeGroupMuted(anyInt(), anyInt()))
- .thenReturn(true);
+ setGroupVolumeMute(true);
+
CarAudioPolicyVolumeCallback callback =
new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
@@ -220,7 +249,8 @@
@Test
public void onVolumeAdjustment_forGroupMute_withAdjustUnMute_UnMutesVolumeGroup() {
- when(mMockCarAudioService.isVolumeGroupMuted(anyInt(), anyInt())).thenReturn(false);
+ setGroupVolumeMute(false);
+
CarAudioPolicyVolumeCallback callback =
new CarAudioPolicyVolumeCallback(mMockCarAudioService, mMockAudioManager, true);
@@ -234,4 +264,9 @@
when(mMockCarAudioService.getGroupVolume(anyInt(), anyInt()))
.thenReturn(groupVolume);
}
+
+ private void setGroupVolumeMute(boolean mute) {
+ when(mMockCarAudioService.isVolumeGroupMuted(anyInt(), anyInt()))
+ .thenReturn(mute);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java
index de39471..ece0f00 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingUtilsTest.java
@@ -23,8 +23,15 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_SAFETY;
+import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE;
import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
+import static com.android.car.audio.CarAudioContext.CALL;
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
+import static com.android.car.audio.CarAudioContext.INVALID;
+import static com.android.car.audio.CarAudioContext.MUSIC;
+import static com.android.car.audio.CarAudioContext.NAVIGATION;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -161,6 +168,16 @@
}
@Test
+ public void getAddressesToDuck_doesNotConsidersInvalidUsage() {
+ CarAudioZone mockZone = generateAudioZoneMock();
+ int[] usages = new int[]{USAGE_VIRTUAL_SOURCE};
+
+ List<String> addresses = CarDuckingUtils.getAddressesToDuck(usages, mockZone);
+
+ assertThat(addresses).isEmpty();
+ }
+
+ @Test
public void getAddressesToDuck_withDuckedAndUnduckedContextsSharingDevice_excludesThatDevice() {
CarAudioZone mockZone = generateAudioZoneMock();
when(mockZone.getAddressForContext(CarAudioContext.SAFETY)).thenReturn(NAVIGATION_ADDRESS);
@@ -220,12 +237,11 @@
private static CarAudioZone generateAudioZoneMock() {
CarAudioZone mockZone = mock(CarAudioZone.class);
- when(mockZone.getAddressForContext(CarAudioContext.MUSIC)).thenReturn(MEDIA_ADDRESS);
- when(mockZone.getAddressForContext(CarAudioContext.EMERGENCY)).thenReturn(
- EMERGENCY_ADDRESS);
- when(mockZone.getAddressForContext(CarAudioContext.CALL)).thenReturn(CALL_ADDRESS);
- when(mockZone.getAddressForContext(CarAudioContext.NAVIGATION)).thenReturn(
- NAVIGATION_ADDRESS);
+ when(mockZone.getAddressForContext(MUSIC)).thenReturn(MEDIA_ADDRESS);
+ when(mockZone.getAddressForContext(EMERGENCY)).thenReturn(EMERGENCY_ADDRESS);
+ when(mockZone.getAddressForContext(CALL)).thenReturn(CALL_ADDRESS);
+ when(mockZone.getAddressForContext(NAVIGATION)).thenReturn(NAVIGATION_ADDRESS);
+ when(mockZone.getAddressForContext(INVALID)).thenThrow(new IllegalArgumentException());
return mockZone;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
index 9e38583..d71a0d5 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
@@ -16,7 +16,15 @@
package com.android.car.audio;
-import static com.google.common.truth.Truth.assertThat;
+import static com.android.car.audio.CarAudioContext.ALARM;
+import static com.android.car.audio.CarAudioContext.CALL;
+import static com.android.car.audio.CarAudioContext.CALL_RING;
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
+import static com.android.car.audio.CarAudioContext.MUSIC;
+import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.NOTIFICATION;
+
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -70,11 +78,12 @@
public void setDeviceInfoForContext_associatesDeviceAddresses() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo);
CarVolumeGroup carVolumeGroup = builder.build();
- assertThat(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
+ assertWithMessage("%s and %s", MEDIA_DEVICE_ADDRESS, NAVIGATION_DEVICE_ADDRESS)
+ .that(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
NAVIGATION_DEVICE_ADDRESS);
}
@@ -82,139 +91,149 @@
public void setDeviceInfoForContext_associatesContexts() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo);
CarVolumeGroup carVolumeGroup = builder.build();
- assertThat(carVolumeGroup.getContexts()).asList().containsExactly(CarAudioContext.MUSIC,
- CarAudioContext.NAVIGATION);
+ assertWithMessage("Music[%s] and Navigation[%s] Context", MUSIC, NAVIGATION)
+ .that(carVolumeGroup.getContexts()).asList().containsExactly(MUSIC, NAVIGATION);
}
@Test
public void setDeviceInfoForContext_withDifferentStepSize_throws() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo differentStepValueDevice = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setStepValue(mMediaDeviceInfo.getStepValue() + 1).build();
IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION,
+ () -> builder.setDeviceInfoForContext(NAVIGATION,
differentStepValueDevice));
- assertThat(thrown).hasMessageThat()
+ assertWithMessage("setDeviceInfoForContext failure for different step size")
+ .that(thrown).hasMessageThat()
.contains("Gain controls within one group must have same step value");
}
@Test
public void setDeviceInfoForContext_withSameContext_throws() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> builder.setDeviceInfoForContext(CarAudioContext.MUSIC,
+ () -> builder.setDeviceInfoForContext(MUSIC,
mNavigationDeviceInfo));
- assertThat(thrown).hasMessageThat()
- .contains("has already been set to");
+ assertWithMessage("setDeviceInfoForSameContext failure for repeated context")
+ .that(thrown).hasMessageThat().contains("has already been set to");
}
@Test
public void setDeviceInfoForContext_withFirstCall_setsMinGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
- assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+ assertWithMessage("Min Gain from builder")
+ .that(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
}
@Test
public void setDeviceInfoForContext_withFirstCall_setsMaxGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
- assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+ assertWithMessage("Max Gain from builder")
+ .that(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
}
@Test
public void setDeviceInfoForContext_withFirstCall_setsDefaultGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
- assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+ assertWithMessage("Default Gain from builder")
+ .that(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
}
@Test
public void setDeviceInfoForContext_SecondCallWithSmallerMinGain_updatesMinGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setMinGain(mMediaDeviceInfo.getMinGain() - 1).build();
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
- assertThat(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
+ assertWithMessage("Second, smaller min gain from builder")
+ .that(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
}
@Test
public void setDeviceInfoForContext_SecondCallWithLargerMinGain_keepsFirstMinGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setMinGain(mMediaDeviceInfo.getMinGain() + 1).build();
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
- assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+ assertWithMessage("First, smaller min gain from builder")
+ .that(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
}
@Test
public void setDeviceInfoForContext_SecondCallWithLargerMaxGain_updatesMaxGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setMaxGain(mMediaDeviceInfo.getMaxGain() + 1).build();
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
- assertThat(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
+ assertWithMessage("Second, larger max gain from builder")
+ .that(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
}
@Test
public void setDeviceInfoForContext_SecondCallWithSmallerMaxGain_keepsFirstMaxGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setMaxGain(mMediaDeviceInfo.getMaxGain() - 1).build();
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
- assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+ assertWithMessage("First, larger max gain from builder")
+ .that(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
}
@Test
public void setDeviceInfoForContext_SecondCallWithLargerDefaultGain_updatesDefaultGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setDefaultGain(mMediaDeviceInfo.getDefaultGain() + 1).build();
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
- assertThat(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
+ assertWithMessage("Second, larger default gain from builder")
+ .that(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
}
@Test
public void setDeviceInfoForContext_SecondCallWithSmallerDefaultGain_keepsFirstDefaultGain() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
.setDefaultGain(mMediaDeviceInfo.getDefaultGain() - 1).build();
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, secondInfo);
- assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+ assertWithMessage("Second, smaller default gain from builder")
+ .that(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
}
@Test
@@ -223,13 +242,14 @@
Exception e = expectThrows(IllegalArgumentException.class, builder::build);
- assertThat(e).hasMessageThat().isEqualTo(
- "setDeviceInfoForContext has to be called at least once before building");
+ assertWithMessage("Builder build failure").that(e).hasMessageThat()
+ .isEqualTo(
+ "setDeviceInfoForContext has to be called at least once before building");
}
@Test
public void builderBuild_withNoStoredGain_usesDefaultGain() {
- CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
mMediaDeviceInfo);
when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
GROUP_ID)).thenReturn(-1);
@@ -237,50 +257,55 @@
CarVolumeGroup carVolumeGroup = builder.build();
- assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ assertWithMessage("Current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
}
@Test
public void builderBuild_withTooLargeStoredGain_usesDefaultGain() {
- CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
mMediaDeviceInfo);
when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
GROUP_ID)).thenReturn(MAX_GAIN_INDEX + 1);
CarVolumeGroup carVolumeGroup = builder.build();
- assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ assertWithMessage("Current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
}
@Test
public void builderBuild_withTooSmallStoredGain_usesDefaultGain() {
- CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
mMediaDeviceInfo);
when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
GROUP_ID)).thenReturn(MIN_GAIN_INDEX - 1);
CarVolumeGroup carVolumeGroup = builder.build();
- assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ assertWithMessage("Current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
}
@Test
public void builderBuild_withValidStoredGain_usesStoredGain() {
- CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(MUSIC,
mMediaDeviceInfo);
when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
GROUP_ID)).thenReturn(MAX_GAIN_INDEX - 1);
CarVolumeGroup carVolumeGroup = builder.build();
- assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
+ assertWithMessage("Current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
}
@Test
public void getAddressForContext_withSupportedContext_returnsAddress() {
CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
- assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC))
+ assertWithMessage("Supported context's address")
+ .that(carVolumeGroup.getAddressForContext(MUSIC))
.isEqualTo(mMediaDeviceInfo.getAddress());
}
@@ -288,14 +313,16 @@
public void getAddressForContext_withUnsupportedContext_returnsNull() {
CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
- assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.NAVIGATION)).isNull();
+ assertWithMessage("Unsupported context's address")
+ .that(carVolumeGroup.getAddressForContext(NAVIGATION)).isNull();
}
@Test
public void isMuted_whenDefault_returnsFalse() {
CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
- assertThat(carVolumeGroup.isMuted()).isFalse();
+ assertWithMessage("Default mute state")
+ .that(carVolumeGroup.isMuted()).isFalse();
}
@Test
@@ -304,7 +331,8 @@
carVolumeGroup.setMute(true);
- assertThat(carVolumeGroup.isMuted()).isTrue();
+ assertWithMessage("Set mute state")
+ .that(carVolumeGroup.isMuted()).isTrue();
}
@Test
@@ -313,7 +341,8 @@
carVolumeGroup.setMute(false);
- assertThat(carVolumeGroup.isMuted()).isFalse();
+ assertWithMessage("Set mute state")
+ .that(carVolumeGroup.isMuted()).isFalse();
}
@Test
@@ -352,8 +381,9 @@
List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
- assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
- CarAudioContext.CALL, CarAudioContext.CALL_RING);
+ assertWithMessage("Contexts for bounded address %s", MEDIA_DEVICE_ADDRESS)
+ .that(contextsList).containsExactly(MUSIC,
+ CALL, CALL_RING);
}
@Test
@@ -362,7 +392,8 @@
List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
- assertThat(contextsList).isEmpty();
+ assertWithMessage("Contexts for non-bounded address %s", OTHER_ADDRESS)
+ .that(contextsList).isEmpty();
}
@Test
@@ -372,7 +403,8 @@
CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
MEDIA_DEVICE_ADDRESS);
- assertThat(actualDevice).isEqualTo(mMediaDeviceInfo);
+ assertWithMessage("Device information for bounded address %s", MEDIA_DEVICE_ADDRESS)
+ .that(actualDevice).isEqualTo(mMediaDeviceInfo);
}
@Test
@@ -382,7 +414,8 @@
CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
OTHER_ADDRESS);
- assertThat(actualDevice).isNull();
+ assertWithMessage("Device information for non-bounded address %s", OTHER_ADDRESS)
+ .that(actualDevice).isNull();
}
@Test
@@ -401,7 +434,8 @@
carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
- assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+ assertWithMessage("Updated current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
}
@Test
@@ -410,7 +444,8 @@
IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
() -> carVolumeGroup.setCurrentGainIndex(MIN_GAIN_INDEX - 1));
- assertThat(thrown).hasMessageThat()
+ assertWithMessage("Set out of bound gain index failure")
+ .that(thrown).hasMessageThat()
.contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
}
@@ -420,7 +455,8 @@
IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
() -> carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX + 1));
- assertThat(thrown).hasMessageThat()
+ assertWithMessage("Set out of bound gain index failure")
+ .that(thrown).hasMessageThat()
.contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
}
@@ -456,7 +492,8 @@
carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
- assertThat(carVolumeGroup.isMuted()).isTrue();
+ assertWithMessage("Saved mute state from settings")
+ .that(carVolumeGroup.isMuted()).isTrue();
}
@Test
@@ -465,7 +502,8 @@
carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
- assertThat(carVolumeGroup.isMuted()).isFalse();
+ assertWithMessage("Default mute state")
+ .that(carVolumeGroup.isMuted()).isFalse();
}
@Test
@@ -474,7 +512,8 @@
carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
- assertThat(carVolumeGroup.isMuted()).isFalse();
+ assertWithMessage("Saved mute state from settings")
+ .that(carVolumeGroup.isMuted()).isFalse();
}
@Test
@@ -483,35 +522,70 @@
carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
- assertThat(carVolumeGroup.isMuted()).isFalse();
+ assertWithMessage("Default mute state")
+ .that(carVolumeGroup.isMuted()).isFalse();
}
@Test
public void hasCriticalAudioContexts_withoutCriticalContexts_returnsFalse() {
CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
- assertThat(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
+ assertWithMessage("Group without critical audio context")
+ .that(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
}
@Test
public void hasCriticalAudioContexts_withCriticalContexts_returnsTrue() {
CarVolumeGroup carVolumeGroup = getBuilder()
- .setDeviceInfoForContext(CarAudioContext.EMERGENCY, mMediaDeviceInfo)
+ .setDeviceInfoForContext(EMERGENCY, mMediaDeviceInfo)
.build();
- assertThat(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+ assertWithMessage("Group with critical audio context")
+ .that(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+ }
+
+ @Test
+ public void getCurrentGainIndex_whileMuted_returnsMinGain() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ carVolumeGroup.setMute(true);
+
+ assertWithMessage("Muted current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MIN_GAIN_INDEX);
+ }
+
+ @Test
+ public void getCurrentGainIndex_whileUnMuted_returnsLastSetGain() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ carVolumeGroup.setMute(false);
+
+ assertWithMessage("Un-muted current gain index")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+ }
+
+ @Test
+ public void setCurrentGainIndex_whileMuted_unMutesVolumeGroup() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+ carVolumeGroup.setMute(true);
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ assertWithMessage("Mute state after volume change")
+ .that(carVolumeGroup.isMuted()).isEqualTo(false);
}
private CarVolumeGroup getCarVolumeGroupWithMusicBound() {
return getBuilder()
- .setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo)
+ .setDeviceInfoForContext(MUSIC, mMediaDeviceInfo)
.build();
}
private CarVolumeGroup getCarVolumeGroupWithNavigationBound(CarAudioSettings settings,
boolean useCarVolumeGroupMute) {
return new CarVolumeGroup.Builder(0, 0, settings, useCarVolumeGroupMute)
- .setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo)
+ .setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo)
.build();
}
@@ -527,13 +601,13 @@
private CarVolumeGroup testVolumeGroupSetup() {
CarVolumeGroup.Builder builder = getBuilder();
- builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.CALL, mMediaDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.CALL_RING, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CALL, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CALL_RING, mMediaDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.ALARM, mNavigationDeviceInfo);
- builder.setDeviceInfoForContext(CarAudioContext.NOTIFICATION, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(NAVIGATION, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(ALARM, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(NOTIFICATION, mNavigationDeviceInfo);
return builder.build();
}
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/CarSafetyAccessibilityServiceTest.java b/tests/carservice_unit_test/src/com/android/car/pm/CarSafetyAccessibilityServiceTest.java
new file mode 100644
index 0000000..7494f5d
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/pm/CarSafetyAccessibilityServiceTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.pm;
+
+
+import static org.mockito.Mockito.verify;
+
+import android.car.AbstractExtendedMockitoCarServiceTestCase;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.car.CarLocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class CarSafetyAccessibilityServiceTest extends AbstractExtendedMockitoCarServiceTestCase {
+ @Mock
+ private CarPackageManagerService mMockCarPackageManagerService;
+
+ private CarSafetyAccessibilityService mCarSafetyAccessibilityService;
+
+ @Before
+ public void setup() {
+ mockGetCarLocalService(CarPackageManagerService.class, mMockCarPackageManagerService);
+ mCarSafetyAccessibilityService = new CarSafetyAccessibilityService();
+ }
+
+ @Test
+ public void onAccessibilityEvent_carPackageManagerServiceNotified() {
+ mCarSafetyAccessibilityService.onAccessibilityEvent(new AccessibilityEvent());
+
+ verify(mMockCarPackageManagerService).onWindowChangeEvent();
+ }
+
+ @Override
+ protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+ session.spyStatic(CarLocalServices.class);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/WindowDumpParserTest.java b/tests/carservice_unit_test/src/com/android/car/pm/WindowDumpParserTest.java
new file mode 100644
index 0000000..b64c6e5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/pm/WindowDumpParserTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class WindowDumpParserTest {
+ private static final String WINDOW_DUMP = "WINDOW MANAGER WINDOWS (dumpsys window windows)\n"
+ + " Window #0 Window{ccae0fb u10 com.app1}:\n"
+ + " mDisplayId=0 rootTaskId=1000006 mSession=Session{ef1bfd2 2683:u10a10088} "
+ + "mClient=android.os.BinderProxy@46bd28a\n"
+ + " mOwnerUid=1010088 showForAllUsers=false package=com.app1 "
+ + "appop=SYSTEM_ALERT_WINDOW\n"
+ + " mAttrs={(0,0)(0x0) gr=TOP RIGHT CENTER sim={adjust=pan} ty=APPLICATION_OVERLAY"
+ + " fmt=TRANSPARENT\n"
+ + " isOnScreen=true\n"
+ + " isVisible=true\n"
+
+ + " Window #1 Window{17aaef4 u0 App 2}:\n"
+ + " mDisplayId=1 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+ + "mClient=android.os.Binderproxy@3f3ea06\n"
+ + " mOwnerUid=10120 showForAllUsers=true package=com.app2 appop=NONE\n"
+ + " mAttrs={(24,0)(84x419) gr=BOTTOM RIGHT CENTER sim={adjust=pan} "
+ + "ty=DISPLAY_OVERLAY fmt=TRANSLUCENT\n"
+ + " fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+ + " pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+ + " bhv=DEFAULT\n"
+ + " fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+ + " Requested w=1080 h=1920 mLayoutSeq=154\n"
+ + " mBaseLayer=21000 mSubLayer=0 mToken=AppWindowToken{546fe66 token=Token"
+ + "{8b7a6c1 ActivityRecord{2789ba8 u0 com.app2/.MainActivity t5}}}\n"
+ + " mAppToken=AppWindowToken{546fe66 token=Token{8b7a6c1 ActivityRecord{2789ba8 u0"
+ + " com.app2/.MainActivity t5}}}\n"
+
+ + ".BinderProxy@3f3ea06}\n"
+ + " Frames: containing=[0,0][1080,600] parent=[0,0][1080,600] display=[-10000,"
+ + "-10000][10000,10000]\n"
+ + " mFrame=[972,181][1056,600] last=[0,0][0,0]\n"
+ + " surface=[0,0][0,0]\n"
+ + " WindowStateAnimator{8edd4a7 HVAC Passenger Temp}:\n"
+ + " mDrawState=NO_SURFACE mLastHidden=false\n"
+ + " mEnterAnimationPending=false mSystemDecorRect=[0,0][0,0]\n"
+ + " mShownAlpha=0.0 mAlpha=1.0 mLastAlpha=0.0\n"
+ + " mForceSeamlesslyRotate=false seamlesslyRotate: pending=null "
+ + "finishedFrameNumber=0\n"
+ + " isOnScreen=false\n"
+ + " isVisible=false\n"
+
+ + " Window #2 Window{1c5571 u0 HVAC Driver Temp}:\n"
+ + " mDisplayId=1 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+ + "mClient=android.os.BinderProxy@99ccafb\n"
+ + " mOwnerUid=10120 showForAllUsers=true package=com.app2 appop=NONE\n"
+ + " mAttrs={(24,0)(84x419) gr=BOTTOM LEFT CENTER sim={adjust=pan} "
+ + "ty=DISPLAY_OVERLAY fmt=TRANSLUCENT\n"
+ + " fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+ + " pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+ + " bhv=DEFAULT\n"
+ + " fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+ + " Requested w=84 h=419 mLayoutSeq=143\n"
+ + " mBaseLayer=21000 mSubLayer=0 mToken=ActivityRecord{b44066 u10 com.app2/"
+ + "SecondActivity t1000031}\n"
+ + " mActivityRecord=ActivityRecord{b44066 u10 com.app2/SecondActivity t1000031}\n"
+ + ".BinderProxy@99ccafb}\n"
+ + " mViewVisibility=0x4 mHaveFrame=true mObscured=false\n"
+ + " mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]\n"
+ + " isOnScreen=false\n"
+ + " isVisible=false\n"
+
+ + " Window #3 Window{1c5571 u0 HVAC Driver Temp}:\n"
+ + " mDisplayId=2 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+ + "mClient=android.os.BinderProxy@99ccafb\n"
+ + " mOwnerUid=10120 showForAllUsers=true package=com.app3 appop=NONE\n"
+ + " mAttrs={(24,0)(84x419) gr=BOTTOM LEFT CENTER sim={adjust=pan} "
+ + "ty=DISPLAY_OVERLAY fmt=TRANSLUCENT\n"
+ + " fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+ + " pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+ + " bhv=DEFAULT\n"
+ + " fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+ + " Requested w=84 h=419 mLayoutSeq=143\n"
+ + " mBaseLayer=291000 mSubLayer=0 mToken=WindowToken{6bd1718 android.os"
+ + " mActivityRecord=ActivityRecord{a3f066 u10 com.app3/MainActivity t1000031}\n"
+ + ".BinderProxy@99ccafb}\n"
+ + " mViewVisibility=0x4 mHaveFrame=true mObscured=false\n"
+ + " mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]\n"
+ + " isOnScreen=false\n"
+ + " isVisible=false\n"
+
+ + " Window #4 Window{1c5571 u0 HVAC Driver Temp}:\n"
+ + " mDisplayId=2 rootTaskId=1000006 mSession=Session{629ba4e 2235:u0a10120} "
+ + "mClient=android.os.BinderProxy@99ccafb\n"
+ + " mOwnerUid=10120 showForAllUsers=true package=com.app3 appop=NONE\n"
+ + " mAttrs={(24,0)(84x419) gr=BOTTOM LEFT CENTER sim={adjust=pan} "
+ + "ty=APPLICATION_STARTING fmt=TRANSLUCENT\n"
+ + " fl=NOT_FOCUSABLE LAYOUT_NO_LIMITS HARDWARE_ACCELERATED\n"
+ + " pfl=NO_MOVE_ANIMATION USE_BLAST INSET_PARENT_FRAME_BY_IME\n"
+ + " bhv=DEFAULT\n"
+ + " fitTypes=STATUS_BARS NAVIGATION_BARS CAPTION_BAR}\n"
+ + " Requested w=84 h=419 mLayoutSeq=143\n"
+ + " mBaseLayer=291000 mSubLayer=0 mToken=WindowToken{6bd1718 android.os"
+ + ".BinderProxy@99ccafb}\n"
+ + " mViewVisibility=0x4 mHaveFrame=true mObscured=false\n"
+ + " mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]\n"
+ + " isOnScreen=false\n"
+ + " isVisible=false\n";
+
+ private static final WindowDumpParser.Window APP_1_WINDOW = new WindowDumpParser.Window(
+ "com.app1", 0, null);
+ private static final WindowDumpParser.Window APP_2_WINDOW = new WindowDumpParser.Window(
+ "com.app2", 1, "2789ba8 u0 com.app2/.MainActivity t5");
+ private static final WindowDumpParser.Window APP_2_WINDOW_2 = new WindowDumpParser.Window(
+ "com.app2", 1, "b44066 u10 com.app2/SecondActivity t1000031");
+ private static final WindowDumpParser.Window APP_3_WINDOW = new WindowDumpParser.Window(
+ "com.app3", 2, "a3f066 u10 com.app3/MainActivity t1000031");
+
+ @Test
+ public void testWindowDumpParsing() {
+ assertThat(WindowDumpParser.getParsedAppWindows(WINDOW_DUMP, "com.app1")).containsExactly(
+ APP_1_WINDOW);
+ assertThat(WindowDumpParser.getParsedAppWindows(WINDOW_DUMP, "com.app2")).containsExactly(
+ APP_2_WINDOW, APP_2_WINDOW_2);
+ assertThat(WindowDumpParser.getParsedAppWindows(WINDOW_DUMP, "com.app3")).containsExactly(
+ APP_3_WINDOW);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
new file mode 100644
index 0000000..edac364
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.telemetry;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.PersistableBundle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ResultStoreTest {
+ private static final PersistableBundle TEST_INTERIM_BUNDLE = new PersistableBundle();
+ private static final PersistableBundle TEST_FINAL_BUNDLE = new PersistableBundle();
+
+ private File mTestRootDir;
+ private File mTestInterimResultDir;
+ private File mTestFinalResultDir;
+ private ResultStore mResultStore;
+
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private ResultStore.FinalResultCallback mMockFinalResultCallback;
+ @Captor
+ private ArgumentCaptor<PersistableBundle> mBundleCaptor;
+
+
+ @Before
+ public void setUp() throws Exception {
+ // execute all handler posts immediately
+ when(mMockHandler.post(any())).thenAnswer(i -> {
+ ((Runnable) i.getArguments()[0]).run();
+ return true;
+ });
+ TEST_INTERIM_BUNDLE.putString("test key", "interim value");
+ TEST_FINAL_BUNDLE.putString("test key", "final value");
+
+ mTestRootDir = Files.createTempDirectory("car_telemetry_test").toFile();
+ mTestInterimResultDir = new File(mTestRootDir, ResultStore.INTERIM_RESULT_DIR);
+ mTestFinalResultDir = new File(mTestRootDir, ResultStore.FINAL_RESULT_DIR);
+
+ mResultStore = new ResultStore(mMockHandler, mMockHandler, mTestRootDir);
+ }
+
+ @Test
+ public void testConstructor_shouldCreateResultsFolder() {
+ // constructor is called in setUp()
+ assertThat(mTestInterimResultDir.exists()).isTrue();
+ assertThat(mTestFinalResultDir.exists()).isTrue();
+ }
+
+ @Test
+ public void testConstructor_shouldLoadInterimResultsIntoMemory() throws Exception {
+ String testInterimFileName = "test_file_1";
+ writeBundleToFile(mTestInterimResultDir, testInterimFileName, TEST_INTERIM_BUNDLE);
+
+ mResultStore = new ResultStore(mMockHandler, mMockHandler, mTestRootDir);
+
+ // should compare value instead of reference
+ assertThat(mResultStore.getInterimResult(testInterimFileName).toString())
+ .isEqualTo(TEST_INTERIM_BUNDLE.toString());
+ }
+
+ @Test
+ public void testShutdown_shouldWriteResultsToFileAndCheckContent() throws Exception {
+ String testInterimFileName = "test_file_1";
+ String testFinalFileName = "test_file_2";
+ writeBundleToFile(mTestInterimResultDir, testInterimFileName, TEST_INTERIM_BUNDLE);
+ writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
+
+ mResultStore.shutdown();
+
+ assertThat(new File(mTestInterimResultDir, testInterimFileName).exists()).isTrue();
+ assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isTrue();
+ // the content check will need to be modified when data encryption is implemented
+ PersistableBundle interimData =
+ readBundleFromFile(mTestInterimResultDir, testInterimFileName);
+ assertThat(interimData.toString()).isEqualTo(TEST_INTERIM_BUNDLE.toString());
+ PersistableBundle finalData = readBundleFromFile(mTestFinalResultDir, testFinalFileName);
+ assertThat(finalData.toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+ }
+
+ @Test
+ public void testGetFinalResult_whenNoData_shouldReceiveNull() throws Exception {
+ String metricsConfigName = "my_metrics_config";
+
+ mResultStore.getFinalResult(metricsConfigName, true, mMockFinalResultCallback);
+
+ verify(mMockFinalResultCallback).onFinalResult(eq(metricsConfigName),
+ mBundleCaptor.capture());
+ assertThat(mBundleCaptor.getValue()).isNull();
+ }
+
+ @Test
+ public void testGetFinalResult_whenDataCorrupt_shouldReceiveNull() throws Exception {
+ String metricsConfigName = "my_metrics_config";
+ Files.write(new File(mTestFinalResultDir, metricsConfigName).toPath(),
+ "not a bundle".getBytes(StandardCharsets.UTF_8));
+
+ mResultStore.getFinalResult(metricsConfigName, true, mMockFinalResultCallback);
+
+ verify(mMockFinalResultCallback).onFinalResult(eq(metricsConfigName),
+ mBundleCaptor.capture());
+ assertThat(mBundleCaptor.getValue()).isNull();
+ }
+
+ @Test
+ public void testGetFinalResult_whenDeleteFlagTrue_shouldDeleteData() throws Exception {
+ String testFinalFileName = "my_metrics_config";
+ writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
+
+ mResultStore.getFinalResult(testFinalFileName, true, mMockFinalResultCallback);
+
+ verify(mMockFinalResultCallback).onFinalResult(eq(testFinalFileName),
+ mBundleCaptor.capture());
+ // should compare value instead of reference
+ assertThat(mBundleCaptor.getValue().toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+ assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isFalse();
+ }
+
+ @Test
+ public void testGetFinalResult_whenDeleteFlagFalse_shouldNotDeleteData() throws Exception {
+ String testFinalFileName = "my_metrics_config";
+ writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE);
+
+ mResultStore.getFinalResult(testFinalFileName, false, mMockFinalResultCallback);
+
+ verify(mMockFinalResultCallback).onFinalResult(eq(testFinalFileName),
+ mBundleCaptor.capture());
+ // should compare value instead of reference
+ assertThat(mBundleCaptor.getValue().toString()).isEqualTo(TEST_FINAL_BUNDLE.toString());
+ assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isTrue();
+ }
+
+ @Test
+ public void testPutFinalResult_shouldRemoveInterimResultFromMemory() throws Exception {
+ String metricsConfigName = "my_metrics_config";
+ mResultStore.putInterimResult(metricsConfigName, TEST_INTERIM_BUNDLE);
+
+ mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE);
+
+ assertThat(mResultStore.getInterimResult(metricsConfigName)).isNull();
+ }
+
+ @Test
+ public void testPutInterimResultAndShutdown_shouldReplaceExistingFile() throws Exception {
+ String newKey = "new key";
+ String newValue = "new value";
+ String metricsConfigName = "my_metrics_config";
+ writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
+ TEST_INTERIM_BUNDLE.putString(newKey, newValue);
+
+ mResultStore.putInterimResult(metricsConfigName, TEST_INTERIM_BUNDLE);
+ mResultStore.shutdown();
+
+ PersistableBundle bundle = readBundleFromFile(mTestInterimResultDir, metricsConfigName);
+ assertThat(bundle.getString(newKey)).isEqualTo(newValue);
+ assertThat(bundle.toString()).isEqualTo(TEST_INTERIM_BUNDLE.toString());
+ }
+
+ @Test
+ public void testPutFinalResultAndShutdown_shouldRemoveInterimResultFile() throws Exception {
+ String metricsConfigName = "my_metrics_config";
+ writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
+
+ mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE);
+ mResultStore.shutdown();
+
+ assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse();
+ assertThat(new File(mTestFinalResultDir, metricsConfigName).exists()).isTrue();
+ }
+
+ /**
+ * Writes a persistable bundle to the result directory with the given directory and file name,
+ * and verifies that it was successfully written.
+ */
+ private void writeBundleToFile(
+ File dir, String fileName, PersistableBundle persistableBundle) throws Exception {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ persistableBundle.writeToStream(byteArrayOutputStream);
+ Files.write(
+ new File(dir, fileName).toPath(),
+ byteArrayOutputStream.toByteArray());
+ assertWithMessage("bundle is not written to the result directory")
+ .that(new File(dir, fileName).exists()).isTrue();
+ }
+
+ /** Reads a persistable bundle from the given path. */
+ private PersistableBundle readBundleFromFile(
+ File dir, String fileName) throws Exception {
+ File file = new File(dir, fileName);
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return PersistableBundle.readFromStream(fis);
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
index 43d063f..221b6a0 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
@@ -23,6 +23,9 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.automotive.telemetry.internal.ICarDataListener;
@@ -109,5 +112,51 @@
assertThat(mPublisher.hasDataSubscriber(invalidDataSubscriber)).isFalse();
}
+ @Test
+ public void testRemoveDataSubscriber_ignoresIfNotFound() {
+ mPublisher.removeDataSubscriber(mMockDataSubscriber);
+ }
+
+ @Test
+ public void testRemoveDataSubscriber_removesOnlySingleSubscriber() throws Exception {
+ mockICarTelemetryInternalBinder();
+ DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
+ when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+ mPublisher.addDataSubscriber(subscriber2);
+
+ mPublisher.removeDataSubscriber(subscriber2);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+ assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
+ verify(mMockCarTelemetryInternal, never()).clearListener();
+ }
+
+ @Test
+ public void testRemoveDataSubscriber_disconnectsFromICarTelemetry() throws Exception {
+ mockICarTelemetryInternalBinder();
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ mPublisher.removeDataSubscriber(mMockDataSubscriber);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+ verify(mMockCarTelemetryInternal, times(1)).clearListener();
+ }
+
+ @Test
+ public void testRemoveAllDataSubscribers_succeeds() throws Exception {
+ mockICarTelemetryInternalBinder();
+ DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
+ when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+ mPublisher.addDataSubscriber(subscriber2);
+
+ mPublisher.removeAllDataSubscribers();
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+ assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
+ verify(mMockCarTelemetryInternal, times(1)).clearListener();
+ }
+
// TODO(b/189142577): add test cases when connecting to cartelemetryd fails
}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
index 8410d02..2fafbce 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
@@ -166,11 +166,8 @@
}
@Test
- public void testRemoveDataSubscriber_failsIfNotFound() {
- Throwable error = assertThrows(IllegalArgumentException.class,
- () -> mVehiclePropertyPublisher.removeDataSubscriber(mMockDataSubscriber));
-
- assertThat(error).hasMessageThat().contains("DataSubscriber was not found");
+ public void testRemoveDataSubscriber_ignoresIfNotFound() {
+ mVehiclePropertyPublisher.removeDataSubscriber(mMockDataSubscriber);
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index bb972c8..1189784 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -81,7 +81,9 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -111,6 +113,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
/**
@@ -157,6 +160,7 @@
mockWatchdogDaemon();
setupUsers();
mCarWatchdogService.init();
+ verifyResourceOveruseConfigurationsSynced(1);
mWatchdogServiceForSystemImpl = registerCarWatchdogService();
captureBroadcastReceiver();
captureDaemonBinderDeathRecipient();
@@ -896,6 +900,42 @@
}
@Test
+ public void testGetPackageKillableStatesAsUserWithSafeToKillPackages() throws Exception {
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration systemConfig =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ systemConfig.componentType = ComponentType.SYSTEM;
+ systemConfig.safeToKillPackages = Collections.singletonList("system_package.non_critical");
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ vendorConfig.componentType = ComponentType.VENDOR;
+ vendorConfig.safeToKillPackages = Collections.singletonList("vendor_package.non_critical");
+ when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations())
+ .thenReturn(Arrays.asList(systemConfig, vendorConfig));
+ mCarWatchdogService.init();
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+
+ injectPackageInfos(Arrays.asList(
+ constructPackageManagerPackageInfo("system_package.non_critical", 1102459, null),
+ constructPackageManagerPackageInfo("third_party_package", 1103456, null),
+ constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
+ constructPackageManagerPackageInfo("vendor_package.non_critical", 1105573, null),
+ constructPackageManagerPackageInfo("third_party_package", 1203456, null),
+ constructPackageManagerPackageInfo("vendor_package.critical", 1201278, null)));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+ .containsExactly(
+ new PackageKillableState("system_package.non_critical", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("vendor_package.non_critical", 11,
+ PackageKillableState.KILLABLE_STATE_YES));
+ }
+
+ @Test
public void testGetPackageKillableStatesAsUserWithSharedUids() throws Exception {
mockUmGetAliveUsers(mMockUserManager, 11, 12);
injectPackageInfos(Arrays.asList(
@@ -926,6 +966,78 @@
}
@Test
+ public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillPackages()
+ throws Exception {
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ vendorConfig.componentType = ComponentType.VENDOR;
+ vendorConfig.safeToKillPackages = Collections.singletonList(
+ "vendor_package.non_critical.A");
+ when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations())
+ .thenReturn(Arrays.asList(vendorConfig));
+ mCarWatchdogService.init();
+ mockUmGetAliveUsers(mMockUserManager, 11);
+
+ injectPackageInfos(Arrays.asList(
+ constructPackageManagerPackageInfo(
+ "vendor_package.non_critical.A", 1103456, "vendor_shared_package.A"),
+ constructPackageManagerPackageInfo(
+ "system_package.A", 1103456, "vendor_shared_package.A"),
+ constructPackageManagerPackageInfo(
+ "vendor_package.B", 1103456, "vendor_shared_package.A"),
+ constructPackageManagerPackageInfo(
+ "third_party_package.C", 1105678, "third_party_shared_package"),
+ constructPackageManagerPackageInfo(
+ "third_party_package.D", 1105678, "third_party_shared_package")));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+ .containsExactly(
+ new PackageKillableState("vendor_package.non_critical.A", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("system_package.A", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.B", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("third_party_package.C", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("third_party_package.D", 11,
+ PackageKillableState.KILLABLE_STATE_YES));
+ }
+
+ @Test
+ public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillSharedPackage()
+ throws Exception {
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ vendorConfig.componentType = ComponentType.VENDOR;
+ vendorConfig.safeToKillPackages = Collections.singletonList(
+ "shared:vendor_shared_package.B");
+ when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations())
+ .thenReturn(Arrays.asList(vendorConfig));
+ mCarWatchdogService.init();
+ mockUmGetAliveUsers(mMockUserManager, 11);
+
+ injectPackageInfos(Arrays.asList(
+ constructPackageManagerPackageInfo(
+ "vendor_package.non_critical.A", 1103456, "vendor_shared_package.B"),
+ constructPackageManagerPackageInfo(
+ "system_package.non_critical.A", 1103456, "vendor_shared_package.B"),
+ constructPackageManagerPackageInfo(
+ "vendor_package.non_critical.B", 1103456, "vendor_shared_package.B")));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+ .containsExactly(
+ new PackageKillableState("vendor_package.non_critical.A", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("system_package.non_critical.A", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.non_critical.B", 11,
+ PackageKillableState.KILLABLE_STATE_YES));
+ }
+
+ @Test
public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
mockUmGetAliveUsers(mMockUserManager, 11, 12);
injectPackageInfos(Arrays.asList(
@@ -986,6 +1098,9 @@
sampleResourceOveruseConfigurations(), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO))
.isEqualTo(CarWatchdogManager.RETURN_CODE_SUCCESS);
+ /* Expect two calls, the first is made at car watchdog service init */
+ verifyResourceOveruseConfigurationsSynced(2);
+
InternalResourceOveruseConfigurationSubject
.assertThat(captureOnSetResourceOveruseConfigurations())
.containsExactlyElementsIn(sampleInternalResourceOveruseConfigurations());
@@ -1100,6 +1215,64 @@
}
@Test
+ public void testFailsSetResourceOveruseConfigurationsOnZeroComponentLevelIoOveruseThresholds()
+ throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs =
+ Collections.singletonList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+ .setComponentLevelThresholds(new PerStateBytes(200, 0, 200))
+ .build())
+ .build());
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnEmptyIoOveruseSystemWideThresholds()
+ throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs =
+ Collections.singletonList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+ .setSystemWideThresholds(new ArrayList<>())
+ .build())
+ .build());
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnIoOveruseInvalidSystemWideThreshold()
+ throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>();
+ resourceOveruseConfigs.add(sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+ .setSystemWideThresholds(Collections.singletonList(
+ new IoOveruseAlertThreshold(30, 0)))
+ .build())
+ .build());
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(
+ resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+ resourceOveruseConfigs.set(0,
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM)
+ .setSystemWideThresholds(Collections.singletonList(
+ new IoOveruseAlertThreshold(0, 300)))
+ .build())
+ .build());
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(
+ resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
public void testFailsSetResourceOveruseConfigurationsOnNullIoOveruseConfiguration()
throws Exception {
List<ResourceOveruseConfiguration> resourceOveruseConfigs = Collections.singletonList(
@@ -1130,7 +1303,8 @@
() -> mCarWatchdogService.getResourceOveruseConfigurations(
CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
- verify(mMockCarWatchdogDaemon, never()).getResourceOveruseConfigurations();
+ /* Method initially called in CarWatchdogService init */
+ verify(mMockCarWatchdogDaemon, times(1)).getResourceOveruseConfigurations();
}
@Test
@@ -1209,6 +1383,7 @@
int nonCriticalVndrPkgUid = getUid(2564);
int thirdPartyPkgUid = getUid(2044);
+ mCarWatchdogService.setResourceOveruseKillingDelay(1000);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"system_package.critical", criticalSysPkgUid, null),
@@ -1259,6 +1434,13 @@
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+ /*
+ * Handling of packages that exceed I/O thresholds is done on the main thread. To ensure
+ * the handling completes before verification, wait for the message to be posted on the
+ * main thread and execute an empty block on the main thread.
+ */
+ delayedRunOnMainSync(() -> {}, /* delayMillis= */2000);
+
List<ResourceOveruseStats> expectedStats = new ArrayList<>();
expectedStats.add(constructResourceOveruseStats(criticalSysPkgUid,
@@ -1292,14 +1474,13 @@
verifyActionsTakenOnResourceOveruse(expectedActions);
}
-
-
@Test
public void testLatestIoOveruseStatsWithSharedUid() throws Exception {
int criticalSysSharedUid = Binder.getCallingUid();
int nonCriticalVndrSharedUid = getUid(2564);
int thirdPartySharedUid = getUid(2044);
+ mCarWatchdogService.setResourceOveruseKillingDelay(1000);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"system_package.A", criticalSysSharedUid, "system_shared_package"),
@@ -1347,6 +1528,13 @@
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+ /*
+ * Handling of packages that exceed I/O thresholds is done on the main thread. To ensure
+ * the handling completes before verification, wait for the message to be posted on the
+ * main thread and execute an empty block on the main thread.
+ */
+ delayedRunOnMainSync(() -> {}, /* delayMillis= */2000);
+
List<ResourceOveruseStats> expectedStats = new ArrayList<>();
expectedStats.add(constructResourceOveruseStats(criticalSysSharedUid,
@@ -1698,6 +1886,18 @@
mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
}
+ private void verifyResourceOveruseConfigurationsSynced(int wantedNumberOfInvocations)
+ throws Exception {
+ /*
+ * Syncing the resource configuration in the service with the daemon is done on the main
+ * thread. To ensure the sync completes before verification, execute an empty block on the
+ * main thread.
+ */
+ CarServiceUtils.runOnMainSync(() -> {});
+ verify(mMockCarWatchdogDaemon,
+ times(wantedNumberOfInvocations)).getResourceOveruseConfigurations();
+ }
+
private ICarWatchdogServiceForSystem registerCarWatchdogService() throws Exception {
/* Registering to daemon is done on the main thread. To ensure the registration completes
* before verification, execute an empty block on the main thread.
@@ -1795,9 +1995,14 @@
ArgumentCaptor<List<PackageResourceOveruseAction>> resourceOveruseActionsCaptor =
ArgumentCaptor.forClass((Class) List.class);
- verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
+ verify(mMockCarWatchdogDaemon,
+ timeout(MAX_WAIT_TIME_MS).times(2)).actionTakenOnResourceOveruse(
resourceOveruseActionsCaptor.capture());
- List<PackageResourceOveruseAction> actual = resourceOveruseActionsCaptor.getValue();
+ List<PackageResourceOveruseAction> actual = new ArrayList<>();
+ for (List<PackageResourceOveruseAction> actions :
+ resourceOveruseActionsCaptor.getAllValues()) {
+ actual.addAll(actions);
+ }
assertThat(actual).comparingElementsUsing(
Correspondence.from(
@@ -2010,6 +2215,24 @@
return action;
}
+ private static void delayedRunOnMainSync(Runnable action, long delayMillis)
+ throws InterruptedException {
+ AtomicBoolean isComplete = new AtomicBoolean();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.postDelayed(() -> {
+ action.run();
+ synchronized (action) {
+ isComplete.set(true);
+ action.notifyAll();
+ }
+ }, delayMillis);
+ synchronized (action) {
+ while (!isComplete.get()) {
+ action.wait();
+ }
+ }
+ }
+
private class TestClient extends ICarWatchdogServiceCallback.Stub {
protected int mLastSessionId = INVALID_SESSION_ID;