diff options
344 files changed, 24371 insertions, 16410 deletions
diff --git a/.gitignore b/.gitignore index 5bf05e5af0..b2928471ca 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .classpath *.iml gen/ +*.pyc +__pycache__ diff --git a/CleanSpec.mk b/CleanSpec.mk index 1c6a5b1fad..4bf7e9aeb0 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -51,6 +51,8 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/androi $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/PACKAGING/android.support.car*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android.car7_intermediates/) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android.car_intermediates/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android.car7_intermediates/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android.car_intermediates/) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt index 94c21be219..b846481148 100644 --- a/car-lib/api/system-current.txt +++ b/car-lib/api/system-current.txt @@ -28,15 +28,12 @@ package android.car { field public static final deprecated java.lang.String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL"; field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED"; field public static final java.lang.String PERMISSION_VENDOR_EXTENSION = "android.car.permission.CAR_VENDOR_EXTENSION"; - field public static final java.lang.String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER"; - field public static final java.lang.String PERMISSION_VMS_SUBSCRIBER = "android.car.permission.VMS_SUBSCRIBER"; field public static final java.lang.String PROJECTION_SERVICE = "projection"; field public static final java.lang.String RADIO_SERVICE = "radio"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String TEST_SERVICE = "car-service-test"; field public static final java.lang.String VENDOR_EXTENSION_SERVICE = "vendor_extension"; field public static final int VERSION = 2; // 0x2 - field public static final java.lang.String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service"; } public final class CarAppFocusManager { diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java index 80fabd04de..6ed5d73d98 100644 --- a/car-lib/src/android/car/Car.java +++ b/car-lib/src/android/car/Car.java @@ -124,7 +124,6 @@ public final class Car { * @FutureFeature Cannot drop due to usage in non-flag protected place. * @hide */ - @SystemApi public static final String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service"; /** @@ -231,7 +230,6 @@ public final class Car { * @hide */ @FutureFeature - @SystemApi public static final String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER"; /** @@ -240,7 +238,6 @@ public final class Car { * @hide */ @FutureFeature - @SystemApi public static final String PERMISSION_VMS_SUBSCRIBER = "android.car.permission.VMS_SUBSCRIBER"; /** @@ -582,9 +579,7 @@ public final class Car { manager = new CarCabinManager(binder, mContext, mEventHandler); break; case DIAGNOSTIC_SERVICE: - if (FeatureConfiguration.ENABLE_DIAGNOSTIC) { - manager = new CarDiagnosticManager(binder, mContext, mEventHandler); - } + manager = new CarDiagnosticManager(binder, mContext, mEventHandler); break; case HVAC_SERVICE: manager = new CarHvacManager(binder, mContext, mEventHandler); diff --git a/car-lib/src/android/car/ICar.aidl b/car-lib/src/android/car/ICar.aidl index 1510438fc3..9f8c367aec 100644 --- a/car-lib/src/android/car/ICar.aidl +++ b/car-lib/src/android/car/ICar.aidl @@ -16,12 +16,14 @@ package android.car; -import android.content.Intent; - -import android.car.ICarConnectionListener; - /** @hide */ interface ICar { - IBinder getCarService(in String serviceName) = 0; - int getCarConnectionType() = 1; + /** + * IBinder is ICarServiceHelper but passed as IBinder due to aidl hidden. + * Only this method is oneway as it is called from system server. + * This should be the 1st method. Do not change the order. + */ + oneway void setCarServiceHelper(in IBinder helper) = 0; + IBinder getCarService(in String serviceName) = 1; + int getCarConnectionType() = 2; } diff --git a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java index b970b2309c..1d825339b9 100644 --- a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java +++ b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java @@ -21,8 +21,10 @@ import android.annotation.Nullable; import android.car.annotation.FutureFeature; import android.os.Parcel; import android.os.Parcelable; +import android.util.JsonWriter; import android.util.SparseArray; import android.util.SparseIntArray; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -78,7 +80,7 @@ public class CarDiagnosticEvent implements Parcelable { int value = in.readInt(); intValues.put(key, value); } - dtc = (String)in.readValue(String.class.getClassLoader()); + dtc = (String) in.readValue(String.class.getClassLoader()); // version 1 up to here } @@ -106,6 +108,48 @@ public class CarDiagnosticEvent implements Parcelable { dest.writeValue(dtc); } + public void writeToJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.beginObject(); + + jsonWriter.name("type"); + switch (frameType) { + case CarDiagnosticManager.FRAME_TYPE_LIVE: + jsonWriter.value("live"); + break; + case CarDiagnosticManager.FRAME_TYPE_FREEZE: + jsonWriter.value("freeze"); + break; + default: + throw new IllegalStateException("unknown frameType " + frameType); + } + + jsonWriter.name("timestamp").value(timestamp); + + jsonWriter.name("intValues").beginArray(); + for (int i = 0; i < intValues.size(); ++i) { + jsonWriter.beginObject(); + jsonWriter.name("id").value(intValues.keyAt(i)); + jsonWriter.name("value").value(intValues.valueAt(i)); + jsonWriter.endObject(); + } + jsonWriter.endArray(); + + jsonWriter.name("floatValues").beginArray(); + for (int i = 0; i < floatValues.size(); ++i) { + jsonWriter.beginObject(); + jsonWriter.name("id").value(floatValues.keyAt(i)); + jsonWriter.name("value").value(floatValues.valueAt(i)); + jsonWriter.endObject(); + } + jsonWriter.endArray(); + + if (dtc != null) { + jsonWriter.name("stringValue").value(dtc); + } + + jsonWriter.endObject(); + } + public static final Parcelable.Creator<CarDiagnosticEvent> CREATOR = new Parcelable.Creator<CarDiagnosticEvent>() { public CarDiagnosticEvent createFromParcel(Parcel in) { @@ -131,7 +175,7 @@ public class CarDiagnosticEvent implements Parcelable { } public static class Builder { - private int mType = CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE; + private int mType = CarDiagnosticManager.FRAME_TYPE_LIVE; private long mTimestamp = 0; private SparseArray<Float> mFloatValues = new SparseArray<>(); private SparseIntArray mIntValues = new SparseIntArray(); @@ -142,11 +186,11 @@ public class CarDiagnosticEvent implements Parcelable { } public static Builder newLiveFrameBuilder() { - return new Builder(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE); + return new Builder(CarDiagnosticManager.FRAME_TYPE_LIVE); } public static Builder newFreezeFrameBuilder() { - return new Builder(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE); + return new Builder(CarDiagnosticManager.FRAME_TYPE_FREEZE); } public Builder atTimestamp(long timestamp) { @@ -176,18 +220,19 @@ public class CarDiagnosticEvent implements Parcelable { /** * Returns a copy of this CarDiagnosticEvent with all vendor-specific sensors removed. + * * @hide */ public CarDiagnosticEvent withVendorSensorsRemoved() { SparseIntArray newIntValues = intValues.clone(); SparseArray<Float> newFloatValues = floatValues.clone(); - for(int i = 0; i < intValues.size(); ++i) { + for (int i = 0; i < intValues.size(); ++i) { int key = intValues.keyAt(i); if (key >= CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.LAST_SYSTEM) { newIntValues.delete(key); } } - for(int i = 0; i < floatValues.size(); ++i) { + for (int i = 0; i < floatValues.size(); ++i) { int key = floatValues.keyAt(i); if (key >= CarDiagnosticSensorIndices.Obd2FloatSensorIndex.LAST_SYSTEM) { newFloatValues.delete(key); @@ -197,11 +242,11 @@ public class CarDiagnosticEvent implements Parcelable { } public boolean isLiveFrame() { - return CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE == frameType; + return CarDiagnosticManager.FRAME_TYPE_LIVE == frameType; } public boolean isFreezeFrame() { - return CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE == frameType; + return CarDiagnosticManager.FRAME_TYPE_FREEZE == frameType; } public boolean isEmptyFrame() { @@ -230,6 +275,82 @@ public class CarDiagnosticEvent implements Parcelable { } @Override + public boolean equals(Object otherObject) { + if (this == otherObject) { + return true; + } + if (null == otherObject) { + return false; + } + if (!(otherObject instanceof CarDiagnosticEvent)) { + return false; + } + CarDiagnosticEvent otherEvent = (CarDiagnosticEvent)otherObject; + if (otherEvent.frameType != frameType) + return false; + if (otherEvent.timestamp != timestamp) + return false; + if (otherEvent.intValues.size() != intValues.size()) + return false; + if (otherEvent.floatValues.size() != floatValues.size()) + return false; + if (!Objects.equals(dtc, otherEvent.dtc)) + return false; + for (int i = 0; i < intValues.size(); ++i) { + int key = intValues.keyAt(i); + int otherKey = otherEvent.intValues.keyAt(i); + if (key != otherKey) { + return false; + } + int value = intValues.valueAt(i); + int otherValue = otherEvent.intValues.valueAt(i); + if (value != otherValue) { + return false; + } + } + for (int i = 0; i < floatValues.size(); ++i) { + int key = floatValues.keyAt(i); + int otherKey = otherEvent.floatValues.keyAt(i); + if (key != otherKey) { + return false; + } + float value = floatValues.valueAt(i); + float otherValue = otherEvent.floatValues.valueAt(i); + if (value != otherValue) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + Integer[] intKeys = new Integer[intValues.size()]; + Integer[] floatKeys = new Integer[floatValues.size()]; + Integer[] intValues = new Integer[intKeys.length]; + Float[] floatValues = new Float[floatKeys.length]; + for (int i = 0; i < intKeys.length; ++i) { + intKeys[i] = this.intValues.keyAt(i); + intValues[i] = this.intValues.valueAt(i); + } + for (int i = 0; i < floatKeys.length; ++i) { + floatKeys[i] = this.floatValues.keyAt(i); + floatValues[i] = this.floatValues.valueAt(i); + } + int intKeysHash = Objects.hash((Object[])intKeys); + int intValuesHash = Objects.hash((Object[])intValues); + int floatKeysHash = Objects.hash((Object[])floatKeys); + int floatValuesHash = Objects.hash((Object[])floatValues); + return Objects.hash(frameType, + timestamp, + dtc, + intKeysHash, + intValuesHash, + floatKeysHash, + floatValuesHash); + } + + @Override public String toString() { return String.format( "%s diagnostic frame {\n" @@ -244,11 +365,13 @@ public class CarDiagnosticEvent implements Parcelable { floatValues.toString()); } - public int getSystemIntegerSensor(@CarDiagnosticSensorIndices.IntegerSensorIndex int sensor, int defaultValue) { + public int getSystemIntegerSensor( + @CarDiagnosticSensorIndices.IntegerSensorIndex int sensor, int defaultValue) { return intValues.get(sensor, defaultValue); } - public float getSystemFloatSensor(@CarDiagnosticSensorIndices.FloatSensorIndex int sensor, float defaultValue) { + public float getSystemFloatSensor( + @CarDiagnosticSensorIndices.FloatSensorIndex int sensor, float defaultValue) { return floatValues.get(sensor, defaultValue); } @@ -260,33 +383,35 @@ public class CarDiagnosticEvent implements Parcelable { return floatValues.get(sensor, defaultValue); } - public @Nullable Integer getSystemIntegerSensor(@CarDiagnosticSensorIndices.IntegerSensorIndex int sensor) { + public @Nullable Integer getSystemIntegerSensor( + @CarDiagnosticSensorIndices.IntegerSensorIndex int sensor) { int index = intValues.indexOfKey(sensor); - if(index < 0) return null; + if (index < 0) return null; return intValues.valueAt(index); } - public @Nullable Float getSystemFloatSensor(@CarDiagnosticSensorIndices.FloatSensorIndex int sensor) { + public @Nullable Float getSystemFloatSensor( + @CarDiagnosticSensorIndices.FloatSensorIndex int sensor) { int index = floatValues.indexOfKey(sensor); - if(index < 0) return null; + if (index < 0) return null; return floatValues.valueAt(index); } public @Nullable Integer getVendorIntegerSensor(int sensor) { int index = intValues.indexOfKey(sensor); - if(index < 0) return null; + if (index < 0) return null; return intValues.valueAt(index); } public @Nullable Float getVendorFloatSensor(int sensor) { int index = floatValues.indexOfKey(sensor); - if(index < 0) return null; + if (index < 0) return null; return floatValues.valueAt(index); } /** - * Represents possible states of the fuel system; - * see {@link CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#FUEL_SYSTEM_STATUS} + * Represents possible states of the fuel system; see {@link + * CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#FUEL_SYSTEM_STATUS} */ public static final class FuelSystemStatus { private FuelSystemStatus() {} @@ -309,8 +434,8 @@ public class CarDiagnosticEvent implements Parcelable { } /** - * Represents possible states of the secondary air system; - * see {@link CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#COMMANDED_SECONDARY_AIR_STATUS} + * Represents possible states of the secondary air system; see {@link + * CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#COMMANDED_SECONDARY_AIR_STATUS} */ public static final class SecondaryAirStatus { private SecondaryAirStatus() {} @@ -331,8 +456,8 @@ public class CarDiagnosticEvent implements Parcelable { } /** - * Represents possible types of fuel; - * see {@link CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#FUEL_TYPE} + * Represents possible types of fuel; see {@link + * CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#FUEL_TYPE} */ public static final class FuelType { private FuelType() {} @@ -393,9 +518,9 @@ public class CarDiagnosticEvent implements Parcelable { } /** - * Represents possible states of the ignition monitors on the vehicle; - * see {@link CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#IGNITION_MONITORS_SUPPORTED} - * see {@link CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#IGNITION_SPECIFIC_MONITORS} + * Represents possible states of the ignition monitors on the vehicle; see {@link + * CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#IGNITION_MONITORS_SUPPORTED} see {@link + * CarDiagnosticSensorIndices.Obd2IntegerSensorIndex#IGNITION_SPECIFIC_MONITORS} */ public static final class IgnitionMonitors { public static final class IgnitionMonitor { @@ -407,26 +532,16 @@ public class CarDiagnosticEvent implements Parcelable { this.incomplete = incomplete; } - public static final class Builder { - private int mAvailableBitmask; - private int mIncompleteBitmask; + public static final class Decoder { + private final int mAvailableBitmask; + private final int mIncompleteBitmask; - Builder() { - mAvailableBitmask = 0; - mIncompleteBitmask = 0; + Decoder(int availableBitmask, int incompleteBitmask) { + mAvailableBitmask = availableBitmask; + mIncompleteBitmask = incompleteBitmask; } - public Builder withAvailableBitmask(int bitmask) { - mAvailableBitmask = bitmask; - return this; - } - - public Builder withIncompleteBitmask(int bitmask) { - mIncompleteBitmask = bitmask; - return this; - } - - public IgnitionMonitor buildForValue(int value) { + public IgnitionMonitor fromValue(int value) { boolean available = (0 != (value & mAvailableBitmask)); boolean incomplete = (0 != (value & mIncompleteBitmask)); @@ -449,36 +564,29 @@ public class CarDiagnosticEvent implements Parcelable { static final int MISFIRE_AVAILABLE = 0x1 << 4; static final int MISFIRE_INCOMPLETE = 0x1 << 5; - static final IgnitionMonitor.Builder COMPONENTS_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(COMPONENTS_AVAILABLE) - .withIncompleteBitmask(COMPONENTS_INCOMPLETE); + static final IgnitionMonitor.Decoder COMPONENTS_DECODER = + new IgnitionMonitor.Decoder(COMPONENTS_AVAILABLE, COMPONENTS_INCOMPLETE); - static final IgnitionMonitor.Builder FUEL_SYSTEM_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(FUEL_SYSTEM_AVAILABLE) - .withIncompleteBitmask(FUEL_SYSTEM_INCOMPLETE); + static final IgnitionMonitor.Decoder FUEL_SYSTEM_DECODER = + new IgnitionMonitor.Decoder(FUEL_SYSTEM_AVAILABLE, FUEL_SYSTEM_INCOMPLETE); - static final IgnitionMonitor.Builder MISFIRE_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(MISFIRE_AVAILABLE) - .withIncompleteBitmask(MISFIRE_INCOMPLETE); + static final IgnitionMonitor.Decoder MISFIRE_DECODER = + new IgnitionMonitor.Decoder(MISFIRE_AVAILABLE, MISFIRE_INCOMPLETE); CommonIgnitionMonitors(int bitmask) { - components = COMPONENTS_BUILDER.buildForValue(bitmask); - fuelSystem = FUEL_SYSTEM_BUILDER.buildForValue(bitmask); - misfire = MISFIRE_BUILDER.buildForValue(bitmask); + components = COMPONENTS_DECODER.fromValue(bitmask); + fuelSystem = FUEL_SYSTEM_DECODER.fromValue(bitmask); + misfire = MISFIRE_DECODER.fromValue(bitmask); } public @Nullable SparkIgnitionMonitors asSparkIgnitionMonitors() { - if (this instanceof SparkIgnitionMonitors) - return (SparkIgnitionMonitors)this; + if (this instanceof SparkIgnitionMonitors) return (SparkIgnitionMonitors) this; return null; } public @Nullable CompressionIgnitionMonitors asCompressionIgnitionMonitors() { if (this instanceof CompressionIgnitionMonitors) - return (CompressionIgnitionMonitors)this; + return (CompressionIgnitionMonitors) this; return null; } } @@ -517,56 +625,45 @@ public class CarDiagnosticEvent implements Parcelable { static final int CATALYST_AVAILABLE = 0x1 << 20; static final int CATALYST_INCOMPLETE = 0x1 << 21; - static final IgnitionMonitor.Builder EGR_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(EGR_AVAILABLE) - .withIncompleteBitmask(EGR_INCOMPLETE); - - static final IgnitionMonitor.Builder OXYGEN_SENSOR_HEATER_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(OXYGEN_SENSOR_HEATER_AVAILABLE) - .withIncompleteBitmask(OXYGEN_SENSOR_HEATER_INCOMPLETE); - - static final IgnitionMonitor.Builder OXYGEN_SENSOR_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(OXYGEN_SENSOR_AVAILABLE) - .withIncompleteBitmask(OXYGEN_SENSOR_INCOMPLETE); - - static final IgnitionMonitor.Builder AC_REFRIGERANT_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(AC_REFRIGERANT_AVAILABLE) - .withIncompleteBitmask(AC_REFRIGERANT_INCOMPLETE); - - static final IgnitionMonitor.Builder SECONDARY_AIR_SYSTEM_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(SECONDARY_AIR_SYSTEM_AVAILABLE) - .withIncompleteBitmask(SECONDARY_AIR_SYSTEM_INCOMPLETE); - - static final IgnitionMonitor.Builder EVAPORATIVE_SYSTEM_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(EVAPORATIVE_SYSTEM_AVAILABLE) - .withIncompleteBitmask(EVAPORATIVE_SYSTEM_INCOMPLETE); - - static final IgnitionMonitor.Builder HEATED_CATALYST_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(HEATED_CATALYST_AVAILABLE) - .withIncompleteBitmask(HEATED_CATALYST_INCOMPLETE); - - static final IgnitionMonitor.Builder CATALYST_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(CATALYST_AVAILABLE) - .withIncompleteBitmask(CATALYST_INCOMPLETE); + static final IgnitionMonitor.Decoder EGR_DECODER = + new IgnitionMonitor.Decoder(EGR_AVAILABLE, EGR_INCOMPLETE); + + static final IgnitionMonitor.Decoder OXYGEN_SENSOR_HEATER_DECODER = + new IgnitionMonitor.Decoder(OXYGEN_SENSOR_HEATER_AVAILABLE, + OXYGEN_SENSOR_HEATER_INCOMPLETE); + + static final IgnitionMonitor.Decoder OXYGEN_SENSOR_DECODER = + new IgnitionMonitor.Decoder(OXYGEN_SENSOR_AVAILABLE, OXYGEN_SENSOR_INCOMPLETE); + + static final IgnitionMonitor.Decoder AC_REFRIGERANT_DECODER = + new IgnitionMonitor.Decoder(AC_REFRIGERANT_AVAILABLE, + AC_REFRIGERANT_INCOMPLETE); + + static final IgnitionMonitor.Decoder SECONDARY_AIR_SYSTEM_DECODER = + new IgnitionMonitor.Decoder(SECONDARY_AIR_SYSTEM_AVAILABLE, + SECONDARY_AIR_SYSTEM_INCOMPLETE); + + static final IgnitionMonitor.Decoder EVAPORATIVE_SYSTEM_DECODER = + new IgnitionMonitor.Decoder(EVAPORATIVE_SYSTEM_AVAILABLE, + EVAPORATIVE_SYSTEM_INCOMPLETE); + + static final IgnitionMonitor.Decoder HEATED_CATALYST_DECODER = + new IgnitionMonitor.Decoder(HEATED_CATALYST_AVAILABLE, + HEATED_CATALYST_INCOMPLETE); + + static final IgnitionMonitor.Decoder CATALYST_DECODER = + new IgnitionMonitor.Decoder(CATALYST_AVAILABLE, CATALYST_INCOMPLETE); SparkIgnitionMonitors(int bitmask) { super(bitmask); - EGR = EGR_BUILDER.buildForValue(bitmask); - oxygenSensorHeater = OXYGEN_SENSOR_HEATER_BUILDER.buildForValue(bitmask); - oxygenSensor = OXYGEN_SENSOR_BUILDER.buildForValue(bitmask); - ACRefrigerant = AC_REFRIGERANT_BUILDER.buildForValue(bitmask); - secondaryAirSystem = SECONDARY_AIR_SYSTEM_BUILDER.buildForValue(bitmask); - evaporativeSystem = EVAPORATIVE_SYSTEM_BUILDER.buildForValue(bitmask); - heatedCatalyst = HEATED_CATALYST_BUILDER.buildForValue(bitmask); - catalyst = CATALYST_BUILDER.buildForValue(bitmask); + EGR = EGR_DECODER.fromValue(bitmask); + oxygenSensorHeater = OXYGEN_SENSOR_HEATER_DECODER.fromValue(bitmask); + oxygenSensor = OXYGEN_SENSOR_DECODER.fromValue(bitmask); + ACRefrigerant = AC_REFRIGERANT_DECODER.fromValue(bitmask); + secondaryAirSystem = SECONDARY_AIR_SYSTEM_DECODER.fromValue(bitmask); + evaporativeSystem = EVAPORATIVE_SYSTEM_DECODER.fromValue(bitmask); + heatedCatalyst = HEATED_CATALYST_DECODER.fromValue(bitmask); + catalyst = CATALYST_DECODER.fromValue(bitmask); } } @@ -596,69 +693,66 @@ public class CarDiagnosticEvent implements Parcelable { static final int NMHC_CATALYST_AVAILABLE = 0x1 << 16; static final int NMHC_CATALYST_INCOMPLETE = 0x1 << 17; - static final IgnitionMonitor.Builder EGR_OR_VVT_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(EGR_OR_VVT_AVAILABLE) - .withIncompleteBitmask(EGR_OR_VVT_INCOMPLETE); + static final IgnitionMonitor.Decoder EGR_OR_VVT_DECODER = + new IgnitionMonitor.Decoder(EGR_OR_VVT_AVAILABLE, EGR_OR_VVT_INCOMPLETE); - static final IgnitionMonitor.Builder PM_FILTER_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(PM_FILTER_AVAILABLE) - .withIncompleteBitmask(PM_FILTER_INCOMPLETE); + static final IgnitionMonitor.Decoder PM_FILTER_DECODER = + new IgnitionMonitor.Decoder(PM_FILTER_AVAILABLE, PM_FILTER_INCOMPLETE); - static final IgnitionMonitor.Builder EXHAUST_GAS_SENSOR_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(EXHAUST_GAS_SENSOR_AVAILABLE) - .withIncompleteBitmask(EXHAUST_GAS_SENSOR_INCOMPLETE); + static final IgnitionMonitor.Decoder EXHAUST_GAS_SENSOR_DECODER = + new IgnitionMonitor.Decoder(EXHAUST_GAS_SENSOR_AVAILABLE, + EXHAUST_GAS_SENSOR_INCOMPLETE); - static final IgnitionMonitor.Builder BOOST_PRESSURE_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(BOOST_PRESSURE_AVAILABLE) - .withIncompleteBitmask(BOOST_PRESSURE_INCOMPLETE); + static final IgnitionMonitor.Decoder BOOST_PRESSURE_DECODER = + new IgnitionMonitor.Decoder(BOOST_PRESSURE_AVAILABLE, + BOOST_PRESSURE_INCOMPLETE); - static final IgnitionMonitor.Builder NOx_SCR_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(NOx_SCR_AVAILABLE) - .withIncompleteBitmask(NOx_SCR_INCOMPLETE); + static final IgnitionMonitor.Decoder NOx_SCR_DECODER = + new IgnitionMonitor.Decoder(NOx_SCR_AVAILABLE, NOx_SCR_INCOMPLETE); - static final IgnitionMonitor.Builder NMHC_CATALYST_BUILDER = - new IgnitionMonitor.Builder() - .withAvailableBitmask(NMHC_CATALYST_AVAILABLE) - .withIncompleteBitmask(NMHC_CATALYST_INCOMPLETE); + static final IgnitionMonitor.Decoder NMHC_CATALYST_DECODER = + new IgnitionMonitor.Decoder(NMHC_CATALYST_AVAILABLE, NMHC_CATALYST_INCOMPLETE); CompressionIgnitionMonitors(int bitmask) { super(bitmask); - EGROrVVT = EGR_OR_VVT_BUILDER.buildForValue(bitmask); - PMFilter = PM_FILTER_BUILDER.buildForValue(bitmask); - exhaustGasSensor = EXHAUST_GAS_SENSOR_BUILDER.buildForValue(bitmask); - boostPressure = BOOST_PRESSURE_BUILDER.buildForValue(bitmask); - NOxSCR = NOx_SCR_BUILDER.buildForValue(bitmask); - NMHCCatalyst = NMHC_CATALYST_BUILDER.buildForValue(bitmask); + EGROrVVT = EGR_OR_VVT_DECODER.fromValue(bitmask); + PMFilter = PM_FILTER_DECODER.fromValue(bitmask); + exhaustGasSensor = EXHAUST_GAS_SENSOR_DECODER.fromValue(bitmask); + boostPressure = BOOST_PRESSURE_DECODER.fromValue(bitmask); + NOxSCR = NOx_SCR_DECODER.fromValue(bitmask); + NMHCCatalyst = NMHC_CATALYST_DECODER.fromValue(bitmask); } } } public @Nullable @FuelSystemStatus.Status Integer getFuelSystemStatus() { - return getSystemIntegerSensor(CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS); + return getSystemIntegerSensor( + CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS); } public @Nullable @SecondaryAirStatus.Status Integer getSecondaryAirStatus() { - return getSystemIntegerSensor(CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS); + return getSystemIntegerSensor( + CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS); } public @Nullable IgnitionMonitors.CommonIgnitionMonitors getIgnitionMonitors() { - Integer ignitionMonitorsType = getSystemIntegerSensor( - CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED); - Integer ignitionMonitorsBitmask = getSystemIntegerSensor( - CarDiagnosticSensorIndices.Obd2IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS); + Integer ignitionMonitorsType = + getSystemIntegerSensor( + CarDiagnosticSensorIndices.Obd2IntegerSensorIndex + .IGNITION_MONITORS_SUPPORTED); + Integer ignitionMonitorsBitmask = + getSystemIntegerSensor( + CarDiagnosticSensorIndices.Obd2IntegerSensorIndex + .IGNITION_SPECIFIC_MONITORS); if (null == ignitionMonitorsType) return null; if (null == ignitionMonitorsBitmask) return null; switch (ignitionMonitorsType) { - case 0: return new IgnitionMonitors.SparkIgnitionMonitors( - ignitionMonitorsBitmask); - case 1: return new IgnitionMonitors.CompressionIgnitionMonitors( - ignitionMonitorsBitmask); - default: return null; + case 0: + return new IgnitionMonitors.SparkIgnitionMonitors(ignitionMonitorsBitmask); + case 1: + return new IgnitionMonitors.CompressionIgnitionMonitors(ignitionMonitorsBitmask); + default: + return null; } } diff --git a/car-lib/src/android/car/hardware/CarDiagnosticManager.java b/car-lib/src/android/car/hardware/CarDiagnosticManager.java index 6d8b5ba58a..0444c14389 100644 --- a/car-lib/src/android/car/hardware/CarDiagnosticManager.java +++ b/car-lib/src/android/car/hardware/CarDiagnosticManager.java @@ -16,6 +16,7 @@ package android.car.hardware; +import android.annotation.IntDef; import android.car.Car; import android.car.CarApiUtil; import android.car.CarLibLog; @@ -32,6 +33,8 @@ import com.android.car.internal.CarPermission; import com.android.car.internal.CarRatedListeners; import com.android.car.internal.SingleMessageHandler; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -40,8 +43,17 @@ import java.util.function.Consumer; /** API for monitoring car diagnostic data. */ /** @hide */ public final class CarDiagnosticManager implements CarManagerBase { - public static final int FRAME_TYPE_FLAG_LIVE = 0; - public static final int FRAME_TYPE_FLAG_FREEZE = 1; + public static final int FRAME_TYPE_LIVE = 0; + public static final int FRAME_TYPE_FREEZE = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({FRAME_TYPE_LIVE, FRAME_TYPE_FREEZE}) + public @interface FrameType {} + + public static final @FrameType int FRAME_TYPES[] = { + FRAME_TYPE_LIVE, + FRAME_TYPE_FREEZE + }; private static final int MSG_DIAGNOSTIC_EVENTS = 0; @@ -93,10 +105,10 @@ public final class CarDiagnosticManager implements CarManagerBase { // OnDiagnosticEventListener registration - private void assertFrameType(int frameType) { + private void assertFrameType(@FrameType int frameType) { switch(frameType) { - case FRAME_TYPE_FLAG_FREEZE: - case FRAME_TYPE_FLAG_LIVE: + case FRAME_TYPE_FREEZE: + case FRAME_TYPE_LIVE: return; default: throw new IllegalArgumentException(String.format( @@ -113,7 +125,9 @@ public final class CarDiagnosticManager implements CarManagerBase { * @throws CarNotConnectedException * @throws IllegalArgumentException */ - public boolean registerListener(OnDiagnosticEventListener listener, int frameType, int rate) + public boolean registerListener(OnDiagnosticEventListener listener, + @FrameType int frameType, + int rate) throws CarNotConnectedException, IllegalArgumentException { assertFrameType(frameType); synchronized(mActiveListeners) { @@ -145,14 +159,15 @@ public final class CarDiagnosticManager implements CarManagerBase { */ public void unregisterListener(OnDiagnosticEventListener listener) { synchronized(mActiveListeners) { - for(int i = 0; i < mActiveListeners.size(); i++) { - doUnregisterListenerLocked(listener, mActiveListeners.keyAt(i)); + for(@FrameType int frameType : FRAME_TYPES) { + doUnregisterListenerLocked(listener, frameType); } } } - private void doUnregisterListenerLocked(OnDiagnosticEventListener listener, int sensor) { - CarDiagnosticListeners listeners = mActiveListeners.get(sensor); + private void doUnregisterListenerLocked(OnDiagnosticEventListener listener, + @FrameType int frameType) { + CarDiagnosticListeners listeners = mActiveListeners.get(frameType); if (listeners != null) { boolean needsServerUpdate = false; if (listeners.contains(listener)) { @@ -160,15 +175,15 @@ public final class CarDiagnosticManager implements CarManagerBase { } if (listeners.isEmpty()) { try { - mService.unregisterDiagnosticListener(sensor, + mService.unregisterDiagnosticListener(frameType, mListenerToService); } catch (RemoteException e) { //ignore } - mActiveListeners.remove(sensor); + mActiveListeners.remove(frameType); } else if (needsServerUpdate) { try { - registerOrUpdateDiagnosticListener(sensor, listeners.getRate()); + registerOrUpdateDiagnosticListener(frameType, listeners.getRate()); } catch (CarNotConnectedException e) { // ignore } @@ -176,7 +191,7 @@ public final class CarDiagnosticManager implements CarManagerBase { } } - private boolean registerOrUpdateDiagnosticListener(int frameType, int rate) + private boolean registerOrUpdateDiagnosticListener(@FrameType int frameType, int rate) throws CarNotConnectedException { try { return mService.registerOrUpdateDiagnosticListener(frameType, rate, mListenerToService); @@ -256,6 +271,72 @@ public final class CarDiagnosticManager implements CarManagerBase { return false; } + /** + * Returns true if this vehicle supports sending live frame information. + * @return + * @throws CarNotConnectedException + */ + public boolean isLiveFrameSupported() throws CarNotConnectedException { + try { + return mService.isLiveFrameSupported(); + } catch (IllegalStateException e) { + CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); + } catch (RemoteException e) { + throw new CarNotConnectedException(); + } + return false; + } + + /** + * Returns true if this vehicle supports sending freeze frame information. + * @return + * @throws CarNotConnectedException + */ + public boolean isFreezeFrameSupported() throws CarNotConnectedException { + try { + return mService.isFreezeFrameSupported(); + } catch (IllegalStateException e) { + CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); + } catch (RemoteException e) { + throw new CarNotConnectedException(); + } + return false; + } + + /** + * Returns true if this vehicle supports retrieving freeze frame timestamps. + * This is only meaningful if freeze frame data is also supported. + * @return + * @throws CarNotConnectedException + */ + public boolean isFreezeFrameTimestampSupported() throws CarNotConnectedException { + try { + return mService.isFreezeFrameTimestampSupported(); + } catch (IllegalStateException e) { + CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); + } catch (RemoteException e) { + throw new CarNotConnectedException(); + } + return false; + } + + /** + * Returns true if this vehicle supports clearing freeze frame timestamps. + * This is only meaningful if freeze frame data is also supported. + * @return + * @throws CarNotConnectedException + */ + public boolean isFreezeFrameClearSupported() throws CarNotConnectedException { + try { + return mService.isFreezeFrameClearSupported(); + } catch (IllegalStateException e) { + CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); + } catch (RemoteException e) { + throw new CarNotConnectedException(); + } + return false; + } + private static class CarDiagnosticEventListenerToService extends ICarDiagnosticEventListener.Stub { private final WeakReference<CarDiagnosticManager> mManager; @@ -284,10 +365,10 @@ public final class CarDiagnosticManager implements CarManagerBase { } void onDiagnosticEvent(final CarDiagnosticEvent event) { - // throw away old sensor data as oneway binder call can change order. + // throw away old data as oneway binder call can change order. long updateTime = event.timestamp; if (updateTime < mLastUpdateTime) { - Log.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old sensor data"); + Log.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old data"); return; } mLastUpdateTime = updateTime; diff --git a/car-lib/src/android/car/hardware/ICarDiagnostic.aidl b/car-lib/src/android/car/hardware/ICarDiagnostic.aidl index 098d2d4fcb..3afffc528b 100644 --- a/car-lib/src/android/car/hardware/ICarDiagnostic.aidl +++ b/car-lib/src/android/car/hardware/ICarDiagnostic.aidl @@ -52,4 +52,24 @@ interface ICarDiagnostic { */ void unregisterDiagnosticListener(int frameType, in ICarDiagnosticEventListener callback) = 6; -}
\ No newline at end of file + + /** + * Returns whether the underlying HAL supports live frames. + */ + boolean isLiveFrameSupported() = 7; + + /** + * Returns whether the underlying HAL supports freeze frames. + */ + boolean isFreezeFrameSupported() = 8; + + /** + * Returns whether the underlying HAL supports retrieving freeze frame timestamps. + */ + boolean isFreezeFrameTimestampSupported() = 9; + + /** + * Returns whether the underlying HAL supports clearing freeze frames. + */ + boolean isFreezeFrameClearSupported() = 10; +} diff --git a/car-lib/src/android/car/vms/IVmsPublisherService.aidl b/car-lib/src/android/car/vms/IVmsPublisherService.aidl index 26b6e523ed..3312794e9a 100644 --- a/car-lib/src/android/car/vms/IVmsPublisherService.aidl +++ b/car-lib/src/android/car/vms/IVmsPublisherService.aidl @@ -40,4 +40,11 @@ interface IVmsPublisherService { * Sets which layers the publisher can publish under which dependencties. */ oneway void setLayersOffering(in IBinder token, in VmsLayersOffering offering) = 2; + + /** + * The first time a publisher calls this API it will store the publisher info and assigns the + * publisher a static ID. Between reboots, subsequent calls with the same publisher info will + * return the same ID so that a restarting process can obtain the same ID as it had before. + */ + int getPublisherStaticId(in byte[] publisherInfo) = 3; } diff --git a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl index 236ae5a94c..923413478d 100644 --- a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl +++ b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl @@ -52,8 +52,12 @@ interface IVmsSubscriberService { in IVmsSubscriberClient listener) = 3; /** - * Tells the VmsSubscriberService a client requests the list of available layers. - * The service should call the client's onLayersAvailabilityChange in response. + * Returns a list of available layers from the closure of the publishers offerings. */ List<VmsLayer> getAvailableLayers() = 4; + + /** + * Returns a the publisher information for a publisher ID. + */ + byte[] getPublisherInfo(in int publisherId) = 5; } diff --git a/car-lib/src/android/car/vms/VmsLayer.java b/car-lib/src/android/car/vms/VmsLayer.java index 702daec9ef..afd0ae7835 100644 --- a/car-lib/src/android/car/vms/VmsLayer.java +++ b/car-lib/src/android/car/vms/VmsLayer.java @@ -24,8 +24,10 @@ import java.util.Objects; /** * A VMS Layer which can be subscribed to by VMS clients. - * Consists of the layer ID and the layer version. + * Consists of the layer ID and the layer major version. * + * This class does not contain the minor version since all minor version are backward and forward + * compatible and should not be used for routing messages. * @hide */ @FutureFeature diff --git a/car-lib/src/android/car/vms/VmsLayerDependency.java b/car-lib/src/android/car/vms/VmsLayerDependency.java index bb588ebf7a..e14c7ecbc2 100644 --- a/car-lib/src/android/car/vms/VmsLayerDependency.java +++ b/car-lib/src/android/car/vms/VmsLayerDependency.java @@ -80,6 +80,10 @@ public final class VmsLayerDependency implements Parcelable { } }; + public String toString() { + return "VmsLayerDependency{ Layer: " + mLayer + " Dependency: " + mDependency + "}"; + } + @Override public void writeToParcel(Parcel out, int flags) { out.writeParcelable(mLayer, flags); diff --git a/car-lib/src/android/car/vms/VmsLayersOffering.java b/car-lib/src/android/car/vms/VmsLayersOffering.java index 12b3dd30b2..51a0b995be 100644 --- a/car-lib/src/android/car/vms/VmsLayersOffering.java +++ b/car-lib/src/android/car/vms/VmsLayersOffering.java @@ -55,6 +55,11 @@ public final class VmsLayersOffering implements Parcelable { }; @Override + public String toString() { + return "VmsLayersOffering{" + mDependencies+ "}"; + } + + @Override public void writeToParcel(Parcel out, int flags) { out.writeParcelableList(mDependencies, flags); } diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java index 2743ff157b..ea265dfe5e 100644 --- a/car-lib/src/android/car/vms/VmsPublisherClientService.java +++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java @@ -106,6 +106,41 @@ public abstract class VmsPublisherClientService extends Service { if (DBG) { Log.d(TAG, "Publishing for layer : " + layer); } + + IBinder token = getTokenForPublisherServiceThreadSafe(); + + try { + mVmsPublisherService.publish(token, layer, payload); + return true; + } catch (RemoteException e) { + Log.e(TAG, "unable to publish message: " + payload, e); + } + return false; + } + + /** + * Uses the VmsPublisherService binder to set the layers offering. + * + * @param offering the layers that the publisher may publish. + * @return if the call to VmsPublisherService.setLayersOffering was successful. + */ + public final boolean setLayersOffering(VmsLayersOffering offering) { + if (DBG) { + Log.d(TAG, "Setting layers offering : " + offering); + } + + IBinder token = getTokenForPublisherServiceThreadSafe(); + + try { + mVmsPublisherService.setLayersOffering(token, offering); + return true; + } catch (RemoteException e) { + Log.e(TAG, "unable to set layers offering: " + offering, e); + } + return false; + } + + private IBinder getTokenForPublisherServiceThreadSafe() { if (mVmsPublisherService == null) { throw new IllegalStateException("VmsPublisherService not set."); } @@ -117,13 +152,24 @@ public abstract class VmsPublisherClientService extends Service { if (token == null) { throw new IllegalStateException("VmsPublisherService does not have a valid token."); } + return token; + } + + public final int getPublisherStaticId(byte[] publisherInfo) { + if (mVmsPublisherService == null) { + throw new IllegalStateException("VmsPublisherService not set."); + } + Integer publisherStaticId = null; try { - mVmsPublisherService.publish(token, layer, payload); - return true; + Log.i(TAG, "Getting publisher static ID"); + publisherStaticId = mVmsPublisherService.getPublisherStaticId(publisherInfo); } catch (RemoteException e) { - Log.e(TAG, "unable to publish message: " + payload, e); + Log.e(TAG, "unable to invoke binder method.", e); } - return false; + if (publisherStaticId == null) { + throw new IllegalStateException("VmsPublisherService cannot get a publisher static ID."); + } + return publisherStaticId; } /** diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java index d0b8c624c8..84405f4a12 100644 --- a/car-lib/src/android/car/vms/VmsSubscriberManager.java +++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java @@ -57,6 +57,9 @@ public final class VmsSubscriberManager implements CarManagerBase { /** Called when layers availability change */ void onLayersAvailabilityChange(List<VmsLayer> availableLayers); + + /** Notifies the client of the disconnect event */ + void onCarDisconnected(); } /** @@ -136,6 +139,8 @@ public final class VmsSubscriberManager implements CarManagerBase { * Therefore, notifications from the {@link com.android.car.VmsSubscriberService} are received * by the {@link #mIListener} and then forwarded to the {@link #mListener}. * + * It is expected that this method is invoked just once during the lifetime of the object. + * * @param listener subscriber listener that will handle onVmsMessageReceived events. * @throws IllegalStateException if the listener was already set. */ @@ -152,13 +157,21 @@ public final class VmsSubscriberManager implements CarManagerBase { } /** - * Removes the listener and unsubscribes from all the layer/version. + * Returns a serialized publisher information for a publisher ID. */ - public void clearListener() { - synchronized (mListenerLock) { - mListener = null; + public byte[] getPublisherInfo(int publisherId) throws CarNotConnectedException, IllegalStateException { + if (DBG) { + Log.d(TAG, "Getting all publishers info."); + } + try { + return mVmsSubscriberService.getPublisherInfo(publisherId); + } catch (RemoteException e) { + Log.e(TAG, "Could not connect: ", e); + throw new CarNotConnectedException(e); + } catch (IllegalStateException ex) { + Car.checkCarNotConnectedExceptionFromCarService(ex); + throw new IllegalStateException(ex); } - // TODO(antoniocortes): logic to unsubscribe from all the layer/version pairs. } /** @@ -167,8 +180,7 @@ public final class VmsSubscriberManager implements CarManagerBase { * @param layer the layer to subscribe to. * @throws IllegalStateException if the listener was not set via {@link #setListener}. */ - public void subscribe(VmsLayer layer) - throws CarNotConnectedException { + public void subscribe(VmsLayer layer) throws CarNotConnectedException { if (DBG) { Log.d(TAG, "Subscribing to layer: " + layer); } @@ -191,8 +203,7 @@ public final class VmsSubscriberManager implements CarManagerBase { } } - public void subscribeAll() - throws CarNotConnectedException { + public void subscribeAll() throws CarNotConnectedException { if (DBG) { Log.d(TAG, "Subscribing passively to all data messages"); } @@ -244,6 +255,29 @@ public final class VmsSubscriberManager implements CarManagerBase { } } + public void unsubscribeAll() { + if (DBG) { + Log.d(TAG, "Unsubscribing passively from all data messages"); + } + VmsSubscriberClientListener listener; + synchronized (mListenerLock) { + listener = mListener; + } + if (listener == null) { + Log.w(TAG, "unsubscribeAll: listener was not set, " + + "setListener must be called first."); + throw new IllegalStateException("Listener was not set."); + } + try { + mVmsSubscriberService.removeVmsSubscriberClientPassiveListener(mIListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister subscriber ", e); + // ignore + } catch (IllegalStateException ex) { + Car.hideCarNotConnectedExceptionFromCarService(ex); + } + } + private void dispatchOnReceiveMessage(VmsLayer layer, byte[] payload) { VmsSubscriberClientListener listener; synchronized (mListenerLock) { @@ -271,7 +305,15 @@ public final class VmsSubscriberManager implements CarManagerBase { /** @hide */ @Override public void onCarDisconnected() { - clearListener(); + VmsSubscriberClientListener listener; + synchronized (mListenerLock) { + listener = mListener; + } + if (listener == null) { + Log.e(TAG, "Listener died, not dispatching event."); + return; + } + listener.onCarDisconnected(); } private static final class VmsDataMessage { diff --git a/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java b/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java index 837146385f..55da8707dc 100644 --- a/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java +++ b/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java @@ -26,6 +26,4 @@ public class FeatureConfiguration { /** product configuration in CarInfoManager */ public static final boolean ENABLE_PRODUCT_CONFIGURATION_INFO = DEFAULT; public static final boolean ENABLE_VEHICLE_MAP_SERVICE = DEFAULT; - public static final boolean ENABLE_DIAGNOSTIC = DEFAULT; - public static final boolean ENABLE_VEHICLE_HAL_V2_1 = DEFAULT; } diff --git a/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java b/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java index 62bfd068dd..66cff604c5 100644 --- a/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java +++ b/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java @@ -26,6 +26,4 @@ public class FeatureConfiguration { /** product configuration in CarInfoManager */ public static final boolean ENABLE_PRODUCT_CONFIGURATION_INFO = DEFAULT; public static final boolean ENABLE_VEHICLE_MAP_SERVICE = DEFAULT; - public static final boolean ENABLE_DIAGNOSTIC = DEFAULT; - public static final boolean ENABLE_VEHICLE_HAL_V2_1 = DEFAULT; } diff --git a/car-maps-placeholder/Android.mk b/car-maps-placeholder/Android.mk index a8bcd04435..5f0b93ad73 100644 --- a/car-maps-placeholder/Android.mk +++ b/car-maps-placeholder/Android.mk @@ -32,6 +32,6 @@ LOCAL_PROGUARD_ENABLED := disabled LOCAL_DEX_PREOPT := false -include packages/services/Car/car-support-lib/car-support.mk +include packages/apps/Car/libs/car-stream-ui-lib/car-stream-ui-lib.mk include $(BUILD_PACKAGE) diff --git a/car-support-lib/Android.mk b/car-support-lib/Android.mk index 2f8400e971..d0052e46f0 100644 --- a/car-support-lib/Android.mk +++ b/car-support-lib/Android.mk @@ -28,14 +28,13 @@ LOCAL_MODULE_CLASS := JAVA_LIBRARIES LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +# Build against the current public APIs of the SDK +LOCAL_SDK_VERSION := current + LOCAL_MANIFEST_FILE := AndroidManifest.xml LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src) LOCAL_JAVA_LIBRARIES += android.car\ - android-support-v4 \ - android-support-v7-appcompat \ - android-support-v7-recyclerview \ - android-support-v7-cardview \ android-support-annotations # Specify 1.7 for backwards compatibility. # Otherwise the lib won't be usable on pre-N devices @@ -51,41 +50,18 @@ ifeq ($(BOARD_IS_AUTOMOTIVE), true) $(call dist-for-goals,dist_files,$(built_aar):android.support.car.aar) endif -# Build the resources. -include $(CLEAR_VARS) -LOCAL_MODULE := android.support.car-res -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res -LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res -LOCAL_RESOURCE_DIR += frameworks/support/v7/cardview/res - -LOCAL_AAPT_FLAGS := --auto-add-overlay \ - --extra-packages android.support.v7.appcompat \ - --extra-packages android.support.v7.recyclerview \ - --extra-packages android.support.v7.cardview - -LOCAL_JAR_EXCLUDE_FILES := none -LOCAL_MANIFEST_FILE := AndroidManifest.xml - -LOCAL_JAVA_LANGUAGE_VERSION := 1.7 -include $(BUILD_STATIC_JAVA_LIBRARY) - # Build support library. # --------------------------------------------- include $(CLEAR_VARS) LOCAL_MODULE := android.support.car +LOCAL_SDK_VERSION := current LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 \ - android-support-v7-appcompat \ - android-support-v7-recyclerview \ - android-support-v7-cardview \ - android-support-annotations +LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations -LOCAL_JAVA_LIBRARIES += android.car \ - android.support.car-res +LOCAL_JAVA_LIBRARIES += android.car LOCAL_JAVA_LANGUAGE_VERSION := 1.7 include $(BUILD_STATIC_JAVA_LIBRARY) @@ -112,11 +88,6 @@ LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src LOCAL_JAVA_LIBRARIES := \ android.car \ - android.support.car-res \ - android-support-v4 \ - android-support-v7-appcompat \ - android-support-v7-recyclerview \ - android-support-v7-cardview \ android-support-annotations LOCAL_MODULE := android.support.car diff --git a/car-support-lib/car-support.mk b/car-support-lib/car-support.mk index 081d6a6852..7feba678f5 100644 --- a/car-support-lib/car-support.mk +++ b/car-support-lib/car-support.mk @@ -22,35 +22,9 @@ ifeq (,$(findstring --auto-add-overlay, $(LOCAL_AAPT_FLAGS))) LOCAL_AAPT_FLAGS += --auto-add-overlay endif -# Include car ui library, if not already included +# Include car support library, if not already included ifeq (,$(findstring android.support.car, $(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += \ - packages/services/Car/car-support-lib/res -LOCAL_AAPT_FLAGS += --extra-packages android.support.car.ui LOCAL_STATIC_JAVA_LIBRARIES += android.support.car endif -## Include transitive dependencies below - -# Include support-v7-appcompat, if not already included -ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res -LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat -endif - -# Include support-v7-recyclerview, if not already included -ifeq (,$(findstring android-support-v7-recyclerview,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res -LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.recyclerview -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview -endif - -# Include support-v7-cardview, if not already included -ifeq (,$(findstring android-support-v7-cardview,$(LOCAL_STATIC_JAVA_LIBRARIES))) -LOCAL_RESOURCE_DIR += frameworks/support/v7/cardview/res -LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.cardview -LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-cardview -endif LOCAL_JAVA_LIBRARIES += android.car - diff --git a/car-support-lib/res/anim/car_fab_state_list_animator.xml b/car-support-lib/res/anim/car_fab_state_list_animator.xml deleted file mode 100644 index a1a22409e2..0000000000 --- a/car-support-lib/res/anim/car_fab_state_list_animator.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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_focused="true" > - <set> - <objectAnimator android:propertyName="translationZ" - android:duration="@integer/car_fab_animation_duration" - android:valueTo="16dp" - android:valueType="floatType"/> - </set> - </item> - <!-- base state --> - <item android:state_focused="false"> - <set> - <objectAnimator android:propertyName="translationZ" - android:duration="@integer/car_fab_animation_duration" - android:valueTo="0dp" - android:valueType="floatType"/> - </set> - </item> -</selector>
\ No newline at end of file diff --git a/car-support-lib/res/anim/car_list_in.xml b/car-support-lib/res/anim/car_list_in.xml deleted file mode 100644 index 29f97646b5..0000000000 --- a/car-support-lib/res/anim/car_list_in.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate - android:fromYDelta="15%" - android:toYDelta="0%" - android:interpolator="@android:interpolator/accelerate_decelerate" - android:duration="250" - android:startOffset="150"/> - - <alpha - android:fromAlpha="0.0" - android:toAlpha="1.0" - android:interpolator="@android:interpolator/accelerate_decelerate" - android:duration="100" - android:startOffset="150" /> -</set> diff --git a/car-support-lib/res/anim/car_list_out.xml b/car-support-lib/res/anim/car_list_out.xml deleted file mode 100644 index 8675cd8361..0000000000 --- a/car-support-lib/res/anim/car_list_out.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate - android:fromXDelta="0%" - android:toXDelta="-8%" - android:interpolator="@android:interpolator/accelerate_decelerate" - android:duration="150" /> - - <alpha - android:fromAlpha="1.0" - android:toAlpha="0.0" - android:interpolator="@android:interpolator/accelerate_decelerate" - android:duration="100" - android:startOffset="50" /> -</set> diff --git a/car-support-lib/res/anim/car_list_pop_out.xml b/car-support-lib/res/anim/car_list_pop_out.xml deleted file mode 100644 index b81d61bf31..0000000000 --- a/car-support-lib/res/anim/car_list_pop_out.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <alpha - android:interpolator="@android:interpolator/accelerate_decelerate" - android:fromAlpha="1.0" - android:toAlpha="0.0" - android:duration="100" - android:startOffset="50"/> - - <scale - android:fromXScale="1.0" - android:toXScale="0.9" - android:fromYScale="1.0" - android:toYScale="0.9" - android:pivotX="50%" - android:pivotY="50%" - android:duration="150" /> -</set>
\ No newline at end of file diff --git a/car-support-lib/res/anim/sdk_list_out.xml b/car-support-lib/res/anim/sdk_list_out.xml deleted file mode 100644 index d8dae8073a..0000000000 --- a/car-support-lib/res/anim/sdk_list_out.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate - android:fromXDelta="0%" - android:toXDelta="-8%" - android:interpolator="@android:interpolator/accelerate_decelerate" - android:duration="150" - android:startOffset="250" /> - - <alpha - android:fromAlpha="1.0" - android:toAlpha="0.0" - android:interpolator="@android:interpolator/accelerate_decelerate" - android:duration="100" - android:startOffset="300" /> -</set> diff --git a/car-support-lib/res/drawable-hdpi/drawer_shadow.9.png b/car-support-lib/res/drawable-hdpi/drawer_shadow.9.png Binary files differdeleted file mode 100644 index 8964dc4f5e..0000000000 --- a/car-support-lib/res/drawable-hdpi/drawer_shadow.9.png +++ /dev/null diff --git a/car-support-lib/res/drawable-hdpi/error_illustration.png b/car-support-lib/res/drawable-hdpi/error_illustration.png Binary files differdeleted file mode 100644 index 61e51457e5..0000000000 --- a/car-support-lib/res/drawable-hdpi/error_illustration.png +++ /dev/null diff --git a/car-support-lib/res/drawable-hdpi/ic_down.png b/car-support-lib/res/drawable-hdpi/ic_down.png Binary files differdeleted file mode 100644 index 6f47204c32..0000000000 --- a/car-support-lib/res/drawable-hdpi/ic_down.png +++ /dev/null diff --git a/car-support-lib/res/drawable-hdpi/ic_launcher.png b/car-support-lib/res/drawable-hdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 71c6d760f0..0000000000 --- a/car-support-lib/res/drawable-hdpi/ic_launcher.png +++ /dev/null diff --git a/car-support-lib/res/drawable-hdpi/ic_remove_circle.png b/car-support-lib/res/drawable-hdpi/ic_remove_circle.png Binary files differdeleted file mode 100644 index 1f40a6f064..0000000000 --- a/car-support-lib/res/drawable-hdpi/ic_remove_circle.png +++ /dev/null diff --git a/car-support-lib/res/drawable-hdpi/ic_up.png b/car-support-lib/res/drawable-hdpi/ic_up.png Binary files differdeleted file mode 100644 index b2b9929c7d..0000000000 --- a/car-support-lib/res/drawable-hdpi/ic_up.png +++ /dev/null diff --git a/car-support-lib/res/drawable-hdpi/ic_up_dark.png b/car-support-lib/res/drawable-hdpi/ic_up_dark.png Binary files differdeleted file mode 100644 index 3586ac296c..0000000000 --- a/car-support-lib/res/drawable-hdpi/ic_up_dark.png +++ /dev/null diff --git a/car-support-lib/res/drawable-mdpi/drawer_shadow.9.png b/car-support-lib/res/drawable-mdpi/drawer_shadow.9.png Binary files differdeleted file mode 100644 index 1417d1f63f..0000000000 --- a/car-support-lib/res/drawable-mdpi/drawer_shadow.9.png +++ /dev/null diff --git a/car-support-lib/res/drawable-mdpi/error_illustration.png b/car-support-lib/res/drawable-mdpi/error_illustration.png Binary files differdeleted file mode 100644 index 1c6ab5ccd6..0000000000 --- a/car-support-lib/res/drawable-mdpi/error_illustration.png +++ /dev/null diff --git a/car-support-lib/res/drawable-mdpi/ic_down.png b/car-support-lib/res/drawable-mdpi/ic_down.png Binary files differdeleted file mode 100644 index 0f691e2b23..0000000000 --- a/car-support-lib/res/drawable-mdpi/ic_down.png +++ /dev/null diff --git a/car-support-lib/res/drawable-mdpi/ic_remove_circle.png b/car-support-lib/res/drawable-mdpi/ic_remove_circle.png Binary files differdeleted file mode 100644 index 795c1ce7e7..0000000000 --- a/car-support-lib/res/drawable-mdpi/ic_remove_circle.png +++ /dev/null diff --git a/car-support-lib/res/drawable-mdpi/ic_up.png b/car-support-lib/res/drawable-mdpi/ic_up.png Binary files differdeleted file mode 100644 index a3fc019d62..0000000000 --- a/car-support-lib/res/drawable-mdpi/ic_up.png +++ /dev/null diff --git a/car-support-lib/res/drawable-mdpi/ic_up_dark.png b/car-support-lib/res/drawable-mdpi/ic_up_dark.png Binary files differdeleted file mode 100644 index 13afaa1927..0000000000 --- a/car-support-lib/res/drawable-mdpi/ic_up_dark.png +++ /dev/null diff --git a/car-support-lib/res/drawable-v21/car_pagination_background.xml b/car-support-lib/res/drawable-v21/car_pagination_background.xml deleted file mode 100644 index 7f72c8bb42..0000000000 --- a/car-support-lib/res/drawable-v21/car_pagination_background.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<ripple - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/car_card_ripple_background" />
\ No newline at end of file diff --git a/car-support-lib/res/drawable-v21/car_pagination_background_dark.xml b/car-support-lib/res/drawable-v21/car_pagination_background_dark.xml deleted file mode 100644 index 8ed8c1ae49..0000000000 --- a/car-support-lib/res/drawable-v21/car_pagination_background_dark.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<ripple - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/car_card_ripple_background_dark" />
\ No newline at end of file diff --git a/car-support-lib/res/drawable-v21/car_pagination_background_light.xml b/car-support-lib/res/drawable-v21/car_pagination_background_light.xml deleted file mode 100644 index c1097392eb..0000000000 --- a/car-support-lib/res/drawable-v21/car_pagination_background_light.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<ripple - xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/car_card_ripple_background_light" /> - diff --git a/car-support-lib/res/drawable-xhdpi/drawer_shadow.9.png b/car-support-lib/res/drawable-xhdpi/drawer_shadow.9.png Binary files differdeleted file mode 100644 index 46cc27301c..0000000000 --- a/car-support-lib/res/drawable-xhdpi/drawer_shadow.9.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xhdpi/error_illustration.png b/car-support-lib/res/drawable-xhdpi/error_illustration.png Binary files differdeleted file mode 100644 index 2533a431a8..0000000000 --- a/car-support-lib/res/drawable-xhdpi/error_illustration.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xhdpi/ic_down.png b/car-support-lib/res/drawable-xhdpi/ic_down.png Binary files differdeleted file mode 100644 index a221d0b8e6..0000000000 --- a/car-support-lib/res/drawable-xhdpi/ic_down.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xhdpi/ic_remove_circle.png b/car-support-lib/res/drawable-xhdpi/ic_remove_circle.png Binary files differdeleted file mode 100644 index 6eda519346..0000000000 --- a/car-support-lib/res/drawable-xhdpi/ic_remove_circle.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xhdpi/ic_up.png b/car-support-lib/res/drawable-xhdpi/ic_up.png Binary files differdeleted file mode 100644 index bbbe1fa767..0000000000 --- a/car-support-lib/res/drawable-xhdpi/ic_up.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xhdpi/ic_up_dark.png b/car-support-lib/res/drawable-xhdpi/ic_up_dark.png Binary files differdeleted file mode 100644 index ed73bd47bf..0000000000 --- a/car-support-lib/res/drawable-xhdpi/ic_up_dark.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xxhdpi/drawer_shadow.9.png b/car-support-lib/res/drawable-xxhdpi/drawer_shadow.9.png Binary files differdeleted file mode 100644 index 7b8a58bd71..0000000000 --- a/car-support-lib/res/drawable-xxhdpi/drawer_shadow.9.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xxhdpi/error_illustration.png b/car-support-lib/res/drawable-xxhdpi/error_illustration.png Binary files differdeleted file mode 100644 index dd4cc28145..0000000000 --- a/car-support-lib/res/drawable-xxhdpi/error_illustration.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xxhdpi/ic_down.png b/car-support-lib/res/drawable-xxhdpi/ic_down.png Binary files differdeleted file mode 100644 index 853ce45843..0000000000 --- a/car-support-lib/res/drawable-xxhdpi/ic_down.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xxhdpi/ic_remove_circle.png b/car-support-lib/res/drawable-xxhdpi/ic_remove_circle.png Binary files differdeleted file mode 100644 index c4fc1dd073..0000000000 --- a/car-support-lib/res/drawable-xxhdpi/ic_remove_circle.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xxhdpi/ic_up.png b/car-support-lib/res/drawable-xxhdpi/ic_up.png Binary files differdeleted file mode 100644 index fb91db931a..0000000000 --- a/car-support-lib/res/drawable-xxhdpi/ic_up.png +++ /dev/null diff --git a/car-support-lib/res/drawable-xxhdpi/ic_up_dark.png b/car-support-lib/res/drawable-xxhdpi/ic_up_dark.png Binary files differdeleted file mode 100644 index 6c44c5d5b7..0000000000 --- a/car-support-lib/res/drawable-xxhdpi/ic_up_dark.png +++ /dev/null diff --git a/car-support-lib/res/drawable/car_empty.xml b/car-support-lib/res/drawable/car_empty.xml deleted file mode 100644 index b3a4b24f5b..0000000000 --- a/car-support-lib/res/drawable/car_empty.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<!-- This is a blank shape, 0x0 in size, that works around the fact that the - android:textSelectHandle xml property requires a drawable with a defined size. --> -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle" > - <size - android:width="0dp" - android:height="0dp" /> -</shape>
\ No newline at end of file diff --git a/car-support-lib/res/drawable/car_header_button_background.xml b/car-support-lib/res/drawable/car_header_button_background.xml deleted file mode 100644 index 120738c1aa..0000000000 --- a/car-support-lib/res/drawable/car_header_button_background.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<inset - xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="24dp"> - <ripple android:color="@color/car_card_ripple_background" /> -</inset> diff --git a/car-support-lib/res/drawable/car_list_item_background.xml b/car-support-lib/res/drawable/car_list_item_background.xml deleted file mode 100644 index 9f6863ff21..0000000000 --- a/car-support-lib/res/drawable/car_list_item_background.xml +++ /dev/null @@ -1,20 +0,0 @@ -<!-- Copyright (C) 2015 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. ---> -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/car_card_ripple_background"> - <item android:id="@android:id/mask"> - <color android:color="#ffffffff" /> - </item> -</ripple>
\ No newline at end of file diff --git a/car-support-lib/res/drawable/car_pagination_background.xml b/car-support-lib/res/drawable/car_pagination_background.xml deleted file mode 100644 index 7aa4df4004..0000000000 --- a/car-support-lib/res/drawable/car_pagination_background.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval" - android:color="@color/car_card_ripple_background" > - <size - android:height="0dp" - android:width="0dp" /> -</shape>
\ No newline at end of file diff --git a/car-support-lib/res/drawable/car_pagination_background_dark.xml b/car-support-lib/res/drawable/car_pagination_background_dark.xml deleted file mode 100644 index 7aa4df4004..0000000000 --- a/car-support-lib/res/drawable/car_pagination_background_dark.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval" - android:color="@color/car_card_ripple_background" > - <size - android:height="0dp" - android:width="0dp" /> -</shape>
\ No newline at end of file diff --git a/car-support-lib/res/drawable/car_pagination_background_light.xml b/car-support-lib/res/drawable/car_pagination_background_light.xml deleted file mode 100644 index 7aa4df4004..0000000000 --- a/car-support-lib/res/drawable/car_pagination_background_light.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval" - android:color="@color/car_card_ripple_background" > - <size - android:height="0dp" - android:width="0dp" /> -</shape>
\ No newline at end of file diff --git a/car-support-lib/res/drawable/ic_chevron_right.xml b/car-support-lib/res/drawable/ic_chevron_right.xml deleted file mode 100644 index 165fcee7fb..0000000000 --- a/car-support-lib/res/drawable/ic_chevron_right.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:fillColor="#212121" - android:strokeWidth="1" - android:pathData="M 10 6 L 8.59 7.41 L 13.17 12 L 8.59 16.59 L 10 18 L 16 12 Z" /> - <path - android:strokeWidth="1" - android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z" /> -</vector>
\ No newline at end of file diff --git a/car-support-lib/res/drawable/ic_down_button.xml b/car-support-lib/res/drawable/ic_down_button.xml deleted file mode 100644 index 0565a636aa..0000000000 --- a/car-support-lib/res/drawable/ic_down_button.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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 android:height="24dp" android:viewportHeight="48.0" - android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#FF000000" android:pathData="M24,0c-13.3,0 -24,10.7 -24,24s10.7,24 24,24s24,-10.7 24,-24S37.3,0 24,0zM24,46c-12.1,0 -22,-9.9 -22,-22s9.9,-22 22,-22s22,9.9 22,22S36.1,46 24,46z"/> - <path android:fillColor="#FF000000" android:pathData="M17.1,19.2l6.9,6.9l6.9,-6.9l2.1,2.1l-9,9.1l-9,-9.1z"/> -</vector> diff --git a/car-support-lib/res/drawable/ic_up_button.xml b/car-support-lib/res/drawable/ic_up_button.xml deleted file mode 100644 index 8ac579ac06..0000000000 --- a/car-support-lib/res/drawable/ic_up_button.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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 android:height="24dp" android:viewportHeight="48.0" - android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#FF000000" android:pathData="M24,48c13.3,0 24,-10.7 24,-24s-10.7,-24 -24,-24s-24,10.7 -24,24S10.7,48 24,48zM24,2c12.1,0 22,9.9 22,22s-9.9,22 -22,22s-22,-9.9 -22,-22S11.9,2 24,2z"/> - <path android:fillColor="#FF000000" android:pathData="M30.9,28.8l-6.9,-6.9l-6.9,6.9l-2.1,-2.1l9,-9.1l9,9.1z"/> -</vector> diff --git a/car-support-lib/res/drawable/rail_fab.xml b/car-support-lib/res/drawable/rail_fab.xml deleted file mode 100644 index 8541cec929..0000000000 --- a/car-support-lib/res/drawable/rail_fab.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval" > - <size - android:width="@dimen/rail_height" - android:height="@dimen/rail_height" /> - <solid android:color="#191f27" /> -</shape> diff --git a/car-support-lib/res/layout/car_imageview.xml b/car-support-lib/res/layout/car_imageview.xml deleted file mode 100644 index ac51118de7..0000000000 --- a/car-support-lib/res/layout/car_imageview.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<ImageView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/car_list_item_right_icon_size" - android:layout_height="@dimen/car_list_item_right_icon_size" - android:scaleType="fitCenter" />
\ No newline at end of file diff --git a/car-support-lib/res/layout/car_list_item_1.xml b/car-support-lib/res/layout/car_list_item_1.xml deleted file mode 100644 index 0bd9ee94b0..0000000000 --- a/car-support-lib/res/layout/car_list_item_1.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height" - android:focusable="true" - android:orientation="horizontal" - android:background="@drawable/car_list_item_background" > - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/car_list_item_icon_size" - android:layout_height="@dimen/car_list_item_icon_size" - android:layout_marginRight="@dimen/car_list_item_icon_right_margin" - android:scaleType="centerCrop" - android:layout_gravity="center_vertical" /> - <TextView - android:id="@+id/text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - style="@style/CarBody1" - android:singleLine="true" /> - <ImageView - android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginRight="@dimen/car_list_item_right_icon_margin" - android:scaleType="center" /> -</LinearLayout> diff --git a/car-support-lib/res/layout/car_list_item_1_card.xml b/car-support-lib/res/layout/car_list_item_1_card.xml deleted file mode 100644 index 63ea0287bf..0000000000 --- a/car-support-lib/res/layout/car_list_item_1_card.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<android.support.v7.widget.CardView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height" - android:layout_marginBottom="@dimen/car_card_bottom_margin" - android:focusable="true" - android:orientation="horizontal" > - <!--app:cardBackgroundColor="@color/car_card" --> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/car_list_item_background" - android:duplicateParentState="true" - android:orientation="horizontal" > - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/car_list_item_icon_size" - android:layout_height="@dimen/car_list_item_icon_size" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:scaleType="centerCrop" - android:layout_gravity="center_vertical" /> - <TextView - android:id="@+id/text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - style="@style/CarBody1" - android:singleLine="true" /> - <ImageView - android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginRight="@dimen/car_list_item_right_icon_margin" - android:scaleType="center" /> - </LinearLayout> -</android.support.v7.widget.CardView> diff --git a/car-support-lib/res/layout/car_list_item_1_small.xml b/car-support-lib/res/layout/car_list_item_1_small.xml deleted file mode 100644 index 526070e027..0000000000 --- a/car-support-lib/res/layout/car_list_item_1_small.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height_small" - android:focusable="true" - android:orientation="horizontal" - android:background="@drawable/car_list_item_background" > - <FrameLayout - android:id="@+id/icon_container" - android:layout_width="@dimen/car_list_item_small_icon_size" - android:layout_height="@dimen/car_list_item_small_icon_size" - android:layout_marginRight="40dp" - android:visibility="visible" - android:layout_gravity="center_vertical"> - <ImageView - android:id="@+id/icon" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" - android:visibility="gone"/> - </FrameLayout> - <TextView - android:id="@+id/title" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginRight="8dp" - android:layout_gravity="center_vertical" - android:ellipsize="end" - android:singleLine="true" - style="@style/CarBody1" /> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:orientation="horizontal" > - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:maxWidth="275dp" - android:layout_gravity="center_vertical" - android:gravity="right" - style="@style/CarCaption" - android:ellipsize="end" - android:singleLine="true" /> - <ImageView - android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:visibility="gone" - android:scaleType="center" /> - </LinearLayout> - -</LinearLayout> diff --git a/car-support-lib/res/layout/car_list_item_1_small_card.xml b/car-support-lib/res/layout/car_list_item_1_small_card.xml deleted file mode 100644 index 2d90f3cf4c..0000000000 --- a/car-support-lib/res/layout/car_list_item_1_small_card.xml +++ /dev/null @@ -1,71 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<android.support.v7.widget.CardView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height_small" - android:focusable="true" - android:orientation="horizontal" - > - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/car_list_item_background" - android:duplicateParentState="true" - android:orientation="horizontal" > - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/car_list_item_small_icon_size" - android:layout_height="@dimen/car_list_item_small_icon_size" - android:layout_marginRight="40dp" - android:layout_gravity="center_vertical" - android:visibility="gone" - android:scaleType="centerCrop" /> - <TextView - android:id="@+id/title" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginRight="8dp" - android:layout_gravity="center_vertical" - android:ellipsize="end" - android:singleLine="true" - style="@style/CarBody1" /> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:orientation="horizontal" > - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:maxWidth="275dp" - android:layout_gravity="center_vertical" - android:gravity="right" - style="@style/CarCaption" - android:ellipsize="end" - android:singleLine="true" /> - <ImageView - android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginRight="@dimen/car_list_item_right_icon_margin" - android:visibility="gone" - android:scaleType="center" /> - </LinearLayout> - </LinearLayout> -</android.support.v7.widget.CardView> diff --git a/car-support-lib/res/layout/car_list_item_2.xml b/car-support-lib/res/layout/car_list_item_2.xml deleted file mode 100644 index 9cf7179ade..0000000000 --- a/car-support-lib/res/layout/car_list_item_2.xml +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height" - android:focusable="true" - android:orientation="horizontal" - android:background="@drawable/car_list_item_background" > - <FrameLayout - android:id="@+id/icon_container" - android:layout_width="@dimen/car_list_item_icon_size" - android:layout_height="@dimen/car_list_item_icon_size" - android:layout_marginRight="@dimen/car_list_item_icon_right_margin" - android:layout_gravity="center_vertical"> - <ImageView - android:id="@+id/icon" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" /> - </FrameLayout> - <LinearLayout - android:id="@+id/text_container" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - android:orientation="vertical" > - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/car_text_vertical_margin" - style="@style/CarBody1" - android:singleLine="true" /> - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/CarBody2" - android:ellipsize="end" - android:gravity="end" - android:singleLine="true" /> - </LinearLayout> - <ImageView - android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginRight="@dimen/car_list_item_right_icon_margin" - android:scaleType="center" /> -</LinearLayout> diff --git a/car-support-lib/res/layout/car_list_item_2_card.xml b/car-support-lib/res/layout/car_list_item_2_card.xml deleted file mode 100644 index 88a2c5f395..0000000000 --- a/car-support-lib/res/layout/car_list_item_2_card.xml +++ /dev/null @@ -1,67 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<android.support.v7.widget.CardView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height" - android:layout_marginBottom="@dimen/car_card_bottom_margin" - android:focusable="true" - android:orientation="horizontal" - > - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/car_list_item_background" - android:duplicateParentState="true" - android:orientation="horizontal" > - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/car_list_item_icon_size" - android:layout_height="@dimen/car_list_item_icon_size" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:scaleType="centerCrop" - android:layout_gravity="center_vertical" /> - <LinearLayout - android:id="@+id/text_container" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical" - android:layout_gravity="center_vertical" > - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/car_text_vertical_margin" - style="@style/CarBody1" - android:singleLine="true" /> - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/CarBody2" - android:singleLine="true" /> - </LinearLayout> - <ImageView - android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginRight="@dimen/car_list_item_right_icon_margin" - android:scaleType="center" /> - </LinearLayout> -</android.support.v7.widget.CardView> diff --git a/car-support-lib/res/layout/car_list_item_empty.xml b/car-support-lib/res/layout/car_list_item_empty.xml deleted file mode 100644 index de0e727b3a..0000000000 --- a/car-support-lib/res/layout/car_list_item_empty.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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/container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginLeft="16dp" - android:focusable="false" - android:orientation="vertical" - android:background="@drawable/car_list_item_background" > - <FrameLayout - android:id="@+id/icon_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="visible"> - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginTop="48dp" - android:layout_marginBottom="22dp" /> - </FrameLayout> - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="16dp" - android:gravity="center" - style="@style/CarBody1" /> -</LinearLayout> diff --git a/car-support-lib/res/layout/car_menu_list_item.xml b/car-support-lib/res/layout/car_menu_list_item.xml deleted file mode 100644 index 9cc3ebf0fd..0000000000 --- a/car-support-lib/res/layout/car_menu_list_item.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_item_height" - android:focusable="true" - android:orientation="horizontal" - android:background="@drawable/car_list_item_background" > - <FrameLayout - android:id="@+id/icon_container" - android:layout_width="@dimen/car_list_item_icon_size" - android:layout_height="@dimen/car_list_item_icon_size" - android:layout_marginRight="@dimen/car_list_item_icon_right_margin" - android:layout_gravity="center_vertical"> - <ImageView - android:id="@+id/icon" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" /> - </FrameLayout> - <LinearLayout - android:id="@+id/text_container" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - android:orientation="vertical" > - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/car_text_vertical_margin" - style="@style/CarBody1" - android:singleLine="true" /> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <FrameLayout - android:id="@+id/remoteviews" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:visibility="gone" /> - <TextView - android:id="@+id/text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/CarBody2" - android:ellipsize="end" - android:gravity="end" - android:singleLine="true" /> - </LinearLayout> - </LinearLayout> - <ViewStub - android:id="@+id/right_item" - android:layout_width="@dimen/car_list_item_right_icon_size" - android:layout_height="@dimen/car_list_item_right_icon_size" - android:layout_marginEnd="32dp" - android:layout_gravity="center_vertical" /> -</LinearLayout> diff --git a/car-support-lib/res/layout/car_paged_recycler_view.xml b/car-support-lib/res/layout/car_paged_recycler_view.xml deleted file mode 100644 index d53bb4697c..0000000000 --- a/car-support-lib/res/layout/car_paged_recycler_view.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> -<merge xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <android.support.car.ui.MaxWidthLayout - android:id="@+id/max_width_layout" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginStart="@dimen/car_drawer_button_container_width"> - <android.support.car.ui.CarRecyclerView - android:id="@+id/recycler_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:clipChildren="false"/> - </android.support.car.ui.MaxWidthLayout> - <!-- The scroll bar should be drawn ontop of the centered recycler view--> - <FrameLayout - android:layout_width="@dimen/car_drawer_button_container_width" - android:layout_height="match_parent"> - <android.support.car.ui.PagedScrollBarView - android:id="@+id/paged_scroll_view" - android:layout_width="@dimen/car_paged_list_view_pagination_width" - android:layout_height="match_parent" - android:paddingBottom="16dp" - android:paddingTop="16dp" - android:layout_gravity="center_horizontal" - android:visibility="invisible"/> - </FrameLayout> -</merge> diff --git a/car-support-lib/res/layout/car_paged_scrollbar_buttons.xml b/car-support-lib/res/layout/car_paged_scrollbar_buttons.xml deleted file mode 100644 index e976f1c574..0000000000 --- a/car-support-lib/res/layout/car_paged_scrollbar_buttons.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="left" - android:gravity="center" - android:orientation="vertical" > - <ImageView - android:id="@+id/page_up" - android:layout_width="@dimen/scroll_button_size" - android:layout_height="@dimen/scroll_button_size" - android:scaleType="fitCenter" - android:background="@drawable/car_pagination_background" - android:focusable="false" - android:hapticFeedbackEnabled="false" /> - <FrameLayout - android:id="@+id/filler" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:layout_marginTop="@dimen/car_paged_list_view_scrollbar_thumb_margin" - android:layout_marginBottom="@dimen/car_paged_list_view_scrollbar_thumb_margin" > - <ImageView - android:id="@+id/scrollbar_thumb" - android:layout_width="@dimen/scroll_bar_thumb_width" - android:layout_height="0dp" - android:layout_gravity="center_horizontal" - android:src="@color/car_scrollbar_thumb" /> - </FrameLayout> - <ImageView - android:id="@+id/page_down" - android:layout_width="@dimen/scroll_button_size" - android:layout_height="@dimen/scroll_button_size" - android:scaleType="fitCenter" - android:background="@drawable/car_pagination_background" - android:focusable="false" - android:hapticFeedbackEnabled="false" /> -</LinearLayout> diff --git a/car-support-lib/res/layout/car_textview.xml b/car-support-lib/res/layout/car_textview.xml deleted file mode 100644 index 5810af73ab..0000000000 --- a/car-support-lib/res/layout/car_textview.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/text" - style="@style/CarCaption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:maxWidth="275dp" - android:gravity="right" - android:ellipsize="end" - android:singleLine="true" />
\ No newline at end of file diff --git a/car-support-lib/res/layout/car_unavailable_category.xml b/car-support-lib/res/layout/car_unavailable_category.xml deleted file mode 100644 index d9da5a1efc..0000000000 --- a/car-support-lib/res/layout/car_unavailable_category.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_unavailable_category_item_height" - android:focusable="true" - android:orientation="horizontal" - android:background="@drawable/car_list_item_background" - android:baselineAligned="false" > - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/car_list_item_icon_size" - android:layout_height="@dimen/car_list_item_icon_size" - android:layout_marginRight="@dimen/car_list_item_icon_right_margin" - android:layout_gravity="center_vertical" - android:src="@drawable/ic_remove_circle" - android:scaleType="centerCrop" /> - <RelativeLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center_vertical" > - <TextView - android:id="@+id/title" - style="@style/CarUnavailableCategory" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/unavailable_category_first_part" /> - <TextView - android:id="@+id/text" - style="@style/CarUnavailableCategory" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/title" - android:text="@string/unavailable_category_second_part" /> - </RelativeLayout> -</LinearLayout>
\ No newline at end of file diff --git a/car-support-lib/res/values-h600dp/dimens.xml b/car-support-lib/res/values-h600dp/dimens.xml deleted file mode 100644 index 5887ab141a..0000000000 --- a/car-support-lib/res/values-h600dp/dimens.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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_drawer_margin_right">320dp</dimen> - <dimen name="car_list_item_right_icon_margin">112dp</dimen> - - <dimen name="car_list_item_icon_right_margin">60dp</dimen> - - <dimen name="car_headline0_size">72sp</dimen> - <dimen name="car_headline1_size">56sp</dimen> - <dimen name="car_headline2_size">50sp</dimen> - <dimen name="car_title_size">32sp</dimen> - <dimen name="car_body1_size">40sp</dimen> - <dimen name="car_body2_size">32sp</dimen> - <dimen name="car_key1_size">50sp</dimen> - <dimen name="car_key2_size">22sp</dimen> - <dimen name="car_caption_size">22sp</dimen> - - <dimen name="car_card_margin">228dp</dimen> - - <dimen name="car_list_item_icon_size">108dp</dimen> - <dimen name="car_list_item_small_icon_size">56dp</dimen> - <dimen name="car_list_item_right_icon_size">56dp</dimen> - - <dimen name="car_list_item_height">128dp</dimen> - <dimen name="car_list_item_height_small">128dp</dimen> -</resources> diff --git a/car-support-lib/res/values-h600dp/strings.xml b/car-support-lib/res/values-h600dp/strings.xml deleted file mode 100644 index 15274047da..0000000000 --- a/car-support-lib/res/values-h600dp/strings.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<resources> - <string name="car_font" translatable="false">sans-serif</string> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values-h600dp/styles.xml b/car-support-lib/res/values-h600dp/styles.xml deleted file mode 100644 index 831d3ca05c..0000000000 --- a/car-support-lib/res/values-h600dp/styles.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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> - <style name="CarDrawerArrowDrawable" > - <item name="carArrowColor">@android:color/white</item> - <item name="carArrowSpinBars">true</item> - <item name="carArrowThickness">4dp</item> - <item name="carArrowDrawableSize">@dimen/car_drawer_header_menu_button_size</item> - <item name="carArrowTopBottomBarSize">26dp</item> - <item name="carArrowBarSize">42dp</item> - <item name="carArrowMiddleBarSize">35dp</item> - <item name="carArrowGapBetweenBars">7dp</item> - </style> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values-night-wheel/colors.xml b/car-support-lib/res/values-night-wheel/colors.xml deleted file mode 100644 index e8512465eb..0000000000 --- a/car-support-lib/res/values-night-wheel/colors.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <color name="car_card_ripple_background">@color/car_controller_ripple_light</color> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values-night/colors.xml b/car-support-lib/res/values-night/colors.xml deleted file mode 100644 index 0ecdb84e18..0000000000 --- a/car-support-lib/res/values-night/colors.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <!-- Aliases for material colors that may be used programmatically --> - <color name="car_headline0">@color/car_headline0_light</color> - <color name="car_headline1">@color/car_headline1_light</color> - <color name="car_headline2">@color/car_headline2_light</color> - <color name="car_title">@color/car_title_light</color> - <color name="car_body1">@color/car_body1_light</color> - <color name="car_body2">@color/car_body2_light</color> - <color name="car_key1">@color/car_key1_light</color> - <color name="car_key2">@color/car_key2_light</color> - <color name="car_caption">@color/car_caption_light</color> - <color name="car_micro">@color/car_micro_light</color> - <color name="car_card">@color/car_card_dark</color> - <color name="car_card_ripple_background">@color/car_card_ripple_background_light</color> - <color name="car_card_ripple_light_color_background">@color/car_card_ripple_light_color_background_light</color> - <color name="car_overscroll_glow">@color/car_overscroll_glow_dark</color> - <color name="car_tint">@color/car_tint_light</color> - <color name="car_list_divider">@color/car_white_1000</color> - <color name="car_unavailable_category">@color/car_grey_50</color> -</resources> diff --git a/car-support-lib/res/values-notouch/bools.xml b/car-support-lib/res/values-notouch/bools.xml deleted file mode 100644 index 26f9c76641..0000000000 --- a/car-support-lib/res/values-notouch/bools.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <bool name="car_true_for_touch">false</bool> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values-w600dp/integers.xml b/car-support-lib/res/values-w600dp/integers.xml deleted file mode 100644 index e32083dc30..0000000000 --- a/car-support-lib/res/values-w600dp/integers.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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> - <integer name="stream_num_of_columns">8</integer> - <integer name="stream_card_default_column_span">6</integer> -</resources> diff --git a/car-support-lib/res/values-w720dp/dimens.xml b/car-support-lib/res/values-w720dp/dimens.xml deleted file mode 100644 index d72006e304..0000000000 --- a/car-support-lib/res/values-w720dp/dimens.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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_paged_list_view_pagination_width">76dp</dimen> - <dimen name="scroll_button_size">76dp</dimen> - <dimen name="scroll_bar_thumb_width">16dp</dimen> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values-wheel/bools.xml b/car-support-lib/res/values-wheel/bools.xml deleted file mode 100644 index 19fe2c4240..0000000000 --- a/car-support-lib/res/values-wheel/bools.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <bool name="has_wheel">true</bool> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values-wheel/colors.xml b/car-support-lib/res/values-wheel/colors.xml deleted file mode 100644 index f29b0705f8..0000000000 --- a/car-support-lib/res/values-wheel/colors.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <color name="car_card_ripple_background">@color/car_controller_ripple_dark</color> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values/attrs.xml b/car-support-lib/res/values/attrs.xml deleted file mode 100644 index c887fa01e3..0000000000 --- a/car-support-lib/res/values/attrs.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <declare-styleable name="PagedListView"> - <!-- Fade duration in ms --> - <attr name="fadeLastItem" format="boolean" /> - <!-- Set to true/false to offset rows as they slide off screen. Defaults to true --> - <attr name="offsetRows" format="boolean" /> - <attr name="glowColor" format="color" /> - <!-- Whether loading list view in drawer or not --> - <attr name="rightGutterEnabled" format="boolean" /> - </declare-styleable> - - <declare-styleable name="DrawerArrowDrawable"> - <!-- The drawing color for the bars --> - <attr name="carArrowColor" format="color"/> - <!-- Whether bars should rotate or not during transition --> - <attr name="carArrowSpinBars" format="boolean"/> - <!-- The total size of the drawable --> - <attr name="carArrowDrawableSize" format="dimension"/> - <!-- The max gap between the bars when they are parallel to each other --> - <attr name="carArrowGapBetweenBars" format="dimension"/> - <!-- The size of the top and bottom bars when they merge to the middle bar to form an arrow --> - <attr name="carArrowTopBottomBarSize" format="dimension"/> - <!-- The size of the middle bar when top and bottom bars merge into middle bar to form an arrow --> - <attr name="carArrowMiddleBarSize" format="dimension"/> - <!-- The size of the bars when they are parallel to each other --> - <attr name="carArrowBarSize" format="dimension"/> - <!-- The thickness (stroke size) for the bar paint --> - <attr name="carArrowThickness" format="dimension"/> - </declare-styleable> - - <attr name="carDrawerArrowStyle" format="reference" /> - - <declare-styleable name="MaxWidthLayout"> - <attr name="carMaxWidth" format="dimension" /> - </declare-styleable> -</resources> diff --git a/car-support-lib/res/values/bools.xml b/car-support-lib/res/values/bools.xml deleted file mode 100644 index dd685b3055..0000000000 --- a/car-support-lib/res/values/bools.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <bool name="car_true_for_touch">true</bool> - <bool name="has_wheel">false</bool> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values/colors.xml b/car-support-lib/res/values/colors.xml deleted file mode 100644 index ccf22aa7e7..0000000000 --- a/car-support-lib/res/values/colors.xml +++ /dev/null @@ -1,142 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <!-- Copy of material colors. --> - <!-- These colors are from http://www.google.com/design/spec/style/color.html#color-ui-color-palette --> - <color name="car_grey_50">#fffafafa</color> - <color name="car_grey_100">#fff5f5f5</color> - <color name="car_grey_200">#ffeeeeee</color> - <color name="car_grey_300">#ffe0e0e0</color> - <color name="car_grey_400">#ffbdbdbd</color> - <color name="car_grey_500">#ff9e9e9e</color> - <color name="car_grey_600">#ff757575</color> - <color name="car_grey_650">#ff6B6B6B</color> - <color name="car_grey_700">#ff616161</color> - <color name="car_grey_800">#ff424242</color> - <color name="car_grey_900">#ff212121</color> - <color name="car_grey_1000">#cc000000</color> - <color name="car_white_1000">#1effffff</color> - <color name="car_blue_grey_800">#ff37474F</color> - <color name="car_blue_grey_900">#ff263238</color> - <color name="car_dark_blue_grey_600">#ff1d272d</color> - <color name="car_dark_blue_grey_700">#ff172026</color> - <color name="car_dark_blue_grey_800">#ff11181d</color> - <color name="car_dark_blue_grey_900">#ff0c1013</color> - <color name="car_dark_blue_grey_1000">#ff090c0f</color> - <color name="car_light_blue_300">#ff4fc3f7</color> - <color name="car_light_blue_500">#ff03A9F4</color> - <color name="car_light_blue_600">#ff039be5</color> - <color name="car_light_blue_700">#ff0288d1</color> - <color name="car_light_blue_800">#ff0277bd</color> - <color name="car_light_blue_900">#ff01579b</color> - <color name="car_blue_300">#ff91a7ff</color> - <color name="car_blue_500">#ff5677fc</color> - <color name="car_green_500">#ff0f9d58</color> - <color name="car_green_700">#ff0b8043</color> - <color name="car_yellow_500">#fff4b400</color> - <color name="car_yellow_800">#ffee8100</color> - <color name="car_red_400">#ffe06055</color> - <color name="car_red_500">#ffdb4437</color> - <color name="car_red_500a">#ffd50000</color> - <color name="car_red_700">#ffc53929</color> - <color name="car_teal_200">#ff80cbc4</color> - <color name="car_teal_700">#ff00796b</color> - <color name="car_indigo_800">#ff283593</color> - <color name="car_700">#ff172026</color> - <color name="car_900">#ff0c1013</color> - <color name="car_fab_view_color">#ff009688</color> - <color name="car_fab_view_clicked_color">#0026a69a</color> - <color name="car_keygroup_circle">#ff104343</color> - <!-- Aliases for material colors that may be used programmatically --> - <color name="car_headline0_light">@color/car_grey_100</color> - <color name="car_headline0_dark">@color/car_grey_800</color> - <color name="car_headline0">@color/car_headline0_dark</color> - - <color name="car_headline1_light">@color/car_grey_100</color> - <color name="car_headline1_dark">@color/car_grey_800</color> - <color name="car_headline1">@color/car_headline1_dark</color> - - <color name="car_headline2_light">@color/car_grey_100</color> - <color name="car_headline2_dark">@color/car_grey_900</color> - <color name="car_headline2">@color/car_headline2_dark</color> - - <color name="car_title_light">@color/car_grey_100</color> - <color name="car_title_dark">@color/car_grey_900</color> - <color name="car_title">@color/car_title_dark</color> - - <color name="car_body1_light">@color/car_grey_100</color> - <color name="car_body1_dark">@color/car_grey_900</color> - <color name="car_body1">@color/car_body1_dark</color> - - <color name="car_body2_dark">@color/car_grey_650</color> - <color name="car_body2_light">@color/car_grey_500</color> - <color name="car_body2">@color/car_body2_dark</color> - - <color name="car_caption_light">@color/car_grey_400</color> - <color name="car_caption_dark">@color/car_grey_700</color> - <color name="car_caption">@color/car_caption_dark</color> - - <color name="car_key1_light">@color/car_grey_100</color> - <color name="car_key1_dark">@color/car_light_blue_700</color> - <color name="car_key1">@color/car_key1_dark</color> - - <color name="car_key2_light">@color/car_grey_400</color> - <color name="car_key2_dark">@color/car_grey_650</color> - <color name="car_key2">@color/car_key2_dark</color> - - <color name="car_micro_light">@color/car_grey_100</color> - <color name="car_micro_dark">@color/car_grey_700</color> - <color name="car_micro">@color/car_micro_dark</color> - - <color name="car_card_light">@color/car_grey_50</color> - <color name="car_card_dark">@color/car_dark_blue_grey_700</color> - <color name="car_card">@color/car_card_light</color> - - <color name="car_card_ripple_background_dark">#17000000</color> - <color name="car_card_ripple_background_light">#27ffffff</color> - <color name="car_card_ripple_background">@color/car_card_ripple_background_dark</color> - - <color name="car_card_ripple_light_color_background_dark">#8F000000</color> - <color name="car_card_ripple_light_color_background_light">#8F000000</color> - <color name="car_card_ripple_light_color_background">@color/car_card_ripple_light_color_background_dark</color> - - <color name="car_controller_ripple_dark">#b27da9c7</color> - <color name="car_controller_ripple_light">#66ffffff</color> - - <color name="car_overscroll_glow_light">@color/car_grey_400</color> - <color name="car_overscroll_glow_dark">@color/car_grey_900</color> - <color name="car_overscroll_glow">@color/car_overscroll_glow_light</color> - - <color name="car_tint_light">@color/car_grey_50</color> - <color name="car_tint_dark">@color/car_grey_900</color> - <color name="car_tint">@color/car_tint_dark</color> - - <color name="car_list_divider">#1f000000</color> - <color name="car_list_divider_light">#1fffffff</color> - <color name="car_list_divider_dark">#1f000000</color> - - <color name="car_ripple">#20444444</color> - - <color name="car_scrollbar_thumb">#80bdbdbd</color> - - <color name="car_focused_color">@color/car_teal_200</color> - - <color name="car_music_accent_color">#ff9700</color> - - <color name="car_error_screen">#ff1e272e</color> - - <color name="car_unavailable_category">@color/car_blue_grey_800</color> -</resources> diff --git a/car-support-lib/res/values/dimens.xml b/car-support-lib/res/values/dimens.xml deleted file mode 100644 index ae8b4b2fda..0000000000 --- a/car-support-lib/res/values/dimens.xml +++ /dev/null @@ -1,115 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <!-- Permission --> - <dimen name="missing_permission_icon_size">208dp</dimen> - - <dimen name="car_app_layout_icon_width">42dp</dimen> - <dimen name="car_app_layout_search_box_small_width">320dp</dimen> - <dimen name="car_app_layout_search_box_small_margin">16dp</dimen> - - <dimen name="car_headline0_size">50sp</dimen> - <dimen name="car_headline1_size">45sp</dimen> - <dimen name="car_headline2_size">36sp</dimen> - <dimen name="car_title_size">26sp</dimen> - <dimen name="car_body1_size">32sp</dimen> - <dimen name="car_body2_size">26sp</dimen> - <dimen name="car_caption_size">18sp</dimen> - <dimen name="car_key1_size">40sp</dimen> - <dimen name="car_key2_size">18sp</dimen> - <dimen name="car_micro_size">20sp</dimen> - - <dimen name="car_action_bar_icon_padding">10dp</dimen> - <dimen name="car_action_bar_margin">15dp</dimen> - - <dimen name="car_drawer_header_height">96dp</dimen> - <dimen name="car_drawer_header_menu_button_size">96dp</dimen> - <dimen name="car_drawer_standard_width">704dp</dimen> - <dimen name="car_drawer_max_width">884dp</dimen> - <dimen name="car_drawer_item_max_width">788dp</dimen> - - <dimen name="car_list_item_height">88dp</dimen> - <dimen name="car_list_item_height_small">64dp</dimen> - <!-- Sample row height used for scroll bar calculations in the off chance that a view hasn't - been measured. It's highly unlikely that this value will actually be used for more than - a frame max. The sample row is a 96dp card + 16dp margin on either side. --> - <dimen name="car_sample_row_height">128dp</dimen> - <!-- The amount of space the LayoutManager will make sure the last item on the screen is - peeking before scrolling down --> - <dimen name="car_last_card_peek_amount">16dp</dimen> - <dimen name="car_paged_list_view_pagination_width">48dp</dimen> - <dimen name="car_paged_list_view_button_background_inset">24dp</dimen> - <dimen name="car_paged_list_view_scrollbar_thumb_margin">8dp</dimen> - - <dimen name="scroll_button_size">48dp</dimen> - <dimen name="scroll_bar_thumb_width">12dp</dimen> - - <!-- height of list dividers --> - <dimen name="car_divider_height">1dp</dimen> - - <dimen name="car_list_item_icon_size">64dp</dimen> - <dimen name="car_list_item_icon_right_margin">32dp</dimen> - <dimen name="car_list_item_icon_size_small">32dp</dimen> - <dimen name="car_list_item_small_icon_size">56dp</dimen> - <dimen name="car_list_item_right_icon_margin">20dp</dimen> - <dimen name="car_list_item_right_icon_size">56dp</dimen> - <dimen name="car_list_unavailable_category_item_height">108dp</dimen> - <dimen name="car_list_truncated_list_card_height">108dp</dimen> - <dimen name="car_list_truncated_list_card_elevation">8dp</dimen> - <dimen name="car_list_truncated_list_icon_size">56dp</dimen> - <dimen name="car_list_truncated_list_icon_padding">22dp</dimen> - <dimen name="car_list_truncated_list_drawable_padding">48dp</dimen> - <dimen name="car_list_truncated_list_padding">10dp</dimen> - <dimen name="car_text_vertical_margin">2dp</dimen> - <dimen name="car_card_bottom_margin">8dp</dimen> - - <dimen name="car_touch_feedback_radius">32dp</dimen> - <dimen name="car_card_view_corner_radius">2dp</dimen> - <dimen name="car_card_view_elevation">8dp</dimen> - <dimen name="car_fab_focused_stroke_width">8dp</dimen> - <dimen name="car_fab_focused_growth">1.2dp</dimen> - <!-- The minimum the scrollbar thumb can shrink to --> - <dimen name="min_thumb_height">48dp</dimen> - <!-- The maximum the scrollbar thumb can grow to --> - <dimen name="max_thumb_height">128dp</dimen> - - <dimen name="car_standard_width">800dp</dimen> - <dimen name="car_card_max_width">768dp</dimen> - <dimen name="car_card_margin">96dp</dimen> - <dimen name="car_drawer_margin_right">96dp</dimen> - <dimen name="rail_height">80dp</dimen> - - <dimen name="car_drawer_button_container_width">@dimen/stream_content_keyline_2</dimen> - - <!-- The following values should match the values provided by car_stream-ui-lib. Eventually, - when the components in this library are broken out, then this dependency can be added - and the values removed. --> - - <!-- The margin on both sizes of the screen. This margin limits the amount of space that - content can take up on screen. --> - <dimen name="stream_margin_size">16dp</dimen> - - <!-- The size of the gutters between each column. --> - <dimen name="stream_gutter_size">16dp</dimen> - - <!-- Keylines for content in the stream. --> - <dimen name="stream_content_keyline_1">24dp</dimen> - <dimen name="stream_content_keyline_2">96dp</dimen> - - <!-- Keylines for content within a card. --> - <dimen name="stream_card_keyline_1">24dp</dimen> - <dimen name="stream_card_keyline_2">96dp</dimen> -</resources> diff --git a/car-support-lib/res/values/integers.xml b/car-support-lib/res/values/integers.xml deleted file mode 100644 index ef6338335d..0000000000 --- a/car-support-lib/res/values/integers.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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> - <integer name="car_fab_animation_duration">150</integer> -</resources>
\ No newline at end of file diff --git a/car-support-lib/res/values/strings.xml b/car-support-lib/res/values/strings.xml index 137f68ed0e..ab697e1d0a 100644 --- a/car-support-lib/res/values/strings.xml +++ b/car-support-lib/res/values/strings.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2017 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. @@ -14,18 +14,5 @@ limitations under the License. --> <resources> - <!-- Format string for the clock. TODO: Use the system preference --> - <string name="car_clock_24_hours_format" translatable="false">kk:mm</string> - <!-- Format string for the clock. TODO: Use the system preference --> - <string name="car_world_clock_12_hours_format" translatable="false">h:mm </string> - - <!-- Permission --> - <skip/> - <string name="app_name">Test App</string> - <!-- The first part of text shown when a list has content, but, is empty because we've hit the maximum number of touches allowed --> - <string name="unavailable_category_first_part">Can\'t show items</string> - <!-- The second part of text shown when a list has content, but, is empty because we've hit the maximum number of touches allowed --> - <string name="unavailable_category_second_part">for safety reasons</string> - - <string name="truncated_list">Can\'t show more items\nfor safety reasons</string> + <string name="resource_warning">Please do not check in any ui resources here.</string> </resources> diff --git a/car-support-lib/res/values/styles.xml b/car-support-lib/res/values/styles.xml deleted file mode 100644 index 67370c9014..0000000000 --- a/car-support-lib/res/values/styles.xml +++ /dev/null @@ -1,189 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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="CarHeadline0" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_headline0_size</item> - <item name="android:textColor">@color/car_headline0</item> - </style> - - <style name="CarHeadline0.Dark" > - <item name="android:textColor">@color/car_headline0_dark</item> - </style> - - <style name="CarHeadline0.Light" > - <item name="android:textColor">@color/car_headline0_light</item> - </style> - - <style name="CarHeadline1" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_headline1_size</item> - <item name="android:textColor">@color/car_headline1</item> - </style> - - <style name="CarHeadline1.Dark" > - <item name="android:textColor">@color/car_headline1_dark</item> - </style> - - <style name="CarHeadline1.Light" > - <item name="android:textColor">@color/car_headline1_light</item> - </style> - - <style name="CarHeadline2" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_headline2_size</item> - <item name="android:textColor">@color/car_headline1</item> - - </style> - - <style name="CarHeadline2.Dark" > - <item name="android:textColor">@color/car_headline1_dark</item> - </style> - - <style name="CarHeadline2.Light" > - <item name="android:textColor">@color/car_headline1_light</item> - </style> - - <style name="CarTitle" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_title_size</item> - <item name="android:textColor">@color/car_title</item> - <item name="android:includeFontPadding">false</item> - </style> - - <style name="CarTitle.Dark" > - <item name="android:textColor">@color/car_title_dark</item> - </style> - - <style name="CarTitle.Light" > - <item name="android:textColor">@color/car_title_light</item> - </style> - - <style name="CarBody1" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_body1_size</item> - <item name="android:textColor">@color/car_body1</item> - </style> - - <style name="CarBody1.Dark" > - <item name="android:textColor">@color/car_body1_dark</item> - </style> - - <style name="CarBody1.Light" > - <item name="android:textColor">@color/car_body1_light</item> - </style> - - <style name="CarBody2" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_body2_size</item> - <item name="android:textColor">@color/car_body2</item> - <item name="android:includeFontPadding">false</item> - </style> - - <style name="CarBody2.Light" > - <item name="android:textColor">@color/car_body2_light</item> - </style> - - <style name="CarKey1" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_key1_size</item> - <item name="android:textColor">@color/car_key1</item> - </style> - - <style name="CarKey1.Dark" > - <item name="android:textColor">@color/car_key1_dark</item> - </style> - - <style name="CarKey1.Light" > - <item name="android:textColor">@color/car_key1_light</item> - </style> - - <style name="CarKey2" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_key2_size</item> - <item name="android:textColor">@color/car_key2</item> - </style> - - <style name="CarKey2.Dark" > - <item name="android:textColor">@color/car_key2_dark</item> - </style> - - <style name="CarKey2.Light" > - <item name="android:textColor">@color/car_key2_light</item> - </style> - - <style name="CarCaption" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_caption_size</item> - <item name="android:textColor">@color/car_caption</item> - </style> - - <style name="CarCaption.Dark" > - <item name="android:textColor">@color/car_caption_dark</item> - </style> - - <style name="CarCaption.Light" > - <item name="android:textColor">@color/car_caption_light</item> - </style> - - <style name="CarMicro" > - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_micro_size</item> - <item name="android:textColor">@color/car_micro</item> - </style> - - <style name="CarMicro.Dark" > - <item name="android:textColor">@color/car_micro_dark</item> - </style> - - <style name="CarMicro.Light" > - <item name="android:textColor">@color/car_micro_light</item> - </style> - - <style name="CarDrawerArrowDrawable" > - <item name="carArrowColor">@android:color/white</item> - <item name="carArrowSpinBars">true</item> - <item name="carArrowThickness">2.5dp</item> - <item name="carArrowDrawableSize">@dimen/car_drawer_header_menu_button_size</item> - <item name="carArrowTopBottomBarSize">18dp</item> - <item name="carArrowBarSize">28dp</item> - <item name="carArrowMiddleBarSize">24dp</item> - <item name="carArrowGapBetweenBars">6dp</item> - </style> - - <style name="Toast" > - <item name="android:fontFamily">@string/car_font</item> - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_title_size</item> - <item name="android:textColor">@color/car_grey_100</item> - <item name="android:includeFontPadding">false</item> - </style> - - <style name="CarUnavailableCategory" > - <item name="android:fontFamily">@string/car_font</item> - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_body2_size</item> - <item name="android:textColor">@color/car_unavailable_category</item> - </style> - - <style name="CarTruncatedList" > - <item name="android:fontFamily">@string/car_font</item> - <item name="android:textStyle">normal</item> - <item name="android:textSize">@dimen/car_body2_size</item> - <item name="android:textColor">@color/car_grey_50</item> - <item name="android:drawableTint">@color/car_grey_50</item> - </style> -</resources> diff --git a/car-support-lib/src/android/support/car/Car.java b/car-support-lib/src/android/support/car/Car.java index 50ddf84c8f..c47b6508d8 100644 --- a/car-support-lib/src/android/support/car/Car.java +++ b/car-support-lib/src/android/support/car/Car.java @@ -44,8 +44,9 @@ import java.util.Set; * {@link CarConnectionCallback} will respond with an {@link CarConnectionCallback#onConnected(Car)} * or {@link CarConnectionCallback#onDisconnected(Car)} message. Nothing can be done with the * car until onConnected is called. When the car disconnects then reconnects you may still use - * the Car object but any manages retried from it should be considered invalid and will need to - * be retrieved. + * the Car object but any manages retrieved from it should be considered invalid and will need to + * be retried. Also, you must call {@link #disconnect} before an instance of Car goes out of scope + * to avoid leaking resources. * * <p/> * Once connected, {@link #getCarManager(String)} or {@link #getCarManager(Class)} can be used to @@ -366,9 +367,6 @@ public class Car { */ public void disconnect() { synchronized (this) { - if (mConnectionState == STATE_DISCONNECTED) { - return; - } tearDownCarManagers(); mConnectionState = STATE_DISCONNECTED; mCarServiceLoader.disconnect(); diff --git a/car-support-lib/src/android/support/car/app/CarActivity.java b/car-support-lib/src/android/support/car/app/CarActivity.java deleted file mode 100644 index e4b5567629..0000000000 --- a/car-support-lib/src/android/support/car/app/CarActivity.java +++ /dev/null @@ -1,539 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.LayoutRes; -import android.support.car.Car; -import android.support.car.app.menu.CarDrawerActivity; -import android.support.car.input.CarInputManager; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.Window; - -/** - * A car specific activity class. It allows an application to run on both "projected" and - * "native" platforms. For the phone-only mode we only support media and messaging apps at this - * time. Please see our guides for writing - * <a href="https://developer.android.com/training/auto/index.html#media">media</a> and - * <a href="https://developer.android.com/training/auto/index.html#messaging">messaging</a> apps. - * <ul> - * <li> - * For "native" systems you'll additionally need to implement a - * {@link CarProxyActivity} and add it to your application's manifest (see - * {@link CarProxyActivity}) for details). - * </li> - * <li> - * For "projected" systems you'll need to implement a - * {@link com.google.android.apps.auto.sdk.activity.CarProxyProjectionActivityService} - * </li> - * </ul> - * - * Applications wishing to write Android Auto applications will need to extend this class or one of - * it's sub classes. You'll most likely want to use {@link CarFragmentActivity} or - * {@link CarDrawerActivity} instead or this class as this one does not support fragments. - * <p/> - * This class has the look and feel of {@link Activity} however, it does not extend {@link Activity} - * or {@link Context}. Applications should use {@link #getContext()} to access the {@link Context}. - * - * @hide - */ -public abstract class CarActivity { - private static final String TAG = "CarActivity"; - public interface RequestPermissionsRequestCodeValidator { - public void validateRequestPermissionsRequestCode(int requestCode); - } - /** - * Interface to connect {@link CarActivity} to {@link android.app.Activity} or other app model. - * This interface provides utility for {@link CarActivity} to do things like manipulating view, - * handling menu, and etc. - */ - public abstract static class Proxy { - abstract public void setIntent(Intent i); - abstract public void setContentView(View view); - abstract public void setContentView(int layoutResID); - abstract public Resources getResources(); - abstract public View findViewById(int id); - abstract public LayoutInflater getLayoutInflater(); - abstract public Intent getIntent(); - abstract public void finish(); - abstract public CarInputManager getCarInputManager(); - abstract public boolean isFinishing(); - abstract public MenuInflater getMenuInflater(); - abstract public void finishAfterTransition(); - abstract public Window getWindow(); - abstract public void setResult(int resultCode); - abstract public void setResult(int resultCode, Intent data); - - public void requestPermissions(String[] permissions, int requestCode) { - Log.w(TAG, "No support for requestPermissions"); - } - public boolean shouldShowRequestPermissionRationale(String permission) { - Log.w(TAG, "No support for shouldShowRequestPermissionRationale"); - return false; - } - public void startActivityForResult(Intent intent, int requestCode) { - Log.w(TAG, "No support for startActivityForResult"); - }; - } - - /** @hide */ - public static final int CMD_ON_CREATE = 0; - /** @hide */ - public static final int CMD_ON_START = 1; - /** @hide */ - public static final int CMD_ON_RESTART = 2; - /** @hide */ - public static final int CMD_ON_RESUME = 3; - /** @hide */ - public static final int CMD_ON_PAUSE = 4; - /** @hide */ - public static final int CMD_ON_STOP = 5; - /** @hide */ - public static final int CMD_ON_DESTROY = 6; - /** @hide */ - public static final int CMD_ON_BACK_PRESSED = 7; - /** @hide */ - public static final int CMD_ON_SAVE_INSTANCE_STATE = 8; - /** @hide */ - public static final int CMD_ON_RESTORE_INSTANCE_STATE = 9; - /** @hide */ - public static final int CMD_ON_CONFIG_CHANGED = 10; - /** @hide */ - public static final int CMD_ON_REQUEST_PERMISSIONS_RESULT = 11; - /** @hide */ - public static final int CMD_ON_NEW_INTENT = 12; - /** @hide */ - public static final int CMD_ON_ACTIVITY_RESULT = 13; - /** @hide */ - public static final int CMD_ON_POST_RESUME = 14; - /** @hide */ - public static final int CMD_ON_LOW_MEMORY = 15; - - private final Proxy mProxy; - private final Context mContext; - private final Car mCar; - private final Handler mHandler = new Handler(); - - public CarActivity(Proxy proxy, Context context, Car car) { - mProxy = proxy; - mContext = context; - mCar = car; - } - - /** - * Returns a standard app {@link Context} object since this class does not extend {@link - * Context} link {@link Activity} does. - */ - public Context getContext() { - return mContext; - } - - /** - * Returns an instance of the {@link Car} object. This is the main entry point to interact - * with the car and it's data. - * - * <p/> - * Note: For "native" platform uses cases you'll need to construct the CarProxy activity with - * the createCar boolean set to true if you want to use the getCar() method. - * <pre> - * {@code - * - * class FooProxyActivity extends CarProxyActivity { - * public FooProxyActivity() { - * super(FooActivity.class, true); - * } - * } - * } - * </pre> - * - * "Projected" use cases will create a Car instance by default. - * - * @throws IllegalStateException if the Car object is not available. - */ - public Car getCar() { - if (mCar == null) { - throw new IllegalStateException("The default Car is not available. You can either " + - "create a Car by yourself or indicate the need of the default Car in the " + - "CarProxyActivity's constructor."); - } - return mCar; - } - - /** - * Returns the input manager for car activities. - */ - public CarInputManager getInputManager() { - return mProxy.getCarInputManager(); - } - - /** - * See {@link Activity#getResources()}. - */ - public Resources getResources() { - return mProxy.getResources(); - } - - /** - * See {@link Activity#setContentView(View)}. - */ - public void setContentView(View view) { - mProxy.setContentView(view); - } - - /** - * See {@link Activity#setContentView(int)}. - */ - public void setContentView(@LayoutRes int resourceId) { - mProxy.setContentView(resourceId); - } - - /** - * See {@link Activity#getLayoutInflater()}. - */ - public LayoutInflater getLayoutInflater() { - return mProxy.getLayoutInflater(); - } - - /** - * See {@link Activity#getIntent()} - */ - public Intent getIntent() { - return mProxy.getIntent(); - } - - /** - * See {@link Activity#setResult(int)} } - */ - public void setResult(int resultCode){ - mProxy.setResult(resultCode); - } - - - /** - * See {@link Activity#setResult(int, Intent)} } - */ - public void setResult(int resultCode, Intent data) { - mProxy.setResult(resultCode, data); - } - - /** - * See {@link Activity#findViewById(int)} } - */ - public View findViewById(int id) { - return mProxy.findViewById(id); - } - - /** - * See {@link Activity#finish()} - */ - public void finish() { - mProxy.finish(); - } - - /** - * See {@link Activity#isFinishing()} - */ - public boolean isFinishing() { - return mProxy.isFinishing(); - } - - /** - * See {@link Activity#shouldShowRequestPermissionRationale(String)} - */ - public boolean shouldShowRequestPermissionRationale(String permission) { - return mProxy.shouldShowRequestPermissionRationale(permission); - } - - /** - * See {@link Activity#requestPermissions(String[], int)} - */ - public void requestPermissions(String[] permissions, int requestCode) { - if (this instanceof RequestPermissionsRequestCodeValidator) { - ((RequestPermissionsRequestCodeValidator) this) - .validateRequestPermissionsRequestCode(requestCode); - } - mProxy.requestPermissions(permissions, requestCode); - } - - /** - * See {@link Activity#setIntent(Intent)} - */ - public void setIntent(Intent i) { - mProxy.setIntent(i); - } - - /** - * See {@link Activity#finishAfterTransition()} - */ - public void finishAfterTransition() { - mProxy.finishAfterTransition(); - } - - /** - * See {@link Activity#getMenuInflater()} - */ - public MenuInflater getMenuInflater() { - return mProxy.getMenuInflater(); - } - - /** - * See {@link Activity#getWindow()} - */ - public Window getWindow() { - return mProxy.getWindow(); - } - - /** - * See {@link Activity#getLastNonConfigurationInstance()} - */ - public Object getLastNonConfigurationInstance() { - return null; - } - - /** - * See {@link Activity#startActivityForResult(Intent, int)} - */ - public void startActivityForResult(Intent intent, int requestCode) { - mProxy.startActivityForResult(intent, requestCode); - } - - /** - * See {@link Activity#runOnUiThread(Runnable)} - */ - public void runOnUiThread(Runnable runnable) { - if (Thread.currentThread() == mHandler.getLooper().getThread()) { - runnable.run(); - } else { - mHandler.post(runnable); - } - } - - /** @hide */ - public void dispatchCmd(int cmd, Object... args) { - - switch (cmd) { - case CMD_ON_CREATE: - assertArgsLength(1, args); - onCreate((Bundle) args[0]); - break; - case CMD_ON_START: - onStart(); - break; - case CMD_ON_RESTART: - onRestart(); - break; - case CMD_ON_RESUME: - onResume(); - break; - case CMD_ON_POST_RESUME: - onPostResume(); - break; - case CMD_ON_PAUSE: - onPause(); - break; - case CMD_ON_STOP: - onStop(); - break; - case CMD_ON_DESTROY: - onDestroy(); - break; - case CMD_ON_BACK_PRESSED: - onBackPressed(); - break; - case CMD_ON_SAVE_INSTANCE_STATE: - assertArgsLength(1, args); - onSaveInstanceState((Bundle) args[0]); - break; - case CMD_ON_RESTORE_INSTANCE_STATE: - assertArgsLength(1, args); - onRestoreInstanceState((Bundle) args[0]); - break; - case CMD_ON_REQUEST_PERMISSIONS_RESULT: - assertArgsLength(3, args); - onRequestPermissionsResult(((Integer) args[0]).intValue(), - (String[]) args[1], convertArray((Integer[]) args[2])); - break; - case CMD_ON_CONFIG_CHANGED: - assertArgsLength(1, args); - onConfigurationChanged((Configuration) args[0]); - break; - case CMD_ON_NEW_INTENT: - assertArgsLength(1, args); - onNewIntent((Intent) args[0]); - break; - case CMD_ON_ACTIVITY_RESULT: - assertArgsLength(3, args); - onActivityResult(((Integer) args[0]).intValue(), ((Integer) args[1]).intValue(), - (Intent) args[2]); - break; - case CMD_ON_LOW_MEMORY: - onLowMemory(); - break; - default: - throw new RuntimeException("Unknown dispatch cmd for CarActivity, " + cmd); - } - - } - - /** - * See {@link Activity#onCreate(Bundle)} - */ - protected void onCreate(Bundle savedInstanceState) { - } - - /** - * See {@link Activity#onStart()} - */ - protected void onStart() { - } - - /** - * See {@link Activity#onRestart()} - */ - protected void onRestart() { - } - - /** - * See {@link Activity#onResume()} - */ - protected void onResume() { - } - - /** - * See {@link Activity#onPostResume()} - */ - protected void onPostResume() { - } - - /** - * See {@link Activity#onPause()} - */ - protected void onPause() { - } - - /** - * See {@link Activity#onStop()} - */ - protected void onStop() { - } - - /** - * See {@link Activity#onDestroy()} - */ - protected void onDestroy() { - } - - /** - * See {@link Activity#onRestoreInstanceState(Bundle)} - */ - protected void onRestoreInstanceState(Bundle savedInstanceState) { - } - - /** - * See {@link Activity#onSaveInstanceState(Bundle)} - */ - protected void onSaveInstanceState(Bundle outState) { - } - - /** - * See {@link Activity#onBackPressed()} - */ - protected void onBackPressed() { - } - - /** - * See {@link Activity#onConfigurationChanged(Configuration)} - */ - protected void onConfigurationChanged(Configuration newConfig) { - } - - /** - * See {@link Activity#onNewIntent(Intent)} - */ - protected void onNewIntent(Intent intent) { - } - - /** - * See {@link Activity#onRequestPermissionsResult(int, String[], int[])} - */ - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - } - - /** - * See {@link Activity#onActivityResult(int, int, Intent)} - */ - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - } - - /** - * See {@link Activity#onRetainNonConfigurationInstance()} - */ - public Object onRetainNonConfigurationInstance() { - return null; - } - - // TODO: hook up panel menu if it's needed in any apps. - /** - * Currently always returns false. - * See {@link Activity#onCreatePanelMenu(int, Menu)} - */ - public boolean onCreatePanelMenu(int featureId, Menu menu) { - return false; // default menu will not be displayed. - } - - /** - * See {@link Activity#onCreateView(View, String, Context, AttributeSet)} - */ - public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { - // CarFragmentActivity can override this to dispatch onCreateView to fragments - return null; - } - - /** - * See {@link Activity#onLowMemory()} - */ - public void onLowMemory() { - } - - private void assertArgsLength(int length, Object... args) { - if (args == null || args.length != length) { - throw new IllegalArgumentException( - String.format("Wrong number of parameters. Expected: %d Actual: %d", - length, args == null ? 0 : args.length)); - } - } - - private static int[] convertArray(Integer[] array) { - int[] results = new int[array.length]; - for(int i = 0; i < results.length; i++) { - results[i] = array[i].intValue(); - } - return results; - } -} diff --git a/car-support-lib/src/android/support/car/app/CarFragmentActivity.java b/car-support-lib/src/android/support/car/app/CarFragmentActivity.java deleted file mode 100644 index 2a6b631ef4..0000000000 --- a/car-support-lib/src/android/support/car/app/CarFragmentActivity.java +++ /dev/null @@ -1,730 +0,0 @@ -/* - * Copyright (C) 2016 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 android.support.car.app; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.car.Car; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentController; -import android.support.v4.app.FragmentHostCallback; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.LoaderManager; -import android.support.v4.util.SimpleArrayMap; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -/** - * Base class for CarActivities that want to use the support-based Fragment and Loader APIs. - * <p/> - * This is mostly a copy of {@link android.support.v4.app.FragmentActivity} retro fitted for use - * with {@link CarActivity}. - * - * @hide - */ -public class CarFragmentActivity extends CarActivity implements - CarActivity.RequestPermissionsRequestCodeValidator { - - public CarFragmentActivity(Proxy proxy, Context context, Car car) { - super(proxy, context, car); - } - - private static final String TAG = "CarFragmentActivity"; - - static final String FRAGMENTS_TAG = "android:support:car:fragments"; - - // This is the SDK API version of Honeycomb (3.0). - private static final int HONEYCOMB = 11; - - static final int MSG_REALLY_STOPPED = 1; - static final int MSG_RESUME_PENDING = 2; - - final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REALLY_STOPPED: - if (mStopped) { - doReallyStop(false); - } - break; - case MSG_RESUME_PENDING: - onResumeFragments(); - mFragments.execPendingActions(); - break; - default: - super.handleMessage(msg); - } - } - - }; - final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); - - boolean mCreated; - boolean mResumed; - boolean mStopped; - boolean mReallyStopped; - boolean mRetaining; - - boolean mRequestedPermissionsFromFragment; - - static final class NonConfigurationInstances { - Object custom; - List<Fragment> fragments; - SimpleArrayMap<String, LoaderManager> loaders; - } - - public void setContentFragment(Fragment fragment, int containerId) { - getSupportFragmentManager().beginTransaction() - .replace(containerId, fragment) - .commit(); - } - - // ------------------------------------------------------------------------ - // HOOKS INTO ACTIVITY - // ------------------------------------------------------------------------ - - /** - * See {@link FragmentActivity#onActivityResult(int, int, Intent)} - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - mFragments.noteStateNotSaved(); - int index = requestCode>>16; - if (index > 0) { - index--; - final int activeFragmentsCount = mFragments.getActiveFragmentsCount(); - if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) { - Log.w(TAG, "Activity result fragment index out of range: 0x" - + Integer.toHexString(requestCode)); - return; - } - final List<Fragment> activeFragments = - mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount)); - Fragment frag = activeFragments.get(index); - if (frag == null) { - Log.w(TAG, "Activity result no fragment exists for index: 0x" - + Integer.toHexString(requestCode)); - } else { - frag.onActivityResult(requestCode&0xffff, resultCode, data); - } - return; - } - - super.onActivityResult(requestCode, resultCode, data); - } - - /** - * See {@link FragmentActivity#startActivityFromFragment(android.app.Fragment, Intent, int)} - */ - public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { - if (requestCode == -1) { - startActivityForResult(intent, -1); - return; - } - if ((requestCode&0xffff0000) != 0) { - throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); - } - super.startActivityForResult(intent, - ((getFragmentIndex(fragment)+1)<<16) + (requestCode&0xffff)); - } - - /** - * See {@link FragmentActivity#startActivityForResult(Intent, int)} - */ - @Override - public void startActivityForResult(Intent intent, int requestCode) { - if (requestCode != -1 && (requestCode&0xffff0000) != 0) { - throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); - } - super.startActivityForResult(intent, requestCode); - } - - /** - * See {@link FragmentActivity#onBackPressed()} - */ - @Override - public void onBackPressed() { - if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) { - supportFinishAfterTransition(); - } - } - - /** - * See {@link FragmentActivity#supportFinishAfterTransition()} - */ - public void supportFinishAfterTransition() { - super.finishAfterTransition(); - } - - /** - * See {@link FragmentActivity#onConfigurationChanged(Configuration)} - */ - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mFragments.dispatchConfigurationChanged(newConfig); - } - - /** - * Perform initialization of all fragments and loaders. - */ - @SuppressWarnings("deprecation") - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - mFragments.attachHost(null /*parent*/); - - super.onCreate(savedInstanceState); - - NonConfigurationInstances nc = - (NonConfigurationInstances) getLastNonConfigurationInstance(); - if (nc != null) { - mFragments.restoreLoaderNonConfig(nc.loaders); - } - if (savedInstanceState != null) { - Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); - mFragments.restoreAllState(p, nc != null ? nc.fragments : null); - } - mFragments.dispatchCreate(); - } - - /** - * See {@link FragmentActivity#onCreatePanelMenu(int, Menu)} - */ - @Override - public boolean onCreatePanelMenu(int featureId, Menu menu) { - if (featureId == Window.FEATURE_OPTIONS_PANEL && getMenuInflater() != null) { - boolean show = super.onCreatePanelMenu(featureId, menu); - show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater()); - if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { - return show; - } - // Prior to Honeycomb, the framework can't invalidate the options - // menu, so we must always say we have one in case the app later - // invalidates it and needs to have it shown. - return true; - } - return super.onCreatePanelMenu(featureId, menu); - } - - /** - * See - * {@link FragmentActivity#dispatchFragmentsOnCreateView(View, String, Context, AttributeSet)} - */ - final View dispatchFragmentsOnCreateView(View parent, String name, Context context, - AttributeSet attrs) { - return mFragments.onCreateView(parent, name, context, attrs); - } - - @Override - public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { - if (!"fragment".equals(name)) { - return super.onCreateView(parent, name, context, attrs); - } - - final View v = dispatchFragmentsOnCreateView(parent, name, context, attrs); - if (v == null) { - return super.onCreateView(parent, name, context, attrs); - } - return v; - } - - /** - * See {@link FragmentActivity#onDestroy()} - */ - @Override - protected void onDestroy() { - super.onDestroy(); - - doReallyStop(false); - - mFragments.dispatchDestroy(); - mFragments.doLoaderDestroy(); - } - - /** - * See {@link FragmentActivity#onLowMemory()} - */ - @Override - public void onLowMemory() { - mFragments.dispatchLowMemory(); - } - - /** - * See {@link FragmentActivity#onPause()} - */ - @Override - protected void onPause() { - super.onPause(); - mResumed = false; - if (mHandler.hasMessages(MSG_RESUME_PENDING)) { - mHandler.removeMessages(MSG_RESUME_PENDING); - onResumeFragments(); - } - mFragments.dispatchPause(); - } - - /** - * See {@link FragmentActivity#onNewIntent(Intent)} - */ - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - mFragments.noteStateNotSaved(); - } - - /** - * See {@link FragmentActivity#onStateNotSaved()} - */ - public void onStateNotSaved() { - mFragments.noteStateNotSaved(); - } - - /** - * See {@link FragmentActivity#onResume()} - */ - @Override - protected void onResume() { - super.onResume(); - mHandler.sendEmptyMessage(MSG_RESUME_PENDING); - mResumed = true; - mFragments.execPendingActions(); - } - - /** - * See {@link FragmentActivity#onPostResume()} - */ - @Override - protected void onPostResume() { - super.onPostResume(); - mHandler.removeMessages(MSG_RESUME_PENDING); - onResumeFragments(); - mFragments.execPendingActions(); - } - - /** - * See {@link FragmentActivity#onResumeFragments()} - */ - protected void onResumeFragments() { - mFragments.dispatchResume(); - } - - /** - * See {@link FragmentActivity#onRetainNonConfigurationInstance()} - */ - @Override - public final Object onRetainNonConfigurationInstance() { - if (mStopped) { - doReallyStop(true); - } - - Object custom = onRetainCustomNonConfigurationInstance(); - - List<Fragment> fragments = mFragments.retainNonConfig(); - SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); - - if (fragments == null && loaders == null && custom == null) { - return null; - } - - NonConfigurationInstances nci = new NonConfigurationInstances(); - nci.custom = custom; - nci.fragments = fragments; - nci.loaders = loaders; - return nci; - } - - /** - * See {@link FragmentActivity#onSaveInstanceState(Bundle)} - */ - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - Parcelable p = mFragments.saveAllState(); - if (p != null) { - outState.putParcelable(FRAGMENTS_TAG, p); - } - } - - /** - * See {@link FragmentActivity#onStart()} - */ - @Override - protected void onStart() { - super.onStart(); - - mStopped = false; - mReallyStopped = false; - mHandler.removeMessages(MSG_REALLY_STOPPED); - - if (!mCreated) { - mCreated = true; - mFragments.dispatchActivityCreated(); - } - - mFragments.noteStateNotSaved(); - mFragments.execPendingActions(); - - mFragments.doLoaderStart(); - - // NOTE: HC onStart goes here. - - mFragments.dispatchStart(); - mFragments.reportLoaderStart(); - } - - /** - * See {@link FragmentActivity#onStop()} - */ - @Override - protected void onStop() { - super.onStop(); - - mStopped = true; - mHandler.sendEmptyMessage(MSG_REALLY_STOPPED); - - mFragments.dispatchStop(); - } - - // ------------------------------------------------------------------------ - // NEW METHODS - // ------------------------------------------------------------------------ - - /** - * See {@link FragmentActivity#onRetainNonConfigurationInstance()} - */ - public Object onRetainCustomNonConfigurationInstance() { - return null; - } - - /** - * See {@link FragmentActivity#getLastNonConfigurationInstance()} - */ - @SuppressWarnings("deprecation") - public Object getLastCustomNonConfigurationInstance() { - NonConfigurationInstances nc = (NonConfigurationInstances) - getLastNonConfigurationInstance(); - return nc != null ? nc.custom : null; - } - - /** - * See {@link FragmentActivity#dump(String, FileDescriptor, PrintWriter, String[])} - */ - public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { - // XXX This can only work if we can call the super-class impl. :/ - //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args); - } - writer.print(prefix); writer.print("Local FragmentActivity "); - writer.print(Integer.toHexString(System.identityHashCode(this))); - writer.println(" State:"); - String innerPrefix = prefix + " "; - writer.print(innerPrefix); writer.print("mCreated="); - writer.print(mCreated); writer.print("mResumed="); - writer.print(mResumed); writer.print(" mStopped="); - writer.print(mStopped); writer.print(" mReallyStopped="); - writer.println(mReallyStopped); - mFragments.dumpLoaders(innerPrefix, fd, writer, args); - mFragments.getSupportFragmentManager().dump(prefix, fd, writer, args); - writer.print(prefix); writer.println("View Hierarchy:"); - dumpViewHierarchy(prefix + " ", writer, getWindow().getDecorView()); - } - - private static String viewToString(View view) { - StringBuilder out = new StringBuilder(128); - out.append(view.getClass().getName()); - out.append('{'); - out.append(Integer.toHexString(System.identityHashCode(view))); - out.append(' '); - switch (view.getVisibility()) { - case View.VISIBLE: out.append('V'); break; - case View.INVISIBLE: out.append('I'); break; - case View.GONE: out.append('G'); break; - default: out.append('.'); break; - } - out.append(view.isFocusable() ? 'F' : '.'); - out.append(view.isEnabled() ? 'E' : '.'); - out.append(view.willNotDraw() ? '.' : 'D'); - out.append(view.isHorizontalScrollBarEnabled()? 'H' : '.'); - out.append(view.isVerticalScrollBarEnabled() ? 'V' : '.'); - out.append(view.isClickable() ? 'C' : '.'); - out.append(view.isLongClickable() ? 'L' : '.'); - out.append(' '); - out.append(view.isFocused() ? 'F' : '.'); - out.append(view.isSelected() ? 'S' : '.'); - out.append(view.isPressed() ? 'P' : '.'); - out.append(' '); - out.append(view.getLeft()); - out.append(','); - out.append(view.getTop()); - out.append('-'); - out.append(view.getRight()); - out.append(','); - out.append(view.getBottom()); - final int id = view.getId(); - if (id != View.NO_ID) { - out.append(" #"); - out.append(Integer.toHexString(id)); - final Resources r = view.getResources(); - if (id != 0 && r != null) { - try { - String pkgname; - switch (id&0xff000000) { - case 0x7f000000: - pkgname="app"; - break; - case 0x01000000: - pkgname="android"; - break; - default: - pkgname = r.getResourcePackageName(id); - break; - } - String typename = r.getResourceTypeName(id); - String entryname = r.getResourceEntryName(id); - out.append(" "); - out.append(pkgname); - out.append(":"); - out.append(typename); - out.append("/"); - out.append(entryname); - } catch (Resources.NotFoundException e) { - } - } - } - out.append("}"); - return out.toString(); - } - - private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) { - writer.print(prefix); - if (view == null) { - writer.println("null"); - return; - } - writer.println(viewToString(view)); - if (!(view instanceof ViewGroup)) { - return; - } - ViewGroup grp = (ViewGroup)view; - final int N = grp.getChildCount(); - if (N <= 0) { - return; - } - prefix = prefix + " "; - for (int i=0; i<N; i++) { - dumpViewHierarchy(prefix, writer, grp.getChildAt(i)); - } - } - - void doReallyStop(boolean retaining) { - if (!mReallyStopped) { - mReallyStopped = true; - mRetaining = retaining; - mHandler.removeMessages(MSG_REALLY_STOPPED); - onReallyStop(); - } - } - - /** - * Pre-HC, we didn't have a way to determine whether an activity was - * being stopped for a config change or not until we saw - * onRetainNonConfigurationInstance() called after onStop(). However - * we need to know this, to know whether to retain fragments. This will - * tell us what we need to know. - */ - void onReallyStop() { - mFragments.doLoaderStop(mRetaining); - - mFragments.dispatchReallyStop(); - } - - // ------------------------------------------------------------------------ - // FRAGMENT SUPPORT - // ------------------------------------------------------------------------ - - /** - * Returns the index of a fragment inside {@link #mFragments}. - * - * This is a workaround for getting {@link android.support.v4.app.Fragment}'s internal index, - * which is package visible. - */ - @SuppressWarnings("ReferenceEquality") - private int getFragmentIndex(Fragment f) { - List<Fragment> fragments = mFragments.getActiveFragments(null); - int index = 0; - boolean found = false; - for (Fragment frag : fragments) { - if (frag == f) { - found = true; - break; - } - index++; - } - if (found) { - return index; - } - return -1; - } - - @Override - public final void validateRequestPermissionsRequestCode(int requestCode) { - // We use 8 bits of the request code to encode the fragment id when - // requesting permissions from a fragment. Hence, requestPermissions() - // should validate the code against that but we cannot override it as - // we can not then call super and also the ActivityCompat would call - // back to this override. To handle this we use dependency inversion - // where we are the validator of request codes when requesting - // permissions in ActivityCompat. - if (mRequestedPermissionsFromFragment) { - mRequestedPermissionsFromFragment = false; - } else if ((requestCode & 0xffffff00) != 0) { - throw new IllegalArgumentException("Can only use lower 8 bits for requestCode"); - } - } - - /** - * See {@link FragmentActivity#onRequestPermissionsResult(int, String[], int[])} - */ - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - int index = (requestCode>>8)&0xff; - if (index != 0) { - index--; - final int activeFragmentsCount = mFragments.getActiveFragmentsCount(); - if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) { - Log.w(TAG, "Activity result fragment index out of range: 0x" - + Integer.toHexString(requestCode)); - return; - } - final List<Fragment> activeFragments = - mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount)); - Fragment frag = activeFragments.get(index); - if (frag == null) { - Log.w(TAG, "Activity result no fragment exists for index: 0x" - + Integer.toHexString(requestCode)); - } else { - frag.onRequestPermissionsResult(requestCode&0xff, permissions, grantResults); - } - } - } - - /** - * Called by Fragment.requestPermissions() to implement its behavior. - */ - private void requestPermissionsFromFragment(Fragment fragment, String[] permissions, - int requestCode) { - if (requestCode == -1) { - super.requestPermissions(permissions, requestCode); - return; - } - - if ((requestCode&0xffffff00) != 0) { - throw new IllegalArgumentException("Can only use lower 8 bits for requestCode"); - } - mRequestedPermissionsFromFragment = true; - super.requestPermissions(permissions, - ((getFragmentIndex(fragment) + 1) << 8) + (requestCode & 0xff)); - } - - /** - * See {@link FragmentActivity#getSupportFragmentManager()} - */ - public FragmentManager getSupportFragmentManager() { - return mFragments.getSupportFragmentManager(); - } - - class HostCallbacks extends FragmentHostCallback<CarFragmentActivity> { - public HostCallbacks() { - super(CarFragmentActivity.this.getContext(), mHandler, 0 /*window animation*/); - } - - @Override - public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - CarFragmentActivity.this.dump(prefix, fd, writer, args); - } - - @Override - public boolean onShouldSaveFragmentState(Fragment fragment) { - return !isFinishing(); - } - - @Override - public LayoutInflater onGetLayoutInflater() { - return CarFragmentActivity.this.getLayoutInflater() - .cloneInContext(CarFragmentActivity.this.getContext()); - } - - @Override - public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { - CarFragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode); - } - - @Override - public void onRequestPermissionsFromFragment(@NonNull Fragment fragment, - @NonNull String[] permissions, int requestCode) { - CarFragmentActivity.this.requestPermissionsFromFragment(fragment, - permissions, requestCode); - } - - @Override - public CarFragmentActivity onGetHost() { - return CarFragmentActivity.this; - } - - @Override - public boolean onHasWindowAnimations() { - return getWindow() != null; - } - - @Override - public int onGetWindowAnimations() { - final Window w = getWindow(); - return (w == null) ? 0 : w.getAttributes().windowAnimations; - } - - @Nullable - @Override - public View onFindViewById(int id) { - return CarFragmentActivity.this.findViewById(id); - } - - @Override - public boolean onHasView() { - final Window w = getWindow(); - return (w != null && w.peekDecorView() != null); - } - } -} diff --git a/car-support-lib/src/android/support/car/app/CarProxyActivity.java b/car-support-lib/src/android/support/car/app/CarProxyActivity.java deleted file mode 100644 index c1c340527f..0000000000 --- a/car-support-lib/src/android/support/car/app/CarProxyActivity.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Bundle; -import android.support.car.Car; -import android.support.car.CarConnectionCallback; -import android.support.car.input.CarInputManager; -import android.util.AttributeSet; -import android.util.Log; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.View; -import android.view.Window; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * android Activity controlling / proxying {@link CarActivity}. Applications should have its own - * {@link android.app.Activity} overriding only constructor. - * - * @hide - */ -public class CarProxyActivity extends Activity { - private static final String TAG = "CarProxyActivity"; - - private final Class<? extends CarActivity> mCarActivityClass; - private final boolean mNeedConnectedCar; - private Car mCar; - // no synchronization, but main thread only - private CarActivity mCarActivity; - // no synchronization, but main thread only - private CarInputManager mInputManager; - - private final CopyOnWriteArrayList<Pair<Integer, Object[]>> mCmds = - new CopyOnWriteArrayList<>(); - private final CarConnectionCallback mConnectionListener= new CarConnectionCallback() { - - @Override - public void onDisconnected(Car car) { - Log.w(TAG, "Car service disconnected"); - } - - @Override - public void onConnected(Car car) { - for (Pair<Integer, Object[]> cmd: mCmds) { - mCarActivity.dispatchCmd(cmd.first, cmd.second); - } - mCmds.clear(); - } - }; - - private final CarActivity.Proxy mCarActivityProxy = new CarActivity.Proxy() { - @Override - public void setContentView(View view) { - CarProxyActivity.this.setContentView(view); - } - - @Override - public void setContentView(int layoutResID) { - CarProxyActivity.this.setContentView(layoutResID); - } - - @Override - public View findViewById(int id) { - return CarProxyActivity.this.findViewById(id); - } - - @Override - public Resources getResources() { - return CarProxyActivity.this.getResources(); - } - - @Override - public void finish() { - CarProxyActivity.this.finish(); - } - - @Override - public LayoutInflater getLayoutInflater() { - return CarProxyActivity.this.getLayoutInflater(); - } - - @Override - public Intent getIntent() { - return CarProxyActivity.this.getIntent(); - } - - @Override - public CarInputManager getCarInputManager() { - return CarProxyActivity.this.mInputManager; - } - - @Override - public void requestPermissions(String[] permissions, int requestCode) { - CarProxyActivity.this.requestPermissions(permissions, requestCode); - } - - @Override - public boolean shouldShowRequestPermissionRationale(String permission) { - return CarProxyActivity.this.shouldShowRequestPermissionRationale(permission); - } - - @Override - public void setIntent(Intent i) { - CarProxyActivity.this.setIntent(i); - } - - @Override - public void setResult(int resultCode) { - CarProxyActivity.this.setResult(resultCode); - } - - @Override - public void setResult(int resultCode, Intent data) { - CarProxyActivity.this.setResult(resultCode, data); - } - - @Override - public MenuInflater getMenuInflater() { - return CarProxyActivity.this.getMenuInflater(); - } - - @Override - public void finishAfterTransition() { - CarProxyActivity.this.finishAfterTransition(); - } - - @Override - public void startActivityForResult(Intent intent, int requestCode) { - CarProxyActivity.this.startActivityForResult(intent, requestCode); - } - - @Override - public boolean isFinishing() { - return CarProxyActivity.this.isFinishing(); - } - - @Override - public Window getWindow() { - return CarProxyActivity.this.getWindow(); - } - }; - - public CarProxyActivity(Class<? extends CarActivity> carActivityClass) { - this(carActivityClass, false); - } - - public CarProxyActivity(Class<? extends CarActivity> carActivityClass, boolean needCar) { - mCarActivityClass = carActivityClass; - mNeedConnectedCar = needCar; - } - - private void createCarActivity() { - if (mNeedConnectedCar) { - mCar = Car.createCar(this, mConnectionListener); - mCar.connect(); - } - Constructor<? extends CarActivity> ctor; - try { - ctor = mCarActivityClass.getDeclaredConstructor(CarActivity.Proxy.class, - Context.class, Car.class); - } catch (NoSuchMethodException e) { - StringBuilder msg = new StringBuilder( - "Cannot construct given CarActivity, no constructor for "); - msg.append(mCarActivityClass.getName()); - msg.append("\nAvailable constructors are ["); - final Constructor<?>[] others = mCarActivityClass.getConstructors(); - for (int i=0; i<others.length; i++ ) { - msg.append("\n "); - msg.append(others[i].toString()); - } - msg.append("\n]"); - throw new RuntimeException(msg.toString(), e); - } - try { - mCarActivity = ctor.newInstance(mCarActivityProxy, this, mCar); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - throw new RuntimeException("Cannot construct given CarActivity, constructor failed for " - + mCarActivityClass.getName(), e); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - createCarActivity(); - super.onCreate(savedInstanceState); - mInputManager = new EmbeddedInputManager(this); - handleCmd(CarActivity.CMD_ON_CREATE, savedInstanceState); - } - - @Override - public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { - final View view = mCarActivity.onCreateView(parent, name, context, attrs); - if (view != null) { - return view; - } - return super.onCreateView(parent, name, context, attrs); - } - - @Override - protected void onStart() { - super.onStart(); - handleCmd(CarActivity.CMD_ON_START); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - handleCmd(CarActivity.CMD_ON_ACTIVITY_RESULT, requestCode, resultCode, data); - } - - @Override - protected void onRestart() { - super.onRestart(); - handleCmd(CarActivity.CMD_ON_RESTART); - } - - @Override - protected void onResume() { - super.onResume(); - handleCmd(CarActivity.CMD_ON_RESUME); - } - - @Override - protected void onPause() { - super.onPause(); - handleCmd(CarActivity.CMD_ON_PAUSE); - } - - @Override - protected void onStop() { - super.onStop(); - handleCmd(CarActivity.CMD_ON_STOP); - } - - @Override - public Object onRetainNonConfigurationInstance() { - return mCarActivity.onRetainNonConfigurationInstance(); - } - - @Override - public void onBackPressed() { - handleCmd(CarActivity.CMD_ON_BACK_PRESSED); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - handleCmd(CarActivity.CMD_ON_DESTROY); - if (mCar != null) { - mCar.disconnect(); - mCar = null; - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - handleCmd(CarActivity.CMD_ON_SAVE_INSTANCE_STATE, outState); - } - - @Override - protected void onRestoreInstanceState(Bundle savedState) { - super.onRestoreInstanceState(savedState); - handleCmd(CarActivity.CMD_ON_RESTORE_INSTANCE_STATE, savedState); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - handleCmd(CarActivity.CMD_ON_CONFIG_CHANGED, newConfig); - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) { - handleCmd(CarActivity.CMD_ON_REQUEST_PERMISSIONS_RESULT, requestCode, permissions, - convertArray(results)); - } - - @Override - protected void onNewIntent(Intent i) { - handleCmd(CarActivity.CMD_ON_NEW_INTENT, i); - } - - @Override - public void onLowMemory() { - super.onLowMemory(); - handleCmd(CarActivity.CMD_ON_LOW_MEMORY); - } - - private static final class EmbeddedInputManager extends CarInputManager { - private static final String TAG = "EmbeddedInputManager"; - - private final InputMethodManager mInputManager; - private final WeakReference<CarProxyActivity> mActivity; - - public EmbeddedInputManager(CarProxyActivity activity) { - mActivity = new WeakReference<>(activity); - mInputManager = (InputMethodManager) mActivity.get() - .getSystemService(Context.INPUT_METHOD_SERVICE); - } - - @Override - public void startInput(EditText view) { - view.requestFocus(); - mInputManager.showSoftInput(view, 0); - } - - @Override - public void stopInput() { - if (mActivity.get() == null) { - return; - } - - View view = mActivity.get().getCurrentFocus(); - if (view != null) { - mInputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } else { - Log.e(TAG, "stopInput called, but no view is accepting input"); - } - } - - @Override - public boolean isValid() { - return mActivity.get() != null; - } - - @Override - public boolean isInputActive() { - return mInputManager.isActive(); - } - - @Override - public boolean isCurrentCarEditable(EditText view) { - return mInputManager.isActive(view); - } - } - - private void handleCmd(int cmd, Object... args) { - if (!mNeedConnectedCar || (mCar != null && mCar.isConnected())) { - mCarActivity.dispatchCmd(cmd, args); - } else { - // not connected yet. queue it and return. - Pair<Integer, Object[]> cmdToQ = new Pair<>(Integer.valueOf(cmd), args); - mCmds.add(cmdToQ); - } - } - - private static Integer[] convertArray(int[] array) { - Integer[] grantResults = new Integer[array.length]; - for (int i = 0; i < array.length; i++) { - grantResults[i] = array[i]; - } - return grantResults; - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/CarDrawerActivity.java b/car-support-lib/src/android/support/car/app/menu/CarDrawerActivity.java deleted file mode 100644 index 2588a8156b..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/CarDrawerActivity.java +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app.menu; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.LayoutRes; -import android.support.car.Car; -import android.support.car.app.CarFragmentActivity; -import android.support.car.app.menu.compat.CarMenuConstantsComapt.MenuItemConstants; -import android.support.car.input.CarInputManager; -import android.support.v4.app.Fragment; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.EditText; - -/** - * Base class for a car app which wants to use a drawer. - * @hide - */ -public abstract class CarDrawerActivity extends CarFragmentActivity { - private static final String TAG = "CarDrawerActivity"; - - private static final String KEY_DRAWERSHOWING = - "android.support.car.app.CarDrawerActivity.DRAWER_SHOWING"; - private static final String KEY_INPUTSHOWING = - "android.support.car.app.CarDrawerActivity.INPUT_SHOWING"; - private static final String KEY_SEARCHBOXENABLED = - "android.support.car.app.CarDrawerActivity.SEARCH_BOX_ENABLED"; - - private final Handler mHandler = new Handler(); - private final CarUiController mUiController; - - private CarMenuCallbacks mMenuCallbacks; - private OnMenuClickListener mMenuClickListener; - private boolean mDrawerShowing; - private boolean mShowingSearchBox; - private boolean mSearchBoxEnabled; - private boolean mOnCreateCalled = false; - private View.OnClickListener mSearchBoxOnClickListener; - - private CarInputManager mInputManager; - private EditText mSearchBoxView; - - public interface OnMenuClickListener { - /** - * Called when the menu button is clicked. - * - * @return True if event was handled. This will prevent the drawer from executing its - * default action (opening/closing/going back). False if the event was not handled - * so the drawer will execute the default action. - */ - boolean onClicked(); - } - - public CarDrawerActivity(Proxy proxy, Context context, Car car) { - super(proxy, context, car); - mUiController = createCarUiController(); - } - - /** - * Create a {@link android.support.car.app.menu.CarUiController}. - * - * Derived class can override this function to return a customized ui controller. - */ - protected CarUiController createCarUiController() { - return CarUiController.createCarUiController(this); - } - - @Override - public void setContentView(View view) { - ViewGroup parent = (ViewGroup) findViewById(mUiController.getFragmentContainerId()); - parent.addView(view); - } - - @Override - public void setContentView(@LayoutRes int resourceId) { - ViewGroup parent = (ViewGroup) findViewById(mUiController.getFragmentContainerId()); - LayoutInflater inflater = getLayoutInflater(); - inflater.inflate(resourceId, parent, true); - } - - @Override - public View findViewById(@LayoutRes int id) { - return super.findViewById(mUiController.getFragmentContainerId()).findViewById(id); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - super.setContentView(mUiController.getContentView()); - mInputManager = getInputManager(); - mHandler.post(new Runnable() { - @Override - public void run() { - if (mMenuCallbacks != null) { - mMenuCallbacks.registerOnChildrenChangedListener(mMenuListener); - } - mOnCreateCalled = true; - } - }); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mHandler.post(new Runnable() { - @Override - public void run() { - if (mMenuCallbacks != null) { - mMenuCallbacks.unregisterOnChildrenChangedListener(mMenuListener); - mMenuCallbacks = null; - } - } - }); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mDrawerShowing = savedInstanceState.getBoolean(KEY_DRAWERSHOWING); - mUiController.onRestoreInstanceState(savedInstanceState); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_DRAWERSHOWING, mDrawerShowing); - mUiController.onSaveInstanceState(outState); - } - - @Override - protected void onStart() { - super.onStart(); - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); - mUiController.onStart(); - } - - @Override - protected void onResume() { - super.onResume(); - mUiController.onResume(); - } - - @Override - protected void onPause() { - super.onPause(); - mUiController.onPause(); - } - - @Override - protected void onStop() { - super.onStop(); - mUiController.onStop(); - } - - /** - * Set the fragment in the main fragment container. - */ - public void setContentFragment(Fragment fragment) { - super.setContentFragment(fragment, mUiController.getFragmentContainerId()); - } - - /** - * Return the main fragment container id for the app. - */ - public int getFragmentContainerId() { - return mUiController.getFragmentContainerId(); - } - - /** - * Set the callbacks for car menu interactions. - */ - public void setCarMenuCallbacks(final CarMenuCallbacks callbacks) { - if (mOnCreateCalled) { - throw new IllegalStateException( - "Cannot call setCarMenuCallbacks after onCreate has been called."); - } - mMenuCallbacks = callbacks; - mUiController.registerCarMenuCallbacks(callbacks); - } - - /** - * Listener that listens for when the menu button is pressed. - * - * @param listener {@link OnMenuClickListener} that will listen for menu button clicks. - */ - public void setOnMenuClickedListener(OnMenuClickListener listener) { - mMenuClickListener = listener; - } - - /** - * Restore the menu button drawable - */ - public void restoreMenuButtonDrawable() { - mUiController.restoreMenuButtonDrawable(); - } - - /** - * Sets the menu button bitmap - * - * @param bitmap Bitmap to the menu button to. - */ - public void setMenuButtonBitmap(Bitmap bitmap) { - mUiController.setMenuButtonBitmap(bitmap); - } - - /** - * Set the title of the menu. - */ - public void setTitle(CharSequence title) { - mUiController.setTitle(title); - } - - /** - * Set the System UI to be light. - */ - public void setLightMode() { - mUiController.setLightMode(); - } - - /** - * Set the System UI to be dark. - */ - public void setDarkMode() { - mUiController.setDarkMode(); - } - - /** - * Set the System UI to be dark during day mode and light during night mode. - */ - public void setAutoLightDarkMode() { - mUiController.setAutoLightDarkMode(); - } - - /** - * Sets the application background to the given {@link android.graphics.Bitmap}. - * - * @param bitmap to use as background. - */ - public void setBackground(Bitmap bitmap) { - mUiController.setBackground(bitmap); - } - - /** - * Sets the color of the scrim to the right of the car menu drawer. - */ - public void setScrimColor(int color) { - mUiController.setScrimColor(color); - } - - /** - * Show the menu associated with the given id in the drawer. - * - * @param id Id of the menu to link to. - * @param title Title that should be displayed. - */ - public void showMenu(String id, String title) { - mUiController.showMenu(id, title); - } - - public boolean onMenuClicked() { - if (mMenuClickListener != null) { - return mMenuClickListener.onClicked(); - } - return false; - } - - public void restoreSearchBox() { - if (isSearchBoxEnabled()) { - mUiController.showSearchBox(mSearchBoxOnClickListener); - mShowingSearchBox = true; - } - } - - private final CarMenuCallbacks.OnChildrenChangedListener mMenuListener = - new CarMenuCallbacks.OnChildrenChangedListener() { - @Override - public void onChildrenChanged(String parentId) { - if (mOnCreateCalled) { - mUiController.onChildrenChanged(parentId); - } - } - - @Override - public void onChildChanged(String parentId, Bundle item, - Drawable leftIcon, Drawable rightIcon) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - if (leftIcon != null) { - item.putParcelable(MenuItemConstants.KEY_LEFTICON, - Utils.snapshot(metrics, leftIcon)); - } - - if (rightIcon != null) { - item.putParcelable(MenuItemConstants.KEY_RIGHTICON, - Utils.snapshot(metrics, rightIcon)); - } - if (mOnCreateCalled) { - mUiController.onChildChanged(parentId, item); - } - } - }; - - public void closeDrawer() { - mUiController.closeDrawer(); - } - - public void openDrawer() { - mUiController.openDrawer(); - } - - public boolean isDrawerShowing() { - return mDrawerShowing; - } - - public void setDrawerShowing(boolean showing) { - mDrawerShowing = showing; - } - - public boolean isSearchBoxEnabled() { - return mSearchBoxEnabled; - } - - public boolean isShowingSearchBox() { - return mShowingSearchBox; - } - - /** - * Shows a small clickable {@link android.widget.EditText}. - * - * {@link View} will be {@code null} in {@link View.OnClickListener#onClick(View)}. - * - * @param listener {@link View.OnClickListener} that is called when user selects the - * {@link android.widget.EditText}. - */ - public void showSearchBox(View.OnClickListener listener) { - if (!isDrawerShowing()) { - mUiController.showSearchBox(listener); - mShowingSearchBox = true; - } - mSearchBoxEnabled = true; - mSearchBoxOnClickListener = listener; - } - - public void showSearchBox() { - showSearchBox(mSearchBoxOnClickListener); - } - - public void hideSearchBox() { - if (isShowingSearchBox()) { - stopInput(); - } - mSearchBoxEnabled = false; - } - - public void setSearchBoxEditListener(SearchBoxEditListener listener) { - mUiController.setSearchBoxEditListener(listener); - } - - public void stopInput() { - // STOPSHIP: sometimes focus is lost and we are not able to hide the keyboard. - // properly fix this before we ship. - if (mSearchBoxView != null) { - mSearchBoxView.requestFocusFromTouch(); - } - mUiController.stopInput(); - mInputManager.stopInput(); - mShowingSearchBox = false; - } - - /** - * Start input on the search box that is provided by a car ui provider. - * TODO: Migrate to use the new input/search api once it becomes stable (b/27108311). - * @param hint Search hint - */ - public void startInput(String hint) { - startInput(hint, mSearchBoxOnClickListener); - } - - /** - * Start input on the search box that is provided by a car ui provider. - * TODO: Migrate to use the new input/search api once it becomes stable (b/27108311). - * @param hint Search hint - * @param onClickListener Listener for the search box clicks. - */ - public void startInput(final String hint, final View.OnClickListener onClickListener) { - mInputManager = getInputManager(); - EditText inputView = mUiController.startInput(hint, onClickListener); - getInputManager().startInput(inputView); - mSearchBoxView = inputView; - mShowingSearchBox = true; - } - - public void setSearchBoxColors(int backgroundColor, int searchLogoColor, int textColor, - int hintTextColor) { - mUiController.setSearchBoxColors(backgroundColor, searchLogoColor, - textColor, hintTextColor); - } - - public void setSearchBoxEndView(View endView) { - mUiController.setSearchBoxEndView(endView); - } - - public void showToast(String text, int duration) { - mUiController.showToast(text, duration); - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/CarMenu.java b/car-support-lib/src/android/support/car/app/menu/CarMenu.java deleted file mode 100644 index 59c4ea76bd..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/CarMenu.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app.menu; - -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.car.app.menu.compat.CarMenuConstantsComapt.MenuItemConstants; -import android.util.DisplayMetrics; -import android.widget.RemoteViews; -import java.util.ArrayList; -import java.util.List; - -/** - * CarMenu is used to pass back the menu items of a sublevel to the CarMenu subscriber. - * Use the {@link Builder} to populate the contents of the sublevel. - * @hide - */ -public class CarMenu { - private boolean mDetachCalled; - private boolean mSendResultCalled; - private final DisplayMetrics mMetrics; - - public CarMenu(DisplayMetrics metrics) { - mMetrics = metrics; - } - - /** - * Send the result back to the caller. - */ - public void sendResult(List<Item> results) { - if (mSendResultCalled) { - throw new IllegalStateException("sendResult() called twice."); - } - mSendResultCalled = true; - - List<Bundle> resultBundle = new ArrayList<>(); - for (Item item : results) { - ItemImpl impl = (ItemImpl) item; - if (impl.mIcon != null) { - impl.mBundle.putParcelable(MenuItemConstants.KEY_LEFTICON, snapshot(impl.mIcon)); - } - if (impl.mRightIcon != null) { - impl.mBundle.putParcelable( - MenuItemConstants.KEY_RIGHTICON, snapshot(impl.mRightIcon)); - } - resultBundle.add(impl.mBundle); - } - onResultReady(resultBundle); - } - - private Bitmap snapshot(Drawable drawable) { - return Utils.snapshot(mMetrics, drawable); - } - - /** - * Detach this message from the current thread and allow the {@link #sendResult} - * call to happen later. This stops blocking the current thread. - */ - public void detach() { - if (mDetachCalled) { - throw new IllegalStateException("detach() called when detach() had already" - + " been called."); - } - if (mSendResultCalled) { - throw new IllegalStateException("detach() called when sendResult() had already" - + " been called"); - } - mDetachCalled = true; - } - - /** - * Returns whether results were actually sent. - * - * @return {@code true} if {@link #sendResult(java.util.List)} or {@link #detach()} has been called. - * @hide - */ - public boolean isDone() { - return mDetachCalled || mSendResultCalled; - } - - /** - * Called when the result is sent, after assertions about not being called twice - * have happened. - * @hide - */ - protected void onResultReady(List<Bundle> result) { - } - - /** - * An individual item in a menu. - */ - public interface Item { - /** - * Gets the id of the menu item. - * - * @return The id of the menu item. - */ - String getId(); - - /** - * Gets the title of the menu item. - * - * @return The title of the menu item. {@code null} if there is no title. - */ - String getTitle(); - - /** - * Gets the text of the menu item. - * - * @return The text of the menu item. {@code null} if there is no text. - */ - String getText(); - - /** - * Gets the integer constant for the widget. - * - * @return Either {@link MenuItemConstants.WidgetTypes#WIDGET_CHECKBOX} or -1, - * if no widget was set. - */ - int getWidget(); - - /** - * Gets the widget state. The return value is only valid if a widget was set. - * - * @return {@code true} if the widget is enabled, {@code false} if the widget is disabled. - */ - boolean getWidgetState(); - - /** - * Gets the flags set for this menu item. - * - * @return The flags set. - */ - int getFlags(); - } - - /** @hide */ - static class ItemImpl implements Item { - final Bundle mBundle; - final Drawable mIcon; - final Drawable mRightIcon; - - ItemImpl(Bundle bundle, Drawable icon, Drawable rightIcon) { - mBundle = bundle; - mIcon = icon; - mRightIcon = rightIcon; - } - - @Override - public String getId() { - return mBundle.getString(MenuItemConstants.KEY_ID); - } - - @Override - public String getTitle() { - return mBundle.getString(MenuItemConstants.KEY_TITLE); - } - - @Override - public String getText() { - return mBundle.getString(MenuItemConstants.KEY_TEXT); - } - - @Override - public int getWidget() { - return mBundle.getInt(MenuItemConstants.KEY_WIDGET, -1); - } - - @Override - public boolean getWidgetState() { - return mBundle.getBoolean(MenuItemConstants.KEY_WIDGET_STATE); - } - - @Override - public int getFlags() { - return mBundle.getInt(MenuItemConstants.KEY_FLAGS); - } - } - - /** - * Builder to build an {@link Item}. Calls to the builder can be chained. - */ - public static class Builder { - private final Bundle mBundle = new Bundle(); - // Drawable icons that will later be turned into Bitmaps and inserted into the Bundle - private Drawable mIcon; - private Drawable mRightIcon; - - /** - * Construct a Builder with a specific id. - * - * @param id Unique id used to identify this menu item. If it is browsable, then it - * will also be used to fetch this item's submenu. - */ - public Builder(String id) { - if (id == null) { - throw new IllegalStateException("Cannot pass a null id to the Builder."); - } - mBundle.putString(MenuItemConstants.KEY_ID, id); - } - - /** - * Sets the title. - * - * @param title Title to set - * @return This to chain calls - */ - public Builder setTitle(String title) { - mBundle.putString(MenuItemConstants.KEY_TITLE, title); - return this; - } - - /** - * Sets the body text. - * - * @param text Text to set - * @return This {@link Builder} to chain calls - */ - public Builder setText(String text) { - mBundle.putString(MenuItemConstants.KEY_TEXT, text); - return this; - } - - /** - * Sets the icon. - * - * @param bitmap Icon to set - * @return This {@link Builder} to chain calls - */ - public Builder setIcon(Bitmap bitmap) { - mBundle.putParcelable(MenuItemConstants.KEY_LEFTICON, bitmap); - return this; - } - - /** - * Sets the icon. - * - * A snapshot of the {@link android.graphics.drawable.Drawable} is captured at the time the {@link Item} obtained - * from this Builder is passed to {@link #sendResult(java.util.List)}. Any changes that are - * made to the Drawable after that point will not affect what is displayed by the menu. - * - * @param drawable Icon to set - * @return This {@link Builder} to chain calls - */ - public Builder setIconFromSnapshot(Drawable drawable) { - mIcon = drawable; - return this; - } - - /** - * Sets the right icon. - * - * @param bitmap Icon to set - * @return This {@link Builder} to chain calls - */ - public Builder setRightIcon(Bitmap bitmap) { - mBundle.putParcelable(MenuItemConstants.KEY_RIGHTICON, bitmap); - return this; - } - - /** - * Sets the right icon. - * - * A snapshot of the {@link android.graphics.drawable.Drawable} is captured at the time the - * {@link Item} obtained - * from this Builder is passed to {@link #sendResult(java.util.List)}. Any changes that are - * made to the Drawable after that point will not affect what is displayed by the menu. - * - * @param drawable Icon to set - * @return This {@link Builder} to chain calls - */ - public Builder setRightIconFromSnapshot(Drawable drawable) { - mRightIcon = drawable; - return this; - } - - /** - * The widget to set. - * It can be anyone of the following: button, checkbox, toggle - * - * @param widget - * @return This {@link Builder} to chain calls - */ - public Builder setWidget(int widget) { - mBundle.putInt(MenuItemConstants.KEY_WIDGET, widget); - return this; - } - - /** - * If a widget is set, the state the widget is in. - * This is only applicable if the widget is "checkbox" or "toggle" - * - * @param on If true, a checkbox is checked and a toggle is set to "on". - * If false, a checkbox will be unchecked and a toggle will be set to "off". - * @return This {@link Builder} to chain calls - */ - public Builder setWidgetState(boolean on) { - mBundle.putBoolean(MenuItemConstants.KEY_WIDGET_STATE, on); - return this; - } - - /** - * Indicates that this is an empty placeholder menu item. - * Only title and icon will be available in this situation. - * - * @param isEmptyPlaceHolder If true, this CarMenu will be a placeholder item for no data - * in menu list. - * @return This {@link Builder} to chain calls - */ - public Builder setIsEmptyPlaceHolder(boolean isEmptyPlaceHolder) { - mBundle.putBoolean(MenuItemConstants.KEY_EMPTY_PLACEHOLDER, isEmptyPlaceHolder); - return this; - } - - /** - * If the widget is {@link MenuItemConstants#WIDGET_TEXT_VIEW}, then this will allow setting - * the right text. - * - * @param text The text to set - * @return this {@link Builder} to chain calls - */ - public Builder setRightText(String text) { - mBundle.putString(MenuItemConstants.KEY_RIGHTTEXT, text); - return this; - } - - public Builder setRemoteViews(RemoteViews views) { - mBundle.putParcelable(MenuItemConstants.KEY_REMOTEVIEWS, views); - return this; - } - - /** - * Sets additional flags for this item. - * {@link MenuItemConstants#FLAG_BROWSABLE} is the only one that can be currently set - * - * @param flags flags to set - * @return This {@link Builder} to chain calls - */ - public Builder setFlags(@MenuItemConstants.MenuItemFlags int flags) { - mBundle.putInt(MenuItemConstants.KEY_FLAGS, flags); - return this; - } - - /** - * Add this item to the list of items to be sent when {@link CarMenu#sendResult(java.util.List)} - * is called - */ - public Item build() { - return new ItemImpl(mBundle, mIcon, mRightIcon); - } - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/CarMenuCallbacks.java b/car-support-lib/src/android/support/car/app/menu/CarMenuCallbacks.java deleted file mode 100644 index 29226b56c8..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/CarMenuCallbacks.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app.menu; - -import android.graphics.drawable.Drawable; -import android.os.Bundle; - -/** - * Class that the CarMenu communicates with to fetch the contents of the CarMenu - * @hide - */ -public abstract class CarMenuCallbacks { - /** - * Listens for calls to notifyChildrenChanged and onChildrenChanged - * @hide - */ - public interface OnChildrenChangedListener { - /** Called when app wants to notify that contents of an entire menu have changed. */ - void onChildrenChanged(String parentId); - /** - * Called when app wants to notify that contents of a single item has changed. - * - * @param item Contents of {@link android.os.Bundle} are used to update the contents of the existing - * item. - * @param leftIcon Drawable to convert to bitmap - * @param rightIcon Drawable to convert to bitmap - */ - void onChildChanged(String parentId, Bundle item, Drawable leftIcon, Drawable rightIcon); - } - - private OnChildrenChangedListener mListener; - - /** - * Called when the CarMenu wants to get the root - * - * @param hints Hints that the Drawer can use to modify behavior. It can be null. - * @return The {@link RootMenu} which contains the root id and any hints - */ - public abstract RootMenu onGetRoot(Bundle hints); - - /** - * Called when the CarMenu subscribes to to a certain id - * - * @param parentId ID to subscribe to - * @param result {@link CarMenu} that is used to communicate back the results - */ - public abstract void onLoadChildren(String parentId, - CarMenu result); - - /** - * Notify the CarMenu that the menu defined by the id has changed. This will cause the CarMenu - * to fetch the menu items. - * - * @param parentId The id which identifies the menu that changed - */ - public void notifyChildrenChanged(String parentId) { - if (mListener != null) { - mListener.onChildrenChanged(parentId); - } - } - - /** - * Register an OnChildrenChangedListener to detect when a menu has changed - * - * @param listener listener to register - * @hide - */ - public void registerOnChildrenChangedListener(OnChildrenChangedListener listener) { - mListener = listener; - } - - /** - * Unregister an OnChildrenChangedListener to detect when a menu has changed. - * - * @param listener listener to unregister - * @hide - */ - public void unregisterOnChildrenChangedListener(OnChildrenChangedListener listener) { - if (listener != mListener) { - throw new IllegalStateException( - "Trying to unregister a listener that was not registered!"); - } - mListener = null; - } - - /** - * Called when the CarMenu is opened - */ - public void onCarMenuOpened() {} - - /** - * Called when the CarMenu is closed - */ - public void onCarMenuClosed() {} - - /** - * Called when the CarMenu is opening - */ - public void onCarMenuOpening() {} - - /** - * Called when the CarMenu is closing - */ - public void onCarMenuClosing() {} - - /** - * Called when an item is clicked - * - * @param id Id of the item that is clicked - */ - public void onItemClicked(String id) {} - - /** - * Called when an item is long clicked - * - * @param id Id of the item that is long clicked - * - * @return Return true if handled, false if not. Returning false also means that the - * onItemClicked handler will be called. - */ - public boolean onItemLongClicked(String id) { - return false; - } - - /** - * Called when the state of the CarMenu has changed. - * TODO: Describe the state. This may be removed moving forward depending on if it is useful - * - * @param newState The new state of the CarMenu - */ - public void onStateChanged(int newState) {} - - /** - * Notify that an item has changed. Use a {@link CarMenu.Builder} to build the item and pu the - * updated contents inside. Note that this cannot be used to change an item's layout, but to - * modify existing contents. - * - * @param parentId parentId of the item. - * @param item Updated contents of the item. - */ - public void notifyChildChanged(String parentId, CarMenu.Item item) { - if (mListener != null) { - CarMenu.ItemImpl realItem = (CarMenu.ItemImpl) item; - mListener.onChildChanged(parentId, - realItem.mBundle, - realItem.mIcon, - realItem.mRightIcon); - } - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/CarUiController.java b/car-support-lib/src/android/support/car/app/menu/CarUiController.java deleted file mode 100644 index afb91ffea9..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/CarUiController.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app.menu; - -import android.graphics.Bitmap; -import android.os.Bundle; -import android.support.car.app.CarAppUtil; -import android.view.View; -import android.widget.EditText; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -/** - * A controller for a {@link android.support.car.app.CarActivity} to manipulate its car UI, and - * under the hood it talks to a car ui provider. - * @hide - */ -public abstract class CarUiController { - static final String PROJECTED_UI_CONTROLLER = - "com.google.android.car.ProjectedCarUiController"; - - protected final CarDrawerActivity mActivity; - - public CarUiController(CarDrawerActivity activity) { - mActivity = activity; - validateCarUiPackage(); - } - - public static CarUiController createCarUiController(CarDrawerActivity activity) { - if (CarAppUtil.isEmbeddedCar(activity.getContext())) { - return new EmbeddedCarUiController(activity); - } else { - return getProjectedCarUiController(PROJECTED_UI_CONTROLLER, activity); - } - } - - private static CarUiController getProjectedCarUiController(String className, - CarDrawerActivity activity) { - Class<? extends CarUiController> uiControllerClass = null; - try { - uiControllerClass = Class.forName(className).asSubclass(CarUiController.class); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Cannot find ProjectedCarUiController:" + className, - e); - } - Constructor<? extends CarUiController> ctor; - try { - ctor = uiControllerClass.getDeclaredConstructor(CarDrawerActivity.class); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("Cannot construct ProjectedCarUiController," + - " no constructor: " + className, e); - } - try { - return ctor.newInstance(activity); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - throw new IllegalArgumentException( - "Cannot construct ProjectedCarUiController, constructor failed for " - + uiControllerClass.getName(), e); - } - } - - public abstract void validateCarUiPackage(); - - public abstract int getFragmentContainerId(); - - public abstract void setTitle(CharSequence title); - - public abstract void setScrimColor(int color); - - public abstract View getContentView(); - - public abstract void registerCarMenuCallbacks(CarMenuCallbacks callbacks); - - public abstract void restoreMenuButtonDrawable(); - - public abstract void setMenuButtonBitmap(Bitmap bitmap); - - public abstract void setLightMode(); - - /** - * Set the System UI to be dark. - */ - public abstract void setDarkMode(); - - /** - * Set the System UI to be dark during day mode and light during night mode. - */ - public abstract void setAutoLightDarkMode(); - - /** - * Sets the application background to the given {@link android.graphics.Bitmap}. - * - * @param bitmap to use as background. - */ - public abstract void setBackground(Bitmap bitmap); - - public abstract void onRestoreInstanceState(Bundle savedState); - - public abstract void onSaveInstanceState(Bundle outState); - - public abstract void closeDrawer(); - - public abstract void openDrawer(); - - public abstract void showMenu(String id, String title); - - public abstract void onStart(); - - public abstract void onResume(); - - public abstract void onPause(); - - public abstract void onStop(); - - public abstract void showSearchBox(View.OnClickListener listener); - - public abstract void setSearchBoxColors(int backgroundColor, int googleLogoColor, int textColor, - int hintTextColor); - - public abstract void setSearchBoxEditListener(SearchBoxEditListener listener); - - public abstract EditText startInput( - String hint, View.OnClickListener searchBoxClickListener); - - public abstract CharSequence getText(); - - public abstract void stopInput(); - - public abstract void setSearchBoxEndView(View view); - - public abstract void onChildrenChanged(String parentId); - - public abstract void onChildChanged(String parentId, Bundle item); - - public abstract void showToast(String msg, int duration); -} diff --git a/car-support-lib/src/android/support/car/app/menu/EmbeddedCarUiController.java b/car-support-lib/src/android/support/car/app/menu/EmbeddedCarUiController.java deleted file mode 100644 index 248245d30b..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/EmbeddedCarUiController.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2016 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 android.support.car.app.menu; - -import android.car.app.menu.CarUiEntry; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.support.car.app.menu.compat.EmbeddedCarMenuCallbacksCompat; -import android.support.car.app.menu.compat.EmbeddedSearchBoxEditListenerCompat; -import android.view.View; -import android.widget.EditText; -import java.lang.reflect.InvocationTargetException; - -/** - * A {@link android.support.car.app.menu.CarUiController} that talks to embedded car ui provider. - * @hide - */ -public class EmbeddedCarUiController extends CarUiController { - - private static final String TAG = "EmbeddedCarUiController"; - // TODO: load the package name and class name from resources - private static final String UI_ENTRY_CLASS_NAME = ".CarUiEntry"; - private static final String CAR_UI_PROVIDER_PKG = "android.car.ui.provider"; - private static final String CAR_SERVICE_PKG = "com.android.car"; - - private CarUiEntry mCarUiEntry; - private EmbeddedCarMenuCallbacksCompat mCallback; - - public EmbeddedCarUiController(CarDrawerActivity activity) { - super(activity); - try { - Context carUiContext = mActivity.getContext().createPackageContext( - CAR_UI_PROVIDER_PKG, - Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); - - ClassLoader classLoader = carUiContext.getClassLoader(); - Class<?> loadedClass = classLoader.loadClass(CAR_UI_PROVIDER_PKG + UI_ENTRY_CLASS_NAME); - mCarUiEntry = (CarUiEntry) loadedClass.getConstructor(Context.class, Context.class) - .newInstance(carUiContext, mActivity.getContext()); - } catch (PackageManager.NameNotFoundException | ClassNotFoundException e) { - throw new RuntimeException("Cannot find CarUiEntry from " + CAR_UI_PROVIDER_PKG + "/" - + UI_ENTRY_CLASS_NAME, e); - } catch (IllegalAccessException | InvocationTargetException | InstantiationException - | NoSuchMethodException e) { - throw new RuntimeException("Cannot cast CarUiEntry.", e); - } - } - - @Override - public void validateCarUiPackage() { - try { - PackageManager packageManager = mActivity.getContext().getPackageManager(); - int flag = packageManager.getApplicationInfo(CAR_UI_PROVIDER_PKG, 0).flags; - if ((flag & ApplicationInfo.FLAG_SYSTEM) == 0 - && (flag & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { - throw new SecurityException("CarUiProvider is not a system app!"); - } - - // Do not change the order of the two packages as it need to be in sync with - // the error message. - int signatureMatchResult = - packageManager.checkSignatures(CAR_SERVICE_PKG, CAR_UI_PROVIDER_PKG); - if (signatureMatchResult != PackageManager.SIGNATURE_MATCH) { - throw new SecurityException("CarUiProvider and CarService signature check" + - " failed. " + getSignatureFailureMessage(signatureMatchResult)); - } - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException("Cannot find CarUiProvider" + CAR_UI_PROVIDER_PKG, e); - } - } - - @Override - public int getFragmentContainerId() { - return mCarUiEntry.getFragmentContainerId(); - } - - @Override - public void setTitle(CharSequence title) { - mCarUiEntry.setTitle(title); - } - - @Override - public void setScrimColor(int color) { - mCarUiEntry.setScrimColor(color); - } - - @Override - public View getContentView() { - return mCarUiEntry.getContentView(); - } - - @Override - public void registerCarMenuCallbacks(final CarMenuCallbacks callbacks) { - mCallback = new EmbeddedCarMenuCallbacksCompat(mActivity, callbacks); - mCarUiEntry.setCarMenuCallbacks(mCallback); - } - - @Override - public void restoreMenuButtonDrawable() { - mCarUiEntry.restoreMenuDrawable(); - } - - @Override - public void setMenuButtonBitmap(Bitmap bitmap) { - mCarUiEntry.setMenuButtonBitmap(bitmap); - } - - @Override - public void setLightMode() { - mCarUiEntry.setLightMode(); - } - - @Override - public void setDarkMode() { - mCarUiEntry.setDarkMode(); - } - - @Override - public void setAutoLightDarkMode() { - mCarUiEntry.setAutoLightDarkMode(); - } - - @Override - public void setBackground(Bitmap bitmap) { - mCarUiEntry.setBackground(bitmap); - } - - @Override - public void onRestoreInstanceState(Bundle savedState) { - mCarUiEntry.onRestoreInstanceState(savedState); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - mCarUiEntry.onSaveInstanceState(outState); - } - - @Override - public void closeDrawer() { - mCarUiEntry.closeDrawer(); - } - - @Override - public void openDrawer() { - mCarUiEntry.openDrawer(); - } - - @Override - public void showMenu(String id, String title) { - mCarUiEntry.showMenu(id, title); - } - - @Override - public void onStart() { - mCarUiEntry.onStart(); - } - - @Override - public void onResume() { - mCarUiEntry.onResume(); - } - - @Override - public void onPause() { - mCarUiEntry.onPause(); - } - - @Override - public void onStop() { - mCarUiEntry.onStop(); - } - - @Override - public void showSearchBox(View.OnClickListener listener) { - mCarUiEntry.showSearchBox(listener); - } - - @Override - public void setSearchBoxColors(int backgroundColor, int searchLogocolor, int textColor, int hintTextColor) { - mCarUiEntry.setSearchBoxColors(backgroundColor, searchLogocolor, textColor, hintTextColor); - } - - @Override - public void setSearchBoxEditListener(SearchBoxEditListener listener) { - mCarUiEntry.setSearchBoxEditListener(new EmbeddedSearchBoxEditListenerCompat(listener)); - } - - @Override - public CharSequence getText() { - return mCarUiEntry.getSearchBoxText(); - } - - @Override - public void stopInput() { - mCarUiEntry.stopInput(); - } - - @Override - public EditText startInput(String hint, View.OnClickListener listener) { - return mCarUiEntry.startInput(hint, listener); - } - - @Override - public void setSearchBoxEndView(View view) { - mCarUiEntry.setSearchBoxEndView(view); - } - - @Override - public void onChildChanged(String parentId, Bundle item) { - mCallback.onChildChanged(parentId, item); - } - - @Override - public void onChildrenChanged(String parentId) { - mCallback.onChildrenChanged(parentId); - } - - @Override - public void showToast(String msg, int duration) { - mCarUiEntry.showToast(msg, duration); - } - - /** - * Return more informative error message from the PackageManager's signature check result. - */ - private static final String getSignatureFailureMessage(int code) { - switch (code) { - case PackageManager.SIGNATURE_NEITHER_SIGNED: - return "Both CarService and CarUiProvider are not signed"; - case PackageManager.SIGNATURE_FIRST_NOT_SIGNED: - return "CarService not signed"; - case PackageManager.SIGNATURE_SECOND_NOT_SIGNED: - return "CarUiProvider not signed"; - case PackageManager.SIGNATURE_NO_MATCH: - return "Signatures do not match"; - case PackageManager.SIGNATURE_UNKNOWN_PACKAGE: - return "CarService not found"; - default: - return "Unknown error code"; - } - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/RootMenu.java b/car-support-lib/src/android/support/car/app/menu/RootMenu.java deleted file mode 100644 index 64193abac9..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/RootMenu.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app.menu; - -import android.os.Bundle; -import android.support.car.app.menu.compat.CarMenuConstantsComapt.MenuItemConstants; - -/** - * Stores the root id for the menu. The RootMenu is the main menu. - * Also allows passing hints through bundles. Hints allow the - * the recipient to alter its behavior based on the hints. - * @hide - */ -public class RootMenu { - private final Bundle mBundle; - - /** - * Create a root with no extra hints. - * - * @param id Root id - */ - public RootMenu(String id) { - this(id, null); - } - - /** - * Create a root with hints - * - * @param id Root id - * @param extras Hints to pass along - */ - public RootMenu(String id, Bundle extras) { - mBundle = new Bundle(); - mBundle.putString(MenuItemConstants.KEY_ID, id); - if (extras != null) { - mBundle.putAll(extras); - } - } - - /** - * Get the root id - * - * @return The root id - */ - public String getId() { - return mBundle.getString(MenuItemConstants.KEY_ID); - } - - /** - * Get any hints - * - * @return A bundle if there are hints; null otherwise. - */ - public Bundle getBundle() { - return new Bundle(mBundle); - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/Utils.java b/car-support-lib/src/android/support/car/app/menu/Utils.java deleted file mode 100644 index f27bc2f6d1..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/Utils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.app.menu; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.util.DisplayMetrics; - -/** - * @hide - */ -public class Utils { - public static Bitmap snapshot(DisplayMetrics metrics, Drawable drawable) { - int width = drawable.getIntrinsicWidth(); - int height = drawable.getIntrinsicHeight(); - Bitmap bitmap = Bitmap.createBitmap(metrics, width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/compat/CarMenuConstantsComapt.java b/car-support-lib/src/android/support/car/app/menu/compat/CarMenuConstantsComapt.java deleted file mode 100644 index d107260068..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/compat/CarMenuConstantsComapt.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2016 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 android.support.car.app.menu.compat; - -import android.support.annotation.IntDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Contains keys to the metadata of car menu, such as id, title, icon, etc. - * @hide - */ -public class CarMenuConstantsComapt { - public static class MenuItemConstants { - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {FLAG_BROWSABLE, FLAG_FIRSTITEM}) - public @interface MenuItemFlags {} - - /** - * Flag: Indicates that the item has children of its own - */ - public static final int FLAG_BROWSABLE = 0x1; - - /** - * Flag: Indicates that the menu should scroll to this item - */ - public static final int FLAG_FIRSTITEM = 0x2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {WIDGET_CHECKBOX, WIDGET_TEXT_VIEW}) - public @interface WidgetTypes {} - - /** - * Use a checkbox widget. - */ - public static final int WIDGET_CHECKBOX = 0x1; - - /** - * Use a TextView widget - */ - public static final int WIDGET_TEXT_VIEW = 0x2; - - /** - * Key for the car menu title. - */ - public static final String KEY_TITLE = "android.car.app.menu.title"; - - /** - * Key for the item title. - */ - public static final String KEY_TEXT = "android.car.app.menu.text"; - - /** - * Key for the left icon. - */ - public static final String KEY_LEFTICON = "android.car.app.menu.leftIcon"; - - /** - * Key for the right icon. - */ - public static final String KEY_RIGHTICON = "android.car.app.menu.rightIcon"; - - /** - * Key for the text to be shown to the right of the item. - */ - public static final String KEY_RIGHTTEXT = "android.car.app.menu.rightText"; - - /** - * Key for the widget type. - */ - public static final String KEY_WIDGET = "android.car.app.menu.widget"; - - /** - * Key for the widget state. - */ - public static final String KEY_WIDGET_STATE = "android.car.app.menu.widget_state"; - - /** - * Key for the value of whether the item is a place holder. - */ - public static final String KEY_EMPTY_PLACEHOLDER = "android.car.app.menu.empty_placeholder"; - - /** - * Key for the flags. - */ - public static final String KEY_FLAGS = "android.car.app.menu.flags"; - - /** - * Key for the menu item id. - */ - public static final String KEY_ID = "android.car.app.menu.id"; - - /** - * Key for the remote views. - */ - public static final String KEY_REMOTEVIEWS = "android.car.app.menu.remoteViews"; - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/compat/EmbeddedCarMenuCallbacksCompat.java b/car-support-lib/src/android/support/car/app/menu/compat/EmbeddedCarMenuCallbacksCompat.java deleted file mode 100644 index cad2848ad1..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/compat/EmbeddedCarMenuCallbacksCompat.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2016 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 android.support.car.app.menu.compat; - -import android.car.app.menu.RootMenu; -import android.car.app.menu.SubscriptionCallbacks; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.car.app.menu.CarDrawerActivity; -import android.support.car.app.menu.CarMenu; -import android.support.car.app.menu.CarMenuCallbacks; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @hide - */ -public class EmbeddedCarMenuCallbacksCompat extends android.car.app.menu.CarMenuCallbacks { - - private final CarMenuCallbacks mCallbacks; - private final CarDrawerActivity mActivity; - - // Map of subscribed ids to their respective callbacks. - // @GuardedBy("this") - private final Map<String, List<SubscriptionCallbacks>> mSubscriptionMap = - new HashMap<String, List<SubscriptionCallbacks>>(); - - private final Handler mHandler = new Handler(); - - public EmbeddedCarMenuCallbacksCompat(CarDrawerActivity activity, - CarMenuCallbacks callbacks) { - mActivity = activity; - mCallbacks = callbacks; - } - - @Override - public RootMenu getRootMenu(Bundle hint) { - android.support.car.app.menu.RootMenu rootMenu = mCallbacks.onGetRoot(hint); - return new RootMenu(rootMenu.getId(), rootMenu.getBundle()); - } - - @Override - public void subscribe(String parentId, SubscriptionCallbacks callbacks) { - synchronized (this) { - if (!mSubscriptionMap.containsKey(parentId)) { - mSubscriptionMap.put(parentId, new ArrayList<SubscriptionCallbacks>()); - } - mSubscriptionMap.get(parentId).add(callbacks); - loadResultsForClient(parentId, callbacks); - } - } - - @Override - public void unsubscribe(String parentId, SubscriptionCallbacks callbacks) { - synchronized (this) { - mSubscriptionMap.get(parentId).remove(callbacks); - } - } - - @Override - public void onCarMenuOpened() { - mActivity.setDrawerShowing(true); - mCallbacks.onCarMenuOpened(); - } - - @Override - public void onCarMenuClosed() { - mActivity.setDrawerShowing(false); - mActivity.restoreSearchBox(); - } - - @Override - public void onItemClicked(String id) { - mCallbacks.onItemClicked(id); - mActivity.stopInput(); - } - - @Override - public boolean onItemLongClicked(String id) { - return mCallbacks.onItemLongClicked(id); - } - - @Override - public boolean onMenuClicked() { - - return mActivity.onMenuClicked(); - } - - @Override - public void onCarMenuOpening() { - mActivity.stopInput(); - } - - @Override - public void onCarMenuClosing() { - mActivity.restoreSearchBox(); - } - - public void onChildrenChanged(final String parentId) { - synchronized (this) { - if (mSubscriptionMap.containsKey(parentId)) { - final List<SubscriptionCallbacks> callbacks = new ArrayList<>(); - callbacks.addAll(mSubscriptionMap.get(parentId)); - mHandler.post(new Runnable() { - @Override - public void run() { - loadResultsForAllClients(parentId, callbacks); - } - }); - } - } - } - - public void onChildChanged(final String parentId, final Bundle item) { - synchronized (this) { - if (mSubscriptionMap.containsKey(parentId)) { - final List<SubscriptionCallbacks> callbacks = new ArrayList<>(); - callbacks.addAll(mSubscriptionMap.get(parentId)); - mHandler.post(new Runnable() { - @Override - public void run() { - for (SubscriptionCallbacks callback : callbacks) { - callback.onChildChanged(parentId, item); - } - } - }); - - } - } - } - - private void loadResultsForAllClients(final String parentId, - @NonNull final List<SubscriptionCallbacks> callbacks) { - final CarMenu result = new CarMenu(mActivity.getResources().getDisplayMetrics()) { - @Override - protected void onResultReady(List<Bundle> list) { - for (SubscriptionCallbacks callback : callbacks) { - callback.onChildrenLoaded(parentId, list); - } - } - }; - - mCallbacks.onLoadChildren(parentId, result); - if (!result.isDone()) { - throw new IllegalStateException("You must either call sendResult() or detach() " + - "before returning!"); - } - } - - private void loadResultsForClient(final String parentId, - final SubscriptionCallbacks callbacks) { - final CarMenu result = new CarMenu(mActivity.getResources().getDisplayMetrics()) { - @Override - protected void onResultReady(List<Bundle> list) { - callbacks.onChildrenLoaded(parentId, list); - } - }; - - mCallbacks.onLoadChildren(parentId, result); - if (!result.isDone()) { - throw new IllegalStateException("You must either call sendResult() or detach() " + - "before returning!"); - } - } -} diff --git a/car-support-lib/src/android/support/car/app/menu/compat/EmbeddedSearchBoxEditListenerCompat.java b/car-support-lib/src/android/support/car/app/menu/compat/EmbeddedSearchBoxEditListenerCompat.java deleted file mode 100644 index 20b9616b86..0000000000 --- a/car-support-lib/src/android/support/car/app/menu/compat/EmbeddedSearchBoxEditListenerCompat.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2016 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 android.support.car.app.menu.compat; - -import android.support.car.app.menu.SearchBoxEditListener; - -/** - * @hide - */ -public class EmbeddedSearchBoxEditListenerCompat extends - android.car.app.menu.SearchBoxEditListener { - private final SearchBoxEditListener mListener; - public EmbeddedSearchBoxEditListenerCompat(SearchBoxEditListener listener) { - mListener = listener; - } - - @Override - public void onSearch(String text) { - mListener.onSearch(text); - } - - @Override - public void onEdit(String text) { - mListener.onEdit(text); - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java b/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java index 633768ac9c..a0eb482d8e 100644 --- a/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java +++ b/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java @@ -127,17 +127,17 @@ class CarSensorsProxy { synchronized (CarSensorsProxy.this) { switch (type) { case Sensor.TYPE_GYROSCOPE: - System.arraycopy(event.values, 0, mLastGyroscopeData, 0, 3); + System.arraycopy((Object) event.values, 0, (Object) mLastGyroscopeData, 0, 3); mLastGyroscopeDataTime = System.nanoTime(); pushSensorChanges(CarSensorManager.SENSOR_TYPE_GYROSCOPE); break; case Sensor.TYPE_MAGNETIC_FIELD: - System.arraycopy(event.values, 0, mLastMagneticFieldData, 0, 3); + System.arraycopy((Object) event.values, 0, (Object) mLastMagneticFieldData, 0, 3); mLastMagneticFieldDataTime = System.nanoTime(); pushSensorChanges(CarSensorManager.SENSOR_TYPE_COMPASS); break; case Sensor.TYPE_ACCELEROMETER: - System.arraycopy(event.values, 0, mLastAccelerometerData, 0, 3); + System.arraycopy((Object) event.values, 0, (Object) mLastAccelerometerData, 0, 3); mLastAccelerometerDataTime = System.nanoTime(); pushSensorChanges(CarSensorManager.SENSOR_TYPE_ACCELEROMETER); pushSensorChanges(CarSensorManager.SENSOR_TYPE_COMPASS); diff --git a/car-support-lib/src/android/support/car/ui/AnimationListenerAdapter.java b/car-support-lib/src/android/support/car/ui/AnimationListenerAdapter.java deleted file mode 100644 index 33fee8b6be..0000000000 --- a/car-support-lib/src/android/support/car/ui/AnimationListenerAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.view.animation.Animation; - -/** - * Provides empty implementations of the methods in - * {@link android.view.animation.Animation.AnimationListener} for convenience reasons. - * @hide - */ -public class AnimationListenerAdapter implements Animation.AnimationListener { - - /** - * {@inheritDoc} - */ - @Override - public void onAnimationStart(Animation animation) { - } - - /** - * {@inheritDoc} - */ - @Override - public void onAnimationEnd(Animation animation) { - } - - /** - * {@inheritDoc} - */ - @Override - public void onAnimationRepeat(Animation animation) { - } -} diff --git a/car-support-lib/src/android/support/car/ui/CarActionExtender.java b/car-support-lib/src/android/support/car/ui/CarActionExtender.java deleted file mode 100644 index 898a0bf580..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarActionExtender.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.NotificationCompat; - -/** - * Android Auto {@link android.app.Notification.Action} extender. - * NOTE: this will move into the platform and support-lib when the API stabilizes. - * @hide - */ -public class CarActionExtender implements NotificationCompat.Action.Extender { - private static final String EXTRA_AUTO_EXTENDER = "android.auto.EXTENSIONS"; - private static final String EXTRA_INTENT = "intent"; - - private Intent mIntent; - - public CarActionExtender() { - } - - public CarActionExtender(NotificationCompat.Action action) { - Bundle autoBundle = action.getExtras(); - - if (autoBundle != null) { - mIntent = autoBundle.getParcelable(EXTRA_INTENT); - } - } - - @Override - public NotificationCompat.Action.Builder extend(NotificationCompat.Action.Builder builder) { - Bundle autoBundle = new Bundle(); - - autoBundle.putParcelable(EXTRA_INTENT, mIntent); - - builder.getExtras().putBundle(EXTRA_AUTO_EXTENDER, autoBundle); - return builder; - } - - public void setIntent(Intent intent) { - mIntent = intent; - } - - public Intent getIntent() { - return mIntent; - } -} diff --git a/car-support-lib/src/android/support/car/ui/CarItemAnimator.java b/car-support-lib/src/android/support/car/ui/CarItemAnimator.java deleted file mode 100644 index 9e819c44b0..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarItemAnimator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.RecyclerView; - -/** - * {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. - * @hide - */ -public class CarItemAnimator extends DefaultItemAnimator { - - private final CarLayoutManager mLayoutManager; - - public CarItemAnimator(CarLayoutManager layoutManager) { - mLayoutManager = layoutManager; - } - - @Override - public boolean animateChange(RecyclerView.ViewHolder oldHolder, - RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { - // The default behavior will cross fade the old view and the new one. However, if we - // have a card on a colored background, it will make it appear as if a changing card - // fades in and out. - float alpha = 0f; - if (newHolder != null) { - alpha = newHolder.itemView.getAlpha(); - } - boolean ret = super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY); - if (newHolder != null) { - newHolder.itemView.setAlpha(alpha); - } - return ret; - } - - @Override - public void onMoveFinished(RecyclerView.ViewHolder item) { - // The item animator uses translation heavily internally. However, we also use translation - // to create the paging affect. When an item's move is animated, it will mess up the - // translation we have set on it so we must re-offset the rows once the animations finish. - - // isRunning(ItemAnimationFinishedListener) is the awkward API used to determine when all - // animations have finished. - isRunning(mFinishedListener); - } - - private final ItemAnimatorFinishedListener mFinishedListener = - new ItemAnimatorFinishedListener() { - @Override - public void onAnimationsFinished() { - mLayoutManager.offsetRows(); - } - }; -} diff --git a/car-support-lib/src/android/support/car/ui/CarLayoutManager.java b/car-support-lib/src/android/support/car/ui/CarLayoutManager.java deleted file mode 100644 index 3195afe649..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarLayoutManager.java +++ /dev/null @@ -1,1528 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.graphics.PointF; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.v7.widget.LinearSmoothScroller; -import android.support.v7.widget.RecyclerView; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; - -/** - * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that - * it has a few tricks up its sleeve. - * <ol> - * <li>In a normal ListView, when views reach the top of the list, they are clipped. In - * CarLayoutManager, views have the option of flying off of the top of the screen as the - * next row settles in to place. This functionality can be enabled or disabled with - * {@link #setOffsetRows(boolean)}. - * <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle - * on the next page. {@link #FLING_THRESHOLD_TO_PAGINATE} and - * {@link #DRAG_DISTANCE_TO_PAGINATE} can be set to have the list settle on the next item - * instead of the next page for small gestures. - * <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that - * the last page can be properly aligned. - * </ol> - * - * This LayoutManger should be used with {@link CarRecyclerView}. - * @hide - */ -public class CarLayoutManager extends RecyclerView.LayoutManager { - private static final String TAG = "CarLayoutManager"; - private static final boolean DEBUG = false; - - /** - * Any fling below the threshold will just scroll to the top fully visible row. The units is - * whatever {@link android.widget.Scroller} would return. - * - * A reasonable value is ~200 - * - * This can be disabled by setting the threshold to -1. - */ - private static final int FLING_THRESHOLD_TO_PAGINATE = -1; - - /** - * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row. - * - * A reasonable value is 15. - * - * This can be disabled by setting the distance to -1. - */ - private static final int DRAG_DISTANCE_TO_PAGINATE = -1; - - /** - * If you scroll really quickly, you can hit the end of the laid out rows before Android has a - * chance to layout more. To help counter this, we can layout a number of extra rows past - * wherever the focus is if necessary. - */ - private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2; - - /** - * Scroll bar calculation is a bit complicated. This basically defines the granularity we want - * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement. - * Setting it too big will risk an overflow (although there is no performance impact). Ideally - * we want to set this higher than the height of our list view. We can't use our list view - * height directly though because we might run into situations where getHeight() returns 0, for - * example, when the view is not yet measured. - */ - private static final int SCROLL_RANGE = 1000; - - @ScrollStyle private final int SCROLL_TYPE = MARIO; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({MARIO, SUPER_MARIO}) - private @interface ScrollStyle {} - private static final int MARIO = 0; - private static final int SUPER_MARIO = 1; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({BEFORE, AFTER}) - private @interface LayoutDirection {} - private static final int BEFORE = 0; - private static final int AFTER = 1; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE}) - public @interface RowOffsetMode {} - public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0; - public static final int ROW_OFFSET_MODE_PAGE = 1; - - public interface OnItemsChangedListener { - void onItemsChanged(); - } - - private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2); - private final Context mContext; - - /** Determines whether or not rows will be offset as they slide off screen **/ - private boolean mOffsetRows = false; - /** Determines whether rows will be offset individually or a page at a time **/ - @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE; - - /** - * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the - * scroll state to be used anywhere. - */ - private int mScrollState = RecyclerView.SCROLL_STATE_IDLE; - /** - * Used to inspect the current scroll state to help with the various calculations. - **/ - private CarSmoothScroller mSmoothScroller; - private OnItemsChangedListener mItemsChangedListener; - - /** The distance that the list has actually scrolled in the most recent drag gesture **/ - private int mLastDragDistance = 0; - /** True if the current drag was limited/capped because it was at some boundary **/ - private boolean mReachedLimitOfDrag; - /** - * The values are continuously updated to keep track of where the current page boundaries are - * on screen. The anchor page break is the page break that is currently within or at the - * top of the viewport. The Upper page break is the page break before it and the lower page - * break is the page break after it. - * - * A page break will be set to -1 if it is unknown or n/a. - * @see #updatePageBreakPositions() - */ - private int mItemCountDuringLastPageBreakUpdate; - // The index of the first item on the current page - private int mAnchorPageBreakPosition = 0; - // The index of the first item on the previous page - private int mUpperPageBreakPosition = -1; - // The index of the first item on the next page - private int mLowerPageBreakPosition = -1; - /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. **/ - private int mLastChildPositionToRequestFocus = -1; - private int mSampleViewHeight = -1; - - /** - * Set the anchor to the following position on the next layout pass. - */ - private int mPendingScrollPosition = -1; - - public CarLayoutManager(Context context) { - mContext = context; - } - - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new RecyclerView.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - - @Override - public boolean canScrollVertically() { - return true; - } - - /** - * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should: - * <ol> - * <li>Check the current views to get the current state of affairs - * <li>Detach all views from the window (a lightweight operation) so that rows - * not re-added will be removed after onLayoutChildren. - * <li>Re-add rows as necessary. - * </ol> - * - * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State) - */ - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - /** - * The anchor view is the first fully visible view on screen at the beginning - * of onLayoutChildren (or 0 if there is none). This row will be laid out first. After that, - * layoutNextRow will layout rows above and below it until the boundaries of what should - * be laid out have been reached. See {@link #shouldLayoutNextRow(View, int)} for - * more information. - */ - int anchorPosition = 0; - int anchorTop = -1; - if (mPendingScrollPosition == -1) { - View anchor = getFirstFullyVisibleChild(); - if (anchor != null) { - anchorPosition = getPosition(anchor); - anchorTop = getDecoratedTop(anchor); - } - } else { - anchorPosition = mPendingScrollPosition; - mPendingScrollPosition = -1; - mAnchorPageBreakPosition = anchorPosition; - mUpperPageBreakPosition = -1; - mLowerPageBreakPosition = -1; - } - - if (DEBUG) { - Log.v(TAG, String.format( - ":: onLayoutChildren anchorPosition:%s, anchorTop:%s," - + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s," - + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s", - anchorPosition, anchorTop, mPendingScrollPosition, mAnchorPageBreakPosition, - mUpperPageBreakPosition, mLowerPageBreakPosition)); - } - - /** - * Detach all attached view for 2 reasons: - * <ol> - * <li> So that views are put in the scrap heap. This enables us to call - * {@link RecyclerView.Recycler#getViewForPosition(int)} which will either return - * one of these detached views if it is in the scrap heap, one from the - * recycled pool (will only call onBind in the adapter), or create an entirely new - * row if needed (will call onCreate and onBind in the adapter). - * <li> So that views are automatically removed if they are not manually re-added. - * </ol> - */ - detachAndScrapAttachedViews(recycler); - - // Layout new rows. - View anchor = layoutAnchor(recycler, anchorPosition, anchorTop); - if (anchor != null) { - View adjacentRow = anchor; - while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) { - adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE); - } - adjacentRow = anchor; - while (shouldLayoutNextRow(state, adjacentRow, AFTER)) { - adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER); - } - } - - updatePageBreakPositions(); - offsetRows(); - - if (DEBUG&& getChildCount() > 1) { - Log.v(TAG, "Currently showing " + getChildCount() + " views " + - getPosition(getChildAt(0)) + " to " + - getPosition(getChildAt(getChildCount() - 1)) + " anchor " + anchorPosition); - } - } - - /** - * scrollVerticallyBy does the work of what should happen when the list scrolls in addition - * to handling cases where the list hits the end. It should be lighter weight than - * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list - * and removes views that have gone out of bounds and lays out new ones that scroll in. - * - * @param dy The amount that the list is supposed to scroll. - * > 0 means the list is scrolling down. - * < 0 means the list is scrolling up. - * @param recycler The recycler that enables views to be reused or created as they scroll in. - * @param state Various information about the current state of affairs. - * @return The amount the list actually scrolled. - * - * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State) - */ - @Override - public int scrollVerticallyBy( - int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) { - // If the list is empty, we can prevent the overscroll glow from showing by just - // telling RecycerView that we scrolled. - if (getItemCount() == 0) { - return dy; - } - - // Prevent redundant computations if there is definitely nowhere to scroll to. - if (getChildCount() <= 1 || dy == 0) { - return 0; - } - - View firstChild = getChildAt(0); - if (firstChild == null) { - return 0; - } - int firstChildPosition = getPosition(firstChild); - RecyclerView.LayoutParams firstChildParams = getParams(firstChild); - int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin; - - View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex()); - if (lastFullyVisibleView == null) { - return 0; - } - boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1; - - View firstFullyVisibleChild = getFirstFullyVisibleChild(); - if (firstFullyVisibleChild == null) { - return 0; - } - int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild); - RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild); - int topRemainingSpace = getDecoratedTop(firstFullyVisibleChild) - - firstFullyVisibleChildParams.topMargin - getPaddingTop(); - - if (isLastViewVisible && firstFullyVisiblePosition == mAnchorPageBreakPosition - && dy > topRemainingSpace && dy > 0) { - // Prevent dragging down more than 1 page. As a side effect, this also prevents you - // from dragging past the bottom because if you are on the second to last page, it - // prevents you from dragging past the last page. - dy = topRemainingSpace; - mReachedLimitOfDrag = true; - } else if (dy < 0 && firstChildPosition == 0 - && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) { - // Prevent scrolling past the beginning - dy = firstChildTopWithMargin - getPaddingTop(); - mReachedLimitOfDrag = true; - } else { - mReachedLimitOfDrag = false; - } - - boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING; - if (isDragging) { - mLastDragDistance += dy; - } - // We offset by -dy because the views translate in the opposite direction that the - // list scrolls (think about it.) - offsetChildrenVertical(-dy); - - // The last item in the layout should never scroll above the viewport - View view = getChildAt(getChildCount() - 1); - if (view.getTop() < 0) { - view.setTop(0); - } - - // This is the meat of this function. We remove views on the trailing edge of the scroll - // and add views at the leading edge as necessary. - View adjacentRow; - if (dy > 0) { - recycleChildrenFromStart(recycler); - adjacentRow = getChildAt(getChildCount() - 1); - while (shouldLayoutNextRow(state, adjacentRow, AFTER)) { - adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER); - } - } else { - recycleChildrenFromEnd(recycler); - adjacentRow = getChildAt(0); - while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) { - adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE); - } - } - // Now that the correct views are laid out, offset rows as necessary so we can do whatever - // fancy animation we want such as having the top view fly off the screen as the next one - // settles in to place. - updatePageBreakPositions(); - offsetRows(); - - if (getChildCount() > 1) { - if (DEBUG) { - Log.v(TAG, String.format("Currently showing %d views (%d to %d)", - getChildCount(), getPosition(getChildAt(0)), - getPosition(getChildAt(getChildCount() - 1)))); - } - } - - return dy; - } - - @Override - public void scrollToPosition(int position) { - mPendingScrollPosition = position; - requestLayout(); - } - - @Override - public void smoothScrollToPosition( - RecyclerView recyclerView, RecyclerView.State state, int position) { - /** - * startSmoothScroll will handle stopping the old one if there is one. - * We only keep a copy of it to handle the translation of rows as they slide off the screen - * in {@link #offsetRowsWithPageBreak()} - */ - mSmoothScroller = new CarSmoothScroller(mContext, position); - mSmoothScroller.setTargetPosition(position); - startSmoothScroll(mSmoothScroller); - } - - /** - * Miscellaneous bookkeeping. - */ - @Override - public void onScrollStateChanged(int state) { - if (DEBUG) { - Log.v(TAG, ":: onScrollStateChanged " + state); - } - if (state == RecyclerView.SCROLL_STATE_IDLE) { - // If the focused view is off screen, give focus to one that is. - // If the first fully visible view is first in the list, focus the first item. - // Otherwise, focus the second so that you have the first item as scrolling context. - View focusedChild = getFocusedChild(); - if (focusedChild != null - && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom() - || getDecoratedBottom(focusedChild) <= getPaddingTop())) { - focusedChild.clearFocus(); - requestLayout(); - } - - } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) { - mLastDragDistance = 0; - } - - if (state != RecyclerView.SCROLL_STATE_SETTLING) { - mSmoothScroller = null; - } - - mScrollState = state; - updatePageBreakPositions(); - } - - @Override - public void onItemsChanged(RecyclerView recyclerView) { - super.onItemsChanged(recyclerView); - if (mItemsChangedListener != null) { - mItemsChangedListener.onItemsChanged(); - } - // When item changed, our sample view height is no longer accurate, and need to be - // recomputed. - mSampleViewHeight = -1; - } - - /** - * Gives us the opportunity to override the order of the focused views. - * By default, it will just go from top to bottom. However, if there is no focused views, we - * take over the logic and start the focused views from the middle of what is visible and move - * from there until the end of the laid out views in the specified direction. - */ - @Override - public boolean onAddFocusables( - RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) { - View focusedChild = getFocusedChild(); - if (focusedChild != null) { - // If there is a view that already has focus, we can just return false and the normal - // Android addFocusables will work fine. - return false; - } - - // Now we know that there isn't a focused view. We need to set up focusables such that - // instead of just focusing the first item that has been laid out, it focuses starting - // from a visible item. - - int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex(); - if (firstFullyVisibleChildIndex == -1) { - // Somehow there is a focused view but there is no fully visible view. There shouldn't - // be a way for this to happen but we'd better stop here and return instead of - // continuing on with -1. - Log.w(TAG, "There is a focused child but no first fully visible child."); - return false; - } - View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex); - int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild); - - int firstFocusableChildIndex = firstFullyVisibleChildIndex; - if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) { - // We are somewhere in the middle of the list. Instead of starting focus on the first - // item, start focus on the second item to give some context that we aren't at - // the beginning. - firstFocusableChildIndex++; - } - - if (direction == View.FOCUS_FORWARD) { - // Iterate from the first focusable view to the end. - for (int i = firstFocusableChildIndex; i < getChildCount(); i++) { - views.add(getChildAt(i)); - } - return true; - } else if (direction == View.FOCUS_BACKWARD) { - // Iterate from the first focusable view to the beginning. - for (int i = firstFocusableChildIndex; i >= 0; i--) { - views.add(getChildAt(i)); - } - return true; - } - return false; - } - - @Override - public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, - RecyclerView.State state) { - return null; - } - - /** - * This is the function that decides where to scroll to when a new view is focused. - * You can get the position of the currently focused child through the child parameter. - * Once you have that, determine where to smooth scroll to and scroll there. - * - * @param parent The RecyclerView hosting this LayoutManager - * @param state Current state of RecyclerView - * @param child Direct child of the RecyclerView containing the newly focused view - * @param focused The newly focused view. This may be the same view as child or it may be null - * @return true if the default scroll behavior should be suppressed - */ - @Override - public boolean onRequestChildFocus(RecyclerView parent, RecyclerView.State state, - View child, View focused) { - if (child == null) { - Log.w(TAG, "onRequestChildFocus with a null child!"); - return true; - } - - if (DEBUG) { - Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child, - focused)); - } - - // We have several distinct scrolling methods. Each implementation has been delegated - // to its own method. - if (SCROLL_TYPE == MARIO) { - return onRequestChildFocusMarioStyle(parent, child); - } else if (SCROLL_TYPE == SUPER_MARIO) { - return onRequestChildFocusSuperMarioStyle(parent, state, child); - } else { - throw new IllegalStateException("Unknown scroll type (" + SCROLL_TYPE + ")"); - } - } - - /** - * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar - * reaches the bottom of the screen when the last item is fully visible. This is because - * there are multiple points that could be considered the bottom since the last item can scroll - * past the bottom edge of the screen. - * - * To find the extent, we divide the number of items that can fit on screen by the number of - * items in total. - */ - @Override - public int computeVerticalScrollExtent(RecyclerView.State state) { - if (getChildCount() <= 1) { - return 0; - } - - int sampleViewHeight = getSampleViewHeight(); - int availableHeight = getAvailableHeight(); - int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight; - - if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) { - return SCROLL_RANGE; - } else { - return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount(); - } - } - - /** - * The scrolling offset is calculated by determining what position is at the top of the list. - * However, instead of using fixed integer positions for each row, the scroll position is - * factored in and the position is recalculated as a float that takes in to account the - * current scroll state. This results in a smooth animation for the scrollbar when the user - * scrolls the list. - */ - @Override - public int computeVerticalScrollOffset(RecyclerView.State state) { - View firstChild = getFirstFullyVisibleChild(); - if (firstChild == null) { - return 0; - } - - RecyclerView.LayoutParams params = getParams(firstChild); - int firstChildPosition = getPosition(firstChild); - - // Assume the previous view is the same height as the current one. - float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin) - / (float) (getDecoratedMeasuredHeight(firstChild) - + params.topMargin + params.bottomMargin); - // If the previous view is actually larger than the current one then this the percent - // can be greater than 1. - percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1); - - float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing; - - int sampleViewHeight = getSampleViewHeight(); - int availableHeight = getAvailableHeight(); - int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight; - int positionWhenLastItemIsVisible = - state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen; - - if (positionWhenLastItemIsVisible <= 0) { - return 0; - } - - if (currentPosition >= positionWhenLastItemIsVisible) { - return SCROLL_RANGE; - } - - return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible); - } - - /** - * The range of the scrollbar can be understood as the granularity of how we want the - * scrollbar to scroll. - */ - @Override - public int computeVerticalScrollRange(RecyclerView.State state) { - return SCROLL_RANGE; - } - - /** - * @return The first view that starts on screen. It assumes that it fully fits on the screen - * though. If the first fully visible child is also taller than the screen then it will - * still be returned. However, since the LayoutManager snaps to view starts, having - * a row that tall would lead to a broken experience anyways. - */ - public int getFirstFullyVisibleChildIndex() { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - RecyclerView.LayoutParams params = getParams(child); - if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) { - return i; - } - } - return -1; - } - - public View getFirstFullyVisibleChild() { - int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex(); - View firstChild = null; - if (firstFullyVisibleChildIndex != -1) { - firstChild = getChildAt(firstFullyVisibleChildIndex); - } - return firstChild; - } - - /** - * @return The last view that ends on screen. It assumes that the start is also on screen - * though. If the last fully visible child is also taller than the screen then it will - * still be returned. However, since the LayoutManager snaps to view starts, having - * a row that tall would lead to a broken experience anyways. - */ - public int getLastFullyVisibleChildIndex() { - for (int i = getChildCount() - 1; i >= 0; i--) { - View child = getChildAt(i); - RecyclerView.LayoutParams params = getParams(child); - int childBottom = getDecoratedBottom(child) + params.bottomMargin; - int listBottom = getHeight() - getPaddingBottom(); - if (childBottom <= listBottom) { - return i; - } - } - return -1; - } - - /** - * @return Whether or not the first view is fully visible. - */ - public boolean isAtTop() { - // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views - // and also means that the list is at the top. - return getFirstFullyVisibleChildIndex() <= 0; - } - - /** - * @return Whether or not the last view is fully visible. - */ - public boolean isAtBottom() { - int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex(); - if (lastFullyVisibleChildIndex == -1) { - return true; - } - View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex); - return getPosition(lastFullyVisibleChild) == getItemCount() - 1; - } - - public void setOffsetRows(boolean offsetRows) { - mOffsetRows = offsetRows; - if (offsetRows) { - offsetRows(); - } else { - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - getChildAt(i).setTranslationY(0); - } - } - } - - public void setRowOffsetMode(@RowOffsetMode int mode) { - if (mode == mRowOffsetMode) { - return; - } - mRowOffsetMode = mode; - offsetRows(); - } - - public void setItemsChangedListener(OnItemsChangedListener listener) { - mItemsChangedListener = listener; - } - - /** - * Finish the pagination taking into account where the gesture started (not where we are now). - * - * @return Whether the list was scrolled as a result of the fling. - */ - public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) { - if (getChildCount() == 0) { - return false; - } - - if (mReachedLimitOfDrag) { - return false; - } - - // If the fling was too slow or too short, settle on the first fully visible row instead. - if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE - || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) { - int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex(); - if (firstFullyVisibleChildIndex != -1) { - int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex)); - parent.smoothScrollToPosition(scrollPosition); - return true; - } - return false; - } - - // Finish the pagination taking into account where the gesture - // started (not where we are now). - boolean isDownGesture = flingVelocity > 0 - || (flingVelocity == 0 && mLastDragDistance >= 0); - boolean isUpGesture = flingVelocity < 0 - || (flingVelocity == 0 && mLastDragDistance < 0); - if (isDownGesture && mLowerPageBreakPosition != -1) { - // If the last view is fully visible then only settle on the first fully visible view - // instead of the original page down position. However, don't page down if the last - // item has come fully into view. - parent.smoothScrollToPosition(mAnchorPageBreakPosition); - return true; - } else if (isUpGesture && mUpperPageBreakPosition != -1) { - parent.smoothScrollToPosition(mUpperPageBreakPosition); - return true; - } else { - Log.e(TAG, "Error setting scroll for fling! flingVelocity: \t" + flingVelocity + - "\tlastDragDistance: " + mLastDragDistance + "\tpageUpAtStartOfDrag: " + - mUpperPageBreakPosition + "\tpageDownAtStartOfDrag: " + - mLowerPageBreakPosition); - // As a last resort, at the last smooth scroller target position if there is one. - if (mSmoothScroller != null) { - parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition()); - return true; - } - } - return false; - } - - /** - * @return The position that paging up from the current position would settle at. - */ - public int getPageUpPosition() { - return mUpperPageBreakPosition; - } - - /** - * @return The position that paging down from the current position would settle at. - */ - public int getPageDownPosition() { - return mLowerPageBreakPosition; - } - - /** - * Layout the anchor row. The anchor row is the first fully visible row. - * - * @param anchorTop The decorated top of the anchor. If it is not known or should be reset - * to the top, pass -1. - */ - private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) { - if (anchorPosition > getItemCount() - 1) { - return null; - } - View anchor = recycler.getViewForPosition(anchorPosition); - RecyclerView.LayoutParams params = getParams(anchor); - measureChildWithMargins(anchor, 0, 0); - int left = getPaddingLeft() + params.leftMargin; - int top = (anchorTop == -1) ? params.topMargin : anchorTop; - int right = left + getDecoratedMeasuredWidth(anchor); - int bottom = top + getDecoratedMeasuredHeight(anchor); - layoutDecorated(anchor, left, top, right, bottom); - addView(anchor); - return anchor; - } - - /** - * Lays out the next row in the specified direction next to the specified adjacent row. - * - * @param recycler The recycler from which a new view can be created. - * @param adjacentRow The View of the adjacent row which will be used to position the new one. - * @param layoutDirection The side of the adjacent row that the new row will be laid out on. - * - * @return The new row that was laid out. - */ - private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow, - @LayoutDirection int layoutDirection) { - - int adjacentRowPosition = getPosition(adjacentRow); - int newRowPosition = adjacentRowPosition; - if (layoutDirection == BEFORE) { - newRowPosition = adjacentRowPosition - 1; - } else if (layoutDirection == AFTER) { - newRowPosition = adjacentRowPosition + 1; - } - - // Because we detach all rows in onLayoutChildren, this will often just return a view from - // the scrap heap. - View newRow = recycler.getViewForPosition(newRowPosition); - - measureChildWithMargins(newRow, 0, 0); - RecyclerView.LayoutParams newRowParams = - (RecyclerView.LayoutParams) newRow.getLayoutParams(); - RecyclerView.LayoutParams adjacentRowParams = - (RecyclerView.LayoutParams) adjacentRow.getLayoutParams(); - int left = getPaddingLeft() + newRowParams.leftMargin; - int right = left + getDecoratedMeasuredWidth(newRow); - int top, bottom; - if (layoutDirection == BEFORE) { - bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin; - top = bottom - getDecoratedMeasuredHeight(newRow); - } else { - top = getDecoratedBottom(adjacentRow) + - adjacentRowParams.bottomMargin + newRowParams.topMargin; - bottom = top + getDecoratedMeasuredHeight(newRow); - } - layoutDecorated(newRow, left, top, right, bottom); - - if (layoutDirection == BEFORE) { - addView(newRow, 0); - } else { - addView(newRow); - } - - return newRow; - } - - /** - * @return Whether another row should be laid out in the specified direction. - */ - private boolean shouldLayoutNextRow(RecyclerView.State state, View adjacentRow, - @LayoutDirection int layoutDirection) { - int adjacentRowPosition = getPosition(adjacentRow); - - if (layoutDirection == BEFORE) { - if (adjacentRowPosition == 0) { - // We already laid out the first row. - return false; - } - } else if (layoutDirection == AFTER) { - if (adjacentRowPosition >= state.getItemCount() - 1) { - // We already laid out the last row. - return false; - } - } - - // If we are scrolling layout views until the target position. - if (mSmoothScroller != null) { - if (layoutDirection == BEFORE - && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) { - return true; - } else if (layoutDirection == AFTER - && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) { - return true; - } - } - - View focusedRow = getFocusedChild(); - if (focusedRow != null) { - int focusedRowPosition = getPosition(focusedRow); - if (layoutDirection == BEFORE && adjacentRowPosition - >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) { - return true; - } else if (layoutDirection == AFTER && adjacentRowPosition - <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) { - return true; - } - } - - RecyclerView.LayoutParams params = getParams(adjacentRow); - int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin; - int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin; - if (layoutDirection == BEFORE - && adjacentRowTop < getPaddingTop() - getHeight()) { - // View is more than 1 page past the top of the screen and also past where the user has - // scrolled to. We want to keep one page past the top to make the scroll up calculation - // easier and scrolling smoother. - return false; - } else if (layoutDirection == AFTER - && adjacentRowBottom > getHeight() - getPaddingBottom()) { - // View is off of the bottom and also past where the user has scrolled to. - return false; - } - - return true; - } - - /** - * Remove and recycle views that are no longer needed. - */ - private void recycleChildrenFromStart(RecyclerView.Recycler recycler) { - // Start laying out children one page before the top of the viewport. - int childrenStart = getPaddingTop() - getHeight(); - - int focusedChildPosition = Integer.MAX_VALUE; - View focusedChild = getFocusedChild(); - if (focusedChild != null) { - focusedChildPosition = getPosition(focusedChild); - } - - // Count the number of views that should be removed. - int detachedCount = 0; - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - int childEnd = getDecoratedBottom(child); - int childPosition = getPosition(child); - - if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) { - break; - } - - detachedCount++; - } - - // Remove the number of views counted above. Done by removing the first child n times. - while (--detachedCount >= 0) { - final View child = getChildAt(0); - removeAndRecycleView(child, recycler); - } - } - - /** - * Remove and recycle views that are no longer needed. - */ - private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) { - // Layout views until the end of the viewport. - int childrenEnd = getHeight(); - - int focusedChildPosition = Integer.MIN_VALUE + 1; - View focusedChild = getFocusedChild(); - if (focusedChild != null) { - focusedChildPosition = getPosition(focusedChild); - } - - // Count the number of views that should be removed. - int firstDetachedPos = 0; - int detachedCount = 0; - int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - final View child = getChildAt(i); - int childStart = getDecoratedTop(child); - int childPosition = getPosition(child); - - if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) { - break; - } - - firstDetachedPos = i; - detachedCount++; - } - - while (--detachedCount >= 0) { - final View child = getChildAt(firstDetachedPos); - removeAndRecycleView(child, recycler); - } - } - - /** - * Offset rows to do fancy animations. If {@link #mOffsetRows} is false, this will do nothing. - * - * @see #offsetRowsIndividually() - * @see #offsetRowsByPage() - */ - public void offsetRows() { - if (!mOffsetRows) { - return; - } - - if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) { - offsetRowsByPage(); - } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) { - offsetRowsIndividually(); - } - } - - /** - * Offset the single row that is scrolling off the screen such that by the time the next row - * reaches the top, it will have accelerated completely off of the screen. - */ - private void offsetRowsIndividually() { - if (getChildCount() == 0) { - if (DEBUG) { - Log.d(TAG, ":: offsetRowsIndividually getChildCount=0"); - } - return; - } - - // Identify the dangling row. It will be the first row that is at the top of the - // list or above. - int danglingChildIndex = -1; - for (int i = getChildCount() - 1; i >= 0; i--) { - View child = getChildAt(i); - if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) { - danglingChildIndex = i; - break; - } - } - - mAnchorPageBreakPosition = danglingChildIndex; - - if (DEBUG) { - Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex); - } - - // Calculate the total amount that the view will need to scroll in order to go completely - // off screen. - RecyclerView rv = (RecyclerView) getChildAt(0).getParent(); - int[] locs = new int[2]; - rv.getLocationInWindow(locs); - int listTopInWindow = locs[1] + rv.getPaddingTop(); - int maxDanglingViewTranslation; - - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - RecyclerView.LayoutParams params = getParams(child); - - maxDanglingViewTranslation = listTopInWindow; - // If the child has a negative margin, we'll actually need to translate the view a - // little but further to get it completely off screen. - if (params.topMargin < 0) { - maxDanglingViewTranslation -= params.topMargin; - } - if (params.bottomMargin < 0) { - maxDanglingViewTranslation -= params.bottomMargin; - } - - if (i < danglingChildIndex) { - child.setAlpha(0f); - } else if (i > danglingChildIndex) { - child.setAlpha(1f); - child.setTranslationY(0); - } else { - int totalScrollDistance = getDecoratedMeasuredHeight(child) + - params.topMargin + params.bottomMargin; - - int distanceLeftInScroll = getDecoratedBottom(child) + - params.bottomMargin - getPaddingTop(); - float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance; - float interpolatedPercentage = - mDanglingRowInterpolator.getInterpolation(percentageIntoScroll); - - child.setAlpha(1f); - child.setTranslationY(-(maxDanglingViewTranslation * interpolatedPercentage)); - } - } - } - - /** - * When the list scrolls, the entire page of rows will offset in one contiguous block. This - * significantly reduces the amount of extra motion at the top of the screen. - */ - private void offsetRowsByPage() { - View anchorView = findViewByPosition(mAnchorPageBreakPosition); - if (anchorView == null) { - if (DEBUG) { - Log.d(TAG, ":: offsetRowsByPage anchorView null"); - } - return; - } - int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin; - - View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition); - int upperViewTop = getDecoratedTop(upperPageBreakView) - - getParams(upperPageBreakView).topMargin; - - int scrollDistance = upperViewTop - anchorViewTop; - - int distanceLeft = anchorViewTop - getPaddingTop(); - float scrollPercentage = (Math.abs(scrollDistance) - distanceLeft) - / (float) Math.abs(scrollDistance); - - if (DEBUG) { - Log.d(TAG, String.format( - ":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, scrollPercentage:%s", - scrollDistance, distanceLeft, scrollPercentage)); - } - - // Calculate the total amount that the view will need to scroll in order to go completely - // off screen. - RecyclerView rv = (RecyclerView) getChildAt(0).getParent(); - int[] locs = new int[2]; - rv.getLocationInWindow(locs); - int listTopInWindow = locs[1] + rv.getPaddingTop(); - - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - int position = getPosition(child); - if (position < mUpperPageBreakPosition) { - child.setAlpha(0f); - child.setTranslationY(-listTopInWindow); - } else if (position < mAnchorPageBreakPosition) { - // If the child has a negative margin, we need to offset the row by a little bit - // extra so that it moves completely off screen. - RecyclerView.LayoutParams params = getParams(child); - int extraTranslation = 0; - if (params.topMargin < 0) { - extraTranslation -= params.topMargin; - } - if (params.bottomMargin < 0) { - extraTranslation -= params.bottomMargin; - } - int translation = (int) ((listTopInWindow + extraTranslation) - * mDanglingRowInterpolator.getInterpolation(scrollPercentage)); - child.setAlpha(1f); - child.setTranslationY(-translation); - } else { - child.setAlpha(1f); - child.setTranslationY(0); - } - } - } - - /** - * Update the page break positions based on the position of the views on screen. This should - * be called whenever view move or change such as during a scroll or layout. - */ - private void updatePageBreakPositions() { - if (getChildCount() == 0) { - if (DEBUG) { - Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0"); - } - return; - } - - if (DEBUG) { - Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions " + - "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, " - + "mLowerPageBreakPosition:%s", - mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition)); - } - - // If the item count has changed, our page boundaries may no longer be accurate. This will - // force the page boundaries to reset around the current view that is closest to the top. - if (getItemCount() != mItemCountDuringLastPageBreakUpdate) { - if (DEBUG) { - Log.d(TAG, "Item count changed. Resetting page break positions."); - } - mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild()); - } - mItemCountDuringLastPageBreakUpdate = getItemCount(); - - if (mAnchorPageBreakPosition == -1) { - Log.w(TAG, "Unable to update anchor positions. There is no anchor position."); - return; - } - - View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition); - if (anchorPageBreakView == null) { - return; - } - int topMargin = getParams(anchorPageBreakView).topMargin; - int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin; - View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition); - int upperPageBreakTop = upperPageBreakView == null ? Integer.MIN_VALUE : - getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin; - - if (DEBUG) { - Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s" - + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, " - + "mLowerPageBreakPosition:%s", topMargin, anchorTop, - mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition)); - } - - if (anchorTop < getPaddingTop()) { - // The anchor has moved above the viewport. We are now on the next page. Shift the page - // break positions and calculate a new lower one. - mUpperPageBreakPosition = mAnchorPageBreakPosition; - mAnchorPageBreakPosition = mLowerPageBreakPosition; - mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition); - } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) { - // The anchor has moved below the viewport. We are now on the previous page. Shift - // the page break positions and calculate a new upper one. - mLowerPageBreakPosition = mAnchorPageBreakPosition; - mAnchorPageBreakPosition = mUpperPageBreakPosition; - mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition); - } else { - mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition); - mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition); - } - - if (DEBUG) { - Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions " + - "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, " - + "mLowerPageBreakPosition:%s", - mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition)); - } - } - - /** - * @return The page break position of the page before the anchor page break position. However, - * if it reaches the end of the laid out children or position 0, it will just return - * that. - */ - private int calculatePreviousPageBreakPosition(int position) { - if (position == -1) { - return -1; - } - View referenceView = findViewByPosition(position); - int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin; - - int previousPagePosition = position; - while (previousPagePosition > 0) { - previousPagePosition--; - View child = findViewByPosition(previousPagePosition); - if (child == null) { - // View has not been laid out yet. - return previousPagePosition + 1; - } - - int childTop = getDecoratedTop(child) - getParams(child).topMargin; - - if (childTop < referenceViewTop - getHeight()) { - return previousPagePosition + 1; - } - } - // Beginning of the list. - return 0; - } - - /** - * @return The page break position of the next page after the anchor page break position. - * However, if it reaches the end of the laid out children or end of the list, it will - * just return that. - */ - private int calculateNextPageBreakPosition(int position) { - if (position == -1) { - return -1; - } - - View referenceView = findViewByPosition(position); - if (referenceView == null) { - return position; - } - int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin; - - int nextPagePosition = position; - - // Search for the first child item after the referenceView that didn't fully fit on to the - // screen. The next page should start from the item before this child, so that users have - // a visual anchoring point of the page change. - while (position < getItemCount() - 1) { - nextPagePosition++; - View child = findViewByPosition(nextPagePosition); - if (child == null) { - // The next view has not been laid out yet. - return nextPagePosition - 1; - } - - int childBottom = getDecoratedBottom(child) + getParams(child).bottomMargin; - if (childBottom - referenceViewTop > getHeight() - getPaddingTop()) { - // If choosing the previous child causes the view to snap back to the referenceView - // position, then skip that and go directly to the child. This avoids the case - // where a tall card in the layout causes the view to constantly snap back to - // the top when scrolled. - return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1; - } - } - // End of the list. - return nextPagePosition; - } - - /** - * In this style, the focus will scroll down to the middle of the screen and lock there - * so that moving in either direction will move the entire list by 1. - */ - private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) { - int focusedPosition = getPosition(child); - if (focusedPosition == mLastChildPositionToRequestFocus) { - return true; - } - mLastChildPositionToRequestFocus = focusedPosition; - - int availableHeight = getAvailableHeight(); - int focusedChildTop = getDecoratedTop(child); - int focusedChildBottom = getDecoratedBottom(child); - - int childIndex = parent.indexOfChild(child); - // Iterate through children starting at the focused child to find the child above it to - // smooth scroll to such that the focused child will be as close to the middle of the screen - // as possible. - for (int i = childIndex; i >= 0; i--) { - View childAtI = getChildAt(i); - if (childAtI == null) { - Log.e(TAG, "Child is null at index " + i); - continue; - } - // We haven't found a view that is more than half of the recycler view height above it - // but we've reached the top so we can't go any further. - if (i == 0) { - parent.smoothScrollToPosition(getPosition(childAtI)); - break; - } - - // Because we want to scroll to the first view that is less than half of the screen - // away from the focused view, we "look ahead" one view. When the look ahead view - // is more than availableHeight / 2 away, the current child at i is the one we want to - // scroll to. However, sometimes, that view can be null (ie, if the view is in - // transition). In that case, just skip that view. - - View childBefore = getChildAt(i - 1); - if (childBefore == null) { - continue; - } - int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore); - int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore); - - if (distanceToChildBeforeFromTop > availableHeight / 2 - || distanceToChildBeforeFromBottom > availableHeight) { - parent.smoothScrollToPosition(getPosition(childAtI)); - break; - } - } - return true; - } - - /** - * In this style, you can free scroll in the middle of the list but if you get to the edge, - * the list will advance to ensure that there is context ahead of the focused item. - */ - private boolean onRequestChildFocusSuperMarioStyle(RecyclerView parent, - RecyclerView.State state, View child) { - int focusedPosition = getPosition(child); - if (focusedPosition == mLastChildPositionToRequestFocus) { - return true; - } - mLastChildPositionToRequestFocus = focusedPosition; - - int bottomEdgeThatMustBeOnScreen; - int focusedIndex = parent.indexOfChild(child); - // The amount of the last card at the end that must be showing to count as visible. - int peekAmount = mContext.getResources() - .getDimensionPixelSize(R.dimen.car_last_card_peek_amount); - if (focusedPosition == state.getItemCount() - 1) { - // The last item is focused. - bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child); - } else if (focusedIndex == getChildCount() - 1) { - // The last laid out item is focused. Scroll enough so that the next card has at least - // the peek size visible - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) child.getLayoutParams(); - // We add params.topMargin as an estimate because we don't actually know the top margin - // of the next row. - bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child) + - params.bottomMargin + params.topMargin + peekAmount; - } else { - View nextChild = getChildAt(focusedIndex + 1); - bottomEdgeThatMustBeOnScreen = getDecoratedTop(nextChild) + peekAmount; - } - - if (bottomEdgeThatMustBeOnScreen > getHeight()) { - // We're going to have to scroll because the bottom edge that must be on screen is past - // the bottom. - int topEdgeToFindViewUnder = getPaddingTop() + - bottomEdgeThatMustBeOnScreen - getHeight(); - - View nextChild = null; - for (int i = 0; i < getChildCount(); i++) { - View potentialNextChild = getChildAt(i); - RecyclerView.LayoutParams params = getParams(potentialNextChild); - float top = getDecoratedTop(potentialNextChild) - params.topMargin; - if (top >= topEdgeToFindViewUnder) { - nextChild = potentialNextChild; - break; - } - } - - if (nextChild == null) { - Log.e(TAG, "There is no view under " + topEdgeToFindViewUnder); - return true; - } - int nextChildPosition = getPosition(nextChild); - parent.smoothScrollToPosition(nextChildPosition); - } else { - int firstFullyVisibleIndex = getFirstFullyVisibleChildIndex(); - if (focusedIndex <= firstFullyVisibleIndex) { - parent.smoothScrollToPosition(Math.max(focusedPosition - 1, 0)); - } - } - return true; - } - - /** - * We don't actually know the size of every single view, only what is currently laid out. - * This makes it difficult to do accurate scrollbar calculations. However, lists in the car - * often consist of views with identical heights. Because of that, we can use - * a single sample view to do our calculations for. The main exceptions are in the first items - * of a list (hero card, last call card, etc) so if the first view is at position 0, we pick - * the next one. - * - * @return The decorated measured height of the sample view plus its margins. - */ - private int getSampleViewHeight() { - if (mSampleViewHeight != -1) { - return mSampleViewHeight; - } - int sampleViewIndex = getFirstFullyVisibleChildIndex(); - View sampleView = getChildAt(sampleViewIndex); - if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) { - sampleView = getChildAt(++sampleViewIndex); - } - RecyclerView.LayoutParams params = getParams(sampleView); - int height = - getDecoratedMeasuredHeight(sampleView) + params.topMargin + params.bottomMargin; - if (height == 0) { - // This can happen if the view isn't measured yet. - Log.w(TAG, "The sample view has a height of 0. Returning a dummy value for now " + - "that won't be cached."); - height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height); - } else { - mSampleViewHeight = height; - } - return height; - } - - /** - * @return The height of the RecyclerView excluding padding. - */ - private int getAvailableHeight() { - return getHeight() - getPaddingTop() - getPaddingBottom(); - } - - /** - * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child - * of {@link RecyclerView}. - */ - private static RecyclerView.LayoutParams getParams(View view) { - return (RecyclerView.LayoutParams) view.getLayoutParams(); - } - - /** - * Custom {@link LinearSmoothScroller} that has: - * a) Custom control over the speed of scrolls. - * b) Scrolling snaps to start. All of our scrolling logic depends on that. - * c) Keeps track of some state of the current scroll so that can aid in things like - * the scrollbar calculations. - */ - private final class CarSmoothScroller extends LinearSmoothScroller { - /** This value (150) was hand tuned by UX for what felt right. **/ - private static final float MILLISECONDS_PER_INCH = 150f; - /** This value (0.45) was hand tuned by UX for what felt right. **/ - private static final float DECELERATION_TIME_DIVISOR = 0.45f; - private static final int NON_TOUCH_MAX_DECELERATION_MS = 1000; - - /** This value (1.8) was hand tuned by UX for what felt right. **/ - private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f); - - private final boolean mHasTouch; - private final int mTargetPosition; - - - public CarSmoothScroller(Context context, int targetPosition) { - super(context); - mTargetPosition = targetPosition; - mHasTouch = mContext.getResources().getBoolean(R.bool.car_true_for_touch); - } - - @Override - public PointF computeScrollVectorForPosition(int i) { - if (getChildCount() == 0) { - return null; - } - final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex())); - final int direction = (mTargetPosition < firstChildPos) ? -1 : 1; - return new PointF(0, direction); - } - - @Override - protected int getVerticalSnapPreference() { - // This is key for most of the scrolling logic that guarantees that scrolling - // will settle with a view aligned to the top. - return LinearSmoothScroller.SNAP_TO_START; - } - - @Override - protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { - int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START); - if (dy == 0) { - if (DEBUG) { - Log.d(TAG, "Scroll distance is 0"); - } - return; - } - - final int time = calculateTimeForDeceleration(dy); - if (time > 0) { - action.update(0, -dy, time, mInterpolator); - } - } - - @Override - protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { - return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; - } - - @Override - protected int calculateTimeForDeceleration(int dx) { - int time = (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR); - return mHasTouch ? time : Math.min(time, NON_TOUCH_MAX_DECELERATION_MS); - } - - public int getTargetPosition() { - return mTargetPosition; - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/CarListItemViewHolder.java b/car-support-lib/src/android/support/car/ui/CarListItemViewHolder.java deleted file mode 100644 index 986b549315..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarListItemViewHolder.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.view.ViewStub; -import android.widget.CheckBox; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -/** - * ViewHolder for @layout/sdk_car_list_item that is used to handle the various sdk item templates - * @hide - */ -public class CarListItemViewHolder extends RecyclerView.ViewHolder { - public final FrameLayout iconContainer; - public final ImageView icon; - public final TextView title; - public final TextView text; - public final ImageView rightImage; - public final CheckBox rightCheckbox; - public final TextView rightText; - public final FrameLayout remoteViewsContainer; - - public CarListItemViewHolder(View v, int viewStubLayoutId) { - super(v); - icon = (ImageView) v.findViewById(R.id.icon); - iconContainer = (FrameLayout) v.findViewById(R.id.icon_container); - title = (TextView) v.findViewById(R.id.title); - text = (TextView) v.findViewById(R.id.text); - remoteViewsContainer = (FrameLayout) v.findViewById(R.id.remoteviews); - ViewStub rightStub = (ViewStub) v.findViewById(R.id.right_item); - if (rightStub != null) { - rightStub.setLayoutResource(viewStubLayoutId); - rightStub.setInflatedId(R.id.right_item); - - if (viewStubLayoutId == R.layout.car_menu_checkbox) { - rightCheckbox = (CheckBox) rightStub.inflate(); - rightImage = null; - rightText = null; - } else if (viewStubLayoutId == R.layout.car_imageview) { - rightImage = (ImageView) rightStub.inflate(); - rightCheckbox = null; - rightText = null; - } else if (viewStubLayoutId == R.layout.car_textview) { - rightText = (TextView) rightStub.inflate(); - rightCheckbox = null; - rightImage = null; - } else { - rightImage = null; - rightCheckbox = null; - rightText = null; - } - } else { - rightImage = null; - rightCheckbox = null; - rightText = null; - } - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/ui/CarNavExtender.java b/car-support-lib/src/android/support/car/ui/CarNavExtender.java deleted file mode 100644 index 26a2c52418..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarNavExtender.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.app.Notification; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.support.annotation.DrawableRes; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.NotificationCompat; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Helper class to add navigation extensions to notifications for use in Android Auto. - * <p> - * To create a notification with navigation extensions: - * <ol> - * <li>Create a {@link android.app.Notification.Builder}, setting any desired - * properties. - * <li>Create a {@link CarNavExtender}. - * <li>Set car-specific properties using the - * {@code add} and {@code set} methods of {@link CarNavExtender}. - * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a - * notification. - * <li>Post the notification to the notification system with the - * {@code NotificationManager.notify(...)} methods. - * </ol> - * - * <pre class="prettyprint"> - * Notification notif = new Notification.Builder(mContext) - * .setContentTitle("Turn right in 2.0 miles on to US 101-N") - * .setContentText("43 mins (32 mi) to Home") - * .setSmallIcon(R.drawable.ic_nav) - * .extend(new CarNavExtender() - * .setContentTitle("US 101-N") - * .setContentText("400 ft") - * .setSubText("43 mins to Home") - * .build(); - * NotificationManager notificationManger = - * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - * notificationManger.notify(0, notif);</pre> - * - * <p>CarNavExtender fields can be accessed on an existing notification by using the - * {@code CarNavExtender(Notification)} constructor, - * and then using the {@code get} methods to access values. - * @hide - */ -public class CarNavExtender implements NotificationCompat.Extender { - /** This value must remain unchanged for compatibility. **/ - private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; - private static final String EXTRA_IS_EXTENDED = - "com.google.android.gms.car.support.CarNavExtender.EXTENDED"; - private static final String EXTRA_CONTENT_ID = "content_id"; - private static final String EXTRA_TYPE = "type"; - private static final String EXTRA_SUB_TEXT = "sub_text"; - private static final String EXTRA_ACTION_ICON = "action_icon"; - /** This value must remain unchanged for compatibility. **/ - private static final String EXTRA_CONTENT_INTENT = "content_intent"; - /** This value must remain unchanged for compatibility. **/ - private static final String EXTRA_COLOR = "app_color"; - private static final String EXTRA_NIGHT_COLOR = "app_night_color"; - /** This value must remain unchanged for compatibility. **/ - private static final String EXTRA_STREAM_VISIBILITY = "stream_visibility"; - /** This value must remain unchanged for compatibility. **/ - private static final String EXTRA_HEADS_UP_VISIBILITY = "heads_up_visibility"; - private static final String EXTRA_IGNORE_IN_STREAM = "ignore_in_stream"; - - @IntDef({TYPE_HERO, TYPE_NORMAL}) - @Retention(RetentionPolicy.SOURCE) - private @interface Type {} - public static final int TYPE_HERO = 0; - public static final int TYPE_NORMAL = 1; - - private boolean mIsExtended; - /** <code>null</code> if not explicitly set. **/ - private Long mContentId; - private int mType = TYPE_NORMAL; - private CharSequence mContentTitle; - private CharSequence mContentText; - private CharSequence mSubText; - private Bitmap mLargeIcon; - private @DrawableRes int mActionIcon; - private Intent mContentIntent; - private int mColor = Notification.COLOR_DEFAULT; - private int mNightColor = Notification.COLOR_DEFAULT; - private boolean mShowInStream = true; - private boolean mShowAsHeadsUp; - private boolean mIgnoreInStream; - - /** - * Create a new CarNavExtender to extend a new notification. - */ - public CarNavExtender() { - } - - /** - * Reconstruct a CarNavExtender from an existing notification. Can be used to retrieve values. - * - * @param notification The notification to retrieve the values from. - */ - public CarNavExtender(@NonNull Notification notification) { - Bundle extras = NotificationCompat.getExtras(notification); - if (extras == null) { - return; - } - Bundle b = extras.getBundle(EXTRA_CAR_EXTENDER); - if (b == null) { - return; - } - - mIsExtended = b.getBoolean(EXTRA_IS_EXTENDED); - mContentId = (Long) b.getSerializable(EXTRA_CONTENT_ID); - // The ternary guarantees that we return either TYPE_HERO or TYPE_NORMAL. - mType = (b.getInt(EXTRA_TYPE, TYPE_NORMAL) == TYPE_HERO) ? TYPE_HERO : TYPE_NORMAL; - mContentTitle = b.getCharSequence(Notification.EXTRA_TITLE); - mContentText = b.getCharSequence(Notification.EXTRA_TEXT); - mSubText = b.getCharSequence(EXTRA_SUB_TEXT); - mLargeIcon = b.getParcelable(Notification.EXTRA_LARGE_ICON); - mActionIcon = b.getInt(EXTRA_ACTION_ICON); - mContentIntent = b.getParcelable(EXTRA_CONTENT_INTENT); - mColor = b.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); - mNightColor = b.getInt(EXTRA_NIGHT_COLOR, Notification.COLOR_DEFAULT); - mShowInStream = b.getBoolean(EXTRA_STREAM_VISIBILITY, true); - mShowAsHeadsUp = b.getBoolean(EXTRA_HEADS_UP_VISIBILITY); - mIgnoreInStream = b.getBoolean(EXTRA_IGNORE_IN_STREAM); - } - - @Override - public NotificationCompat.Builder extend(NotificationCompat.Builder builder) { - Bundle b = new Bundle(); - b.putBoolean(EXTRA_IS_EXTENDED, true); - b.putSerializable(EXTRA_CONTENT_ID, mContentId); - b.putInt(EXTRA_TYPE, mType); - b.putCharSequence(Notification.EXTRA_TITLE, mContentTitle); - b.putCharSequence(Notification.EXTRA_TEXT, mContentText); - b.putCharSequence(EXTRA_SUB_TEXT, mSubText); - b.putParcelable(Notification.EXTRA_LARGE_ICON, mLargeIcon); - b.putInt(EXTRA_ACTION_ICON, mActionIcon); - b.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); - b.putInt(EXTRA_COLOR, mColor); - b.putInt(EXTRA_NIGHT_COLOR, mNightColor); - b.putBoolean(EXTRA_STREAM_VISIBILITY, mShowInStream); - b.putBoolean(EXTRA_HEADS_UP_VISIBILITY, mShowAsHeadsUp); - b.putBoolean(EXTRA_IGNORE_IN_STREAM, mIgnoreInStream); - builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, b); - return builder; - } - - /** - * @return <code>true</code> if the notification was extended with {@link CarNavExtender}. - */ - public boolean isExtended() { - return mIsExtended; - } - - /** - * Static version of {@link #isExtended()}. - */ - public static boolean isExtended(Notification notification) { - Bundle extras = NotificationCompat.getExtras(notification); - if (extras == null) { - return false; - } - - extras = extras.getBundle(EXTRA_CAR_EXTENDER); - return extras != null && extras.getBoolean(EXTRA_IS_EXTENDED); - } - - /** - * Sets an id for the content of this notification. If the content id matches an existing - * notification, any timers that control ranking and heads up notification will remain - * unchanged. However, if it differs from the previous notification with the same id then - * this notification will be treated as a new notification with respect to heads up - * notifications and ranking. - * - * If no content id is specified, it will be treated like a new content id. - * - * A content id will only be compared to the existing notification, not the entire history of - * content ids. - * - * @param contentId The content id that represents this notification. - * @return This object for method chaining. - */ - public CarNavExtender setContentId(long contentId) { - mContentId = contentId; - return this; - } - - /** - * @return The content id for this notification or <code>null</code> if it was not specified. - */ - @Nullable - public Long getContentId() { - return mContentId; - } - - /** - * @param type The type of notification that this will be displayed as in the Android Auto. - * @return This object for method chaining. - * - * @see #TYPE_NORMAL - * @see #TYPE_HERO - */ - public CarNavExtender setType(@Type int type) { - mType = type; - return this; - } - - /** - * @return The type of notification - * - * @see #TYPE_NORMAL - * @see #TYPE_HERO - */ - @Type - public int getType() { - return mType; - } - - /** - * @return The type without having to construct an entire {@link CarNavExtender} object. - */ - @Type - public static int getType(Notification notification) { - Bundle extras = NotificationCompat.getExtras(notification); - if (extras == null) { - return TYPE_NORMAL; - } - Bundle b = extras.getBundle(EXTRA_CAR_EXTENDER); - if (b == null) { - return TYPE_NORMAL; - } - - // The ternary guarantees that we return either TYPE_HERO or TYPE_NORMAL. - return (b.getInt(EXTRA_TYPE, TYPE_NORMAL) == TYPE_HERO) ? TYPE_HERO : TYPE_NORMAL; - } - - /** - * @param contentTitle Override for the notification's content title. - * @return This object for method chaining. - */ - public CarNavExtender setContentTitle(CharSequence contentTitle) { - mContentTitle = contentTitle; - return this; - } - - /** - * @return The content title for the notification if one was explicitly set with - * {@link #setContentTitle(CharSequence)}. - */ - public CharSequence getContentTitle() { - return mContentTitle; - } - - /** - * @param contentText Override for the notification's content text. If set to an empty string, - * it will be treated as if there is no context text by the UI. - * @return This object for method chaining. - */ - public CarNavExtender setContentText(CharSequence contentText) { - mContentText = contentText; - return this; - } - - /** - * @return The content text for the notification if one was explicitly set with - * {@link #setContentText(CharSequence)}. - */ - @Nullable - public CharSequence getContentText() { - return mContentText; - } - - /** - * @param subText A third text field that will be displayed on hero cards. - * @return This object for method chaining. - */ - public CarNavExtender setSubText(CharSequence subText) { - mSubText = subText; - return this; - } - - /** - * @return The secondary content text for the notification or null if it wasn't set. - */ - @Nullable - public CharSequence getSubText() { - return mSubText; - } - - /** - * @param largeIcon Override for the notification's large icon. - * @return This object for method chaining. - */ - public CarNavExtender setLargeIcon(Bitmap largeIcon) { - mLargeIcon = largeIcon; - return this; - } - - /** - * @return The large icon for the notification if one was explicitly set with - * {@link #setLargeIcon(android.graphics.Bitmap)}. - */ - public Bitmap getLargeIcon() { - return mLargeIcon; - } - - /** - * By default, Android Auto will show a navigation chevron on cards. However, a separate icon - * can be set here to override it. - * - * @param actionIcon The action icon resource id from your package that you would like to - * use instead of the navigation chevron. - * @return This object for method chaining. - */ - public CarNavExtender setActionIcon(@DrawableRes int actionIcon) { - mActionIcon = actionIcon; - return this; - } - - /** - * @return The overridden action icon or 0 if one wasn't set. - */ - @DrawableRes - public int getActionIcon() { - return mActionIcon; - } - - /** - * @param contentIntent The content intent that will be sent using - * {@link com.google.android.gms.car.CarActivity#startCarProjectionActivity(android.content.Intent)} - * It is STRONGLY suggested that you set a content intent or else the - * notification will have no action when tapped. - * @return This object for method chaining. - */ - public CarNavExtender setContentIntent(Intent contentIntent) { - mContentIntent = contentIntent; - return this; - } - - /** - * @return The content intent that will be sent using - * {@link com.google.android.gms.car.CarActivity#startCarProjectionActivity(android.content.Intent)} - */ - public Intent getContentIntent() { - return mContentIntent; - } - - /** - * @param color Override for the notification color. - * @return This object for method chaining. - * - * @see android.app.Notification.Builder#setColor(int) - */ - public CarNavExtender setColor(int color) { - mColor = color; - return this; - } - - /** - * @return The color specified by the notification or {@link android.app.Notification#COLOR_DEFAULT} if - * one wasn't explicitly set with {@link #setColor(int)}. - */ - public int getColor() { - return mColor; - } - - /** - * @param nightColor Override for the notification color at night. - * @return This object for method chaining. - * - * @see android.app.Notification.Builder#setColor(int) - */ - public CarNavExtender setNightColor(int nightColor) { - mNightColor = nightColor; - return this; - } - - /** - * @return The night color specified by the notification or {@link android.app.Notification#COLOR_DEFAULT} - * if one wasn't explicitly set with {@link #setNightColor(int)}. - */ - public int getNightColor() { - return mNightColor; - } - - /** - * @param show Whether or not to show the notification in the stream. - * @return This object for method chaining. - */ - public CarNavExtender setShowInStream(boolean show) { - mShowInStream = show; - return this; - } - - /** - * @return Whether or not to show the notification in the stream. - */ - public boolean getShowInStream() { - return mShowInStream; - } - - /** - * @param show Whether or not to show the notification as a heads up notification. - * @return This object for method chaining. - */ - public CarNavExtender setShowAsHeadsUp(boolean show) { - mShowAsHeadsUp = show; - return this; - } - - /** - * @return Whether or not to show the notification as a heads up notification. - */ - public boolean getShowAsHeadsUp() { - return mShowAsHeadsUp; - } - - /** - * @param ignore Whether or not this notification can be shown as a heads-up notification if - * the user is already on the stream. - * @return This object for method chaining. - */ - public CarNavExtender setIgnoreInStream(boolean ignore) { - mIgnoreInStream = ignore; - return this; - } - - /** - * @return Whether or not the stream item can be shown as a heads-up notification if ther user - * already is on the stream. - */ - public boolean getIgnoreInStream() { - return mIgnoreInStream; - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/ui/CarRecyclerView.java b/car-support-lib/src/android/support/car/ui/CarRecyclerView.java deleted file mode 100644 index 9838e18371..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarRecyclerView.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.graphics.Canvas; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -/** - * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate. - * - * It also has the ability to fade children as they scroll off screen that can be set - * with {@link #setFadeLastItem(boolean)}. - * @hide - */ -public class CarRecyclerView extends RecyclerView { - private static final String PARCEL_CLASS = "android.os.Parcel"; - private static final String SAVED_STATE_CLASS = - "android.support.v7.widget.RecyclerView.SavedState"; - private boolean mFadeLastItem; - private Constructor<?> mSavedStateConstructor; - /** - * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be - * called. However, we want to make sure that the list still snaps to the next page when this - * happens. - */ - private boolean mWasFlingCalledForGesture; - - public CarRecyclerView(Context context) { - this(context, null); - } - - public CarRecyclerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CarRecyclerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setFocusableInTouchMode(false); - setFocusable(false); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (state.getClass().getClassLoader() != getClass().getClassLoader()) { - if (mSavedStateConstructor == null) { - mSavedStateConstructor = getSavedStateConstructor(); - } - // Class loader mismatch, recreate from parcel. - Parcel obtain = Parcel.obtain(); - state.writeToParcel(obtain, 0); - try { - Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain); - super.onRestoreInstanceState(newState); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - // Fail loudy here. - throw new RuntimeException(e); - } - } else { - super.onRestoreInstanceState(state); - } - } - - @Override - public boolean fling(int velocityX, int velocityY) { - mWasFlingCalledForGesture = true; - return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY); - } - - @Override - public boolean onTouchEvent(MotionEvent e) { - // We want the parent to handle all touch events. There's a lot going on there, - // and there is no reason to overwrite that functionality. If we do, bad things will happen. - final boolean ret = super.onTouchEvent(e); - - int action = e.getActionMasked(); - if (action == MotionEvent.ACTION_UP) { - if (!mWasFlingCalledForGesture) { - ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0); - } - mWasFlingCalledForGesture = false; - } - - return ret; - } - - @Override - public boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) { - if (mFadeLastItem) { - float onScreen = 1f; - if ((child.getTop() < getBottom() && child.getBottom() > getBottom())) { - onScreen = ((float) (getBottom() - child.getTop())) / (float) child.getHeight(); - } else if ((child.getTop() < getTop() && child.getBottom() > getTop())) { - onScreen = ((float) (child.getBottom() - getTop())) / (float) child.getHeight(); - } - float alpha = 1 - (1 - onScreen) * (1 - onScreen); - fadeChild(child, alpha); - } - - return super.drawChild(canvas, child, drawingTime); - } - - public void setFadeLastItem(boolean fadeLastItem) { - mFadeLastItem = fadeLastItem; - } - - public void pageUp() { - CarLayoutManager lm = (CarLayoutManager) getLayoutManager(); - int pageUpPosition = lm.getPageUpPosition(); - if (pageUpPosition == -1) { - return; - } - - smoothScrollToPosition(pageUpPosition); - } - - public void pageDown() { - CarLayoutManager lm = (CarLayoutManager) getLayoutManager(); - int pageDownPosition = lm.getPageDownPosition(); - if (pageDownPosition == -1) { - return; - } - - smoothScrollToPosition(pageDownPosition); - } - - /** - * Sets {@link #mSavedStateConstructor} to private SavedState constructor. - */ - private Constructor<?> getSavedStateConstructor() { - Class<?> savedStateClass = null; - // Find package private subclass RecyclerView$SavedState. - for (Class<?> c : RecyclerView.class.getDeclaredClasses()) { - if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) { - savedStateClass = c; - break; - } - } - if (savedStateClass == null) { - throw new RuntimeException("RecyclerView$SavedState not found!"); - } - // Find constructor that takes a {@link Parcel}. - for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) { - Class<?>[] parameterTypes = c.getParameterTypes(); - if (parameterTypes.length == 1 - && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) { - mSavedStateConstructor = c; - mSavedStateConstructor.setAccessible(true); - break; - } - } - if (mSavedStateConstructor == null) { - throw new RuntimeException("RecyclerView$SavedState constructor not found!"); - } - return mSavedStateConstructor; - } - - /** - * Fades child by alpha. If child is a {@link android.view.ViewGroup} then it will recursively fade its - * children instead. - */ - private void fadeChild(@NonNull View child, float alpha) { - if (child instanceof ViewGroup) { - ViewGroup vg = (ViewGroup) child; - for (int i = 0; i < vg.getChildCount(); i++) { - fadeChild(vg.getChildAt(i), alpha); - } - } else { - child.setAlpha(alpha); - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/CarUiResourceLoader.java b/car-support-lib/src/android/support/car/ui/CarUiResourceLoader.java deleted file mode 100644 index bc0a769280..0000000000 --- a/car-support-lib/src/android/support/car/ui/CarUiResourceLoader.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.util.DisplayMetrics; -import android.util.Log; - -/** - * @hide - */ -public class CarUiResourceLoader { - private static final String TAG = "CarUiResourceLoader"; - private static final String CAR_UI_PACKAGE = "android.car.ui.provider"; - private static final String DRAWABLE = "drawable"; - private static final String BOOL = "bool"; - private static final String DIMEN = "dimen"; - - public static synchronized Drawable getDrawable( - Context context, String drawableName) { - return getDrawable(context, drawableName, null); - } - - public static synchronized Drawable getDrawable( - Context context, String drawableName, DisplayMetrics metrics) { - Resources res; - try { - res = context.getPackageManager().getResourcesForApplication(CAR_UI_PACKAGE); - } catch (NameNotFoundException e) { - Log.w(TAG, "CarUiProvider not installed, this class will return blank drawables."); - return new ColorDrawable(Color.TRANSPARENT); - } - - int id = res.getIdentifier(drawableName, DRAWABLE, CAR_UI_PACKAGE); - if (id == 0) { - Log.w(TAG, "Resource not found in CarUiProvider.apk: " + drawableName); - return new ColorDrawable(Color.TRANSPARENT); - } - if (metrics == null) { - return res.getDrawable(id, null); - } else { - return res.getDrawableForDensity(id, metrics.densityDpi, null); - } - } - - public static synchronized boolean getBoolean( - Context context, String boolName, boolean def) { - Resources res; - try { - res = context.getPackageManager().getResourcesForApplication(CAR_UI_PACKAGE); - } catch (NameNotFoundException e) { - Log.w(TAG, "CarUiProvider not installed, returning default"); - return def; - } - - int id = res.getIdentifier(boolName, BOOL, CAR_UI_PACKAGE); - if (id == 0) { - Log.w(TAG, "Resource not found in CarUiProvider.apk: " + boolName); - return def; - } - return res.getBoolean(id); - } - - public static synchronized float getDimen( - Context context, String dimenName, float def) { - Resources res; - try { - res = context.getPackageManager().getResourcesForApplication(CAR_UI_PACKAGE); - } catch (NameNotFoundException e) { - Log.w(TAG, "CarUiProvider not installed, returning default"); - return def; - } - - int id = res.getIdentifier(dimenName, DIMEN, CAR_UI_PACKAGE); - if (id == 0) { - Log.w(TAG, "Resource not found in CarUiProvider.apk: " + dimenName); - return def; - } - return res.getDimension(id); - } -} diff --git a/car-support-lib/src/android/support/car/ui/CheckboxWrapperView.java b/car-support-lib/src/android/support/car/ui/CheckboxWrapperView.java deleted file mode 100644 index 157f7fb142..0000000000 --- a/car-support-lib/src/android/support/car/ui/CheckboxWrapperView.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.widget.CheckBox; - -/** - * A wrapper class for CheckBox that is required because the state is created by two different - * class loaders, which causes a crash. When the state and class are created by different class - * loaders, the default state will be restored instead of the saved state. - * Reflection cannot be used to recreate the state because the class that stores the state - * (CompoundButton$SavedState) has been stripped out of the Android SDK. - * @hide - */ -public class CheckboxWrapperView extends CheckBox { - - public CheckboxWrapperView(Context context) { - super(context); - } - - public CheckboxWrapperView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CheckboxWrapperView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - // If the class loaders are different, just restore the default state. This is fine - // since we refetch the menus anyways and any state will be restored then. - if (state.getClass().getClassLoader() != getClass().getClassLoader()) { - super.onRestoreInstanceState(onSaveInstanceState()); - } else { - super.onRestoreInstanceState(state); - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/CircleBitmapDrawable.java b/car-support-lib/src/android/support/car/ui/CircleBitmapDrawable.java deleted file mode 100644 index 3f3696b096..0000000000 --- a/car-support-lib/src/android/support/car/ui/CircleBitmapDrawable.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.v4.graphics.drawable.RoundedBitmapDrawable; -import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; - - -/** - * A drawable for displaying a circular bitmap. This is a wrapper over RoundedBitmapDrawable, - * since that implementation doesn't behave quite as desired. - * - * Note that not all drawable functionality is passed to the RoundedBitmapDrawable at this - * time. Feel free to add more as necessary. - * @hide - */ -public class CircleBitmapDrawable extends Drawable { - private final Resources mResources; - - private Bitmap mBitmap; - private RoundedBitmapDrawable mDrawable; - private int mAlpha = -1; - private ColorFilter mCf = null; - - public CircleBitmapDrawable(@NonNull Resources res, @NonNull Bitmap bitmap) { - mBitmap = bitmap; - mResources = res; - } - - @Override - public void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); - int width = bounds.right - bounds.left; - int height = bounds.bottom - bounds.top; - - Bitmap processed = mBitmap; - /* if (processed.getWidth() != width || processed.getHeight() != height) { - processed = BitmapUtils.scaleBitmap(processed, width, height); - } - // RoundedBitmapDrawable is actually just a rounded rectangle. So it can't turn - // rectangular images into circles. - if (processed.getWidth() != processed.getHeight()) { - int diam = Math.min(width, height); - Bitmap cropped = BitmapUtils.cropBitmap(processed, diam, diam); - if (processed != mBitmap) { - processed.recycle(); - } - processed = cropped; - }*/ - mDrawable = RoundedBitmapDrawableFactory.create(mResources, processed); - mDrawable.setBounds(bounds); - mDrawable.setAntiAlias(true); - mDrawable.setCornerRadius(Math.min(width, height) / 2f); - if (mAlpha != -1) { - mDrawable.setAlpha(mAlpha); - } - if (mCf != null) { - mDrawable.setColorFilter(mCf); - } - invalidateSelf(); - } - - @Override - public void draw(Canvas canvas) { - if (mDrawable != null) { - mDrawable.draw(canvas); - } - } - - @Override - public int getOpacity() { - return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - mAlpha = alpha; - if (mDrawable != null) { - mDrawable.setAlpha(alpha); - invalidateSelf(); - } - } - - @Override - public void setColorFilter(ColorFilter cf) { - mCf = cf; - if (mDrawable != null) { - mDrawable.setColorFilter(cf); - invalidateSelf(); - } - } - - /** - * Convert the drawable to a bitmap. - * @param size The target size of the bitmap in pixels. - * @return A bitmap representation of the drawable. - */ - public Bitmap toBitmap(int size) { - Bitmap largeIcon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(largeIcon); - Rect bounds = getBounds(); - setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - draw(canvas); - setBounds(bounds); - return largeIcon; - } -} - diff --git a/car-support-lib/src/android/support/car/ui/CircularClipAnimation.java b/car-support-lib/src/android/support/car/ui/CircularClipAnimation.java deleted file mode 100644 index 4952720478..0000000000 --- a/car-support-lib/src/android/support/car/ui/CircularClipAnimation.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.graphics.Path; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.List; - -/** - * Handles the quantum animation to show or hide elements using a circular clip animation. Views - * that should be clipped can be added to the animation and are notified through the - * {@link PathClippingView} interface. - * - * The effect is implemented using a circular clip. The state runs the animation and at each - * step computes the effect's parameters (circle center and radius), which it then passes to - * registered {@link PathClippingView}s. - * - * This a modified version of GoogleSearch/com.google.android.shared.ui/CircularClipAnimation - * @hide - */ -public class CircularClipAnimation - implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { - private static final boolean DBG = false; - private static final String TAG = "CircularClipAnimation"; - - public static final int DURATION_MS = 300; - - private static float getFillRadius(int width, int height, int x, int y) { - return (float) Math.sqrt( - Math.pow(Math.max(width - x, x), 2) + Math.pow(Math.max(height - y, y), 2)); - } - - /** - * Container view, in whose coordinate space the clip parameters are computed. The offset for - * each listener view is computed relative to this view. - */ - private final ViewGroup mContainer; - - /** - * Listeners that will be called when the clip animation updates. - */ - private final List<PathClipingViewInfo> mViewInfos = new ArrayList<>(); - - private final Rect mRect; - private final ValueAnimator mAnimator; - - /** A view, to which the circle's center can be anchored to during animation. */ - private View mAnchorView; - - /* - * Circular crop's parameters. These are updated by the animator but can also be set manually - * prior to starting the animation. Do not modify when animation is in progress. - */ - private int mCircleX; - private int mCircleY; - private float mCircleRadius; - private int mCircleStartRadius; - - public CircularClipAnimation(ViewGroup container) { - mRect = new Rect(); - - mContainer = container; - - mAnimator = new ValueAnimator(); - mAnimator.setInterpolator( - new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0, 1, 0)); - - mAnimator.addUpdateListener(this); - mAnimator.addListener(this); - mAnimator.setDuration(DURATION_MS); - mCircleStartRadius = container.getContext().getResources().getDimensionPixelSize( - R.dimen.car_touch_feedback_radius); - } - - private int findViewInfo(PathClippingView clipView) { - for (int c = 0; c < mViewInfos.size(); c++) { - PathClipingViewInfo info = mViewInfos.get(c); - if (info.mClippingView == clipView) { - return c; - } - } - return -1; - } - - /** - * @param clipView Listener that will receive updates to the animation. - * @param view The view in whose coordinate space we will call onClipUpdate. - */ - public void addView(PathClippingView clipView, View view) { - if (findViewInfo(clipView) == -1) { - mViewInfos.add(new PathClipingViewInfo(clipView, view)); - } - } - - public void removeView(PathClippingView clipView) { - if (!mAnimator.isRunning()) { - Log.e(TAG, "Animator is not running."); - return; - } - int index = findViewInfo(clipView); - if (index > -1) { - mViewInfos.remove(index); - } - } - - /** - * Updates the circle's center to given point in container coordinate space. - * Overrides any previous calls to {@link #setupCenter}. - */ - public void setupCenter(int x, int y) { - mCircleX = x; - mCircleY = y; - } - - /** - * Triggers the effect. Any previous animation will be cancelled. - * - * @param showing Whether the animation is supposed to show or hide components. - * @param x X of the content to be revealed or hidden. - * @param y Y of the content to be revealed or hidden. - * @param width Width of the content to be revealed or hidden. - * @param height Height of the content to be revealed or hidden. - * @param anchorView See {@link #mAnchorView}. - */ - public void start(boolean showing, int x, int y, int width, int height, View anchorView) { - if (DBG) Log.v(TAG, - "start(" + showing + ", " + width + ", " + height + ", " + anchorView + ")"); - - // Compute the radius. - final float radius = getFillRadius(width, height, - mCircleX - x, mCircleY - y); - - // Set anchor view. - mAnchorView = anchorView; - - // Set up animation values. Note that the minimum radius should be larger than 0, - // otherwise the addCircle operation becomes a No-op, leading to inverted clipping. - if (showing) { - mAnimator.setFloatValues(mCircleStartRadius, radius); - } else { - mAnimator.setFloatValues(radius, 1); - } - mAnimator.start(); - } - - /** - * Updates the circle's center to that of the anchor view. - */ - private void updateCircleCenterTo(View view) { - mRect.set(0, 0, view.getWidth(), view.getHeight()); - mContainer.offsetDescendantRectToMyCoords(view, mRect); - - mCircleX = mRect.centerX(); - mCircleY = mRect.centerY(); - } - - // Animation listeners. - @Override - public void onAnimationStart(Animator animation) { - for (int c = 0; c < mViewInfos.size(); c++) { - // We find out the offset of each Listeners View from the mContainer and store it - // so we can offset the paths we pass to each Listener into its coords space. - mRect.left = 0; - mRect.top = 0; - mContainer.offsetRectIntoDescendantCoords(mViewInfos.get(c).mView, mRect); - mViewInfos.get(c).mOffset.set(mRect.left, mRect.top); - } - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - if (mAnchorView != null) { - updateCircleCenterTo(mAnchorView); - } - - mCircleRadius = (Float) animation.getAnimatedValue(); - - // Notify listeners. - for (int c = 0; c < mViewInfos.size(); c++) { - PathClipingViewInfo info = mViewInfos.get(c); - info.mPath.reset(); - info.mPath.addCircle(mCircleX + info.mOffset.x, mCircleY + info.mOffset.y, - mCircleRadius, Path.Direction.CW); - info.mClippingView.setClipPath(info.mPath); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - for (int c = 0; c < mViewInfos.size(); c++) { - PathClipingViewInfo info = mViewInfos.get(c); - info.mClippingView.setClipPath(null); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - public void addListener(Animator.AnimatorListener listener) { - mAnimator.addListener(listener); - } - - /** - * Internal structure to keep track of the clipping components. - */ - private static class PathClipingViewInfo { - final PathClippingView mClippingView; - final View mView; - final Point mOffset; - final Path mPath; - - PathClipingViewInfo(PathClippingView clippingView, View view) { - mClippingView = clippingView; - mView = view; - mOffset = new Point(); - mPath = new Path(); - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/ClippableFrameLayout.java b/car-support-lib/src/android/support/car/ui/ClippableFrameLayout.java deleted file mode 100644 index cbc627c5a7..0000000000 --- a/car-support-lib/src/android/support/car/ui/ClippableFrameLayout.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Path; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -/** - * FrameLayout that enables the user to set a Path that the view will be clipped to - * - * From GoogleSearch/com.google.android.shared.ui/CircularClipAnimation - * @hide - */ -public class ClippableFrameLayout extends FrameLayout implements PathClippingView { - private Path mClipPath; - - public ClippableFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void setClipPath(Path clipPath) { - mClipPath = clipPath; - setClipToPadding(true); - setClipChildren(true); - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - setWillNotDraw(false); - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - if (mClipPath != null) { - canvas.clipPath(mClipPath); - } - super.onDraw(canvas); - } -} diff --git a/car-support-lib/src/android/support/car/ui/ColorChecker.java b/car-support-lib/src/android/support/car/ui/ColorChecker.java deleted file mode 100644 index 8191d6fa60..0000000000 --- a/car-support-lib/src/android/support/car/ui/ColorChecker.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.graphics.Color; -import android.util.Log; - -/** - * @hide - */ -public class ColorChecker { - private static final String TAG = "GH.ColorChecker"; - private static final double MIN_CONTRAST_RATIO = 4.5; - /** - * Non-critical information doesn't have to meet as stringent contrast requirements. - */ - private static final double MIN_NON_CRITICAL_CONTRAST_RATIO = 1.5; - - /** - * Calls {@link #getTintColor(int, int...)} with: - * {@link R.color#car_tint_light} and - * {@link R.color#car_tint_dark} - */ - public static int getTintColor(Context context, int backgroundColor) { - int lightTintColor = context.getResources().getColor(R.color.car_tint_light); - int darkTintColor = context.getResources().getColor(R.color.car_tint_dark); - - return getTintColor(backgroundColor, lightTintColor, darkTintColor); - } - - /** - * Calls {@link #getNonCriticalTintColor(int, int...)} with: - * {@link R.color#car_tint_light} and - * {@link R.color#car_tint_dark} - */ - public static int getNonCriticalTintColor(Context context, int backgroundColor) { - int lightTintColor = context.getResources().getColor(R.color.car_tint_light); - int darkTintColor = context.getResources().getColor(R.color.car_tint_dark); - - return getNonCriticalTintColor(backgroundColor, lightTintColor, darkTintColor); - } - - /** - * Calls {@link #getTintColor(int, int...)} with {@link #MIN_CONTRAST_RATIO}. - */ - public static int getTintColor(int backgroundColor, int... tintColors) { - return getTintColor(MIN_CONTRAST_RATIO, backgroundColor, tintColors); - } - - /** - * Calls {@link #getTintColor(int, int...)} with {@link #MIN_NON_CRITICAL_CONTRAST_RATIO}. - */ - public static int getNonCriticalTintColor(int backgroundColor, int... tintColors) { - return getTintColor(MIN_NON_CRITICAL_CONTRAST_RATIO, backgroundColor, tintColors); - } - - /** - * - * Determines what color to tint icons given the background color that they sit on. - * - * @param minAllowedContrastRatio The minimum contrast ratio - * @param bgColor The background color that the icons sit on. - * @param tintColors A list of potential colors to tint the icons with. - * @return The color that the icons should be tinted. Will be the first tinted color that - * meets the requirements. If none of the tint colors meet the minimum requirements, - * either black or white will be returned, whichever has a higher contrast. - */ - public static int getTintColor(double minAllowedContrastRatio, int bgColor, int... tintColors) { - for (int tc : tintColors) { - double contrastRatio = getContrastRatio(bgColor, tc); - if (contrastRatio >= minAllowedContrastRatio) { - return tc; - } - } - double blackContrastRatio = getContrastRatio(bgColor, Color.BLACK); - double whiteContrastRatio = getContrastRatio(bgColor, Color.WHITE); - if (whiteContrastRatio >= blackContrastRatio) { - Log.w(TAG, "Tint color does not meet contrast requirements. Using white."); - return Color.WHITE; - } else { - Log.w(TAG, "Tint color does not meet contrast requirements. Using black."); - return Color.BLACK; - } - } - - public static double getContrastRatio(int color1, int color2) { - return getContrastRatio(getLuminance(color1), getLuminance(color2)); - } - - public static double getContrastRatio(double luminance1, double luminance2) { - return (Math.max(luminance1, luminance2) + 0.05) / - (Math.min(luminance1, luminance2) + 0.05); - } - - /** - * Calculates the luminance of a color as specified by: - * http://www.w3.org/TR/WCAG20-TECHS/G17.html - * - * @param color The color to calculate the luminance of. - * @return The luminance. - */ - public static double getLuminance(int color) { - // Values are in sRGB - double r = convert8BitToLuminanceComponent(Color.red(color)); - double g = convert8BitToLuminanceComponent(Color.green(color)); - double b = convert8BitToLuminanceComponent(Color.blue(color)); - return r * 0.2126 + g * 0.7152 + b * 0.0722; - } - - /** - * Converts am 8 bit color component (0-255) to the luminance component as specified by: - * http://www.w3.org/TR/WCAG20-TECHS/G17.html - */ - private static double convert8BitToLuminanceComponent(double component) { - component /= 255.0; - if (component <= 0.03928) { - return component / 12.92; - } else { - return Math.pow(((component + 0.055) / 1.055), 2.4); - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/Constants.java b/car-support-lib/src/android/support/car/ui/Constants.java deleted file mode 100644 index e73f6df516..0000000000 --- a/car-support-lib/src/android/support/car/ui/Constants.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -/** - * Constants shared by car ui lib and car support lib. - * - * @hide - */ -public class Constants { - public class CarMenuConstants { - public static final String KEY_TITLE = "title"; - public static final String KEY_TEXT = "text"; - public static final String KEY_LEFTICON = "leftIcon"; - public static final String KEY_RIGHTICON = "rightIcon"; - public static final String KEY_RIGHTTEXT = "rightText"; - public static final String KEY_WIDGET = "widget"; - public static final String KEY_WIDGET_STATE = "widget_state"; - public static final String KEY_EMPTY_PLACEHOLDER = "empty_placeholder"; - public static final String KEY_FLAGS = "flags"; - public static final String KEY_ID = "id"; - public static final String KEY_REMOTEVIEWS = "remoteViews"; - } -} diff --git a/car-support-lib/src/android/support/car/ui/CursorFilter.java b/car-support-lib/src/android/support/car/ui/CursorFilter.java deleted file mode 100644 index 7ebb86522a..0000000000 --- a/car-support-lib/src/android/support/car/ui/CursorFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.database.Cursor; -import android.widget.Filter; - -/** - * <p>The CursorFilter delegates most of the work to the CursorAdapter. - * Subclasses should override these delegate methods to run the queries - * and convert the results into String that can be used by auto-completion - * widgets.</p> - * - * NOTE: this is copied directly from android.widget.CursorFilter - * @hide - */ -class CursorFilter extends Filter { - - CursorFilterClient mClient; - - interface CursorFilterClient { - CharSequence convertToString(Cursor cursor); - Cursor runQueryOnBackgroundThread(CharSequence constraint); - Cursor getCursor(); - void changeCursor(Cursor cursor); - } - - CursorFilter(CursorFilterClient client) { - mClient = client; - } - - @Override - public CharSequence convertResultToString(Object resultValue) { - return mClient.convertToString((Cursor) resultValue); - } - - @Override - protected FilterResults performFiltering(CharSequence constraint) { - Cursor cursor = mClient.runQueryOnBackgroundThread(constraint); - - FilterResults results = new FilterResults(); - if (cursor != null) { - results.count = cursor.getCount(); - results.values = cursor; - } else { - results.count = 0; - results.values = null; - } - return results; - } - - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - Cursor oldCursor = mClient.getCursor(); - - if (results.values != null && results.values != oldCursor) { - mClient.changeCursor((Cursor) results.values); - } - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/ui/CursorRecyclerViewAdapter.java b/car-support-lib/src/android/support/car/ui/CursorRecyclerViewAdapter.java deleted file mode 100644 index bb7ff87139..0000000000 --- a/car-support-lib/src/android/support/car/ui/CursorRecyclerViewAdapter.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.provider.BaseColumns; -import android.support.v7.widget.RecyclerView; - -/** - * Adapter that exposes data from a Cursor to a {@link RecyclerView} widget. - * The Cursor must include an Id column or this class will not work. - * @hide - */ -public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> - extends RecyclerView.Adapter<VH> { - - protected Context mContext; - protected Cursor mCursor; - protected int mRowIdColumn; - - public CursorRecyclerViewAdapter(Context context, Cursor cursor) { - mContext = context; - mCursor = cursor; - mRowIdColumn = -1; - if (mCursor != null) { - mRowIdColumn = getRowIdColumnIndex(mCursor); - mCursor.registerDataSetObserver(mDataSetObserver); - } - } - - public Cursor getCursor() { - return mCursor; - } - - @Override - public int getItemCount() { - if (mCursor != null) { - return mCursor.getCount(); - } - return 0; - } - - @Override - public long getItemId(int position) { - if (mCursor != null && mCursor.moveToPosition(position)) { - return mCursor.getLong(mRowIdColumn); - } - return 0; - } - - public void onBindViewHolder(VH viewHolder, Cursor cursor) {} - - @Override - public void onBindViewHolder(VH viewHolder, int position) { - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("can't move cursor to position " + position); - } - onBindViewHolder(viewHolder, mCursor); - } - - /** - * Change the underlying cursor to a new cursor. If there is an existing cursor it will - * be closed. - * - * @param cursor The new cursor to be used. - */ - public void changeCursor(Cursor cursor) { - Cursor old = swapCursor(cursor); - if (old != null) { - old.close(); - } - } - - /** - * Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(android.database.Cursor), - * the returned old Cursor is not closed. - * - * @param newCursor The new cursor to be used. - */ - public Cursor swapCursor(Cursor newCursor) { - if (newCursor == mCursor) { - return null; - } - - Cursor oldCursor = mCursor; - if (oldCursor != null) { - if (mDataSetObserver != null) { - oldCursor.unregisterDataSetObserver(mDataSetObserver); - } - } - - mCursor = newCursor; - if (mCursor != null) { - if (mDataSetObserver != null) { - mCursor.registerDataSetObserver(mDataSetObserver); - } - mRowIdColumn = getRowIdColumnIndex(mCursor); - notifyDataSetChanged(); - } else { - mRowIdColumn = -1; - notifyDataSetChanged(); - } - return oldCursor; - } - - protected int getRowIdColumnIndex(Cursor cursor) { - return cursor.getColumnIndex(BaseColumns._ID); - } - - protected DataSetObserver mDataSetObserver = new DataSetObserver() { - @Override - public void onChanged() { - super.onChanged(); - notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - super.onInvalidated(); - mCursor = null; - notifyDataSetChanged(); - } - }; -} diff --git a/car-support-lib/src/android/support/car/ui/DrawerArrowDrawable.java b/car-support-lib/src/android/support/car/ui/DrawerArrowDrawable.java deleted file mode 100644 index 718a249db8..0000000000 --- a/car-support-lib/src/android/support/car/ui/DrawerArrowDrawable.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -/** - * A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them. - * - * This is copied from android.support.v7.app.DrawerArrowDrawable except with the styles prefixed - * with carArrow and the abstract {@link #isLayoutRtl()} set to false. - * @hide - */ -public class DrawerArrowDrawable extends Drawable { - - private final Paint mPaint = new Paint(); - - // The angle in degrees that the arrow head is inclined at. - private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45); - private final float mBarThickness; - // The length of top and bottom bars when they merge into an arrow - private final float mTopBottomArrowSize; - // The length of middle bar - private final float mBarSize; - // The length of the middle bar when arrow is shaped - private final float mMiddleArrowSize; - // The space between bars when they are parallel - private final float mBarGap; - // Whether bars should spin or not during progress - private final boolean mSpin; - // Use Path instead of canvas operations so that if color has transparency, overlapping sections - // wont look different - private final Path mPath = new Path(); - // The reported intrinsic size of the drawable. - private final int mSize; - // Whether we should mirror animation when animation is reversed. - private boolean mVerticalMirror = false; - // The interpolated version of the original progress - private float mProgress; - // the amount that overlaps w/ bar size when rotation is max - private final float mMaxCutForBarSize; - // The distance of arrow's center from top when horizontal - private float mCenterOffset; - - /** - * @param context used to get the configuration for the drawable from - */ - public DrawerArrowDrawable(Context context) { - final TypedArray typedArray = context.getTheme() - .obtainStyledAttributes(null, R.styleable.DrawerArrowDrawable, - R.attr.carDrawerArrowStyle, - R.style.CarDrawerArrowDrawable); - mPaint.setAntiAlias(true); - mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowDrawable_carArrowColor, 0)); - mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowDrawable_carArrowDrawableSize, 0); - // round this because having this floating may cause bad measurements - mBarSize = Math.round(typedArray.getDimension(R.styleable.DrawerArrowDrawable_carArrowBarSize, 0)); - // round this because having this floating may cause bad measurements - mTopBottomArrowSize = Math.round(typedArray.getDimension( - R.styleable.DrawerArrowDrawable_carArrowTopBottomBarSize, 0)); - mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowDrawable_carArrowThickness, 0); - // round this because having this floating may cause bad measurements - mBarGap = Math.round(typedArray.getDimension( - R.styleable.DrawerArrowDrawable_carArrowGapBetweenBars, 0)); - mSpin = typedArray.getBoolean(R.styleable.DrawerArrowDrawable_carArrowSpinBars, true); - mMiddleArrowSize = typedArray - .getDimension(R.styleable.DrawerArrowDrawable_carArrowMiddleBarSize, 0); - final int remainingSpace = (int) (mSize - mBarThickness * 3 - mBarGap * 2); - mCenterOffset = (remainingSpace / 4) * 2; //making sure it is a multiple of 2. - mCenterOffset += mBarThickness * 1.5 + mBarGap; - typedArray.recycle(); - - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeJoin(Paint.Join.MITER); - mPaint.setStrokeCap(Paint.Cap.BUTT); - mPaint.setStrokeWidth(mBarThickness); - - mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE)); - } - - public boolean isLayoutRtl() { - return false; - } - - /** - * If set, canvas is flipped when progress reached to end and going back to start. - */ - protected void setVerticalMirror(boolean verticalMirror) { - mVerticalMirror = verticalMirror; - } - - @Override - public void draw(Canvas canvas) { - Rect bounds = getBounds(); - final boolean isRtl = isLayoutRtl(); - // Interpolated widths of arrow bars - final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress); - final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress); - // Interpolated size of middle bar - final float middleBarCut = Math.round(lerp(0, mMaxCutForBarSize, mProgress)); - // The rotation of the top and bottom bars (that make the arrow head) - final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress); - - // The whole canvas rotates as the transition happens - final float canvasRotate = lerp(isRtl ? 0 : -180, isRtl ? 180 : 0, mProgress); - final float arrowWidth = Math.round(arrowSize * Math.cos(rotation)); - final float arrowHeight = Math.round(arrowSize * Math.sin(rotation)); - - - mPath.rewind(); - final float topBottomBarOffset = lerp(mBarGap + mBarThickness, -mMaxCutForBarSize, - mProgress); - - final float arrowEdge = -middleBarSize / 2; - // draw middle bar - mPath.moveTo(arrowEdge + middleBarCut, 0); - mPath.rLineTo(middleBarSize - middleBarCut * 2, 0); - - // bottom bar - mPath.moveTo(arrowEdge, topBottomBarOffset); - mPath.rLineTo(arrowWidth, arrowHeight); - - // top bar - mPath.moveTo(arrowEdge, -topBottomBarOffset); - mPath.rLineTo(arrowWidth, -arrowHeight); - - mPath.close(); - - canvas.save(); - // Rotate the whole canvas if spinning, if not, rotate it 180 to get - // the arrow pointing the other way for RTL. - canvas.translate(bounds.centerX(), mCenterOffset); - if (mSpin) { - canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1)); - } else if (isRtl) { - canvas.rotate(180); - } - canvas.drawPath(mPath, mPaint); - - canvas.restore(); - } - - @Override - public void setAlpha(int i) { - mPaint.setAlpha(i); - } - - @Override - public boolean isAutoMirrored() { - // Draws rotated 180 degrees in RTL mode. - return true; - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mPaint.setColorFilter(colorFilter); - } - - @Override - public int getIntrinsicHeight() { - return mSize; - } - - @Override - public int getIntrinsicWidth() { - return mSize; - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - public float getProgress() { - return mProgress; - } - - public void setProgress(float progress) { - if (progress == 1f) { - setVerticalMirror(true); - } else if (progress == 0f) { - setVerticalMirror(false); - } - mProgress = progress; - invalidateSelf(); - } - - /** - * Linear interpolate between a and b with parameter t. - */ - private static float lerp(float a, float b, float t) { - return a + (b - a) * t; - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/ui/FabDrawable.java b/car-support-lib/src/android/support/car/ui/FabDrawable.java deleted file mode 100644 index 100df6754e..0000000000 --- a/car-support-lib/src/android/support/car/ui/FabDrawable.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.view.animation.DecelerateInterpolator; - -/** - * Custom drawable that can be used as the background for fabs. - * - * When not focused or pressed, the fab will be a solid circle of the color specified with - * {@link #setFabColor(int)}. When it is pressed or focused, the fab will grow or shrink - * and it will gain a stroke that has the color specified with {@link #setStrokeColor(int)}. - * - * {@link #FabDrawable(android.content.Context)} provides a quick way to use fab drawable using - * default values for size and animation values provided for consistency. - * - * {@link #FabDrawable(int, int, int)} can also be used for added customization. - * @hide - */ -public class FabDrawable extends Drawable { - private final int mFabGrowth; - private final int mStrokeWidth; - private final Paint mFabPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final ValueAnimator mStrokeAnimator; - - private boolean mStrokeAnimatorIsReversing; - private int mFabRadius; - private int mStrokeRadius; - private Outline mOutline; - - /** - * Default constructor to provide consistent fab values across uses. - */ - public FabDrawable(Context context) { - this(context.getResources().getDimensionPixelSize(R.dimen.car_fab_focused_growth), - context.getResources().getDimensionPixelSize(R.dimen.car_fab_focused_stroke_width), - context.getResources().getInteger(R.integer.car_fab_animation_duration)); - } - - /** - * Custom constructor allows extra customization of the fab's behavior. - * - * @param fabGrowth The amount that the fab should change by when it is focused in pixels. - * @param strokeWidth The width of the stroke when the fab is focused in pixels. - * @param duration The animation duration for the growth of the fab and stroke. - */ - public FabDrawable(int fabGrowth, int strokeWidth, int duration) { - if (fabGrowth < 0) { - throw new IllegalArgumentException("Fab growth must be >= 0."); - } else if (fabGrowth > strokeWidth) { - throw new IllegalArgumentException("Fab growth must be <= strokeWidth."); - } else if (strokeWidth < 0) { - throw new IllegalArgumentException("Stroke width must be >= 0."); - } - mFabGrowth = fabGrowth; - mStrokeWidth = strokeWidth; - mStrokeAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); - mStrokeAnimator.setInterpolator(new DecelerateInterpolator()); - mStrokeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - updateRadius(); - } - }); - } - - /** - * @param color The primary color of the fab. It will be the entire fab color when not selected - * or pressed and will be the color of the interior circle when selected - * or pressed. - */ - public void setFabColor(int color) { - mFabPaint.setColor(color); - } - - /** - * @param color The color of the stroke on the fab that appears when the fab is selected - * or pressed. - */ - public void setStrokeColor(int color) { - mStrokePaint.setColor(color); - } - - /** - * Default implementation of {@link #setFabAndStrokeColor(int, float)} with valueMultiplier - * set to 0.9. - */ - public void setFabAndStrokeColor(int color) { - setFabAndStrokeColor(color, 0.9f); - } - - /** - * @param color The primary color of the fab. - * @param valueMultiplier The hsv value multiplier that will be set as the stroke color. - */ - public void setFabAndStrokeColor(int color, float valueMultiplier) { - setFabColor(color); - float[] hsv = new float[3]; - Color.colorToHSV(color, hsv); - hsv[2] *= valueMultiplier; - setStrokeColor(Color.HSVToColor(hsv)); - } - - @Override - protected boolean onStateChange(int[] stateSet) { - boolean superChanged = super.onStateChange(stateSet); - - boolean focused = false; - boolean pressed = false; - - for (int state : stateSet) { - if (state == android.R.attr.state_focused) { - focused = true; - } if (state == android.R.attr.state_pressed) { - pressed = true; - } - } - - if ((focused || pressed) && mStrokeAnimatorIsReversing) { - mStrokeAnimator.start(); - mStrokeAnimatorIsReversing = false; - } else if (!(focused || pressed) && !mStrokeAnimatorIsReversing) { - mStrokeAnimator.reverse(); - mStrokeAnimatorIsReversing = true; - } - - return superChanged || focused; - } - - @Override - public void draw(Canvas canvas) { - int cx = canvas.getWidth() / 2; - int cy = canvas.getHeight() / 2; - - canvas.drawCircle(cx, cy, mStrokeRadius, mStrokePaint); - canvas.drawCircle(cx, cy, mFabRadius, mFabPaint); - } - - @Override - protected void onBoundsChange(Rect bounds) { - updateRadius(); - } - - @Override - public void setAlpha(int alpha) { - mFabPaint.setAlpha(alpha); - mStrokePaint.setAlpha(alpha); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mFabPaint.setColorFilter(colorFilter); - mStrokePaint.setColorFilter(colorFilter); - } - - @Override - public int getOpacity() { - return PixelFormat.OPAQUE; - } - - @Override - public void getOutline(Outline outline) { - mOutline = outline; - updateOutline(); - } - - @Override - public boolean isStateful() { - return true; - } - - private void updateRadius() { - int normalRadius = Math.min(getBounds().width(), getBounds().height()) / 2 - mStrokeWidth; - float fraction = mStrokeAnimator.getAnimatedFraction(); - mStrokeRadius = (int) (normalRadius + (mStrokeWidth * fraction)); - mFabRadius = (int) (normalRadius + (mFabGrowth * fraction)); - updateOutline(); - invalidateSelf(); - } - - private void updateOutline() { - int cx = getBounds().width() / 2; - int cy = getBounds().height() / 2; - if (mOutline != null) { - mOutline.setRoundRect( - cx - mStrokeRadius, - cy - mStrokeRadius, - cx + mStrokeRadius, - cy + mStrokeRadius, - mStrokeRadius); - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/GroupingCursorRecyclerViewAdapter.java b/car-support-lib/src/android/support/car/ui/GroupingCursorRecyclerViewAdapter.java deleted file mode 100644 index 08f64a2c96..0000000000 --- a/car-support-lib/src/android/support/car/ui/GroupingCursorRecyclerViewAdapter.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.List; - -/** - * Maintains a list that groups adjacent items sharing the same value of - * a "group-by" field. The list has three types of elements: stand-alone, group header and group - * child. Groups are collapsible and collapsed by default. - * @hide - */ -public abstract class GroupingCursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> - extends CursorRecyclerViewAdapter<VH> { - private static final String TAG = "CAR.UI.GroupingCursorRecyclerViewAdapter"; - - public static final int VIEW_TYPE_STANDALONE = 0; - public static final int VIEW_TYPE_GROUP_HEADER = 1; - public static final int VIEW_TYPE_IN_GROUP = 2; - - /** - * Build all groups based on grouping rules given cursor and calls {@link #addGroup} for - * each of them. - */ - protected abstract void buildGroups(Cursor cursor); - - protected abstract VH onCreateStandAloneViewHolder(Context context, ViewGroup parent); - protected abstract void onBindStandAloneViewHolder(VH holder, Context context, Cursor cursor); - - protected abstract VH onCreateGroupViewHolder(Context context, ViewGroup parent); - protected abstract void onBindGroupViewHolder(VH holder, Context context, Cursor cursor, - int groupSize, boolean expanded); - - protected abstract VH onCreateChildViewHolder(Context context, ViewGroup parent); - protected abstract void onBindChildViewHolder(VH holder, Context context, Cursor cursor); - - private int mCount; - private List<GroupMetadata> mGroupMetadata; - - public GroupingCursorRecyclerViewAdapter(Context context, Cursor cursor) { - super(context, cursor); - mGroupMetadata = new ArrayList<>(); - resetGroup(); - } - - @Override - public Cursor swapCursor(Cursor newCursor) { - if (newCursor != mCursor) { - resetGroup(); - if (newCursor != null) { - buildGroups(newCursor); - rebuildGroupMetadata(); - } - } - return super.swapCursor(newCursor); - } - - @Override - public int getItemCount() { - if (mCursor != null && mCount != -1) { - return mCount; - } - return 0; - } - - @Override - public long getItemId(int position) { - Cursor cursor = getItem(position); - if (cursor != null) { - return cursor.getLong(mRowIdColumn); - } - return 0; - } - - @Override - public int getItemViewType(int position) { - return getPositionMetadata(position).itemType; - } - - public Cursor getItem(int position) { - if (mCursor == null) { - return null; - } - - PositionMetadata pMetadata = getPositionMetadata(position); - if (!mCursor.moveToPosition(pMetadata.cursorPosition)) { - throw new IllegalStateException( - "can't move cursor to position " + pMetadata.cursorPosition); - } - return mCursor; - } - - @Override - public VH onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_TYPE_STANDALONE: - return onCreateStandAloneViewHolder(mContext, parent); - case VIEW_TYPE_GROUP_HEADER: - return onCreateGroupViewHolder(mContext, parent); - case VIEW_TYPE_IN_GROUP: - return onCreateChildViewHolder(mContext, parent); - } - Log.e(TAG, "Unknown viewType. Returning null ViewHolder"); - return null; - } - - @Override - public void onBindViewHolder(VH holder, int position) { - PositionMetadata pMetadata = getPositionMetadata(position); - mCursor.moveToPosition(pMetadata.cursorPosition); - switch (pMetadata.itemType) { - case VIEW_TYPE_STANDALONE: - onBindStandAloneViewHolder(holder, mContext, mCursor); - break; - case VIEW_TYPE_GROUP_HEADER: - onBindGroupViewHolder(holder, mContext, mCursor, pMetadata.gMetadata.itemNumber, - pMetadata.gMetadata.isExpanded()); - break; - case VIEW_TYPE_IN_GROUP: - onBindChildViewHolder(holder, mContext, mCursor); - break; - } - } - - public boolean toggleGroup(int position) { - PositionMetadata pMetadata = getPositionMetadata(position); - if (pMetadata.itemType != VIEW_TYPE_GROUP_HEADER) { - return false; - } - - pMetadata.gMetadata.isExpanded = !pMetadata.gMetadata.isExpanded; - rebuildGroupMetadata(); - notifyDataSetChanged(); - return true; - } - - /** - * Records information about grouping in the list. Should only be called by the overridden - * {@link #buildGroups} method. - */ - protected void addGroup(int cursorPosition, int size, boolean expanded) { - mGroupMetadata.add(GroupMetadata.obtain(cursorPosition, size, expanded)); - } - - private void resetGroup() { - mCount = -1; - mGroupMetadata.clear(); - } - - private void rebuildGroupMetadata() { - int currentPos = 0; - for (int groupIndex = 0; groupIndex < mGroupMetadata.size(); groupIndex++) { - GroupMetadata gMetadata = mGroupMetadata.get(groupIndex); - gMetadata.offsetInList = currentPos; - currentPos += gMetadata.getActualSize(); - } - mCount = currentPos; - } - - private PositionMetadata getPositionMetadata(int position) { - int left = 0; - int right = mGroupMetadata.size() - 1; - int mid; - GroupMetadata midItem; - - while (left <= right) { - mid = (right - left) / 2 + left; - midItem = mGroupMetadata.get(mid); - - if (position > midItem.offsetInList + midItem.getActualSize() - 1) { - left = mid + 1; - continue; - } - - if (position < midItem.offsetInList) { - right = mid - 1; - continue; - } - - int cursorOffset = midItem.offsetInCursor + (position - midItem.offsetInList); - int viewType; - if (midItem.offsetInList == position) { - if (midItem.isStandAlone()) { - viewType = VIEW_TYPE_STANDALONE; - } else { - viewType = VIEW_TYPE_GROUP_HEADER; - } - } else { - viewType = VIEW_TYPE_IN_GROUP; - // Offset cursorOffset by 1, because the group_header and the first child - // will share the same cursor. - cursorOffset--; - } - return new PositionMetadata(viewType, cursorOffset, midItem); - } - - throw new IllegalStateException( - "illegal position " + position + ", total size is " + mCount); - } - - /** - * Information about where groups are located in the list, how large they are - * and whether they are expanded. - */ - protected static class GroupMetadata { - private int offsetInList; - private int offsetInCursor; - private int itemNumber; - private boolean isExpanded; - - static GroupMetadata obtain(int offset, int itemNumber, boolean isExpanded) { - GroupMetadata gm = new GroupMetadata(); - gm.offsetInCursor = offset; - gm.itemNumber = itemNumber; - gm.isExpanded = isExpanded; - return gm; - } - - public boolean isExpanded() { - return !isStandAlone() && isExpanded; - } - - public boolean isStandAlone() { - return itemNumber == 1; - } - - public int getActualSize() { - if (!isExpanded()) { - return 1; - } else { - return itemNumber + 1; - } - } - } - - protected static class PositionMetadata { - int itemType; - int cursorPosition; - GroupMetadata gMetadata; - - public PositionMetadata(int itemType, int cursorPosition, GroupMetadata gMetadata) { - this.itemType = itemType; - this.cursorPosition = cursorPosition; - this.gMetadata = gMetadata; - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/GroupingRecyclerViewAdapter.java b/car-support-lib/src/android/support/car/ui/GroupingRecyclerViewAdapter.java deleted file mode 100644 index 844f07f0e1..0000000000 --- a/car-support-lib/src/android/support/car/ui/GroupingRecyclerViewAdapter.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.List; - -/** - * Maintains a list that groups adjacent items sharing the same value of - * a "group-by" field. The list has three types of elements: stand-alone, group header and group - * child. Groups are collapsible and collapsed by default. - * @hide - */ -public abstract class GroupingRecyclerViewAdapter<E, VH extends RecyclerView.ViewHolder> - extends RecyclerView.Adapter<VH> { - private static final String TAG = "CAR.UI.GroupingRecyclerViewAdapter"; - - public static final int VIEW_TYPE_STANDALONE = 0; - public static final int VIEW_TYPE_GROUP_HEADER = 1; - public static final int VIEW_TYPE_IN_GROUP = 2; - - /** - * Build all groups based on grouping rules given cursor and calls {@link #addGroup} for - * each of them. - */ - protected abstract void buildGroups(List<E> data); - - protected abstract VH onCreateStandAloneViewHolder(Context context, ViewGroup parent); - protected abstract void onBindStandAloneViewHolder( - VH holder, Context context, int positionInData); - - protected abstract VH onCreateGroupViewHolder(Context context, ViewGroup parent); - protected abstract void onBindGroupViewHolder(VH holder, Context context, int positionInData, - int groupSize, boolean expanded); - - protected abstract VH onCreateChildViewHolder(Context context, ViewGroup parent); - protected abstract void onBindChildViewHolder(VH holder, Context context, int positionInData); - - protected Context mContext; - protected List<E> mData; - - private int mCount; - private List<GroupMetadata> mGroupMetadata; - - public GroupingRecyclerViewAdapter(Context context) { - mContext = context; - mGroupMetadata = new ArrayList<>(); - resetGroup(); - } - - public void setData(List<E> data) { - mData = data; - resetGroup(); - if (mData != null) { - buildGroups(mData); - rebuildGroupMetadata(); - } - notifyDataSetChanged(); - } - - @Override - public int getItemCount() { - if (mData != null && mCount != -1) { - return mCount; - } - return 0; - } - - @Override - public long getItemId(int position) { - E item = getItem(position); - if (item != null) { - return item.hashCode(); - } - return 0; - } - - @Override - public int getItemViewType(int position) { - return getPositionMetadata(position).itemType; - } - - public E getItem(int position) { - if (mData == null) { - return null; - } - - PositionMetadata pMetadata = getPositionMetadata(position); - return mData.get(pMetadata.positionInData); - } - - @Override - public VH onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_TYPE_STANDALONE: - return onCreateStandAloneViewHolder(mContext, parent); - case VIEW_TYPE_GROUP_HEADER: - return onCreateGroupViewHolder(mContext, parent); - case VIEW_TYPE_IN_GROUP: - return onCreateChildViewHolder(mContext, parent); - } - Log.e(TAG, "Unknown viewType. Returning null ViewHolder"); - return null; - } - - @Override - public void onBindViewHolder(VH holder, int position) { - PositionMetadata pMetadata = getPositionMetadata(position); - switch (holder.getItemViewType()) { - case VIEW_TYPE_STANDALONE: - onBindStandAloneViewHolder(holder, mContext, pMetadata.positionInData); - break; - case VIEW_TYPE_GROUP_HEADER: - onBindGroupViewHolder(holder, mContext, pMetadata.positionInData, - pMetadata.gMetadata.itemNumber, pMetadata.gMetadata.isExpanded()); - break; - case VIEW_TYPE_IN_GROUP: - onBindChildViewHolder(holder, mContext, pMetadata.positionInData); - break; - } - } - - public boolean toggleGroup(int positionInData, int positionOnUI) { - PositionMetadata pMetadata = getPositionMetadata(positionInData); - if (pMetadata.itemType != VIEW_TYPE_GROUP_HEADER) { - return false; - } - - pMetadata.gMetadata.isExpanded = !pMetadata.gMetadata.isExpanded; - rebuildGroupMetadata(); - if (pMetadata.gMetadata.isExpanded) { - notifyItemRangeInserted(positionOnUI + 1, pMetadata.gMetadata.itemNumber); - } else { - notifyItemRangeRemoved(positionOnUI + 1, pMetadata.gMetadata.itemNumber); - } - return true; - } - - /** - * Return True if the item on the given position is a group header and the group is expanded, - * otherwise False. - */ - public boolean isGroupExpanded(int position) { - PositionMetadata pMetadata = getPositionMetadata(position); - if (pMetadata.itemType != VIEW_TYPE_GROUP_HEADER) { - return false; - } - - return pMetadata.gMetadata.isExpanded(); - } - - /** - * Records information about grouping in the list. Should only be called by the overridden - * {@link #buildGroups} method. - */ - protected void addGroup(int offset, int size, boolean expanded) { - mGroupMetadata.add(GroupMetadata.obtain(offset, size, expanded)); - } - - private void resetGroup() { - mCount = -1; - mGroupMetadata.clear(); - } - - private void rebuildGroupMetadata() { - int currentPos = 0; - for (int groupIndex = 0; groupIndex < mGroupMetadata.size(); groupIndex++) { - GroupMetadata gMetadata = mGroupMetadata.get(groupIndex); - gMetadata.offsetInDisplayList = currentPos; - currentPos += gMetadata.getActualSize(); - } - mCount = currentPos; - } - - private PositionMetadata getPositionMetadata(int position) { - int left = 0; - int right = mGroupMetadata.size() - 1; - int mid; - GroupMetadata midItem; - - while (left <= right) { - mid = (right - left) / 2 + left; - midItem = mGroupMetadata.get(mid); - - if (position > midItem.offsetInDisplayList + midItem.getActualSize() - 1) { - left = mid + 1; - continue; - } - - if (position < midItem.offsetInDisplayList) { - right = mid - 1; - continue; - } - - int cursorOffset = midItem.offsetInDataList + (position - midItem.offsetInDisplayList); - int viewType; - if (midItem.offsetInDisplayList == position) { - if (midItem.isStandAlone()) { - viewType = VIEW_TYPE_STANDALONE; - } else { - viewType = VIEW_TYPE_GROUP_HEADER; - } - } else { - viewType = VIEW_TYPE_IN_GROUP; - // Offset cursorOffset by 1, because the group_header and the first child - // will share the same cursor. - cursorOffset--; - } - return new PositionMetadata(viewType, cursorOffset, midItem); - } - - throw new IllegalStateException( - "illegal position " + position + ", total size is " + mCount); - } - - /** - * Information about where groups are located in the list, how large they are - * and whether they are expanded. - */ - protected static class GroupMetadata { - private int offsetInDisplayList; - private int offsetInDataList; - private int itemNumber; - private boolean isExpanded; - - static GroupMetadata obtain(int offset, int itemNumber, boolean isExpanded) { - GroupMetadata gm = new GroupMetadata(); - gm.offsetInDataList = offset; - gm.itemNumber = itemNumber; - gm.isExpanded = isExpanded; - return gm; - } - - public boolean isExpanded() { - return !isStandAlone() && isExpanded; - } - - public boolean isStandAlone() { - return itemNumber == 1; - } - - public int getActualSize() { - if (!isExpanded()) { - return 1; - } else { - return itemNumber + 1; - } - } - } - - protected static class PositionMetadata { - int itemType; - int positionInData; - GroupMetadata gMetadata; - - public PositionMetadata(int itemType, int positionInData, GroupMetadata gMetadata) { - this.itemType = itemType; - this.positionInData = positionInData; - this.gMetadata = gMetadata; - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/LogDecelerateInterpolator.java b/car-support-lib/src/android/support/car/ui/LogDecelerateInterpolator.java deleted file mode 100644 index d5f7adb7fe..0000000000 --- a/car-support-lib/src/android/support/car/ui/LogDecelerateInterpolator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.view.animation.Interpolator; - -/** - * @hide - */ -public class LogDecelerateInterpolator implements Interpolator { - - int mBase; - int mDrift; - final float mLogScale; - - public LogDecelerateInterpolator(int base, int drift) { - mBase = base; - mDrift = drift; - - mLogScale = 1f / computeLog(1, mBase, mDrift); - } - - static float computeLog(float t, int base, int drift) { - return (float) -Math.pow(base, -t) + 1 + (drift * t); - } - - @Override - public float getInterpolation(float t) { - return computeLog(t, mBase, mDrift) * mLogScale; - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/ui/MaxWidthLayout.java b/car-support-lib/src/android/support/car/ui/MaxWidthLayout.java deleted file mode 100644 index cbb4d12d8f..0000000000 --- a/car-support-lib/src/android/support/car/ui/MaxWidthLayout.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -import java.util.ArrayList; -import java.util.List; - -/** - * Acts as a container to make the width of all its children not larger than the setup max width. - * - * To use MaxWidthLayout, put it as the outermost layout of all children you want to limit its max - * width and set the maxWidth appropriately. - * @hide - */ -public class MaxWidthLayout extends FrameLayout { - - // If mMaxChildrenWidth == 0, it means that it doesn't set the max width, just use the current - // width directly. - private int mMaxChildrenWidth; - - public MaxWidthLayout(Context context) { - super(context); - initialize(context.obtainStyledAttributes(R.styleable.MaxWidthLayout)); - } - - public MaxWidthLayout(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(context.obtainStyledAttributes(attrs, R.styleable.MaxWidthLayout)); - } - - public MaxWidthLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(context - .obtainStyledAttributes(attrs, R.styleable.MaxWidthLayout, defStyleAttr, 0)); - } - - /** - * Initialize MaxWidthLayout specific attributes and recycle the TypeArray. - */ - private void initialize(TypedArray ta) { - mMaxChildrenWidth = (int) (ta.getDimension(R.styleable.MaxWidthLayout_carMaxWidth, 0)); - ta.recycle(); - } - - /** - * Re-measure the child width if it is greater than max width. - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mMaxChildrenWidth == 0) { - return; - } - - final List<View> matchParentChildren = new ArrayList<View>(); - for (int i = getChildCount() - 1; i >= 0; --i) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.width == LayoutParams.MATCH_PARENT) { - matchParentChildren.add(child); - } - } - } - for (int i = matchParentChildren.size() - 1; i >= 0; --i) { - final View child = matchParentChildren.get(i); - final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); - if (child.getMeasuredWidth() > mMaxChildrenWidth) { - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - mMaxChildrenWidth - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); - int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - child.getMeasuredHeight(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/PagedLayoutManager.java b/car-support-lib/src/android/support/car/ui/PagedLayoutManager.java deleted file mode 100644 index f3dc94fab6..0000000000 --- a/car-support-lib/src/android/support/car/ui/PagedLayoutManager.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.graphics.PointF; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.LinearSmoothScroller; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.View; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; - -/** - * An extension of {@link LinearLayoutManager} that adds some helper methods for paging - * such as whether or not it is at the top or bottom of a list and layout param checking. - * @hide - */ -public class PagedLayoutManager extends LinearLayoutManager { - private static final String TAG = PagedLayoutManager.class.getSimpleName(); - - private final LinearSmoothScroller mSmoothScrollerForDrag; - private final LinearSmoothScroller mSmoothScrollerForNonDrag; - - private int mLastScrollPosition = 0; - private boolean mScrollingEnabled = true; - public Runnable mItemsChangedRunnable; - - public PagedLayoutManager(Context context) { - super(context, VERTICAL, false); - mSmoothScrollerForDrag = new SnapToStartSmoothScroller(context, true); - mSmoothScrollerForNonDrag = new SnapToStartSmoothScroller(context, true); - } - - public void setItemsChangedListener(Runnable runnable) { - mItemsChangedRunnable = runnable; - } - - @Override - public void onItemsChanged(RecyclerView recyclerView) { - super.onItemsChanged(recyclerView); - if (mItemsChangedRunnable != null) { - mItemsChangedRunnable.run(); - } - } - - @Override - protected int getExtraLayoutSpace(RecyclerView.State state) { - return getHeight() - getPaddingTop() - getPaddingBottom(); - } - - @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - boolean forDrag = recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING; - LinearSmoothScroller ss; - if (forDrag) { - ss = mSmoothScrollerForDrag; - } else { - ss = mSmoothScrollerForNonDrag; - } - ss.setTargetPosition(position); - startSmoothScroll(ss); - } - - public int getLastScrollPosition() { - return mLastScrollPosition; - } - - @Override - public void scrollToPosition(int position) { - super.scrollToPosition(position); - mLastScrollPosition = position; - } - - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler r, RecyclerView.State s) { - // Our isAtBottom will return true if the view is on screen the the margin extends below - // the bottom. This will make it so that you can't scroll if only the margin is hanging - // off the bottom. - if (isAtBottom() && dy > 0) { - return 0; - } - return super.scrollVerticallyBy(dy, r, s); - } - - @Override - public boolean canScrollVertically() { - return mScrollingEnabled; - } - - public void setScrollingEnabled(boolean enabled) { - mScrollingEnabled = enabled; - } - - public boolean isAtTop() { - if (getChildCount() == 0 || getItemCount() == 0) { - return true; - } - return findFirstCompletelyVisibleItemPosition() < 1; - } - - public boolean isAtBottom() { - if (getChildCount() == 0 || getItemCount() == 0) { - return true; - } - return findLastCompletelyVisibleItemPosition() == getItemCount() - 1; - } - - private class SnapToStartSmoothScroller extends LinearSmoothScroller { - private static final int DURATION_MS = 500; - private final Interpolator mInterpolator; - - public SnapToStartSmoothScroller(Context context, boolean forDrag) { - super(context); - int interpolator = forDrag ? android.R.interpolator.decelerate_quint : - android.R.interpolator.fast_out_slow_in; - mInterpolator = AnimationUtils.loadInterpolator(context, interpolator); - } - - @Override - protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { - int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START); - if (dy == 0) { - Log.w(TAG, "Scroll distance is 0."); - return; - } - - action.update(0, -dy, DURATION_MS, mInterpolator); - } - - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - return PagedLayoutManager.this - .computeScrollVectorForPosition(targetPosition); - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/PagedListView.java b/car-support-lib/src/android/support/car/ui/PagedListView.java deleted file mode 100644 index 5c9b5043c0..0000000000 --- a/car-support-lib/src/android/support/car/ui/PagedListView.java +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.TextView; - - -/** - * Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that - * resembles a {@link android.widget.ListView} but also has page up and page down arrows - * on the right side. - * @hide - */ -public class PagedListView extends FrameLayout { - private static final String TAG = "PagedListView"; - - /** - * The amount of time after settling to wait before autoscrolling to the next page when the - * user holds down a pagination button. - */ - private static final int PAGINATION_HOLD_DELAY_MS = 400; - - private final CarRecyclerView mRecyclerView; - private final CarLayoutManager mLayoutManager; - private final PagedScrollBarView mScrollBarView; - private final Handler mHandler = new Handler(); - private Decoration mDecor = new Decoration(getContext()); - - /** Maximum number of pages to show. Values < 0 show all pages. */ - private int mMaxPages = -1; - /** Number of visible rows per page */ - private int mRowsPerPage = -1; - - /** - * Used to check if there are more items added to the list. - */ - private int mLastItemCount = 0; - - private RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter; - - private boolean mNeedsFocus; - private OnScrollBarListener mOnScrollBarListener; - - /** - * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to cap the - * number of items. - * <p>NOTE: it is still up to the adapter to use maxItems in - * {@link android.support.v7.widget.RecyclerView.Adapter#getItemCount()}. - * - * the recommended way would be with: - * <pre> - * @Override - * public int getItemCount() { - * return Math.min(super.getItemCount(), mMaxItems); - * } - * </pre> - */ - public interface ItemCap { - public static final int UNLIMITED = -1; - - /** - * Sets the maximum number of items available in the adapter. A value less than '0' - * means the list should not be capped. - */ - void setMaxItems(int maxItems); - } - - public PagedListView(Context context, AttributeSet attrs) { - this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/); - } - - public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs) { - this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/); - } - - public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { - super(context, attrs, defStyleAttrs, defStyleRes); - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes); - boolean rightGutterEnabled = - a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false); - LayoutInflater.from(context) - .inflate(R.layout.car_paged_recycler_view, this /*root*/, true /*attachToRoot*/); - if (rightGutterEnabled) { - FrameLayout maxWidthLayout = (FrameLayout) findViewById(R.id.max_width_layout); - LayoutParams params = - (LayoutParams) maxWidthLayout.getLayoutParams(); - params.rightMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin); - maxWidthLayout.setLayoutParams(params); - } - mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view); - boolean fadeLastItem = a.getBoolean(R.styleable.PagedListView_fadeLastItem, false); - mRecyclerView.setFadeLastItem(fadeLastItem); - boolean offsetRows = a.getBoolean(R.styleable.PagedListView_offsetRows, false); - a.recycle(); - - mMaxPages = getDefaultMaxPages(); - - mLayoutManager = new CarLayoutManager(context); - mLayoutManager.setOffsetRows(offsetRows); - mLayoutManager.setItemsChangedListener(mItemsChangedListener); - mRecyclerView.setLayoutManager(mLayoutManager); - mRecyclerView.addItemDecoration(mDecor); - mRecyclerView.setOnScrollListener(mOnScrollListener); - mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12); - mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager)); - - mScrollBarView = (PagedScrollBarView) findViewById(R.id.paged_scroll_view); - mScrollBarView.setPaginationListener(new PagedScrollBarView.PaginationListener() { - @Override - public void onPaginate(int direction) { - if (direction == PagedScrollBarView.PaginationListener.PAGE_UP) { - mRecyclerView.pageUp(); - } else if (direction == PagedScrollBarView.PaginationListener.PAGE_DOWN) { - mRecyclerView.pageDown(); - } else { - Log.e(TAG, "Unknown pagination direction (" + direction + ")"); - } - } - }); - - setAutoDayNightMode(); - updatePaginationButtons(false /*animate*/); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mHandler.removeCallbacks(mUpdatePaginationRunnable); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_DOWN) { - // The user has interacted with the list using touch. All movements will now paginate - // the list. - mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_PAGE); - } - return super.onInterceptTouchEvent(e); - } - - @Override - public void requestChildFocus(View child, View focused) { - super.requestChildFocus(child, focused); - // The user has interacted with the list using the controller. Movements through the list - // will now be one row at a time. - mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL); - } - - public int positionOf(@Nullable View v) { - if (v == null || v.getParent() != mRecyclerView) { - return -1; - } - return mLayoutManager.getPosition(v); - } - - @NonNull - public CarRecyclerView getRecyclerView() { - return mRecyclerView; - } - - public void scrollToPosition(int position) { - mLayoutManager.scrollToPosition(position); - - // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure - // the pagination arrows actually get updated. - mHandler.post(mUpdatePaginationRunnable); - } - - /** - * Sets the adapter for the list. - * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw - * an {@link IllegalArgumentException}. - */ - public void setAdapter( - @NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) { - if (!(adapter instanceof ItemCap)) { - throw new IllegalArgumentException("ERROR: adapter " - + "[" + adapter.getClass().getCanonicalName() + "] MUST implement ItemCap"); - } - - mAdapter = adapter; - mRecyclerView.setAdapter(adapter); - tryUpdateMaxPages(); - } - - @NonNull - public CarLayoutManager getLayoutManager() { - return mLayoutManager; - } - - @Nullable - @SuppressWarnings("unchecked") - public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> getAdapter() { - return mRecyclerView.getAdapter(); - } - - public void setMaxPages(int maxPages) { - mMaxPages = maxPages; - tryUpdateMaxPages(); - } - - public int getMaxPages() { - return mMaxPages; - } - - public void resetMaxPages() { - mMaxPages = getDefaultMaxPages(); - } - - public void setDefaultItemDecoration(Decoration decor) { - removeDefaultItemDecoration(); - mDecor = decor; - addItemDecoration(mDecor); - } - - public void removeDefaultItemDecoration() { - mRecyclerView.removeItemDecoration(mDecor); - } - - public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) { - mRecyclerView.addItemDecoration(decor); - } - - public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) { - mRecyclerView.removeItemDecoration(decor); - } - - public void setAutoDayNightMode() { - mScrollBarView.setAutoDayNightMode(); - mDecor.updateDividerColor(); - } - - public void setLightMode() { - mScrollBarView.setLightMode(); - mDecor.updateDividerColor(); - } - - public void setDarkMode() { - mScrollBarView.setDarkMode(); - mDecor.updateDividerColor(); - } - - public void setOnScrollBarListener(OnScrollBarListener listener) { - mOnScrollBarListener = listener; - } - - /** Returns the page the given position is on, starting with page 0. */ - public int getPage(int position) { - if (mRowsPerPage == -1) { - return -1; - } - return position / mRowsPerPage; - } - - /** Returns the default number of pages the list should have */ - protected int getDefaultMaxPages() { - // assume list shown in response to a click, so, reduce number of clicks by one - //return ProjectionUtils.getMaxClicks(getContext().getContentResolver()) - 1; - return 5; - } - - private void tryUpdateMaxPages() { - if (mAdapter == null) { - return; - } - - View firstChild = mLayoutManager.getChildAt(0); - int firstRowHeight = firstChild == null ? 0 : firstChild.getHeight(); - mRowsPerPage = firstRowHeight == 0 ? 1 : getHeight() / firstRowHeight; - - int newMaxItems; - if (mMaxPages < 0) { - newMaxItems = -1; - } else if (mMaxPages == 0) { - // At the last click of 6 click limit, we show one more warning item at the top of menu. - newMaxItems = mRowsPerPage + 1; - } else { - newMaxItems = mRowsPerPage * mMaxPages; - } - - int originalCount = mAdapter.getItemCount(); - ((ItemCap) mAdapter).setMaxItems(newMaxItems); - int newCount = mAdapter.getItemCount(); - if (newCount < originalCount) { - mAdapter.notifyItemRangeChanged(newCount, originalCount); - } else if (newCount > originalCount) { - mAdapter.notifyItemInserted(originalCount); - } - } - - @Override - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - // if a late item is added to the top of the layout after the layout is stabilized, causing - // the former top item to be pushed to the 2nd page, the focus will still be on the former - // top item. Since our car layout manager tries to scroll the viewport so that the focused - // item is visible, the view port will be on the 2nd page. That means the newly added item - // will not be visible, on the first page. - - // what we want to do is: if the formerly focused item is the first one in the list, any - // item added above it will make the focus to move to the new first item. - // if the focus is not on the formerly first item, then we don't need to do anything. Let - // the layout manager do the job and scroll the viewport so the currently focused item - // is visible. - - // we need to calculate whether we want to request focus here, before the super call, - // because after the super call, the first born might be changed. - View focusedChild = mLayoutManager.getFocusedChild(); - View firstBorn = mLayoutManager.getChildAt(0); - - super.onLayout(changed, left, top, right, bottom); - - if (mAdapter != null) { - int itemCount = mAdapter.getItemCount(); - // if () { - Log.d(TAG, String.format( - "onLayout hasFocus: %s, mLastItemCount: %s, itemCount: %s, focusedChild: " + - "%s, firstBorn: %s, isInTouchMode: %s, mNeedsFocus: %s", - hasFocus(), mLastItemCount, itemCount, focusedChild, firstBorn, - isInTouchMode(), mNeedsFocus)); - // } - tryUpdateMaxPages(); - // This is a workaround for missing focus because isInTouchMode() is not always - // returning the right value. - // This is okay for the Engine release since focus is always showing. - // However, in Tala and Fender, we want to show focus only when the user uses - // hardware controllers, so we need to revisit this logic. b/22990605. - if (mNeedsFocus && itemCount > 0) { - if (focusedChild == null) { - requestFocusFromTouch(); - } - mNeedsFocus = false; - } - if (itemCount > mLastItemCount && focusedChild == firstBorn && - getContext().getResources().getBoolean(R.bool.has_wheel)) { - requestFocusFromTouch(); - } - mLastItemCount = itemCount; - } - updatePaginationButtons(true /*animate*/); - } - - @Override - public boolean requestFocus(int direction, Rect rect) { - if (getContext().getResources().getBoolean(R.bool.has_wheel)) { - mNeedsFocus = true; - } - return super.requestFocus(direction, rect); - } - - public View findViewByPosition(int position) { - return mLayoutManager.findViewByPosition(position); - } - - private void updatePaginationButtons(boolean animate) { - boolean isAtTop = mLayoutManager.isAtTop(); - boolean isAtBottom = mLayoutManager.isAtBottom(); - if (isAtTop && isAtBottom) { - mScrollBarView.setVisibility(View.INVISIBLE); - } else { - mScrollBarView.setVisibility(View.VISIBLE); - } - mScrollBarView.setUpEnabled(!isAtTop); - mScrollBarView.setDownEnabled(!isAtBottom); - - mScrollBarView.setParameters( - mRecyclerView.computeVerticalScrollRange(), - mRecyclerView.computeVerticalScrollOffset(), - mRecyclerView.computeVerticalScrollExtent(), - animate); - invalidate(); - } - - private final RecyclerView.OnScrollListener mOnScrollListener = - new RecyclerView.OnScrollListener() { - - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - if (mOnScrollBarListener != null) { - if (!mLayoutManager.isAtTop() && mLayoutManager.isAtBottom()) { - mOnScrollBarListener.onReachBottom(); - } - if (mLayoutManager.isAtTop() || !mLayoutManager.isAtBottom()) { - mOnScrollBarListener.onLeaveBottom(); - } - } - updatePaginationButtons(false); - } - - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS); - } - } - }; - private final Runnable mPaginationRunnable = new Runnable() { - @Override - public void run() { - boolean upPressed = mScrollBarView.isUpPressed(); - boolean downPressed = mScrollBarView.isDownPressed(); - if (upPressed && downPressed) { - // noop - } else if (upPressed) { - mRecyclerView.pageUp(); - } else if (downPressed) { - mRecyclerView.pageDown(); - } - } - }; - - private final Runnable mUpdatePaginationRunnable = new Runnable() { - @Override - public void run() { - updatePaginationButtons(true /*animate*/); - } - }; - - private final CarLayoutManager.OnItemsChangedListener mItemsChangedListener = - new CarLayoutManager.OnItemsChangedListener() { - @Override - public void onItemsChanged() { - updatePaginationButtons(true /*animate*/); - } - }; - - abstract static public class OnScrollBarListener { - public void onReachBottom() {} - public void onLeaveBottom() {} - } - - public static class Decoration extends RecyclerView.ItemDecoration { - protected final Paint mPaint; - protected final int mDividerHeight; - protected final Context mContext; - - - public Decoration(Context context) { - mContext = context; - mPaint = new Paint(); - updateDividerColor(); - mDividerHeight = mContext.getResources() - .getDimensionPixelSize(R.dimen.car_divider_height); - } - - @Override - public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { - final int left = getLeft(parent.getChildAt(0)); - final int right = parent.getWidth() - parent.getPaddingRight(); - int top; - int bottom; - - c.drawRect(left, 0, right, mDividerHeight, mPaint); - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = parent.getChildAt(i); - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child - .getLayoutParams(); - bottom = child.getBottom() - params.bottomMargin; - top = bottom - mDividerHeight; - if (top > 0) { - c.drawRect(left, top, right, bottom, mPaint); - } - } - } - - /** - * Updates the list divider color which may have changed due to a day night transition. - */ - public void updateDividerColor() { - mPaint.setColor(mContext.getResources().getColor(R.color.car_list_divider)); - } - - /** - * Find the left edge of the decoration line. It should be left aligned with the left edge - * of the first {@link android.widget.TextView}. - */ - private int getLeft(View root) { - if (root == null) { - return 0; - } - View view = findTextView(root); - if (view == null) { - view = root; - } - int left = 0; - while (view != null && view != root) { - left += view.getLeft(); - view = (View) view.getParent(); - } - return left; - } - - private TextView findTextView(View root) { - if (root == null) { - return null; - } - if (root instanceof TextView) { - return (TextView) root; - } - if (root instanceof ViewGroup) { - ViewGroup parent = (ViewGroup) root; - final int childCount = parent.getChildCount(); - for(int i = 0; i < childCount; i++) { - TextView tv = findTextView(parent.getChildAt(i)); - if (tv != null) { - return tv; - } - } - } - return null; - } - } -} diff --git a/car-support-lib/src/android/support/car/ui/PagedScrollBarView.java b/car-support-lib/src/android/support/car/ui/PagedScrollBarView.java deleted file mode 100644 index d6ddfb63ad..0000000000 --- a/car-support-lib/src/android/support/car/ui/PagedScrollBarView.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Interpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; - -/** - * A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator. - * - * @hide - */ -public class PagedScrollBarView extends FrameLayout - implements View.OnClickListener, View.OnLongClickListener { - private static final float BUTTON_DISABLED_ALPHA = 0.2f; - - /** - * Listener for when the list should paginate. - */ - public interface PaginationListener { - int PAGE_UP = 0; - int PAGE_DOWN = 1; - - /** Called when the linked view should be paged in the given direction */ - void onPaginate(int direction); - } - - private final ImageView mUpButton; - private final ImageView mDownButton; - private final ImageView mScrollThumb; - /** The "filler" view between the up and down buttons */ - private final View mFiller; - private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator(); - private final int mMinThumbLength; - private final int mMaxThumbLength; - private PaginationListener mPaginationListener; - - public PagedScrollBarView( - Context context, AttributeSet attrs) { - this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/); - } - - public PagedScrollBarView( - Context context, AttributeSet attrs, int defStyleAttrs) { - this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/); - } - - public PagedScrollBarView( - Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { - super(context, attrs, defStyleAttrs, defStyleRes); - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate( - R.layout.car_paged_scrollbar_buttons, this /*root*/, true /*attachToRoot*/); - - mUpButton = (ImageView) findViewById(R.id.page_up); - mUpButton.setImageDrawable(context.getDrawable(R.drawable.ic_up_button)); - mUpButton.setOnClickListener(this); - mUpButton.setOnLongClickListener(this); - mDownButton = (ImageView) findViewById(R.id.page_down); - mDownButton.setImageDrawable(context.getDrawable(R.drawable.ic_down_button)); - mDownButton.setOnClickListener(this); - mDownButton.setOnLongClickListener(this); - - mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb); - mScrollThumb.setAlpha(0.5f); - - mFiller = findViewById(R.id.filler); - - mMinThumbLength = getResources().getDimensionPixelSize(R.dimen.min_thumb_height); - mMaxThumbLength = getResources().getDimensionPixelSize(R.dimen.max_thumb_height); - - if (!context.getResources().getBoolean(R.bool.car_true_for_touch)) { - // Don't show the pagination buttons if there isn't touch. - mUpButton.setVisibility(View.GONE); - mDownButton.setVisibility(View.GONE); - } - } - - @Override - public void onClick(View v) { - dispatchPageClick(v); - } - - @Override - public boolean onLongClick(View v) { - dispatchPageClick(v); - return true; - } - - public void setPaginationListener(PaginationListener listener) { - mPaginationListener = listener; - } - - /** Returns {@code true} if the "up" button is pressed */ - public boolean isUpPressed() { - return mUpButton.isPressed(); - } - - /** Returns {@code true} if the "down" button is pressed */ - public boolean isDownPressed() { - return mDownButton.isPressed(); - } - - /** Sets the range, offset and extent of the scroll bar. See {@link android.view.View}. */ - protected void setParameters(int range, int offset, int extent, boolean animate) { - final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom(); - - int thumbLength = extent * size / range; - thumbLength = Math.max(Math.min(thumbLength, mMaxThumbLength), mMinThumbLength); - - int thumbOffset = size - thumbLength; - if (isDownEnabled()) { - // We need to adjust the offset so that it fits into the possible space inside the - // filler with regarding to the constraints set by mMaxThumbLength and mMinThumbLength. - thumbOffset = (size - thumbLength) * offset / range; - } - - // Sets the size of the thumb and request a redraw if needed. - final ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams(); - if (lp.height != thumbLength) { - lp.height = thumbLength; - mScrollThumb.requestLayout(); - } - - moveY(mScrollThumb, thumbOffset, animate); - } - - /** Sets auto day/night mode */ - protected void setAutoDayNightMode() { - int color = getResources().getColor(R.color.car_tint); - mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - mUpButton.setBackgroundResource(R.drawable.car_pagination_background); - mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - mDownButton.setBackgroundResource(R.drawable.car_pagination_background); - - mScrollThumb.setBackgroundColor(color); - } - - /** Sets auto light mode */ - protected void setLightMode() { - int color = getResources().getColor(R.color.car_tint_light); - mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - mUpButton.setBackgroundResource(R.drawable.car_pagination_background_light); - mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - mDownButton.setBackgroundResource(R.drawable.car_pagination_background_light); - - mScrollThumb.setBackgroundColor(color); - } - - /** Sets auto dark mode */ - protected void setDarkMode() { - int color = getResources().getColor(R.color.car_tint_dark); - mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - mUpButton.setBackgroundResource(R.drawable.car_pagination_background_dark); - mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN); - mDownButton.setBackgroundResource(R.drawable.car_pagination_background_dark); - - mScrollThumb.setBackgroundColor(color); - } - - protected void setUpEnabled(boolean enabled) { - mUpButton.setEnabled(enabled); - mUpButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA); - } - - protected void setDownEnabled(boolean enabled) { - mDownButton.setEnabled(enabled); - mDownButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA); - } - - protected boolean isDownEnabled() { - return mDownButton.isEnabled(); - } - - private void dispatchPageClick(View v) { - final PaginationListener listener = mPaginationListener; - if (listener == null) { - return; - } - - final int direction = (v.getId() == R.id.page_up) - ? PaginationListener.PAGE_UP : PaginationListener.PAGE_DOWN; - listener.onPaginate(direction); - } - - /** Moves the given view to the specified 'y' position. */ - private void moveY(final View view, float newPosition, boolean animate) { - final int duration = animate ? 200 : 0; - view.animate() - .y(newPosition) - .setDuration(duration) - .setInterpolator(mPaginationInterpolator) - .start(); - } -}
\ No newline at end of file diff --git a/car-support-lib/src/android/support/car/ui/QuantumInterpolator.java b/car-support-lib/src/android/support/car/ui/QuantumInterpolator.java deleted file mode 100644 index cc10d4a5fb..0000000000 --- a/car-support-lib/src/android/support/car/ui/QuantumInterpolator.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - -import android.animation.TimeInterpolator; - -/** - * Interpolator that can animate any of the quantum curves. - * You can also specify - * @hide - */ -public class QuantumInterpolator implements TimeInterpolator { - - /** - * Lookup table values. - * Generated using a Bezier curve from (0,0) to (1,1) with control points: - * P0 (0,0) - * P1 (0.4, 0) - * P2 (0.2, 1.0) - * P3 (1.0, 1.0) - * - * Values sampled with x at regular intervals between 0 and 1. - * - * These values were generated using: - * ./scripts/bezier_interpolator_values_gen.py 0.4 0.2 - */ - public static final float[] FAST_OUT_SLOW_IN = new float[] { - 0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f, - 0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f, - 0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f, - 0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f, - 0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f, - 0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f, - 0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f, - 0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f, - 0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f, - 0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f - }; - - /** - * These values were generated using: - * ./scripts/bezier_interpolator_values_gen.py 0.0 0.2 - */ - public static final float[] LINEAR_OUT_SLOW_IN = new float[] { - 0.0029f, 0.043f, 0.0785f, 0.1147f, 0.1476f, 0.1742f, 0.2024f, 0.2319f, 0.2575f, 0.2786f, - 0.3055f, 0.3274f, 0.3498f, 0.3695f, 0.3895f, 0.4096f, 0.4299f, 0.4474f, 0.4649f, - 0.4824f, 0.5f, 0.5176f, 0.5322f, 0.5468f, 0.5643f, 0.5788f, 0.5918f, 0.6048f, 0.6191f, - 0.6333f, 0.6446f, 0.6573f, 0.6698f, 0.6808f, 0.6918f, 0.704f, 0.7148f, 0.7254f, 0.7346f, - 0.7451f, 0.7554f, 0.7655f, 0.7731f, 0.783f, 0.7916f, 0.8f, 0.8084f, 0.8166f, 0.8235f, - 0.8315f, 0.8393f, 0.8459f, 0.8535f, 0.8599f, 0.8672f, 0.8733f, 0.8794f, 0.8853f, - 0.8911f, 0.8967f, 0.9023f, 0.9077f, 0.9121f, 0.9173f, 0.9224f, 0.9265f, 0.9313f, - 0.9352f, 0.9397f, 0.9434f, 0.9476f, 0.9511f, 0.9544f, 0.9577f, 0.9614f, 0.9644f, - 0.9673f, 0.9701f, 0.9727f, 0.9753f, 0.9777f, 0.98f, 0.9818f, 0.9839f, 0.9859f, 0.9877f, - 0.9891f, 0.9907f, 0.9922f, 0.9933f, 0.9946f, 0.9957f, 0.9966f, 0.9974f, 0.9981f, - 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f - }; - - /** - * These values were generated using: - * ./scripts/bezier_interpolator_values_gen.py 0.0 0.2 - */ - public static final float[] FAST_OUT_LINEAR_IN = new float[] { - 0.0f, 0.0002f, 0.0008f, 0.0019f, 0.0032f, 0.0049f, 0.0069f, 0.0093f, 0.0119f, 0.0149f, - 0.0182f, 0.0218f, 0.0257f, 0.0299f, 0.0344f, 0.0392f, 0.0443f, 0.0496f, 0.0552f, - 0.0603f, 0.0656f, 0.0719f, 0.0785f, 0.0853f, 0.0923f, 0.0986f, 0.1051f, 0.1128f, - 0.1206f, 0.1287f, 0.1359f, 0.1433f, 0.1519f, 0.1607f, 0.1696f, 0.1776f, 0.1857f, - 0.1952f, 0.2048f, 0.2145f, 0.2232f, 0.2319f, 0.2421f, 0.2523f, 0.2627f, 0.2733f, - 0.2826f, 0.2919f, 0.3027f, 0.3137f, 0.3247f, 0.3358f, 0.3469f, 0.3582f, 0.3695f, - 0.3809f, 0.3924f, 0.4039f, 0.4154f, 0.427f, 0.4386f, 0.4503f, 0.4619f, 0.4751f, 0.4883f, - 0.5f, 0.5117f, 0.5264f, 0.5381f, 0.5497f, 0.5643f, 0.5759f, 0.5904f, 0.6033f, 0.6162f, - 0.6305f, 0.6446f, 0.6587f, 0.6698f, 0.6836f, 0.7f, 0.7134f, 0.7267f, 0.7425f, 0.7554f, - 0.7706f, 0.7855f, 0.8f, 0.8143f, 0.8281f, 0.8438f, 0.8588f, 0.8733f, 0.8892f, 0.9041f, - 0.9215f, 0.9344f, 0.9518f, 0.9667f, 0.9826f, 0.9993f - - }; - - private final float[] mValues; - private final float mStepSize; - private final float mStartTime; - private final float mEndTime; - - public QuantumInterpolator(float[] values, float pre, float during, float post) { - super(); - mValues = values; - mStepSize = 1.0f / (mValues.length - 1); - mStartTime = pre / (pre + during + post); - mEndTime = mStartTime + (during / (pre + during + post)); - } - - @Override - public float getInterpolation(float input) { - return getInterpolation(input, mStartTime, mEndTime); - } - - public float getReverseInterpolation(float input) { - return getInterpolation(input, 1 - mEndTime, 1 - mStartTime); - } - - public float getInterpolation(float input, float startTime, float endTime) { - if (input <= startTime) { - return 0.0f; - } else if (input >= endTime) { - return 1.0f; - } - - input = (input - startTime) / (endTime - startTime); - - int position = Math.min( - (int)(input * (mValues.length - 1)), - mValues.length - 2); - - float quantized = position * mStepSize; - float difference = input - quantized; - float weight = difference / mStepSize; - - return mValues[position] + weight * (mValues[position + 1] - mValues[position]); - } -} diff --git a/car-support-lib/src/android/support/car/ui/ReversibleInterpolator.java b/car-support-lib/src/android/support/car/ui/ReversibleInterpolator.java deleted file mode 100644 index ff9843d149..0000000000 --- a/car-support-lib/src/android/support/car/ui/ReversibleInterpolator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car.ui; - - -import android.animation.TimeInterpolator; -import android.support.annotation.NonNull; - -/** - * Interpolator that can provide custom interpolations for forward and reverse animations - * @hide - */ -public class ReversibleInterpolator { - - private final TimeInterpolator mForwardInterpolator; - private final TimeInterpolator mReverseInterpolator; - - public ReversibleInterpolator(@NonNull TimeInterpolator forwardInterpolator, - @NonNull TimeInterpolator reverseInterpolator) { - mForwardInterpolator = forwardInterpolator; - mReverseInterpolator = reverseInterpolator; - } - - public float getForwardInterpolation(float input) { - return mForwardInterpolator.getInterpolation(input); - } - - public float getReverseInterpolation(float input) { - return mReverseInterpolator.getInterpolation(input); - } -} diff --git a/car-ui-provider/res/drawable-hdpi/ic_google.png b/car-ui-provider/res/drawable-hdpi/ic_google.png Binary files differdeleted file mode 100644 index 6e79734164..0000000000 --- a/car-ui-provider/res/drawable-hdpi/ic_google.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-hdpi/ic_googleg.png b/car-ui-provider/res/drawable-hdpi/ic_googleg.png Binary files differdeleted file mode 100644 index 0d4158fc35..0000000000 --- a/car-ui-provider/res/drawable-hdpi/ic_googleg.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-mdpi/ic_google.png b/car-ui-provider/res/drawable-mdpi/ic_google.png Binary files differdeleted file mode 100644 index b00365a61b..0000000000 --- a/car-ui-provider/res/drawable-mdpi/ic_google.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-mdpi/ic_googleg.png b/car-ui-provider/res/drawable-mdpi/ic_googleg.png Binary files differdeleted file mode 100644 index 4871a0fbec..0000000000 --- a/car-ui-provider/res/drawable-mdpi/ic_googleg.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-xhdpi/ic_google.png b/car-ui-provider/res/drawable-xhdpi/ic_google.png Binary files differdeleted file mode 100644 index db17d6f797..0000000000 --- a/car-ui-provider/res/drawable-xhdpi/ic_google.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-xhdpi/ic_googleg.png b/car-ui-provider/res/drawable-xhdpi/ic_googleg.png Binary files differdeleted file mode 100644 index addab88d4d..0000000000 --- a/car-ui-provider/res/drawable-xhdpi/ic_googleg.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-xxhdpi/ic_google.png b/car-ui-provider/res/drawable-xxhdpi/ic_google.png Binary files differdeleted file mode 100644 index a27a8bc825..0000000000 --- a/car-ui-provider/res/drawable-xxhdpi/ic_google.png +++ /dev/null diff --git a/car-ui-provider/res/drawable-xxhdpi/ic_googleg.png b/car-ui-provider/res/drawable-xxhdpi/ic_googleg.png Binary files differdeleted file mode 100644 index 0e89bbb534..0000000000 --- a/car-ui-provider/res/drawable-xxhdpi/ic_googleg.png +++ /dev/null diff --git a/car-ui-provider/res/layout/car_activity.xml b/car-ui-provider/res/layout/car_activity.xml deleted file mode 100644 index 041afd3946..0000000000 --- a/car-ui-provider/res/layout/car_activity.xml +++ /dev/null @@ -1,192 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_height="match_parent" - android:layout_width="match_parent"> - - <ImageView - android:id="@+id/background" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" /> - - <android.car.ui.provider.CarDrawerLayout - android:id="@+id/drawer_container" - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <FrameLayout - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - - <FrameLayout - android:id="@+id/drawer" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="left" - android:layout_marginEnd="96dp" - android:background="@color/car_card" - android:paddingTop="@dimen/lens_header_height" > - - <android.car.ui.provider.PagedListView - android:id="@+id/list_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - - <ProgressBar - android:id="@+id/progress" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_gravity="center" - android:indeterminate="true" /> - - <android.support.v7.widget.CardView - android:id="@+id/truncated_list_card" - android:layout_width="match_parent" - android:layout_height="@dimen/car_list_truncated_list_card_height" - android:layout_gravity="bottom" - android:layout_marginLeft="@dimen/car_list_truncated_list_padding" - android:layout_marginRight="@dimen/car_list_truncated_list_padding" - android:layout_marginBottom="@dimen/car_list_truncated_list_padding" - android:visibility="gone" - app:cardBackgroundColor="@color/car_blue_grey_800" - app:cardCornerRadius="@dimen/car_card_view_corner_radius" - app:cardElevation="@dimen/car_card_view_elevation" > - - <TextView - style="@style/CarTruncatedList" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="@dimen/car_list_truncated_list_icon_padding" - android:drawablePadding="@dimen/car_list_truncated_list_drawable_padding" - android:gravity="center_vertical" - android:drawableLeft="@drawable/ic_remove_circle" - android:text="@string/truncated_list" /> - - </android.support.v7.widget.CardView> - </FrameLayout> - </android.car.ui.provider.CarDrawerLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="@dimen/lens_header_height"> - - <FrameLayout - android:layout_width="@dimen/car_drawer_button_container_width" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/car_drawer_button" - android:layout_width="@dimen/car_drawer_header_menu_button_size" - android:layout_height="@dimen/car_drawer_header_menu_button_size" - android:background="@drawable/car_header_button_background" - android:focusable="false" - android:scaleType="center" - android:layout_gravity="center" /> - </FrameLayout> - - <FrameLayout - android:id="@+id/car_drawer_title_container" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" > - <TextView - android:id="@+id/car_drawer_title" - style="@style/CarTitle.Light" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:ellipsize="end" - android:focusable="false" - android:gravity="center_vertical" - android:singleLine="true" - /> - </FrameLayout> - <android.support.v7.widget.CardView - android:id="@+id/car_search_box" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_marginBottom="16dp" - android:layout_marginTop="16dp" - android:gravity="center_vertical" - app:cardBackgroundColor="@android:color/transparent" > - - <FrameLayout - android:id="@+id/car_search_box_contents" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:animateLayoutChanges="false"> - - <FrameLayout - android:id="@+id/car_search_box_search_logo_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingStart="16dp" > - - <ImageView - android:id="@+id/car_search_box_search_logo" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start|center_vertical" - android:paddingTop="6dp" /> - </FrameLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - - <ImageView - android:id="@+id/car_search_box_super_logo" - android:layout_width="@dimen/car_list_item_icon_size_small" - android:layout_height="@dimen/car_list_item_icon_size_small" - android:layout_gravity="center_vertical" - android:layout_marginStart="24dp" - android:scaleType="center" /> - <!-- - Use a 0x0 drawable for textSelectHandle; null drawable crashes, so does - transparent color. Also set textCursorDrawable to null because this forces - Android to render a cursor using the text color instead of not rendering - one at all. See - http://stackoverflow.com/questions/21397977/android-edit-text-cursor-is-not-visible - --> - - <android.support.car.input.CarRestrictedEditText - android:id="@+id/car_search_box_edit_text" - style="@style/CarTitle.Dark" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center_vertical" - android:imeOptions="actionSearch" - android:paddingEnd="16dp" - android:paddingStart="36dp" - android:singleLine="true" - android:textCursorDrawable="@null" - android:textSelectHandle="@drawable/car_empty" /> - - <FrameLayout - android:id="@+id/car_search_box_end_view" - android:layout_width="wrap_content" - android:layout_height="match_parent" /> - </LinearLayout> - </FrameLayout> - </android.support.v7.widget.CardView> - </LinearLayout> - -</FrameLayout> diff --git a/car-ui-provider/res/layout/car_paged_recycler_view.xml b/car-ui-provider/res/layout/car_paged_recycler_view.xml deleted file mode 100644 index a0f9537198..0000000000 --- a/car-ui-provider/res/layout/car_paged_recycler_view.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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. ---> -<!-- Cloned from car ui lib for use in CarUiProvider. Must be kept in sync. --> -<merge xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <android.car.ui.provider.MaxWidthLayout - android:id="@+id/max_width_layout" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginStart="@dimen/car_drawer_button_container_width"> - <android.car.ui.provider.CarRecyclerView - android:id="@+id/recycler_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:clipChildren="false"/> - </android.car.ui.provider.MaxWidthLayout> - <!-- The scroll bar should be drawn ontop of the centered recycler view--> - <FrameLayout - android:layout_width="@dimen/car_drawer_button_container_width" - android:layout_height="match_parent"> - <android.car.ui.provider.PagedScrollBarView - android:id="@+id/paged_scroll_view" - android:layout_width="@dimen/car_paged_list_view_pagination_width" - android:layout_height="match_parent" - android:paddingBottom="16dp" - android:paddingTop="16dp" - android:layout_gravity="center_horizontal" - android:visibility="invisible"/> - </FrameLayout> -</merge>
\ No newline at end of file diff --git a/car-ui-provider/res/values-h600dp/dimens.xml b/car-ui-provider/res/values-h600dp/dimens.xml deleted file mode 100644 index c972bad8e9..0000000000 --- a/car-ui-provider/res/values-h600dp/dimens.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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_drawer_header_height">192dp</dimen> - <dimen name="car_drawer_header_menu_button_size">152dp</dimen> - <dimen name="car_drawer_button_margin_right">12dp</dimen> - <dimen name="lens_header_height">148dp</dimen> -</resources> diff --git a/car-ui-provider/res/values/dimens.xml b/car-ui-provider/res/values/dimens.xml deleted file mode 100644 index 378e6f84cb..0000000000 --- a/car-ui-provider/res/values/dimens.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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_drawer_header_menu_button_size">96dp</dimen> - - <!-- The top margin before the start of content in an application. --> - <dimen name="lens_header_height">72dp</dimen> -</resources> diff --git a/car-ui-provider/res/values/strings.xml b/car-ui-provider/res/values/strings.xml deleted file mode 100644 index c6a70d75bb..0000000000 --- a/car-ui-provider/res/values/strings.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. ---> -<resources> - <string name="app_name">CarUiProvider</string> -</resources>
\ No newline at end of file diff --git a/car-ui-provider/src/android/car/ui/provider/CarDrawerLayout.java b/car-ui-provider/src/android/car/ui/provider/CarDrawerLayout.java deleted file mode 100644 index 5e704206fe..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/CarDrawerLayout.java +++ /dev/null @@ -1,1466 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.car.ui.CarUiResourceLoader; -import android.support.car.ui.QuantumInterpolator; -import android.support.car.ui.R; -import android.support.car.ui.ReversibleInterpolator; -import android.support.v4.view.GravityCompat; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewGroupCompat; -import android.support.v4.widget.ViewDragHelper; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -/** - * Acts as a top-level container for window content that allows for - * interactive "drawer" views to be pulled out from the edge of the window. - * - * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code> - * attribute on child views corresponding to which side of the view you want the drawer - * to emerge from: left or right. (Or start/end on platform versions that support layout direction.) - * </p> - * - * <p> To use CarDrawerLayout, add your drawer view as the first view in the CarDrawerLayout - * element and set the <code>layout_gravity</code> appropriately. Drawers commonly use - * <code>match_parent</code> for height with a fixed width. Add the content views as sibling views - * after the drawer view.</p> - * - * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views. - * Avoid performing expensive operations such as layout during animation as it can cause - * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state. - * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p> - */ -public class CarDrawerLayout extends ViewGroup { - /** - * Indicates that any drawers are in an idle, settled state. No animation is in progress. - */ - public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; - - /** - * The drawer is unlocked. - */ - public static final int LOCK_MODE_UNLOCKED = 0; - - /** - * The drawer is locked closed. The user may not open it, though - * the app may open it programmatically. - */ - public static final int LOCK_MODE_LOCKED_CLOSED = 1; - - /** - * The drawer is locked open. The user may not close it, though the app - * may close it programmatically. - */ - public static final int LOCK_MODE_LOCKED_OPEN = 2; - - private static final float MAX_SCRIM_ALPHA = 0.8f; - - private static final boolean SCRIM_ENABLED = true; - - private static final boolean SHADOW_ENABLED = true; - - /** - * Minimum velocity that will be detected as a fling - */ - private static final int MIN_FLING_VELOCITY = 400; // dips per second - - /** - * Experimental feature. - */ - private static final boolean ALLOW_EDGE_LOCK = false; - - private static final boolean EDGE_DRAG_ENABLED = false; - - private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; - - private static final float TOUCH_SLOP_SENSITIVITY = 1.f; - - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.layout_gravity - }; - - public static final int DEFAULT_SCRIM_COLOR = 0xff262626; - - private int mScrimColor = DEFAULT_SCRIM_COLOR; - private final Paint mScrimPaint = new Paint(); - private final Paint mEdgeHighlightPaint = new Paint(); - - private final ViewDragHelper mDragger; - - private final Runnable mInvalidateRunnable = new Runnable() { - @Override - public void run() { - requestLayout(); - invalidate(); - } - }; - - // view faders who will be given different colors as the drawer opens - private final Set<ViewFaderHolder> mViewFaders; - private final ReversibleInterpolator mViewFaderInterpolator; - private final ReversibleInterpolator mDrawerFadeInterpolator; - private final Handler mHandler = new Handler(); - - private int mEndingViewColor; - private int mStartingViewColor; - private int mDrawerState; - private boolean mInLayout; - /** Whether we have done a layout yet. Used to initialize some view-related state. */ - private boolean mFirstLayout = true; - private boolean mHasInflated; - private int mLockModeLeft; - private int mLockModeRight; - private boolean mChildrenCanceledTouch; - private DrawerListener mDrawerListener; - private DrawerControllerListener mDrawerControllerListener; - private Drawable mShadow; - private View mDrawerView; - private View mContentView; - private boolean mNeedsFocus; - /** Whether or not the drawer started open for the current gesture */ - private boolean mStartedOpen; - private boolean mHasWheel; - - /** - * Listener for monitoring events about drawers. - */ - public interface DrawerListener { - /** - * Called when a drawer's position changes. - * @param drawerView The child view that was moved - * @param slideOffset The new offset of this drawer within its range, from 0-1 - */ - void onDrawerSlide(View drawerView, float slideOffset); - - /** - * Called when a drawer has settled in a completely open state. - * The drawer is interactive at this point. - * - * @param drawerView Drawer view that is now open - */ - void onDrawerOpened(View drawerView); - - /** - * Called when a drawer has settled in a completely closed state. - * - * @param drawerView Drawer view that is now closed - */ - void onDrawerClosed(View drawerView); - - /** - * Called when a drawer is starting to open. - * - * @param drawerView Drawer view that is opening - */ - void onDrawerOpening(View drawerView); - - /** - * Called when a drawer is starting to close. - * - * @param drawerView Drawer view that is closing - */ - void onDrawerClosing(View drawerView); - - /** - * Called when the drawer motion state changes. The new state will - * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * - * @param newState The new drawer motion state - */ - void onDrawerStateChanged(int newState); - } - - /** - * Used to execute when the drawer needs to handle state that the underlying views would like - * to handle in a specific way. - */ - public interface DrawerControllerListener { - void onBack(); - boolean onScroll(); - } - - /** - * Stub/no-op implementations of all methods of {@link DrawerListener}. - * Override this if you only care about a few of the available callback methods. - */ - public static abstract class SimpleDrawerListener implements DrawerListener { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - } - - @Override - public void onDrawerOpened(View drawerView) { - } - - @Override - public void onDrawerClosed(View drawerView) { - } - - @Override - public void onDrawerOpening(View drawerView) { - } - - @Override - public void onDrawerClosing(View drawerView) { - } - - @Override - public void onDrawerStateChanged(int newState) { - } - } - - /** - * Sets the color of (or tints) a view (or views). - */ - public interface ViewFader { - void setColor(int color); - } - - public CarDrawerLayout(Context context) { - this(context, null); - } - - public CarDrawerLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CarDrawerLayout(final Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - mViewFaders = new HashSet<>(); - mEndingViewColor = getResources().getColor(R.color.car_tint); - - mEdgeHighlightPaint.setColor(getResources().getColor(android.R.color.black)); - - final float density = getResources().getDisplayMetrics().density; - final float minVel = MIN_FLING_VELOCITY * density; - - ViewDragCallback viewDragCallback = new ViewDragCallback(); - mDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, viewDragCallback); - mDragger.setMinVelocity(minVel); - viewDragCallback.setDragger(mDragger); - - ViewGroupCompat.setMotionEventSplittingEnabled(this, false); - - if (SHADOW_ENABLED) { - setDrawerShadow(CarUiResourceLoader.getDrawable(context, "drawer_shadow")); - } - - Resources.Theme theme = context.getTheme(); - TypedArray ta = theme.obtainStyledAttributes(new int[] { - android.R.attr.colorPrimaryDark - }); - setScrimColor(ta.getColor(0, context.getResources().getColor(R.color.car_grey_900))); - - mViewFaderInterpolator = new ReversibleInterpolator( - new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.25f, 0.25f, 0.5f), - new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.43f, 0.14f, 0.43f) - ); - mDrawerFadeInterpolator = new ReversibleInterpolator( - new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.625f, 0.25f, 0.125f), - new QuantumInterpolator(QuantumInterpolator.FAST_OUT_LINEAR_IN, 0.58f, 0.14f, 0.28f) - ); - - mHasWheel = CarUiResourceLoader.getBoolean(context, "has_wheel", false); - } - - @Override - public boolean dispatchKeyEvent(@NonNull KeyEvent keyEvent) { - int action = keyEvent.getAction(); - int keyCode = keyEvent.getKeyCode(); - final View drawerView = findDrawerView(); - if (drawerView != null && getDrawerLockMode(drawerView) == LOCK_MODE_UNLOCKED) { - if (isDrawerOpen()) { - if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT - || keyCode == KeyEvent.KEYCODE_SOFT_RIGHT) { - closeDrawer(); - return true; - } else if (keyCode == KeyEvent.KEYCODE_BACK - && action == KeyEvent.ACTION_UP - && mDrawerControllerListener != null) { - mDrawerControllerListener.onBack(); - return true; - } else { - return drawerView.dispatchKeyEvent(keyEvent); - } - } - } - - return mContentView.dispatchKeyEvent(keyEvent); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent ev) { - final View drawerView = findDrawerView(); - if (drawerView != null - && ev.getAction() == MotionEvent.ACTION_SCROLL - && mDrawerControllerListener != null - && mDrawerControllerListener.onScroll()) { - return true; - } - return super.dispatchGenericMotionEvent(ev); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mHasInflated = true; - setAutoDayNightMode(); - - setOnGenericMotionListener(new OnGenericMotionListener() { - @Override - public boolean onGenericMotion(View view, MotionEvent event) { - if (getChildCount() == 0) { - return false; - } - if (isDrawerOpen()) { - View drawerView = findDrawerView(); - ViewGroup viewGroup = (ViewGroup) ((FrameLayout) drawerView).getChildAt(0); - return viewGroup.getChildAt(0).onGenericMotionEvent(event); - } - View contentView = findContentView(); - ViewGroup viewGroup = (ViewGroup) ((FrameLayout) contentView).getChildAt(0); - return viewGroup.getChildAt(0).onGenericMotionEvent(event); - } - }); - } - - /** - * Set a simple drawable used for the left or right shadow. - * The drawable provided must have a nonzero intrinsic width. - * - * @param shadowDrawable Shadow drawable to use at the edge of a drawer - */ - public void setDrawerShadow(Drawable shadowDrawable) { - mShadow = shadowDrawable; - invalidate(); - } - - - - /** - * Set a color to use for the scrim that obscures primary content while a drawer is open. - * - * @param color Color to use in 0xAARRGGBB format. - */ - public void setScrimColor(int color) { - mScrimColor = color; - invalidate(); - } - - /** - * Set a listener to be notified of drawer events. - * - * @param listener Listener to notify when drawer events occur - * @see DrawerListener - */ - public void setDrawerListener(DrawerListener listener) { - mDrawerListener = listener; - } - - public void setDrawerControllerListener(DrawerControllerListener listener) { - mDrawerControllerListener = listener; - } - - /** - * Enable or disable interaction with all drawers. - * - * <p>This allows the application to restrict the user's ability to open or close - * any drawer within this layout. DrawerLayout will still respond to calls to - * {@link #openDrawer()}, {@link #closeDrawer()} and friends if a drawer is locked.</p> - * - * <p>Locking drawers open or closed will implicitly open or close - * any drawers as appropriate.</p> - * - * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - */ - public void setDrawerLockMode(int lockMode) { - LayoutParams lp = (LayoutParams) findDrawerView().getLayoutParams(); - setDrawerLockMode(lockMode, lp.gravity); - } - - /** - * Enable or disable interaction with the given drawer. - * - * <p>This allows the application to restrict the user's ability to open or close - * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer()}, - * {@link #closeDrawer()} and friends if a drawer is locked.</p> - * - * <p>Locking a drawer open or closed will implicitly open or close - * that drawer as appropriate.</p> - * - * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - * @param edgeGravity Gravity.LEFT, RIGHT, START or END. - * Expresses which drawer to change the mode for. - * - * @see #LOCK_MODE_UNLOCKED - * @see #LOCK_MODE_LOCKED_CLOSED - * @see #LOCK_MODE_LOCKED_OPEN - */ - public void setDrawerLockMode(int lockMode, int edgeGravity) { - final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, - ViewCompat.getLayoutDirection(this)); - if (absGravity == Gravity.LEFT) { - mLockModeLeft = lockMode; - } else if (absGravity == Gravity.RIGHT) { - mLockModeRight = lockMode; - } - if (lockMode != LOCK_MODE_UNLOCKED) { - // Cancel interaction in progress - mDragger.cancel(); - } - switch (lockMode) { - case LOCK_MODE_LOCKED_OPEN: - openDrawer(); - break; - case LOCK_MODE_LOCKED_CLOSED: - closeDrawer(); - break; - // default: do nothing - } - } - - /** - * All view faders will be light when the drawer is open and fade to dark and it closes. - * NOTE: this will clear any existing view faders. - */ - public void setLightMode() { - mStartingViewColor = getResources().getColor(R.color.car_title_light); - mEndingViewColor = getResources().getColor(R.color.car_tint); - updateViewFaders(); - } - - /** - * All view faders will be dark when the drawer is open and stay that way when it closes. - * NOTE: this will clear any existing view faders. - */ - public void setDarkMode() { - mStartingViewColor = getResources().getColor(R.color.car_title_dark); - mEndingViewColor = getResources().getColor(R.color.car_tint); - updateViewFaders(); - } - - /** - * All view faders will be dark during the day and light at night. - * NOTE: this will clear any existing view faders. - */ - public void setAutoDayNightMode() { - mStartingViewColor = getResources().getColor(R.color.car_title); - mEndingViewColor = getResources().getColor(R.color.car_tint); - updateViewFaders(); - } - - private void resetViewFaders() { - mViewFaders.clear(); - } - - /** - * Check the lock mode of the given drawer view. - * - * @param drawerView Drawer view to check lock mode - * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or - * {@link #LOCK_MODE_LOCKED_OPEN}. - */ - public int getDrawerLockMode(View drawerView) { - final int absGravity = getDrawerViewAbsoluteGravity(drawerView); - if (absGravity == Gravity.LEFT) { - return mLockModeLeft; - } else if (absGravity == Gravity.RIGHT) { - return mLockModeRight; - } - return LOCK_MODE_UNLOCKED; - } - - /** - * Resolve the shared state of all drawers from the component ViewDragHelpers. - * Should be called whenever a ViewDragHelper's state changes. - */ - private void updateDrawerState(int activeState) { - View drawerView = findDrawerView(); - if (drawerView != null && activeState == STATE_IDLE) { - if (onScreen() == 0) { - dispatchOnDrawerClosed(drawerView); - } else if (onScreen() == 1) { - dispatchOnDrawerOpened(drawerView); - } - } - - if (mDragger.getViewDragState() != mDrawerState) { - mDrawerState = mDragger.getViewDragState(); - - if (mDrawerListener != null) { - mDrawerListener.onDrawerStateChanged(mDragger.getViewDragState()); - } - } - } - - private void dispatchOnDrawerClosed(View drawerView) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (lp.knownOpen) { - lp.knownOpen = false; - if (mDrawerListener != null) { - mDrawerListener.onDrawerClosed(drawerView); - } - } - } - - private void dispatchOnDrawerOpened(View drawerView) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (!lp.knownOpen) { - lp.knownOpen = true; - if (mDrawerListener != null) { - mDrawerListener.onDrawerOpened(drawerView); - } - } - } - - private void dispatchOnDrawerSlide(View drawerView, float slideOffset) { - if (mDrawerListener != null) { - mDrawerListener.onDrawerSlide(drawerView, slideOffset); - } - } - - private void dispatchOnDrawerOpening(View drawerView) { - if (mDrawerListener != null) { - mDrawerListener.onDrawerOpening(drawerView); - } - } - - private void dispatchOnDrawerClosing(View drawerView) { - if (mDrawerListener != null) { - mDrawerListener.onDrawerClosing(drawerView); - } - } - - private void setDrawerViewOffset(View drawerView, float slideOffset) { - if (slideOffset == onScreen()) { - return; - } - - LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - lp.onScreen = slideOffset; - dispatchOnDrawerSlide(drawerView, slideOffset); - } - - private float onScreen() { - return ((LayoutParams) findDrawerView().getLayoutParams()).onScreen; - } - - /** - * @return the absolute gravity of the child drawerView, resolved according - * to the current layout direction - */ - private int getDrawerViewAbsoluteGravity(View drawerView) { - final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; - return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); - } - - private boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) { - final int absGravity = getDrawerViewAbsoluteGravity(drawerView); - return (absGravity & checkFor) == checkFor; - } - - /** - * @return the drawer view - */ - private View findDrawerView() { - if (mDrawerView != null) { - return mDrawerView; - } - - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final int childAbsGravity = getDrawerViewAbsoluteGravity(child); - if (childAbsGravity != Gravity.NO_GRAVITY) { - mDrawerView = child; - return child; - } - } - throw new IllegalStateException("No drawer view found."); - } - - /** - * @return the content. NOTE: this is the view with no gravity. - */ - private View findContentView() { - if (mContentView != null) { - return mContentView; - } - - final int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; --i) { - final View child = getChildAt(i); - if (isDrawerView(child)) { - continue; - } - mContentView = child; - return child; - } - throw new IllegalStateException("No content view found."); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - } - - @Override - public boolean requestFocus(int direction, Rect rect) { - // Optimally we want to check isInTouchMode(), but that value isn't always correct. - if (mHasWheel) { - mNeedsFocus = true; - } - return super.requestFocus(direction, rect); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mFirstLayout = true; - // There needs to be a layout pending if we're not going to animate the drawer until the - // next layout, so make it so. - requestLayout(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { - if (isInEditMode()) { - // Don't crash the layout editor. Consume all of the space if specified - // or pick a magic number from thin air otherwise. - // TODO Better communication with tools of this bogus state. - // It will crash on a real device. - if (widthMode == MeasureSpec.UNSPECIFIED) { - widthSize = 300; - } - else if (heightMode == MeasureSpec.UNSPECIFIED) { - heightSize = 300; - } - } else { - throw new IllegalArgumentException( - "DrawerLayout must be measured with MeasureSpec.EXACTLY."); - } - } - - setMeasuredDimension(widthSize, heightSize); - - View view = findContentView(); - LayoutParams lp = ((LayoutParams) view.getLayoutParams()); - // Content views get measured at exactly the layout's size. - final int contentWidthSpec = MeasureSpec.makeMeasureSpec( - widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); - final int contentHeightSpec = MeasureSpec.makeMeasureSpec( - heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); - view.measure(contentWidthSpec, contentHeightSpec); - - view = findDrawerView(); - lp = ((LayoutParams) view.getLayoutParams()); - final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, - lp.leftMargin + lp.rightMargin, - lp.width); - final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, - lp.topMargin + lp.bottomMargin, - lp.height); - view.measure(drawerWidthSpec, drawerHeightSpec); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mInLayout = true; - final int width = r - l; - - View contentView = findContentView(); - View drawerView = findDrawerView(); - - LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); - LayoutParams contentLp = (LayoutParams) contentView.getLayoutParams(); - - int contentRight = contentLp.getMarginStart() + getWidth(); - contentView.layout(contentRight - contentView.getMeasuredWidth(), - contentLp.topMargin, contentRight, - contentLp.topMargin + contentView.getMeasuredHeight()); - - final int childHeight = drawerView.getMeasuredHeight(); - int onScreen = (int) (drawerView.getWidth() * drawerLp.onScreen); - int offset; - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - offset = onScreen - drawerView.getWidth(); - } else { - offset = width - onScreen; - } - drawerView.layout(drawerLp.getMarginStart() + offset, drawerLp.topMargin, - width - drawerLp.getMarginEnd() + offset, - childHeight + drawerLp.topMargin); - updateDrawerAlpha(); - updateViewFaders(); - if (mFirstLayout) { - - // TODO(b/15394507): Normally, onMeasure()/onLayout() are called three times when - // you create CarDrawerLayout, but when you pop it back it's only called once which - // leaves us in a weird state. This is a pretty ugly hack to fix that. - mHandler.post(mInvalidateRunnable); - - mFirstLayout = false; - } - - if (mNeedsFocus) { - if (initializeFocus()) { - mNeedsFocus = false; - } - } - - mInLayout = false; - } - - private boolean initializeFocus() { - // Only request focus if the current view that needs focus doesn't already have it. This - // prevents some nasty bugs where focus ends up snapping to random elements and also saves - // a bunch of cycles in the average case. - mDrawerView.setFocusable(false); - mContentView.setFocusable(false); - boolean needFocus = !mDrawerView.hasFocus() && !mContentView.hasFocus(); - if (!needFocus) { - return true; - } - - // Find something in the hierarchy to give focus to. - List<View> focusables; - boolean drawerOpen = isDrawerOpen(); - if (drawerOpen) { - focusables = mDrawerView.getFocusables(FOCUS_DOWN); - } else { - focusables = mContentView.getFocusables(FOCUS_DOWN); - } - - // The 2 else cases here are a catch all for when nothing is focusable in view hierarchy. - // If you don't have anything focusable on screen, key events will not be delivered to - // the view hierarchy and you end up getting stuck without being able to open / close the - // drawer or launch gsa. - - if (!focusables.isEmpty()) { - focusables.get(0).requestFocus(); - return true; - } else if (drawerOpen) { - mDrawerView.setFocusable(true); - } else { - mContentView.setFocusable(true); - } - return false; - } - - @Override - public void requestLayout() { - if (!mInLayout) { - super.requestLayout(); - } - } - - @Override - public void computeScroll() { - if (mDragger.continueSettling(true)) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - private static boolean hasOpaqueBackground(View v) { - final Drawable bg = v.getBackground(); - return bg != null && bg.getOpacity() == PixelFormat.OPAQUE; - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - final int height = getHeight(); - final boolean drawingContent = isContentView(child); - int clipLeft = findContentView().getLeft(); - int clipRight = findContentView().getRight(); - final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; - - final int restoreCount = canvas.save(); - if (drawingContent) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View v = getChildAt(i); - if (v == child || v.getVisibility() != VISIBLE || - !hasOpaqueBackground(v) || !isDrawerView(v) || - v.getHeight() < height) { - continue; - } - - if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { - final int vright = v.getRight(); - if (vright > clipLeft) { - clipLeft = vright; - } - } else { - final int vleft = v.getLeft(); - if (vleft < clipRight) { - clipRight = vleft; - } - } - } - canvas.clipRect(clipLeft, 0, clipRight, getHeight()); - } - final boolean result = super.drawChild(canvas, child, drawingTime); - canvas.restoreToCount(restoreCount); - - if (drawingContent) { - int scrimAlpha = SCRIM_ENABLED ? - (int) (baseAlpha * Math.max(0, Math.min(1, onScreen())) * MAX_SCRIM_ALPHA) : 0; - - if (scrimAlpha > 0) { - int color = scrimAlpha << 24 | (mScrimColor & 0xffffff); - mScrimPaint.setColor(color); - - canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); - - canvas.drawRect(clipLeft - 1, 0, clipLeft, getHeight(), mEdgeHighlightPaint); - } - - LayoutParams drawerLp = (LayoutParams) findDrawerView().getLayoutParams(); - if (mShadow != null - && checkDrawerViewAbsoluteGravity(findDrawerView(), Gravity.LEFT)) { - final int offScreen = (int) ((1 - drawerLp.onScreen) * findDrawerView().getWidth()); - final int drawerRight = getWidth() - drawerLp.getMarginEnd() - offScreen; - final int shadowWidth = mShadow.getIntrinsicWidth(); - final float alpha = - Math.max(0, Math.min((float) drawerRight / mDragger.getEdgeSize(), 1.f)); - mShadow.setBounds(drawerRight, child.getTop(), - drawerRight + shadowWidth, child.getBottom()); - mShadow.setAlpha((int) (255 * alpha * alpha * alpha)); - mShadow.draw(canvas); - } else if (mShadow != null - && checkDrawerViewAbsoluteGravity(findDrawerView(),Gravity.RIGHT)) { - final int onScreen = (int) (findDrawerView().getWidth() * drawerLp.onScreen); - final int drawerLeft = drawerLp.getMarginStart() + getWidth() - onScreen; - final int shadowWidth = mShadow.getIntrinsicWidth(); - final float alpha = - Math.max(0, Math.min((float) onScreen / mDragger.getEdgeSize(), 1.f)); - canvas.save(); - canvas.translate(2 * drawerLeft - shadowWidth, 0); - canvas.scale(-1.0f, 1.0f); - mShadow.setBounds(drawerLeft - shadowWidth, child.getTop(), - drawerLeft, child.getBottom()); - mShadow.setAlpha((int) (255 * alpha * alpha * alpha * alpha)); - mShadow.draw(canvas); - canvas.restore(); - } - } - return result; - } - - private boolean isContentView(View child) { - return child == findContentView(); - } - - private boolean isDrawerView(View child) { - return child == findDrawerView(); - } - - private void updateDrawerAlpha() { - float alpha; - if (mStartedOpen) { - alpha = mDrawerFadeInterpolator.getReverseInterpolation(onScreen()); - } else { - alpha = mDrawerFadeInterpolator.getForwardInterpolation(onScreen()); - } - ViewGroup drawerView = (ViewGroup) findDrawerView(); - int drawerChildCount = drawerView.getChildCount(); - for (int i = 0; i < drawerChildCount; i++) { - drawerView.getChildAt(i).setAlpha(alpha); - } - } - - /** - * Add a view fader whose color will be set as the drawer opens and closes. - */ - public void addViewFader(ViewFader viewFader) { - addViewFader(viewFader, mStartingViewColor, mEndingViewColor); - } - - public void addViewFader(ViewFader viewFader, int startingColor, int endingColor) { - mViewFaders.add(new ViewFaderHolder(viewFader, startingColor, endingColor)); - updateViewFaders(); - } - - public void removeViewFader(ViewFader viewFader) { - for (Iterator<ViewFaderHolder> it = mViewFaders.iterator(); it.hasNext(); ) { - ViewFaderHolder viewFaderHolder = it.next(); - if (viewFaderHolder.viewFader.equals(viewFader)) { - it.remove(); - } - } - } - - private void updateViewFaders() { - if (!mHasInflated) { - return; - } - - float fadeProgress; - if (mStartedOpen) { - fadeProgress = mViewFaderInterpolator.getReverseInterpolation(onScreen()); - } else { - fadeProgress = mViewFaderInterpolator.getForwardInterpolation(onScreen()); - } - for (Iterator<ViewFaderHolder> it = mViewFaders.iterator(); it.hasNext(); ) { - ViewFaderHolder viewFaderHolder = it.next(); - int startingColor = viewFaderHolder.startingColor; - int endingColor = viewFaderHolder.endingColor; - int alpha = weightedAverage(Color.alpha(startingColor), - Color.alpha(endingColor), fadeProgress); - int red = weightedAverage(Color.red(startingColor), - Color.red(endingColor), fadeProgress); - int green = weightedAverage(Color.green(startingColor), - Color.green(endingColor), fadeProgress); - int blue = weightedAverage(Color.blue(startingColor), - Color.blue(endingColor), fadeProgress); - viewFaderHolder.viewFader.setColor(alpha << 24 | red << 16 | green << 8 | blue); - } - } - - private int weightedAverage(int starting, int ending, float weight) { - return (int) ((1f - weight) * starting + weight * ending); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - - // "|" used deliberately here; both methods should be invoked. - final boolean interceptForDrag = mDragger.shouldInterceptTouchEvent(ev); - - boolean interceptForTap = false; - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - if (onScreen() > 0 && isContentView(mDragger.findTopChildUnder((int) x, (int) y))) { - interceptForTap = true; - } - mChildrenCanceledTouch = false; - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - mChildrenCanceledTouch = false; - } - } - - return interceptForDrag || interceptForTap || mChildrenCanceledTouch; - } - - @Override - public boolean onTouchEvent(@NonNull MotionEvent ev) { - mDragger.processTouchEvent(ev); - final int absGravity = getDrawerViewAbsoluteGravity(findDrawerView()); - final int edge; - if (absGravity == Gravity.LEFT) { - edge = ViewDragHelper.EDGE_LEFT; - } else { - edge = ViewDragHelper.EDGE_RIGHT; - } - - // don't allow views behind the drawer to be touched - boolean drawerPartiallyOpen = onScreen() > 0; - return mDragger.isEdgeTouched(edge) || - mDragger.getCapturedView() != null || - drawerPartiallyOpen; - } - - @Override - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - if (CHILDREN_DISALLOW_INTERCEPT) { - // If we have an edge touch we want to skip this and track it for later instead. - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - - View drawerView = findDrawerView(); - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.RIGHT)) { - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - } - - /** - * Open the drawer view by animating it into view. - */ - public void openDrawer() { - ViewGroup drawerView = (ViewGroup) findDrawerView(); - mStartedOpen = false; - - if (hasWindowFocus()) { - int left; - LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - left = drawerLp.getMarginStart(); - } else { - left = drawerLp.getMarginStart() + getWidth() - drawerView.getWidth(); - } - mDragger.smoothSlideViewTo(drawerView, left, drawerView.getTop()); - dispatchOnDrawerOpening(drawerView); - } else { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - lp.onScreen = 1.f; - dispatchOnDrawerOpened(drawerView); - } - - ViewGroup contentView = (ViewGroup) findContentView(); - contentView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - drawerView.setDescendantFocusability(ViewGroup. FOCUS_AFTER_DESCENDANTS); - - View focusable = drawerView.getChildAt(0); - if (focusable != null) { - focusable.requestFocus(); - } - invalidate(); - } - - /** - * Close the specified drawer view by animating it into view. - */ - public void closeDrawer() { - ViewGroup drawerView = (ViewGroup) findDrawerView(); - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); - } - mStartedOpen = true; - - // Don't trigger the close drawer animation if drawer is not open. - if (hasWindowFocus() && isDrawerOpen()) { - int left; - LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - left = drawerLp.getMarginStart() - drawerView.getWidth(); - } else { - left = drawerLp.getMarginStart() + getWidth(); - } - mDragger.smoothSlideViewTo(drawerView, left, drawerView.getTop()); - dispatchOnDrawerClosing(drawerView); - } else { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - lp.onScreen = 0.f; - dispatchOnDrawerClosed(drawerView); - } - - ViewGroup contentView = (ViewGroup) findContentView(); - drawerView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - contentView.setDescendantFocusability(ViewGroup. FOCUS_AFTER_DESCENDANTS); - - if (!isInTouchMode()) { - List<View> focusables = contentView.getFocusables(FOCUS_DOWN); - if (focusables.size() > 0) { - View candidate = focusables.get(0); - candidate.requestFocus(); - } - } - invalidate(); - } - - @Override - public void addFocusables(@NonNull ArrayList<View> views, int direction, int focusableMode) { - boolean drawerOpen = isDrawerOpen(); - if (drawerOpen) { - findDrawerView().addFocusables(views, direction, focusableMode); - } else { - findContentView().addFocusables(views, direction, focusableMode); - } - } - - /** - * Check if the given drawer view is currently in an open state. - * To be considered "open" the drawer must have settled into its fully - * visible state. To check for partial visibility use - * {@link #isDrawerVisible(android.view.View)}. - * - * @return true if the given drawer view is in an open state - * @see #isDrawerVisible(android.view.View) - */ - public boolean isDrawerOpen() { - return ((LayoutParams) findDrawerView().getLayoutParams()).knownOpen; - } - - /** - * Check if a given drawer view is currently visible on-screen. The drawer - * may be fully extended or anywhere in between. - * - * @param drawer Drawer view to check - * @return true if the given drawer is visible on-screen - * @see #isDrawerOpen() - */ - public boolean isDrawerVisible(View drawer) { - if (!isDrawerView(drawer)) { - throw new IllegalArgumentException("View " + drawer + " is not a drawer"); - } - return onScreen() > 0; - } - - /** - * Check if a given drawer view is currently visible on-screen. The drawer - * may be fully extended or anywhere in between. - * If there is no drawer with the given gravity this method will return false. - * - * @return true if the given drawer is visible on-screen - */ - public boolean isDrawerVisible() { - final View drawerView = findDrawerView(); - return drawerView != null && isDrawerVisible(drawerView); - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams - ? new LayoutParams((LayoutParams) p) - : p instanceof MarginLayoutParams - ? new LayoutParams((MarginLayoutParams) p) - : new LayoutParams(p); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && super.checkLayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - private boolean hasVisibleDrawer() { - return findVisibleDrawer() != null; - } - - private View findVisibleDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (isDrawerView(child) && isDrawerVisible(child)) { - return child; - } - } - return null; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - SavedState ss = null; - if (state.getClass().getClassLoader() != getClass().getClassLoader()) { - // Class loader mismatch, recreate from parcel. - Parcel stateParcel = Parcel.obtain(); - state.writeToParcel(stateParcel, 0); - ss = SavedState.CREATOR.createFromParcel(stateParcel); - } else { - ss = (SavedState) state; - } - super.onRestoreInstanceState(ss.getSuperState()); - - if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { - openDrawer(); - } - - setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); - setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); - } - - @Override - protected Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - - final SavedState ss = new SavedState(superState); - - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (!isDrawerView(child)) { - continue; - } - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.knownOpen) { - ss.openDrawerGravity = lp.gravity; - // Only one drawer can be open at a time. - break; - } - } - - ss.lockModeLeft = mLockModeLeft; - ss.lockModeRight = mLockModeRight; - - return ss; - } - - /** - * State persisted across instances - */ - protected static class SavedState extends BaseSavedState { - int openDrawerGravity = Gravity.NO_GRAVITY; - int lockModeLeft = LOCK_MODE_UNLOCKED; - int lockModeRight = LOCK_MODE_UNLOCKED; - - public SavedState(Parcel in) { - super(in); - openDrawerGravity = in.readInt(); - lockModeLeft = in.readInt(); - lockModeRight = in.readInt(); - } - - public SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(openDrawerGravity); - dest.writeInt(lockModeLeft); - dest.writeInt(lockModeRight); - } - - @SuppressWarnings("hiding") - public static final Creator<SavedState> CREATOR = - new Creator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel source) { - return new SavedState(source); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - private class ViewDragCallback extends ViewDragHelper.Callback { - @SuppressWarnings("hiding") - private ViewDragHelper mDragger; - - public void setDragger(ViewDragHelper dragger) { - mDragger = dragger; - } - - @Override - public boolean tryCaptureView(View child, int pointerId) { - CarDrawerLayout.LayoutParams lp = (LayoutParams) findDrawerView().getLayoutParams(); - int edges = EDGE_DRAG_ENABLED ? ViewDragHelper.EDGE_ALL : 0; - boolean captured = isContentView(child) && - getDrawerLockMode(child) == LOCK_MODE_UNLOCKED && - (lp.knownOpen || mDragger.isEdgeTouched(edges)); - if (captured && lp.knownOpen) { - mStartedOpen = true; - } else if (captured && !lp.knownOpen) { - mStartedOpen = false; - } - // We want dragging starting on the content view to drag the drawer. Therefore when - // touch events try to capture the content view, we force capture of the drawer view. - if (captured) { - mDragger.captureChildView(findDrawerView(), pointerId); - } - return false; - } - - @Override - public void onViewDragStateChanged(int state) { - updateDrawerState(state); - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - float offset; - View drawerView = findDrawerView(); - final int drawerWidth = drawerView.getWidth(); - // This reverses the positioning shown in onLayout. - if (checkDrawerViewAbsoluteGravity(findDrawerView(), Gravity.LEFT)) { - offset = (float) (left + drawerWidth) / drawerWidth; - } else { - offset = (float) (getWidth() - left) / drawerWidth; - } - setDrawerViewOffset(findDrawerView(), offset); - - updateDrawerAlpha(); - - updateViewFaders(); - invalidate(); - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - final View drawerView = findDrawerView(); - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - int left; - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - // Open the drawer if they are swiping right or if they are not currently moving but - // have moved the drawer in the current gesture and released the drawer when it was - // fully open. - // Close otherwise. - left = xvel > 0 ? lp.getMarginStart() : lp.getMarginStart() - drawerView.getWidth(); - } else { - // See comment for left drawer. - left = xvel < 0 ? lp.getMarginStart() + getWidth() - drawerView.getWidth() - : lp.getMarginStart() + getWidth(); - } - - mDragger.settleCapturedViewAt(left, releasedChild.getTop()); - invalidate(); - } - - @Override - public boolean onEdgeLock(int edgeFlags) { - if (ALLOW_EDGE_LOCK) { - if (!isDrawerOpen()) { - closeDrawer(); - } - return true; - } - return false; - } - - @Override - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - View drawerView = findDrawerView(); - if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.RIGHT)) { - drawerView = null; - } - } else { - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - drawerView = null; - } - } - - if (drawerView != null && getDrawerLockMode(drawerView) == LOCK_MODE_UNLOCKED) { - mDragger.captureChildView(drawerView, pointerId); - } - } - - @Override - public int getViewHorizontalDragRange(View child) { - return child.getWidth(); - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - final View drawerView = findDrawerView(); - LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - return Math.max(drawerLp.getMarginStart() - drawerView.getWidth(), - Math.min(left, drawerLp.getMarginStart())); - } else { - return Math.max(drawerLp.getMarginStart() + getWidth() - drawerView.getWidth(), - Math.min(left, drawerLp.getMarginStart() + getWidth())); - } - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - return child.getTop(); - } - } - - public static class LayoutParams extends MarginLayoutParams { - - public int gravity = Gravity.NO_GRAVITY; - float onScreen; - boolean knownOpen; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - - final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - gravity = a.getInt(0, Gravity.NO_GRAVITY); - a.recycle(); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(int width, int height, int gravity) { - this(width, height); - this.gravity = gravity; - } - - public LayoutParams(LayoutParams source) { - super(source); - gravity = source.gravity; - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(MarginLayoutParams source) { - super(source); - } - } - - private static final class ViewFaderHolder { - public final ViewFader viewFader; - public final int startingColor; - public final int endingColor; - - public ViewFaderHolder(ViewFader viewFader, int startingColor, int endingColor) { - this.viewFader = viewFader; - this.startingColor = startingColor; - this.endingColor = endingColor; - } - - } -} diff --git a/car-ui-provider/src/android/car/ui/provider/CarRecyclerView.java b/car-ui-provider/src/android/car/ui/provider/CarRecyclerView.java deleted file mode 100644 index 04fcd63b63..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/CarRecyclerView.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.content.Context; -import android.util.AttributeSet; - -/** - * Clone of {@link android.support.car.ui.CarRecyclerView} to be used by CarUiProvider. - * Workaround for b/25595320 - */ -public class CarRecyclerView extends android.support.car.ui.CarRecyclerView { - public CarRecyclerView(Context context) { - super(context); - } - - public CarRecyclerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CarRecyclerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } -} diff --git a/car-ui-provider/src/android/car/ui/provider/CarUiEntry.java b/car-ui-provider/src/android/car/ui/provider/CarUiEntry.java deleted file mode 100644 index 3668e03c5d..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/CarUiEntry.java +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.car.app.menu.CarMenuCallbacks; -import android.car.app.menu.RootMenu; -import android.car.app.menu.SearchBoxEditListener; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.BitmapDrawable; -import android.os.Bundle; -import android.support.car.input.CarRestrictedEditText; -import android.support.car.ui.DrawerArrowDrawable; -import android.support.car.ui.PagedListView; -import android.support.v7.widget.CardView; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.Gravity; -import android.view.KeyEvent; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.view.View; -import android.view.LayoutInflater; - -import android.support.car.ui.R; - -public class CarUiEntry extends android.car.app.menu.CarUiEntry { - private static final String TAG = "Embedded_CarUiEntry"; - - // These values and setSearchBoxMode exist rather than separate methods to make sure exactly the - // same set of things get set for each mode, just to different values. - /** The search box is not visible. */ - private static final int SEARCH_BOX_MODE_NONE = 0; - /** The small search box is shown in the header beneath the microphone button. */ - private static final int SEARCH_BOX_MODE_SMALL = 1; - /** The whole header between the menu button and the microphone button is taken up by the - * search box. */ - private static final int SEARCH_BOX_MODE_LARGE = 2; - - private View mContentView; - private ImageView mMenuButton; - private TextView mTitleView; - private CardView mTruncatedListCardView; - private CarDrawerLayout mDrawerLayout; - private DrawerController mDrawerController; - private PagedListView mListView; - private DrawerArrowDrawable mDrawerArrowDrawable; - private CarRestrictedEditText mCarRestrictedEditText; - private SearchBoxClickListener mSearchBoxClickListener; - - private View mSearchBox; - private View mSearchBoxContents; - private View mSearchBoxSearchLogoContainer; - private ImageView mSearchBoxSearchLogo; - private ImageView mSearchBoxSuperSearchLogo; - private FrameLayout mSearchBoxEndView; - private View mTitleContainer; - private SearchBoxEditListener mSearchBoxEditListener; - - public interface SearchBoxClickListener { - /** - * The user clicked the search box while it was in small mode. - */ - void onClick(); - } - - public CarUiEntry(Context providerContext, Context appContext) { - super(providerContext, appContext); - } - - @Override - public View getContentView() { - LayoutInflater inflater = LayoutInflater.from(mUiLibContext); - mContentView = inflater.inflate(R.layout.car_activity, null); - mDrawerLayout = (CarDrawerLayout) mContentView.findViewById(R.id.drawer_container); - adjustDrawer(); - mMenuButton = (ImageView) mContentView.findViewById(R.id.car_drawer_button); - mTitleView = (TextView) mContentView.findViewById(R.id.car_drawer_title); - mTruncatedListCardView = (CardView) mContentView.findViewById(R.id.truncated_list_card); - mDrawerArrowDrawable = new DrawerArrowDrawable(mUiLibContext); - restoreMenuDrawable(); - mListView = (PagedListView) mContentView.findViewById(R.id.list_view); - mListView.setOnScrollBarListener(mOnScrollBarListener); - mMenuButton.setOnClickListener(mMenuListener); - mDrawerController = new DrawerController(this, mMenuButton, - mDrawerLayout, mListView, mTruncatedListCardView); - mTitleContainer = mContentView.findViewById(R.id.car_drawer_title_container); - - mSearchBoxEndView = (FrameLayout) mContentView.findViewById(R.id.car_search_box_end_view); - mSearchBox = mContentView.findViewById(R.id.car_search_box); - mSearchBoxContents = mContentView.findViewById(R.id.car_search_box_contents); - mSearchBoxSearchLogoContainer = mContentView.findViewById( - R.id.car_search_box_search_logo_container); - mSearchBoxSearchLogoContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mSearchBoxClickListener != null) { - mSearchBoxClickListener.onClick(); - } - } - }); - mSearchBoxSearchLogo = (ImageView) mContentView.findViewById( - R.id.car_search_box_search_logo); - mSearchBoxSearchLogo.setImageDrawable(mUiLibContext.getResources() - .getDrawable(R.drawable.ic_google)); - mSearchBoxSuperSearchLogo = (ImageView) mContentView.findViewById( - R.id.car_search_box_super_logo); - mSearchBoxSuperSearchLogo.setImageDrawable(mUiLibContext.getResources() - .getDrawable(R.drawable.ic_googleg)); - - mCarRestrictedEditText = (CarRestrictedEditText) mContentView.findViewById( - R.id.car_search_box_edit_text); - mCarRestrictedEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { - if (mSearchBoxEditListener != null) { - mSearchBoxEditListener.onSearch(mCarRestrictedEditText.getText().toString()); - } - return false; - } - }); - mCarRestrictedEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence text, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence text, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable text) { - if (mSearchBoxEditListener != null) { - mSearchBoxEditListener.onEdit(text.toString()); - } - } - }); - setSearchBoxMode(SEARCH_BOX_MODE_NONE); - return mContentView; - } - - private final View.OnClickListener mMenuListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - CarUiEntry.this.mDrawerController.openDrawer(); - } - }; - - @Override - public void setCarMenuCallbacks(CarMenuCallbacks callbacks){ - RootMenu rootMenu = callbacks.getRootMenu(null); - if (rootMenu != null) { - mDrawerController.setRootAndCallbacks( - rootMenu.getId(), callbacks); - mDrawerController.setDrawerEnabled(true); - } else { - hideMenuButton(); - } - } - - @Override - public int getFragmentContainerId() { - return R.id.container; - } - - @Override - public void setBackground(Bitmap bitmap) { - BitmapDrawable bd = new BitmapDrawable(mUiLibContext.getResources(), bitmap); - ImageView bg = (ImageView) mContentView.findViewById(R.id.background); - bg.setBackground(bd); - } - - @Override - public void hideMenuButton() { - mMenuButton.setVisibility(View.GONE); - } - - @Override - public void restoreMenuDrawable() { - mMenuButton.setImageDrawable(mDrawerArrowDrawable); - } - - public void setMenuButtonBitmap(Bitmap bitmap) { - mMenuButton.setImageDrawable(new BitmapDrawable(mUiLibContext.getResources(), bitmap)); - } - - @Override - public void setScrimColor(int color) { - mDrawerLayout.setScrimColor(color); - } - - @Override - public void setTitle(CharSequence title) { - mDrawerController.setTitle(title); - } - - @Override - public void closeDrawer() { - mDrawerController.closeDrawer(); - } - - @Override - public void openDrawer() { - mDrawerController.openDrawer(); - } - - @Override - public void showMenu(String id, String title) { - mDrawerController.showMenu(id, title); - } - - - @Override - public void setMenuButtonColor(int color) { - setViewColor(mMenuButton, color); - setViewColor(mTitleView, color); - } - - @Override - public void showTitle() { - mTitleView.setVisibility(View.VISIBLE); - } - - @Override - public void hideTitle() { - mTitleView.setVisibility(View.GONE); - } - - @Override - public void setLightMode() { - mDrawerController.setLightMode(); - } - - @Override - public void setDarkMode() { - mDrawerController.setDarkMode(); - } - - @Override - public void setAutoLightDarkMode() { - mDrawerController.setAutoLightDarkMode(); - } - - @Override - public void showToast(String msg, long duration) { - // TODO: add toast support - } - - @Override - public CharSequence getSearchBoxText() { - return mCarRestrictedEditText.getText(); - } - - @Override - public EditText startInput(String hint, - View.OnClickListener searchBoxClickListener) { - mSearchBoxClickListener = wrapSearchBoxClickListener(searchBoxClickListener); - setSearchBoxModeLarge(hint); - return mCarRestrictedEditText; - } - - - @Override - public void onRestoreInstanceState(Bundle savedInstanceState) { - if (mDrawerController != null) { - mDrawerController.restoreState(savedInstanceState); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - if (mDrawerController != null) { - mDrawerController.saveState(outState); - } - } - - @Override - public void onStart() { - - } - - @Override - public void onResume() { - - } - - @Override - public void onPause() { - - } - - @Override - public void onStop() { - - } - - /** - * Sets the colors of all the parts of the search box (regardless of whether it is currently - * showing). - */ - @Override - public void setSearchBoxColors(int backgroundColor, int searchLogoColor, int textColor, - int hintTextColor) { - // set background color of mSearchBox to get rid of the animation artifact in b/23767062 - mSearchBox.setBackgroundColor(backgroundColor); - mSearchBoxContents.setBackgroundColor(backgroundColor); - mSearchBoxSearchLogo.setColorFilter(searchLogoColor, PorterDuff.Mode.SRC_IN); - mCarRestrictedEditText.setTextColor(textColor); - mCarRestrictedEditText.setHintTextColor(hintTextColor); - } - - /** - * Sets the view to be displayed at the end of the search box, or null to clear any existing - * views. - */ - @Override - public void setSearchBoxEndView(View endView) { - if (endView == null) { - mSearchBoxEndView.removeAllViews(); - } else if (mSearchBoxEndView.getChildCount() == 0) { - mSearchBoxEndView.addView(endView); - } else if (mSearchBoxEndView.getChildAt(0) != endView) { - mSearchBoxEndView.removeViewAt(0); - mSearchBoxEndView.addView(endView); - } - } - - @Override - public void showSearchBox(final View.OnClickListener listener) { - setSearchBoxMode(SEARCH_BOX_MODE_SMALL); - mSearchBoxClickListener = wrapSearchBoxClickListener(listener); - } - - @Override - public void stopInput() { - setSearchBoxMode(SEARCH_BOX_MODE_NONE); - } - - @Override - public void setSearchBoxEditListener(SearchBoxEditListener listener) { - mSearchBoxEditListener = listener; - } - - - /** - * Set the progress of the animated {@link DrawerArrowDrawable}. - * @param progress 0f displays a menu button - * 1f displays a back button - * anything in between will be an interpolation of the drawable between - * back and menu - */ - public void setMenuProgress(float progress) { - mDrawerArrowDrawable.setProgress(progress); - } - - private void setSearchBoxModeLarge(String hint) { - mCarRestrictedEditText.setHint(hint); - setSearchBoxMode(SEARCH_BOX_MODE_LARGE); - } - - public void setTitleText(CharSequence title) { - mTitleView.setText(title); - } - - /** - * Sets all the view visibilities and layout params for a search box mode. - */ - private void setSearchBoxMode(int searchBoxMode) { - // Set the visibility and width of the search box, and whether the rest of the header sits - // beside or beneath the microphone button. - LinearLayout.LayoutParams searchBoxLayoutParams = - (LinearLayout.LayoutParams) mSearchBox.getLayoutParams(); - if (searchBoxMode == SEARCH_BOX_MODE_LARGE) { - int screenWidth = mAppContext.getResources().getDisplayMetrics().widthPixels; - int searchBoxMargin = mUiLibContext.getResources() - .getDimensionPixelSize(R.dimen.car_drawer_header_menu_button_size); - int maxSearchBoxWidth = mUiLibContext.getResources().getDimensionPixelSize( - R.dimen.car_card_max_width); - int searchBoxMarginStart = 0; - int searchBoxMarginEnd = searchBoxMargin; - // If the width of search bar is larger than max card width, we adjust margin to fix it. - if (screenWidth - searchBoxMargin * 2 > maxSearchBoxWidth) { - searchBoxMarginEnd = (screenWidth - maxSearchBoxWidth) / 2; - searchBoxMarginStart = searchBoxMarginEnd - searchBoxMargin; - } - searchBoxLayoutParams.width = 0; - searchBoxLayoutParams.weight = 1.0f; - searchBoxLayoutParams.setMarginStart(searchBoxMarginStart); - searchBoxLayoutParams.setMarginEnd(searchBoxMarginEnd); - } else if (searchBoxMode == SEARCH_BOX_MODE_SMALL) { - searchBoxLayoutParams.width = mUiLibContext.getResources().getDimensionPixelSize( - R.dimen.car_app_layout_search_box_small_width); - searchBoxLayoutParams.weight = 0.0f; - searchBoxLayoutParams.setMarginStart(mUiLibContext.getResources() - .getDimensionPixelOffset(R.dimen.car_app_layout_search_box_small_margin)); - searchBoxLayoutParams.setMarginEnd(mUiLibContext.getResources().getDimensionPixelOffset( - R.dimen.car_app_layout_search_box_small_margin)); - } else { - searchBoxLayoutParams.width = mUiLibContext.getResources().getDimensionPixelSize( - R.dimen.car_app_layout_search_box_small_width); - searchBoxLayoutParams.weight = 0.0f; - searchBoxLayoutParams.setMarginStart(mUiLibContext.getResources().getDimensionPixelSize( - R.dimen.car_drawer_header_menu_button_size)); - searchBoxLayoutParams.setMarginEnd(-searchBoxLayoutParams.width); - } - mSearchBox.setLayoutParams(searchBoxLayoutParams); - - // Animate the visibility of the contents of the search box - either the Search logo or the - // edit text is visible (the super logo also is visible when the edit text is visible). - View searchBoxEditTextContainer = (View) mCarRestrictedEditText.getParent(); - if (searchBoxMode == SEARCH_BOX_MODE_SMALL) { - if (mSearchBoxSearchLogoContainer.getVisibility() != View.VISIBLE) { - mSearchBoxSearchLogoContainer.setAlpha(0f); - mSearchBoxSearchLogoContainer.setVisibility(View.VISIBLE); - } - // 300ms delay to stagger the fade in behind the fade out animation. - mSearchBoxSearchLogoContainer.animate().alpha(1f).setStartDelay(300); - // Animate the container so it includes the super G logo. - if (searchBoxEditTextContainer.getVisibility() == View.VISIBLE) { - searchBoxEditTextContainer.animate().alpha(0f).setStartDelay(0) - .withEndAction(mSetEditTextGoneRunnable); - } - } else if (searchBoxMode == SEARCH_BOX_MODE_LARGE) { - if (searchBoxEditTextContainer.getVisibility() != View.VISIBLE) { - searchBoxEditTextContainer.setAlpha(0f); - searchBoxEditTextContainer.setVisibility(View.VISIBLE); - } - searchBoxEditTextContainer.animate().alpha(1f).setStartDelay(300); - if (mSearchBoxSearchLogoContainer.getVisibility() == View.VISIBLE) { - mSearchBoxSearchLogoContainer.animate().alpha(0f).setStartDelay(0) - .withEndAction(mSetSearchBoxLogoGoneRunnable); - } - } else { - searchBoxEditTextContainer.setVisibility(View.GONE); - } - - // Set the visibility of the title and status containers. - if (searchBoxMode == SEARCH_BOX_MODE_LARGE) { - mTitleContainer.setVisibility(View.GONE); - } else { - mTitleContainer.setVisibility(View.VISIBLE); - } - } - - - private final Runnable mSetEditTextGoneRunnable = new Runnable() { - @Override - public void run() { - ((View) mCarRestrictedEditText.getParent()).setVisibility(View.GONE); - } - }; - - private final Runnable mSetSearchBoxLogoGoneRunnable = new Runnable() { - @Override - public void run() { - mSearchBoxSearchLogoContainer.setVisibility(View.GONE); - } - }; - - - private SearchBoxClickListener wrapSearchBoxClickListener(final View.OnClickListener listener) { - return new SearchBoxClickListener() { - @Override - public void onClick() { - listener.onClick(null); - } - }; - } - - - private static void setViewColor(View view, int color) { - if (view instanceof TextView) { - ((TextView) view).setTextColor(color); - } else if (view instanceof ImageView) { - ImageView imageView = (ImageView) view; - PorterDuffColorFilter filter = - new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); - imageView.setColorFilter(filter); - } else { - if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "Setting color is only supported for TextView and ImageView."); - } - } - } - - private void adjustDrawer() { - Resources resources = mUiLibContext.getResources(); - float width = resources.getDisplayMetrics().widthPixels; - CarDrawerLayout.LayoutParams layoutParams = new CarDrawerLayout.LayoutParams( - CarDrawerLayout.LayoutParams.MATCH_PARENT, - CarDrawerLayout.LayoutParams.MATCH_PARENT); - layoutParams.gravity = Gravity.LEFT; - // 1. If the screen width is larger than 800dp, the drawer width is kept as 704dp; - // 2. Else the drawer width is adjusted to keep the margin end of drawer as 96dp. - -// if (width > resources.getDimension(R.dimen.car_standard_width)) { -// layoutParams.setMarginEnd( -// (int) (width - resources.getDimension(R.dimen.car_drawer_standard_width))); -// } else { -// layoutParams.setMarginEnd( -// (int) resources.getDimension(R.dimen.car_card_margin)); -// } - // TODO: For UX, need to update max drawer width for the large screen use case. The previous - // 704dp width no longer works. - layoutParams.setMarginEnd((int) resources.getDimension(R.dimen.car_card_margin)); - mContentView.findViewById(R.id.drawer).setLayoutParams(layoutParams); - } - - private final PagedListView.OnScrollBarListener mOnScrollBarListener = - new PagedListView.OnScrollBarListener() { - - @Override - public void onReachBottom() { - if (mDrawerController.isTruncatedList()) { - mTruncatedListCardView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onLeaveBottom() { - mTruncatedListCardView.setVisibility(View.GONE); - } - }; -} diff --git a/car-ui-provider/src/android/car/ui/provider/DrawerApiAdapter.java b/car-ui-provider/src/android/car/ui/provider/DrawerApiAdapter.java deleted file mode 100644 index 894938b406..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/DrawerApiAdapter.java +++ /dev/null @@ -1,492 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.os.Handler; -import android.os.SystemProperties; -import android.support.car.ui.CarListItemViewHolder; -import android.support.car.ui.PagedListView; -import android.support.car.ui.R; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.RemoteViews; -import android.widget.TextView; - -import android.support.car.app.menu.CarMenu; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.FLAG_BROWSABLE; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.FLAG_FIRSTITEM; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_EMPTY_PLACEHOLDER; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_FLAGS; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_ID; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_LEFTICON; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_REMOTEVIEWS; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_RIGHTICON; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_RIGHTTEXT; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_TEXT; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_TITLE; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_WIDGET; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_WIDGET_STATE; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.WIDGET_CHECKBOX; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.WIDGET_TEXT_VIEW; - -public class DrawerApiAdapter extends RecyclerView.Adapter<CarListItemViewHolder> - implements PagedListView.ItemCap { - private static final String TAG = "CAR.UI.ADAPTER"; - private static final String INDEX_OUT_OF_BOUNDS_MESSAGE = "invalid item position"; - private static final String KEY_ID_UNAVAILABLE_CATEGORY = "UNAVAILABLE_CATEGORY"; - private static final String UNLIMITED_MODE_PROPERTY = "android.car.drawer.unlimited"; - - public interface OnItemSelectedListener { - void onItemClicked(Bundle item, int position); - boolean onItemLongClicked(Bundle item); - } - - private final Map<String, Integer> mIdToPosMap = new HashMap<>(); - - private final Object mItemsLock = new Object(); - private List<Bundle> mItems; - private boolean mIsCapped; - private OnItemSelectedListener mListener; - private int mMaxItems; - private boolean mUseSmallHolder; - private boolean mNoLeftIcon; - private boolean mIsEmptyPlaceholder; - private int mFirstItemIndex = 0; - - private final Handler mHandler = new Handler(); - - public DrawerApiAdapter() { - setHasStableIds(true); - } - - @Override - public int getItemViewType(int position) { - Bundle item; - try { - item = mItems.get(position); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return 0; - } - - if (KEY_ID_UNAVAILABLE_CATEGORY.equals(item.getString(KEY_ID))) { - return R.layout.car_unavailable_category; - } - - if (item.containsKey(KEY_EMPTY_PLACEHOLDER) && item.getBoolean(KEY_EMPTY_PLACEHOLDER)) { - return R.layout.car_list_item_empty; - } - - int flags = item.getInt(KEY_FLAGS); - if ((flags & FLAG_BROWSABLE) != 0 || item.containsKey(KEY_RIGHTICON)) { - return R.layout.car_imageview; - } - - if (!item.containsKey(KEY_WIDGET)) { - return 0; - } - - switch (item.getInt(KEY_WIDGET)) { - case WIDGET_CHECKBOX: - return R.layout.car_menu_checkbox; - case WIDGET_TEXT_VIEW: - return R.layout.car_textview; - default: - return 0; - } - } - - @Override - public CarListItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - View view; - if (viewType == R.layout.car_unavailable_category || - viewType == R.layout.car_list_item_empty) { - view = inflater.inflate(viewType, parent, false); - } else { - view = inflater.inflate(R.layout.car_menu_list_item, parent, false); - } - return new CarListItemViewHolder(view, viewType); - } - - @Override - public void setMaxItems(int maxItems) { - if (SystemProperties.getBoolean(UNLIMITED_MODE_PROPERTY, false)) { - mMaxItems = PagedListView.ItemCap.UNLIMITED; - } else { - mMaxItems = maxItems; - } - } - - @Override - public void onBindViewHolder(final CarListItemViewHolder holder, final int position) { - if (holder.getItemViewType() == R.layout.car_list_item_empty) { - onBindEmptyPlaceHolder(holder, position); - } else if (holder.getItemViewType() == R.layout.car_unavailable_category) { - onBindUnavailableCategoryView(holder); - } else { - onBindNormalView(holder, position); - if (mIsCapped) { - // Disable all menu items if it is under unavailable category case. - // TODO(b/24163545): holder.itemView.setAlpha() doesn't work all the time, - // which makes some items are gray out, the others are not. - setHolderStatus(holder, false, 0.3f); - } else { - setHolderStatus(holder, true, 1.0f); - } - } - - holder.itemView.setTag(position); - holder.itemView.setOnClickListener(mOnClickListener); - holder.itemView.setOnLongClickListener(mOnLongClickListener); - - // Ensure correct day/night mode colors are set and not out of sync. - setDayNightModeColors(holder); - } - - @Override - public int getItemCount() { - synchronized (mItemsLock) { - if (mItems != null) { - return mMaxItems != PagedListView.ItemCap.UNLIMITED ? - Math.min(mItems.size(), mMaxItems) : mItems.size(); - } - } - return 0; - } - - @Override - public long getItemId(int position) { - synchronized (mItemsLock) { - if (mItems != null) { - try { - return mItems.get(position).getString(KEY_ID).hashCode(); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, "invalid item index", e); - return RecyclerView.NO_ID; - } - } - } - return super.getItemId(position); - } - - public synchronized void setItems(List<Bundle> items, boolean isCapped) { - synchronized (mItemsLock) { - mItems = items; - } - mIsCapped = isCapped; - mFirstItemIndex = 0; - if (mItems != null) { - mIdToPosMap.clear(); - mUseSmallHolder = true; - mNoLeftIcon = true; - mIsEmptyPlaceholder = false; - int index = 0; - for (Bundle bundle : items) { - if (bundle.containsKey(KEY_EMPTY_PLACEHOLDER) - && bundle.getBoolean(KEY_EMPTY_PLACEHOLDER)) { - mIsEmptyPlaceholder = true; - if (items.size() != 1) { - throw new IllegalStateException("Empty placeholder should be the only" - + "item showing in the menu list!"); - } - } - - if (bundle.containsKey(KEY_TEXT) || bundle.containsKey(KEY_REMOTEVIEWS)) { - mUseSmallHolder = false; - } - if (bundle.containsKey(KEY_LEFTICON)) { - mNoLeftIcon = false; - } - if (bundle.containsKey(KEY_FLAGS) && - (bundle.getInt(KEY_FLAGS) & FLAG_FIRSTITEM) != 0) { - mFirstItemIndex = index; - } - mIdToPosMap.put(bundle.getString(KEY_ID), index); - index++; - } - } - notifyDataSetChanged(); - } - - public int getMaxItemsNumber() { - return mMaxItems; - } - - public void setItemSelectedListener(OnItemSelectedListener listener) { - mListener = listener; - } - - public int getFirstItemIndex() { - return mFirstItemIndex; - } - - public boolean isEmptyPlaceholder() { - return mIsEmptyPlaceholder; - } - - public void onChildChanged(RecyclerView.ViewHolder holder, Bundle bundle) { - synchronized (mItemsLock) { - // The holder will be null if the view has not been bound yet - if (holder != null) { - int position = holder.getAdapterPosition(); - if (position >= 0 && mItems != null && position < mItems.size()) { - final Bundle oldBundle; - try { - oldBundle = mItems.get(position); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return; - } - oldBundle.putAll(bundle); - notifyItemChanged(position); - } - } else { - String id = bundle.getString(KEY_ID); - int position = mIdToPosMap.get(id); - if (position >= 0 && mItems != null && position < mItems.size()) { - final Bundle item; - try { - item = mItems.get(position); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return; - } - if (id.equals(item.getString(KEY_ID))) { - item.putAll(bundle); - notifyItemChanged(position); - } - } - } - } - } - - public void setDayNightModeColors(RecyclerView.ViewHolder viewHolder) { - CarListItemViewHolder holder = (CarListItemViewHolder) viewHolder; - Context context = holder.itemView.getContext(); - holder.itemView.setBackgroundResource(R.drawable.car_list_item_background); - if (holder.getItemViewType() == R.layout.car_unavailable_category) { - holder.title.setTextAppearance(context, R.style.CarUnavailableCategory); - if (holder.text != null) { - holder.text.setTextAppearance(context, R.style.CarUnavailableCategory); - } - holder.icon.setImageTintList(ColorStateList - .valueOf(context.getResources().getColor(R.color.car_unavailable_category))); - } else { - holder.title.setTextAppearance(context, R.style.CarBody1); - if (holder.text != null) { - holder.text.setTextAppearance(context, R.style.CarBody2); - } - if (holder.rightCheckbox != null) { - holder.rightCheckbox.setButtonTintList( - ColorStateList.valueOf(context.getResources().getColor(R.color.car_tint))); - } else if (holder.rightImage != null) { - Object tag = holder.rightImage.getTag(); - if (tag != null && (int) tag != -1) { - holder.rightImage.setImageResource((int) tag); - } - } - } - } - - private void onBindEmptyPlaceHolder(final CarListItemViewHolder holder, final int position) { - maybeSetText(position, KEY_TITLE, holder.title); - if (!mNoLeftIcon) { - maybeSetBitmap(position, KEY_LEFTICON, holder.icon); - holder.iconContainer.setVisibility(View.VISIBLE); - } else { - holder.iconContainer.setVisibility(View.GONE); - } - } - - private void onBindUnavailableCategoryView(final CarListItemViewHolder holder) { - mNoLeftIcon = false; - holder.itemView.setEnabled(false); - } - - private void onBindNormalView(final CarListItemViewHolder holder, final int position) { - maybeSetText(position, KEY_TITLE, holder.title); - maybeSetText(position, KEY_TEXT, holder.text); - final Bundle item; - try { - item = new Bundle(mItems.get(position)); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return; - } - final int flags = item.getInt(KEY_FLAGS); - if ((flags & FLAG_BROWSABLE) != 0) { - // Set the resource id as the tag so we can reload it on day/night mode change. - // If the tag is -1 or not set, then assume the app will send an updated bitmap - holder.rightImage.setTag(R.drawable.ic_chevron_right); - holder.rightImage.setImageResource(R.drawable.ic_chevron_right); - } else if (holder.rightImage != null) { - maybeSetBitmap(position, KEY_RIGHTICON, holder.rightImage); - } - - if (holder.rightCheckbox != null) { - holder.rightCheckbox.setChecked(item.getBoolean( - KEY_WIDGET_STATE, false)); - holder.rightCheckbox.setOnClickListener(mOnClickListener); - holder.rightCheckbox.setTag(position); - } - if (holder.rightText != null) { - maybeSetText(position, KEY_RIGHTTEXT, holder.rightText); - } - if (!mNoLeftIcon) { - maybeSetBitmap(position, KEY_LEFTICON, holder.icon); - holder.iconContainer.setVisibility(View.VISIBLE); - } else { - holder.iconContainer.setVisibility(View.GONE); - } - if (item.containsKey(KEY_REMOTEVIEWS)) { - holder.remoteViewsContainer.setVisibility(View.VISIBLE); - RemoteViews views = item.getParcelable(KEY_REMOTEVIEWS); - View view = views.apply(holder.remoteViewsContainer.getContext(), - holder.remoteViewsContainer); - holder.remoteViewsContainer.removeAllViews(); - holder.remoteViewsContainer.addView(view); - } else { - holder.remoteViewsContainer.removeAllViews(); - holder.remoteViewsContainer.setVisibility(View.GONE); - } - - // Set the view holder size - Resources r = holder.itemView.getResources(); - ViewGroup.LayoutParams params = holder.itemView.getLayoutParams(); - params.height = mUseSmallHolder ? - r.getDimensionPixelSize(R.dimen.car_list_item_height_small) : - r.getDimensionPixelSize(R.dimen.car_list_item_height); - holder.itemView.setLayoutParams(params); - - // Set Icon size - params = holder.iconContainer.getLayoutParams(); - params.height = params.width = mUseSmallHolder ? - r.getDimensionPixelSize(R.dimen.car_list_item_small_icon_size) : - r.getDimensionPixelSize(R.dimen.car_list_item_icon_size); - - } - - private void maybeSetText(int position, String key, TextView view) { - Bundle item; - try { - item = mItems.get(position); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return; - } - if (item.containsKey(key)) { - view.setText(item.getString(key)); - view.setVisibility(View.VISIBLE); - } else { - view.setVisibility(View.GONE); - } - } - - private void maybeSetBitmap(int position, String key, ImageView view) { - Bundle item; - try { - item = mItems.get(position); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return; - } - if (item.containsKey(key)) { - view.setImageBitmap((Bitmap) item.getParcelable(key)); - view.setVisibility(View.VISIBLE); - view.setTag(-1); - } else { - view.setVisibility(View.GONE); - } - } - - private void setHolderStatus(final CarListItemViewHolder holder, - boolean isEnabled, float alpha) { - holder.itemView.setEnabled(isEnabled); - if (holder.icon != null) { - holder.icon.setAlpha(alpha); - } - if (holder.title != null) { - holder.title.setAlpha(alpha); - } - if (holder.text != null) { - holder.text.setAlpha(alpha); - } - if (holder.rightCheckbox != null) { - holder.rightCheckbox.setAlpha(alpha); - } - if (holder.rightImage != null) { - holder.rightImage.setAlpha(alpha); - } - if (holder.rightText != null) { - holder.rightText.setAlpha(alpha); - } - } - - private final View.OnClickListener mOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - final Bundle item; - int position = (int) view.getTag(); - try { - item = mItems.get(position); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return; - } - View right = view.findViewById(R.id.right_item); - if (right != null && view != right && right instanceof CompoundButton) { - ((CompoundButton) right).toggle(); - } - if (mListener != null) { - mListener.onItemClicked(item, position); - } - } - }; - - private final View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - final Bundle item; - try { - item = mItems.get((int) view.getTag()); - } catch (IndexOutOfBoundsException e) { - Log.w(TAG, INDEX_OUT_OF_BOUNDS_MESSAGE, e); - return true; - } - final String id = item.getString(KEY_ID); - if (mListener != null) { - return mListener.onItemLongClicked(item); - } - return false; - } - }; -} diff --git a/car-ui-provider/src/android/car/ui/provider/DrawerController.java b/car-ui-provider/src/android/car/ui/provider/DrawerController.java deleted file mode 100644 index 241224a2f1..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/DrawerController.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.car.app.menu.CarMenuCallbacks; -import android.content.Context; -import android.graphics.Canvas; -import android.os.Bundle; -import android.support.car.ui.PagedListView; -import android.support.car.ui.R; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.ProgressBar; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.Stack; - -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.FLAG_BROWSABLE; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_FLAGS; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_ID; -import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_TITLE; - -/** - * Controls the drawer for SDK app - */ -public class DrawerController - implements CarDrawerLayout.DrawerListener, DrawerApiAdapter.OnItemSelectedListener, - CarDrawerLayout.DrawerControllerListener { - private static final String TAG = "CAR.UI.DrawerController"; - // Qualify with full package name to make it less likely there will be a collision - private static final String KEY_IDS = "android.support.car.ui.drawer.sdk.IDS"; - private static final String KEY_DRAWERSTATE = - "android.support.car.ui.drawer.sdk.DRAWER_STATE"; - private static final String KEY_TITLES = "android.support.car.ui.drawer.sdk.TITLES"; - private static final String KEY_ROOT = "android.support.car.ui.drawer.sdk.ROOT"; - private static final String KEY_ID_UNAVAILABLE_CATEGORY = "UNAVAILABLE_CATEGORY"; - private static final String KEY_CLICK_STACK = - "android.support.car.ui.drawer.sdk.CLICK_STACK"; - private static final String KEY_MAX_PAGES = - "android.support.car.ui.drawer.sdk.MAX_PAGES"; - private static final String KEY_IS_CAPPED = - "android.support.car.ui.drawer.sdk.IS_CAPPED"; - - /** Drawer is in Auto dark/light mode */ - private static final int MODE_AUTO = 0; - /** Drawer is in Light mode */ - private static final int MODE_LIGHT = 1; - /** Drawer is in Dark mode */ - private static final int MODE_DARK = 2; - - private final Stack<String> mSubscriptionIds = new Stack<>(); - private final Stack<CharSequence> mTitles = new Stack<>(); - private final SubscriptionCallbacks mSubscriptionCallbacks = new SubscriptionCallbacks(); - // Named to be consistent with CarDrawerFragment to make copying code easier and less error - // prone - private final CarDrawerLayout mContainer; - private final PagedListView mListView; -// private final CardView mTruncatedListCardView; - private final ProgressBar mProgressBar; - private final Context mContext; - private final ViewAnimationController mPlvAnimationController; - private final CardView mTruncatedListCardView; - private final Stack<Integer> mClickCountStack = new Stack<>(); - - private CarMenuCallbacks mCarMenuCallbacks; - private DrawerApiAdapter mAdapter; - private int mScrimColor = CarDrawerLayout.DEFAULT_SCRIM_COLOR; - private boolean mIsDrawerOpen; - private boolean mIsDrawerAnimating; - private boolean mIsCapped; - private int mItemsNumber; - private int mDrawerMode; - private CharSequence mContentTitle; - private String mRootId; - private boolean mRestartedFromDayNightMode; - private CarUiEntry mUiEntry; - - public DrawerController(CarUiEntry uiEntry, View menuButton, CarDrawerLayout drawerLayout, - PagedListView listView, CardView cardView) { - //mCarAppLayout = appLayout; - menuButton.setOnClickListener(mMenuClickListener); - mContainer = drawerLayout; - mListView = listView; - mUiEntry = uiEntry; - mTruncatedListCardView = cardView; - mListView.setDefaultItemDecoration(new DrawerMenuListDecoration(mListView.getContext())); - mProgressBar = (ProgressBar) mContainer.findViewById(R.id.progress); - mContext = mListView.getContext(); - mPlvAnimationController = new ViewAnimationController( - mListView, R.anim.car_list_in, R.anim.sdk_list_out, R.anim.car_list_pop_out); - mRootId = null; - - mContainer.setDrawerListener(this); - mContainer.setDrawerControllerListener(this); - setAutoLightDarkMode(); - } - - - @Override - public void onDrawerOpened(View drawerView) { - mIsDrawerOpen = true; - mIsDrawerAnimating = false; - mUiEntry.setMenuProgress(1.0f); - // This can be null on day/night mode changes - if (mCarMenuCallbacks != null) { - mCarMenuCallbacks.onCarMenuOpened(); - } - } - - @Override - public void onDrawerClosed(View drawerView) { - mIsDrawerOpen = false; - mIsDrawerAnimating = false; - clearMenu(); - mUiEntry.setMenuProgress(0); - mUiEntry.setTitle(mContentTitle); - // This can be null on day/night mode changes - if (mCarMenuCallbacks != null) { - mCarMenuCallbacks.onCarMenuClosed(); - } - } - - @Override - public void onDrawerStateChanged(int newState) { - } - - @Override - public void onDrawerOpening(View drawerView) { - mIsDrawerAnimating = true; - // This can be null on day/night mode changes - if (mCarMenuCallbacks != null) { - mCarMenuCallbacks.onCarMenuOpening(); - } - } - - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - mUiEntry.setMenuProgress(slideOffset); - } - - @Override - public void onDrawerClosing(View drawerView) { - mIsDrawerAnimating = true; - // This can be null on day/night mode changes - if (mCarMenuCallbacks != null) { - mCarMenuCallbacks.onCarMenuClosing(); - } - } - - @Override - public void onItemClicked(Bundle item, int position) { - // Don't allow selection while animating - if (mPlvAnimationController.isAnimating()) { - return; - } - int flags = item.getInt(KEY_FLAGS); - String id = item.getString(KEY_ID); - - // Page number is 0 index, + 1 for the actual click. - int clicksUsed = mListView.getPage(position) + 1; - mClickCountStack.push(clicksUsed); - mListView.setMaxPages(mListView.getMaxPages() - clicksUsed); - mCarMenuCallbacks.onItemClicked(id); - if ((flags & FLAG_BROWSABLE) != 0) { - if (mListView.getMaxPages() == 0) { - mIsCapped = true; - } - CharSequence title = item.getString(KEY_TITLE); - if (TextUtils.isEmpty(title)) { - title = mContentTitle; - } - mUiEntry.setTitleText(title); - mTitles.push(title); - if (!mSubscriptionIds.isEmpty()) { - mPlvAnimationController.enqueueExitAnimation(mClearAdapterRunnable); - } - mProgressBar.setVisibility(View.VISIBLE); - if (!mSubscriptionIds.isEmpty()) { - mCarMenuCallbacks.unsubscribe(mSubscriptionIds.peek(), mSubscriptionCallbacks); - } - mSubscriptionIds.push(id); - subscribe(id); - } else { - closeDrawer(); - } - } - - @Override - public boolean onItemLongClicked(Bundle item) { - return mCarMenuCallbacks.onItemLongClicked(item.getString(KEY_ID)); - } - - @Override - public void onBack() { - backOrClose(); - } - - @Override - public boolean onScroll() { - // Consume scroll event if we are animating. - return mPlvAnimationController.isAnimating(); - } - - public void setTitle(CharSequence title) { - Log.d(TAG, "setTitle in drawer" + title); - if (!TextUtils.isEmpty(title)) { - mContentTitle = title; - mUiEntry.showTitle(); - mUiEntry.setTitleText(title); - } else { - mUiEntry.hideTitle(); - } - } - - public void setRootAndCallbacks(String rootId, CarMenuCallbacks callbacks) { - mAdapter = new DrawerApiAdapter(); - mAdapter.setItemSelectedListener(this); - mListView.setAdapter(mAdapter); - mCarMenuCallbacks = callbacks; - // HACK: Due to the handler, setRootId will be called after onRestoreState. - // If onRestoreState has been called, the root id will already be set. So nothing to do. - if (mSubscriptionIds.isEmpty()) { - setRootId(rootId); - } else { - subscribe(mSubscriptionIds.peek()); - openDrawer(); - } - } - - public void saveState(Bundle out) { - out.putStringArray(KEY_IDS, mSubscriptionIds.toArray(new String[mSubscriptionIds.size()])); - out.putStringArray(KEY_TITLES, mTitles.toArray(new String[mTitles.size()])); - out.putString(KEY_ROOT, mRootId); - out.putBoolean(KEY_DRAWERSTATE, mIsDrawerOpen); - out.putIntegerArrayList(KEY_CLICK_STACK, new ArrayList<Integer>(mClickCountStack)); - out.putBoolean(KEY_IS_CAPPED, mIsCapped); - out.putInt(KEY_MAX_PAGES, mListView.getMaxPages()); - } - - public void restoreState(Bundle in) { - if (in != null) { - // Restore subscribed CarMenu ids - String[] ids = in.getStringArray(KEY_IDS); - mSubscriptionIds.clear(); - if (ids != null) { - mSubscriptionIds.addAll(Arrays.asList(ids)); - } - // Restore drawer titles if there are any - String[] titles = in.getStringArray(KEY_TITLES); - mTitles.clear(); - if (titles != null) { - mTitles.addAll(Arrays.asList(titles)); - } - if (!mTitles.isEmpty()) { - mUiEntry.setTitleText(mTitles.peek()); - } - mRootId = in.getString(KEY_ROOT); - mIsDrawerOpen = in.getBoolean(KEY_DRAWERSTATE); - ArrayList<Integer> clickCount = in.getIntegerArrayList(KEY_CLICK_STACK); - mClickCountStack.clear(); - if (clickCount != null) { - mClickCountStack.addAll(clickCount); - } - mIsCapped = in.getBoolean(KEY_IS_CAPPED); - mListView.setMaxPages(in.getInt(KEY_MAX_PAGES)); - if (!mRestartedFromDayNightMode && mIsDrawerOpen) { - closeDrawer(); - } - } - } - - public void setScrimColor(int color) { - mScrimColor = color; - mContainer.setScrimColor(color); - updateViewFaders(); - } - - public void setAutoLightDarkMode() { - mDrawerMode = MODE_AUTO; - mContainer.setAutoDayNightMode(); - updateViewFaders(); - } - - public void setLightMode() { - mDrawerMode = MODE_LIGHT; - mContainer.setLightMode(); - updateViewFaders(); - } - - public void setDarkMode() { - mDrawerMode = MODE_DARK; - mContainer.setDarkMode(); - updateViewFaders(); - } - - public void openDrawer() { - // If we have no root, then we can't open the drawer. - if (mRootId == null) { - return; - } - mContainer.openDrawer(); - } - - public void closeDrawer() { - if (mRootId == null) { - return; - } - mTruncatedListCardView.setVisibility(View.GONE); - mPlvAnimationController.stopAndClearAnimations(); - mContainer.closeDrawer(); - mUiEntry.setTitle(mContentTitle); - } - - public void setDrawerEnabled(boolean enabled) { - if (enabled) { - mContainer.setDrawerLockMode(CarDrawerLayout.LOCK_MODE_UNLOCKED); - } else { - mContainer.setDrawerLockMode(CarDrawerLayout.LOCK_MODE_LOCKED_CLOSED); - } - } - - public void showMenu(String id, String title) { - // The app wants to show the menu associated with the given id. Create a fake item using the - // given inputs and then pretend as if the user clicked on the item, so that the drawer - // will subscribe to that menu id, set the title appropriately, and properly handle the - // subscription stack. - Bundle bundle = new Bundle(); - bundle.putString(KEY_ID, id); - bundle.putString(KEY_TITLE, title); - bundle.putInt(KEY_FLAGS, FLAG_BROWSABLE); - onItemClicked(bundle, 0 /* position */); - } - - public void setRootId(String rootId) { - mRootId = rootId; - } - - public void setRestartedFromDayNightMode(boolean restarted) { - mRestartedFromDayNightMode = restarted; - } - - public boolean isTruncatedList() { - int maxItems = mAdapter.getMaxItemsNumber(); - return maxItems != PagedListView.ItemCap.UNLIMITED && mItemsNumber > maxItems; - } - - private void clearMenu() { - if (!mSubscriptionIds.isEmpty()) { - mCarMenuCallbacks.unsubscribe(mSubscriptionIds.peek(), mSubscriptionCallbacks); - mSubscriptionIds.clear(); - mTitles.clear(); - } - mListView.setVisibility(View.GONE); - mListView.resetMaxPages(); - mClickCountStack.clear(); - mIsCapped = false; - } - - /** - * Check if the drawer is inside of a CarAppLayout and add the relevant views if it is, - * automagically add view faders for the correct views - */ - private void updateViewFaders() { - mContainer.removeViewFader(mStatusViewViewFader); - mContainer.addViewFader(mStatusViewViewFader); - } - - private void subscribe(String id) { - mProgressBar.setVisibility(View.VISIBLE); - mCarMenuCallbacks.subscribe(id, mSubscriptionCallbacks); - } - - private final CarDrawerLayout.ViewFader mStatusViewViewFader = new CarDrawerLayout.ViewFader() { - @Override - public void setColor(int color) { - mUiEntry.setMenuButtonColor(color); - } - }; - - private void backOrClose() { - if (mSubscriptionIds.size() > 1) { - mPlvAnimationController.enqueueBackAnimation(mClearAdapterRunnable); - mProgressBar.setVisibility(View.VISIBLE); - mCarMenuCallbacks.unsubscribe(mSubscriptionIds.pop(), - mSubscriptionCallbacks); - subscribe(mSubscriptionIds.peek()); - // Restore the title for this menu level. - mTitles.pop(); - CharSequence title = mTitles.peek(); - if (TextUtils.isEmpty(title)) { - title = mContentTitle; - } - mUiEntry.setTitleText(title); - } else { - closeDrawer(); - } - } - - private final View.OnClickListener mMenuClickListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mIsDrawerAnimating || mCarMenuCallbacks.onMenuClicked()) { - return; - } - // Check if drawer has root set. - if (mRootId == null) { - return; - } - mTruncatedListCardView.setVisibility(View.GONE); - if (mIsDrawerOpen) { - if (!mClickCountStack.isEmpty()) { - mListView.setMaxPages(mListView.getMaxPages() + mClickCountStack.pop()); - } - mIsCapped = false; - backOrClose(); - } else { - mSubscriptionIds.push(mRootId); - mTitles.push(mContentTitle); - subscribe(mRootId); - openDrawer(); - } - } - }; - - private final Runnable mClearAdapterRunnable = new Runnable() { - @Override - public void run() { - mListView.setVisibility(View.GONE); - } - }; - - public void updateDayNightMode() { - mContainer.findViewById(R.id.drawer).setBackgroundColor( - mContext.getResources().getColor(R.color.car_card)); - mListView.setAutoDayNightMode(); - switch (mDrawerMode) { - case MODE_AUTO: - setAutoLightDarkMode(); - break; - case MODE_LIGHT: - setLightMode(); - break; - case MODE_DARK: - setDarkMode(); - break; - } - updateViewFaders(); - RecyclerView rv = mListView.getRecyclerView(); - for (int i = 0; i < mAdapter.getItemCount(); ++i) { - mAdapter.setDayNightModeColors(rv.findViewHolderForAdapterPosition(i)); - } - } - - private static class ViewAnimationController implements Animation.AnimationListener { - private final Animation mExitAnim; - private final Animation mEnterAnim; - private final Animation mBackAnim; - private final View mView; - private final Context mContext; - private final Queue<Animation> mQueue = new LinkedList<>(); - - private Runnable mOnEnterAnimStartRunnable; - private Runnable mOnExitAnimCompleteRunnable; - - private Animation mCurrentAnimation; - - public ViewAnimationController(View view, int enter, int exit, int back) { - mView = view; - mContext = view.getContext(); - - mEnterAnim = AnimationUtils.loadAnimation(mContext, enter); - mExitAnim = AnimationUtils.loadAnimation(mContext, exit); - mBackAnim = AnimationUtils.loadAnimation(mContext, back); - - mExitAnim.setAnimationListener(this); - mEnterAnim.setAnimationListener(this); - mBackAnim.setAnimationListener(this); - } - - @Override - public void onAnimationStart(Animation animation) { - if (animation == mEnterAnim && mOnEnterAnimStartRunnable != null) { - mOnEnterAnimStartRunnable.run(); - mOnEnterAnimStartRunnable = null; - } - } - - @Override - public void onAnimationEnd(Animation animation) { - if ((animation == mExitAnim || animation == mBackAnim) - && mOnExitAnimCompleteRunnable != null) { - mOnExitAnimCompleteRunnable.run(); - mOnExitAnimCompleteRunnable = null; - } - Animation nextAnimation = mQueue.poll(); - if (nextAnimation != null) { - mCurrentAnimation = animation; - mView.startAnimation(nextAnimation); - } else { - mCurrentAnimation = null; - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - - public void enqueueEnterAnimation(Runnable r) { - if (r != null) { - mOnEnterAnimStartRunnable = r; - } - enqueueAnimation(mEnterAnim); - } - - public void enqueueExitAnimation(Runnable r) { - // If the view isn't visible, don't play the exit animation. - // It will cause flicker. - if (mView.getVisibility() != View.VISIBLE) { - return; - } - if (r != null) { - mOnExitAnimCompleteRunnable = r; - } - enqueueAnimation(mExitAnim); - } - - public void enqueueBackAnimation(Runnable r) { - // If the view isn't visible, don't play the back animation. - if (mView.getVisibility() != View.VISIBLE) { - return; - } - if (r != null) { - mOnExitAnimCompleteRunnable = r; - } - enqueueAnimation(mBackAnim); - } - - public synchronized void stopAndClearAnimations() { - if (mExitAnim.hasStarted()) { - mExitAnim.cancel(); - } - - if (mEnterAnim.hasStarted()) { - mEnterAnim.cancel(); - } - - mQueue.clear(); - mCurrentAnimation = null; - } - - public boolean isAnimating() { - return mCurrentAnimation != null; - } - - private synchronized void enqueueAnimation(final Animation animation) { - if (mQueue.contains(animation)) { - return; - } - if (mCurrentAnimation != null) { - mQueue.add(animation); - } else { - mCurrentAnimation = animation; - mView.startAnimation(animation); - } - } - } - - private class SubscriptionCallbacks extends android.car.app.menu.SubscriptionCallbacks { - private final Object mItemLock = new Object(); - private volatile List<Bundle> mItems; - - @Override - public void onChildrenLoaded(String parentId, final List<Bundle> items) { - if (mSubscriptionIds.isEmpty() || parentId.equals(mSubscriptionIds.peek())) { - // Add unavailable category explanation at the first item of menu. - if (mIsCapped) { - Bundle extra = new Bundle(); - extra.putString(KEY_ID, KEY_ID_UNAVAILABLE_CATEGORY); - items.add(0, extra); - } - mItems = items; - mItemsNumber = mItems.size(); - mProgressBar.setVisibility(View.GONE); - mPlvAnimationController.enqueueEnterAnimation(new Runnable() { - @Override - public void run() { - synchronized (mItemLock) { - mAdapter.setItems(mItems, mIsCapped); - mListView.setVisibility(View.VISIBLE); - mItems = null; - } - mListView.scrollToPosition(mAdapter.getFirstItemIndex()); - } - }); - } - } - - @Override - public void onError(String id) { - // TODO: do something useful here. - } - - @Override - public void onChildChanged(String parentId, Bundle bundle) { - if (!mSubscriptionIds.isEmpty() && parentId.equals(mSubscriptionIds.peek())) { - // List is still animating, so adapter hasn't been updated. Update the list that - // needs to be set. - String id = bundle.getString(KEY_ID); - synchronized (mItemLock) { - if (mItems != null) { - for (Bundle item : mItems) { - if (item.getString(KEY_ID).equals(id)) { - item.putAll(bundle); - break; - } - } - return; - } - } - RecyclerView rv = mListView.getRecyclerView(); - RecyclerView.ViewHolder holder = rv.findViewHolderForItemId(id.hashCode()); - mAdapter.onChildChanged(holder, bundle); - } - } - } - - private class DrawerMenuListDecoration extends PagedListView.Decoration { - - public DrawerMenuListDecoration(Context context) { - super(context); - } - - @Override - public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { - if (mAdapter != null && mAdapter.isEmptyPlaceholder()) { - return; - } - super.onDrawOver(c, parent, state); - } - } -} diff --git a/car-ui-provider/src/android/car/ui/provider/MaxWidthLayout.java b/car-ui-provider/src/android/car/ui/provider/MaxWidthLayout.java deleted file mode 100644 index 4311c5a856..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/MaxWidthLayout.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.content.Context; -import android.util.AttributeSet; - -/** - * Clone of {@link android.support.car.ui.MaxWidthLayout} to be used by CarUiProvider. - * Workaround for b/25595320 - */ -public class MaxWidthLayout extends android.support.car.ui.MaxWidthLayout { - public MaxWidthLayout(Context context) { - super(context); - } - - public MaxWidthLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public MaxWidthLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } -}
\ No newline at end of file diff --git a/car-ui-provider/src/android/car/ui/provider/PagedListView.java b/car-ui-provider/src/android/car/ui/provider/PagedListView.java deleted file mode 100644 index 9648dd6745..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/PagedListView.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.content.Context; -import android.util.AttributeSet; - -/** - * Clone of {@link android.support.car.ui.PagedListView} to be used by CarUiProvider. - * Workaround for b/25595320 - */ -public class PagedListView extends android.support.car.ui.PagedListView { - public PagedListView(Context context, AttributeSet attrs) { - this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/); - } - - public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs) { - this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/); - } - - public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { - super(context, attrs, defStyleAttrs, defStyleRes); - } -} diff --git a/car-ui-provider/src/android/car/ui/provider/PagedScrollBarView.java b/car-ui-provider/src/android/car/ui/provider/PagedScrollBarView.java deleted file mode 100644 index 91a25ab048..0000000000 --- a/car-ui-provider/src/android/car/ui/provider/PagedScrollBarView.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.ui.provider; - -import android.content.Context; -import android.util.AttributeSet; - -/** - * Clone of {@link android.support.car.ui.PagedScrollBarView} to be used by CarUiProvider. - * Workaround for b/25595320 - */ -public class PagedScrollBarView extends android.support.car.ui.PagedScrollBarView { - public PagedScrollBarView( - Context context, AttributeSet attrs) { - this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/); - } - - public PagedScrollBarView( - Context context, AttributeSet attrs, int defStyleAttrs) { - this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/); - } - - public PagedScrollBarView( - Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) { - super(context, attrs, defStyleAttrs, defStyleRes); - } -} diff --git a/car-usb-handler/res/values/strings.xml b/car-usb-handler/res/values/strings.xml index 2242648941..be1e7a2332 100644 --- a/car-usb-handler/res/values/strings.xml +++ b/car-usb-handler/res/values/strings.xml @@ -26,4 +26,5 @@ <string name="usb_pref_delete_yes">Yes</string> <string name="usb_pref_delete_cancel">Cancel</string> <string name="usb_resolving_handlers">Getting supported handlers</string> + <string name="usb_unknown_device">Unknown USB device</string> </resources> diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java index 5084414093..288f5982fe 100644 --- a/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java +++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java @@ -17,7 +17,6 @@ package android.car.usb.handler; import android.content.ComponentName; import android.hardware.usb.UsbDevice; -import com.android.internal.util.Preconditions; /** * Settings for USB device. @@ -34,8 +33,6 @@ public final class UsbDeviceSettings { private boolean mDefaultHandler; UsbDeviceSettings(String serialNumber, int vid, int pid) { - Preconditions.checkNotNull(serialNumber); - mSerialNumber = serialNumber; mVid = vid; mPid = pid; @@ -96,7 +93,15 @@ public final class UsbDeviceSettings { * Checks if setting matches {@code UsbDevice}. */ public boolean matchesDevice(UsbDevice device) { - return getSerialNumber().equals(device.getSerialNumber()); + String deviceSerial = device.getSerialNumber(); + if (AoapInterface.isDeviceInAoapMode(device)) { + return mAoap && deviceSerial.equals(mSerialNumber); + } else if (deviceSerial == null) { + return mVid == device.getVendorId() && mPid == device.getProductId(); + } else { + return mVid == device.getVendorId() && mPid == device.getProductId() + && deviceSerial.equals(mSerialNumber); + } } /** diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java index b705928fec..5c61a98091 100644 --- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java +++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java @@ -68,10 +68,10 @@ public final class UsbHostController public void onReceive(Context context, Intent intent) { if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) { UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); - unsetActiveDeviceIfSerialMatch(device); + unsetActiveDeviceIfMatch(device); } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); - setActiveDeviceIfSerialMatch(device); + setActiveDeviceIfMatch(device); } } }; @@ -79,9 +79,6 @@ public final class UsbHostController @GuardedBy("this") private UsbDevice mActiveDevice; - @GuardedBy("this") - private String mProcessingDeviceSerial; - public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) { mContext = context; mCallback = callbacks; @@ -96,17 +93,17 @@ public final class UsbHostController } - private synchronized void setActiveDeviceIfSerialMatch(UsbDevice device) { - if (device != null && device.getSerialNumber() != null - && device.getSerialNumber().equals(mProcessingDeviceSerial)) { + private synchronized void setActiveDeviceIfMatch(UsbDevice device) { + if (mActiveDevice != null && device != null + && UsbUtil.isDevicesMatching(device, mActiveDevice)) { mActiveDevice = device; } } - private synchronized void unsetActiveDeviceIfSerialMatch(UsbDevice device) { + private synchronized void unsetActiveDeviceIfMatch(UsbDevice device) { mHandler.requestDeviceRemoved(); - if (mActiveDevice != null && mActiveDevice.getSerialNumber() != null - && mActiveDevice.getSerialNumber().equals(device.getSerialNumber())) { + if (mActiveDevice != null && device != null + && UsbUtil.isDevicesMatching(device, mActiveDevice)) { mActiveDevice = null; } } @@ -114,7 +111,6 @@ public final class UsbHostController private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) { if (mActiveDevice == null) { mActiveDevice = device; - mProcessingDeviceSerial = device.getSerialNumber(); return true; } return false; @@ -122,7 +118,6 @@ public final class UsbHostController private synchronized void stopDeviceProcessing() { mActiveDevice = null; - mProcessingDeviceSerial = null; } private synchronized UsbDevice getActiveDevice() { @@ -131,8 +126,22 @@ public final class UsbHostController private boolean deviceMatchedActiveDevice(UsbDevice device) { UsbDevice activeDevice = getActiveDevice(); - return activeDevice != null && activeDevice.getSerialNumber() != null - && activeDevice.getSerialNumber().equals(device.getSerialNumber()); + return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device); + } + + private String generateTitle() { + String manufacturer = mActiveDevice.getManufacturerName(); + String product = mActiveDevice.getProductName(); + if (manufacturer == null && product == null) { + return mContext.getString(R.string.usb_unknown_device); + } + if (manufacturer != null && product != null) { + return manufacturer + " " + product; + } + if (manufacturer != null) { + return manufacturer; + } + return product; } /** @@ -147,7 +156,7 @@ public final class UsbHostController mCallback.optionsUpdated(mEmptyList); mCallback.processingStateChanged(true); - UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device.getSerialNumber()); + UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device); if (settings != null && mUsbResolver.dispatch( mActiveDevice, settings.getHandler(), settings.getAoap())) { if (LOCAL_LOGV) { @@ -156,7 +165,7 @@ public final class UsbHostController } return; } - mCallback.titleChanged(device.getManufacturerName() + " " + device.getProductName()); + mCallback.titleChanged(generateTitle()); mUsbResolver.resolve(device); } diff --git a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java index 157c92f9d6..1b251f8eee 100644 --- a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java +++ b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java @@ -22,6 +22,7 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.hardware.usb.UsbDevice; import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -47,26 +48,43 @@ public final class UsbSettingsStorage { mDbHelper = new UsbSettingsDbHelper(context); } + private Cursor queryFor(SQLiteDatabase db, UsbDevice device) { + String serial = device.getSerialNumber(); + String selection; + String[] selectionArgs; + if (AoapInterface.isDeviceInAoapMode(device)) { + selection = COLUMN_SERIAL + " = ? AND " + COLUMN_AOAP + " = 1"; + selectionArgs = new String[] {serial}; + } else if (serial == null) { + selection = COLUMN_SERIAL + " IS NULL AND " + + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?"; + selectionArgs = new String[] { + Integer.toString(device.getVendorId()), + Integer.toString(device.getProductId())}; + } else { + selection = + COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?"; + selectionArgs = new String[] { + device.getSerialNumber(), + Integer.toString(device.getVendorId()), + Integer.toString(device.getProductId())}; + } + return db.query(TABLE_USB_SETTINGS, null, selection, selectionArgs, null, null, null); + } + /** * Returns settings for {@serialNumber} or null if it doesn't exist. */ @Nullable - public UsbDeviceSettings getSettings(String serialNumber) { + public UsbDeviceSettings getSettings(UsbDevice device) { try (SQLiteDatabase db = mDbHelper.getReadableDatabase(); - Cursor resultCursor = db.query( - TABLE_USB_SETTINGS, - null, - COLUMN_SERIAL + " = ?", - new String[]{serialNumber}, - null, - null, - null)) { + Cursor resultCursor = queryFor(db, device)) { if (resultCursor.getCount() > 1) { - throw new RuntimeException("Querying for serial number: " + serialNumber + throw new RuntimeException("Querying for device: " + device + " returned " + resultCursor.getCount() + " results"); } if (resultCursor.getCount() == 0) { - Log.w(TAG, "Usb setting missing for device serial: " + serialNumber); + Log.w(TAG, "Usb setting missing for device: " + device); return null; } List<UsbDeviceSettings> settings = constructSettings(resultCursor); @@ -168,7 +186,7 @@ public final class UsbSettingsStorage { private static class UsbSettingsDbHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 1; + private static final int DATABASE_VERSION = 2; private static final String DATABASE_NAME = "usb_devices.db"; UsbSettingsDbHelper(Context context) { @@ -177,20 +195,47 @@ public final class UsbSettingsStorage { @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_USB_SETTINGS + " (" + createTable(db, TABLE_USB_SETTINGS); + createSerialIndex(db); + } + + private void createTable(SQLiteDatabase db, String tableName) { + db.execSQL("CREATE TABLE " + tableName + " (" + COLUMN_SERIAL + " TEXT," + COLUMN_VID + " INTEGER," + COLUMN_PID + " INTEGER," + COLUMN_NAME + " TEXT, " + COLUMN_HANDLER + " TEXT," + COLUMN_AOAP + " INTEGER," - + COLUMN_DEFAULT_HANDLER + " INTEGER," + "PRIMARY KEY (" + COLUMN_SERIAL + + COLUMN_DEFAULT_HANDLER + " INTEGER," + + "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", " + COLUMN_PID + "))"); } + private void createSerialIndex(SQLiteDatabase db) { + db.execSQL("CREATE INDEX " + TABLE_USB_SETTINGS + "_" + COLUMN_SERIAL + " ON " + + TABLE_USB_SETTINGS + "(" + COLUMN_SERIAL + ")"); + } + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // Do nothing at this point. Not required for v1 database. + for (; oldVersion != newVersion; oldVersion++) { + switch (oldVersion) { + case 1: + String tempTableName = "temp_" + TABLE_USB_SETTINGS; + createTable(db, tempTableName); + db.execSQL("INSERT INTO " + tempTableName + + " SELECT * FROM " + TABLE_USB_SETTINGS); + db.execSQL("DROP TABLE " + TABLE_USB_SETTINGS); + db.execSQL("ALTER TABLE " + tempTableName + " RENAME TO " + + TABLE_USB_SETTINGS); + createSerialIndex(db); + break; + default: + throw new IllegalArgumentException( + "Unknown database version " + oldVersion); + } + } } } } diff --git a/car_product/build/car.mk b/car_product/build/car.mk index 16aa275218..1c2e7eedc2 100644 --- a/car_product/build/car.mk +++ b/car_product/build/car.mk @@ -74,7 +74,6 @@ PRODUCT_PROPERTY_OVERRIDES += \ PRODUCT_PACKAGES += \ vehicle_monitor_service \ CarService \ - CarUiProvider \ CarTrustAgentService \ CarDialerApp \ CarRadioApp \ diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk index 3e8d7768d2..a6f2f43183 100644 --- a/car_product/build/car_base.mk +++ b/car_product/build/car_base.mk @@ -62,6 +62,7 @@ PRODUCT_PACKAGES += \ libstagefright_soft_amrwbenc \ libstagefright_soft_avcdec \ libstagefright_soft_avcenc \ + libstagefright_soft_flacdec \ libstagefright_soft_flacenc \ libstagefright_soft_g711dec \ libstagefright_soft_gsmdec \ @@ -83,15 +84,18 @@ PRODUCT_PACKAGES += \ A2dpSinkService \ # EVS resources -PRODUCT_PACKAGES += android.hardware.automotive.evs@1.0-service PRODUCT_PACKAGES += android.automotive.evs.manager@1.0 PRODUCT_PACKAGES += evs_app +# The following packages, or their vendor specific equivalents should be include in the device.mk +#PRODUCT_PACKAGES += evs_app_default_resources +#PRODUCT_PACKAGES += android.hardware.automotive.evs@1.0-service +#PRODUCT_PACKAGES += android.hardware.automotive.evs@1.0-sample + +# Device running Android is a car +PRODUCT_COPY_FILES += \ + frameworks/native/data/etc/android.hardware.type.automotive.xml:system/etc/permissions/android.hardware.type.automotive.xml -ifeq ($(TARGET_USES_CAR_FUTURE_FEATURES),true) PRODUCT_PACKAGES += android.hardware.automotive.vehicle@2.1-service -else -PRODUCT_PACKAGES += android.hardware.automotive.vehicle@2.0-service -endif $(call inherit-product, $(SRC_TARGET_DIR)/product/core_minimal.mk) diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-night-nodpi/default_wallpaper.png b/car_product/overlay/frameworks/base/core/res/res/drawable-night-nodpi/default_wallpaper.png Binary files differnew file mode 100644 index 0000000000..9a4033e5de --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/drawable-night-nodpi/default_wallpaper.png diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.png b/car_product/overlay/frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.png Binary files differindex 22441ae1ff..8d20548790 100644 --- a/car_product/overlay/frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.png +++ b/car_product/overlay/frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.png diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-night/default_wallpaper.png b/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-night/default_wallpaper.png Binary files differnew file mode 100644 index 0000000000..9a4033e5de --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-night/default_wallpaper.png diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png Binary files differindex cc651def2e..8d20548790 100644 --- a/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png +++ b/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-night/default_wallpaper.png b/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-night/default_wallpaper.png Binary files differnew file mode 100644 index 0000000000..9a4033e5de --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-night/default_wallpaper.png diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png Binary files differindex 03b93aaad8..8d20548790 100644 --- a/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png +++ b/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable/ic_collapse_notification.xml b/car_product/overlay/frameworks/base/core/res/res/drawable/ic_collapse_notification.xml new file mode 100644 index 0000000000..0252706f96 --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/drawable/ic_collapse_notification.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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="56dp" + android:height="56dp" + android:viewportWidth="56.0" + android:viewportHeight="56.0"> + <path + android:pathData="M28,56C12.54,56 0,43.46 0,28C0,12.54 12.54,0 28,0C43.46,0 56,12.54 56,28C56,43.46 43.46,56 28,56ZM35.64,33L38,30.71L28,21L18,30.71L20.36,33L28,25.58L35.64,33Z" + android:strokeColor="#00000000" + android:fillColor="@android:color/black" + android:strokeWidth="1"/> +</vector> diff --git a/car_product/overlay/frameworks/base/core/res/res/drawable/ic_expand_notification.xml b/car_product/overlay/frameworks/base/core/res/res/drawable/ic_expand_notification.xml new file mode 100644 index 0000000000..c93390c75d --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/drawable/ic_expand_notification.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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="56dp" + android:height="56dp" + android:viewportWidth="56.0" + android:viewportHeight="56.0"> + <path + android:pathData="M28,0C43.46,0 56,12.54 56,28C56,43.46 43.46,56 28,56C12.54,56 0,43.46 0,28C0,12.54 12.54,0 28,0ZM20.36,23L18,25.29L28,35L38,25.29L35.64,23L28,30.42L20.36,23Z" + android:strokeColor="#00000000" + android:fillColor="@android:color/black" + android:strokeWidth="1"/> +</vector> diff --git a/car-support-lib/res/values-w1024dp/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/drawable/notification_action_bg.xml index f329135d1a..f13b28adb6 100644 --- a/car-support-lib/res/values-w1024dp/dimens.xml +++ b/car_product/overlay/frameworks/base/core/res/res/drawable/notification_action_bg.xml @@ -13,8 +13,9 @@ 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="stream_margin_size">112dp</dimen> - <dimen name="stream_content_keyline_1">112dp</dimen> - <dimen name="stream_content_keyline_2">228dp</dimen> -</resources> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#ffeeeeee" /> + <corners + android:bottomRightRadius="16dp" + android:bottomLeftRadius="16dp"/> +</shape> diff --git a/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_action.xml b/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_action.xml new file mode 100644 index 0000000000..cefd83004f --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_action.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<Button + xmlns:android="http://schemas.android.com/apk/res/android" + style="@android:style/Widget.Material.Light.Button.Borderless.Small" + android:id="@+id/action0" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center" + android:fontFamily="sans-serif" + android:gravity="start|center_vertical" + android:layout_marginStart="0dp" + android:textColor="@color/notification_default_color" + android:textSize="@dimen/notification_text_size" + android:textStyle="normal" + android:singleLine="true" + android:ellipsize="end" + android:paddingStart="@dimen/notification_content_margin_start" + android:paddingEnd="@dimen/notification_content_margin_start" + android:background="@drawable/notification_material_action_background" /> diff --git a/car-support-lib/res/layout/car_menu_checkbox.xml b/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_media_action.xml index fb9fa07f5c..29aafd4eda 100644 --- a/car-support-lib/res/layout/car_menu_checkbox.xml +++ b/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_media_action.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2017 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,10 +13,14 @@ 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. --> -<android.support.car.ui.CheckboxWrapperView +<ImageButton xmlns:android="http://schemas.android.com/apk/res/android" - android:focusable="false" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:buttonTint="@color/car_tint" - android:buttonTintMode="src_atop" />
\ No newline at end of file + style="@android:style/Widget.Material.Button.Borderless.Small" + android:id="@+id/action0" + android:background="@drawable/notification_material_media_action_background" + android:layout_width="0dp" + android:layout_height="@dimen/media_notification_action_button_size" + android:layout_weight="1" + android:padding="0dp" + android:gravity="center" + android:scaleType="fitCenter" /> diff --git a/car_product/overlay/frameworks/base/core/res/res/layout/notification_template_right_icon.xml b/car_product/overlay/frameworks/base/core/res/res/layout/notification_template_right_icon.xml new file mode 100644 index 0000000000..068e8b60c9 --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/layout/notification_template_right_icon.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> + +<ImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/right_icon" + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_marginEnd="@dimen/notification_content_margin_end" + android:layout_marginTop="76dp" + android:layout_marginBottom="76dp" + android:layout_gravity="center_vertical|end" + android:scaleType="centerCrop" /> diff --git a/car_product/overlay/frameworks/base/core/res/res/values-h600dp/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/values-h600dp/dimens.xml new file mode 100644 index 0000000000..b659bef661 --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/values-h600dp/dimens.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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> + <!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) --> + <dimen name="notification_title_text_size">40sp</dimen> + + <!-- Size of notification text (see TextAppearance.StatusBar.EventContent) --> + <dimen name="notification_text_size">32sp</dimen> + + <!-- The absolute size of the application icon in the notification header. --> + <dimen name="notification_header_icon_size">32dp</dimen> + + <!-- The height of the header of a notification. --> + <dimen name="notification_header_height">76dp</dimen> + + <!-- Top margin to accommodate for the header before the notification content. This value + is smaller than the notification_header_height to bring the text closer. Otherwise, + spacing in the font itself makes the space look too large. --> + <dimen name="notification_content_margin_top">68dp</dimen> + + <!-- The height of the notification action list. --> + <dimen name="notification_action_list_height">96dp</dimen> + + <!-- The size of the media actions in the media notification. --> + <dimen name="media_notification_action_button_size">56dp</dimen> +</resources> diff --git a/car_product/overlay/frameworks/base/core/res/res/values-night/colors.xml b/car_product/overlay/frameworks/base/core/res/res/values-night/colors.xml new file mode 100644 index 0000000000..2774c9e93f --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/values-night/colors.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2017, 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> + <!-- The background color for the container of notification actions. --> + <color name="notification_action_list">#ff11181d</color> <!-- Dark Blue Grey 800 --> +</resources> diff --git a/car-support-lib/res/values-w480dp/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/values-w840dp/dimens.xml index b4c611a894..ea826b4dd0 100644 --- a/car-support-lib/res/values-w480dp/dimens.xml +++ b/car_product/overlay/frameworks/base/core/res/res/values-w840dp/dimens.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 The Android Open Source Project +<!-- Copyright (C) 2017 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. @@ -14,7 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> <resources> - <dimen name="stream_margin_size">24dp</dimen> - <dimen name="stream_content_keyline_2">116dp</dimen> - <dimen name="stream_card_keyline_2">116dp</dimen> + <!-- The margin on the start of the content view. This value should match card keyline1. --> + <dimen name="notification_content_margin_start">32dp</dimen> + + <!-- The margin on the end of the content view with a picture. This value is the size of + the right icon (64dp) + notification_content_margin_end + 16dp. --> + <dimen name="notification_content_picture_margin">112dp</dimen> </resources> diff --git a/car_product/overlay/frameworks/base/core/res/res/values/colors.xml b/car_product/overlay/frameworks/base/core/res/res/values/colors.xml new file mode 100644 index 0000000000..f7a7a12319 --- /dev/null +++ b/car_product/overlay/frameworks/base/core/res/res/values/colors.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2017, 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> + <!-- The background color for the container of notification actions. --> + <color name="notification_action_list">#ffeeeeee</color> <!-- Grey 200 --> +</resources> 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 2a97f640d7..a9fb0e78bc 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 @@ -48,7 +48,16 @@ <integer name="config_jobSchedulerIdleWindowSlop">0</integer> <bool name="config_supportsMultiWindow">false</bool> + <!-- Automotive Bluetooth pairing option --> <bool name="enable_pbap_pce_profile">true</bool> + <!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of + the default framework version. --> + <string name="config_customResolverActivity" translatable="false">com.android.support.car.lenspicker/.LensResolverActivity</string> + + <!-- Flag indicating that the entire notification header can be clicked to expand the + notification. If false, then the expand icon has to be clicked in order for the expand + to occur. --> + <bool name="config_notificationHeaderClickableForExpand">true</bool> </resources> diff --git a/car_product/overlay/frameworks/base/core/res/res/values/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/values/dimens.xml index 136ca803d3..125e1fb68d 100644 --- a/car_product/overlay/frameworks/base/core/res/res/values/dimens.xml +++ b/car_product/overlay/frameworks/base/core/res/res/values/dimens.xml @@ -20,4 +20,88 @@ <dimen name="status_bar_height">56dp</dimen> <dimen name="navigation_bar_height_car_mode">112dp</dimen> <dimen name="status_bar_icon_size">40dp</dimen> + + <!-- The height of the header of a notification. --> + <dimen name="notification_header_height">58dp</dimen> + + <!-- The absolute size of the notification expand icon. --> + <dimen name="notification_header_expand_icon_size">55dp</dimen> + + <!-- The top padding for the notification expand button. --> + <dimen name="notification_expand_button_padding_top">0dp</dimen> + + <!-- The end margin after the application icon in the notification header --> + <dimen name="notification_header_icon_margin_end">10dp</dimen> + + <!-- The absolute size of the application icon in the notification header. --> + <dimen name="notification_header_icon_size">24dp</dimen> + + <!-- The margins before and after each of the items in the notification header.--> + <dimen name="notification_header_separating_margin">6dp</dimen> + + <!-- The margins before the start of the app name in the header. --> + <dimen name="notification_header_app_name_margin_start">@dimen/notification_header_separating_margin</dimen> + + <!-- The padding at the top of the notification header. --> + <dimen name="notification_header_padding_top">0dp</dimen> + + <!-- The padding at the bottom of the notification header. --> + <dimen name="notification_header_padding_bottom">0dp</dimen> + + <!-- The margin at the bottom of the notification header. --> + <dimen name="notification_header_margin_bottom">0dp</dimen> + + <!-- The absolute height for the header in a media notification. --> + <dimen name="media_notification_header_height">@dimen/notification_header_height</dimen> + + <!-- Top margin to accommodate for the header before the notification content. This value + is 8dp smaller than the notification_header_height to bring the text closer. Otherwise, + spacing in the font itself makes the space look too large. --> + <dimen name="notification_content_margin_top">55dp</dimen> + + <!-- The bottom margin after the notification content.--> + <dimen name="notification_content_margin_bottom">24dp</dimen> + + <!-- The margin on the start of the content view. This value should match card keyline1. --> + <dimen name="notification_content_margin_start">24dp</dimen> + + <!-- The margin on the end of the content view. Keep in sync with + notification_content_plus_picture_margin! --> + <dimen name="notification_content_margin_end">@dimen/notification_content_margin_start</dimen> + + <!-- The margin on the end of the content view with a picture. This value is the size of + the right icon (64dp) + notification_content_margin_end + 16dp. --> + <dimen name="notification_content_picture_margin">106dp</dimen> + + <!-- The margin on the end of the content view with a picture, plus the standard + content end margin. --> + <dimen name="notification_content_plus_picture_margin_end">80dp</dimen> + + <!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title). --> + <dimen name="notification_title_text_size">32sp</dimen> + + <!-- Size of notification text (see TextAppearance.StatusBar.EventContent). --> + <dimen name="notification_text_size">26sp</dimen> + + <!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, + Info, Time). --> + <dimen name="notification_subtext_size">@dimen/notification_text_size</dimen> + + <!-- The margin on top of the text of the notification. --> + <dimen name="notification_text_margin_top">0dp</dimen> + + <!-- The height of the notification action list. --> + <dimen name="notification_action_list_height">76dp</dimen> + + <!-- The size of the media actions in the media notification. --> + <dimen name="media_notification_action_button_size">36dp</dimen> + + <!-- The bottom padding for the media actions container. --> + <dimen name="media_notification_actions_padding_bottom">0dp</dimen> + + <!-- The height of the progress bar. --> + <dimen name="notification_progress_bar_height">25dp</dimen> + + <!-- The top margin before the notification progress bar. --> + <dimen name="notification_progress_margin_top">16dp</dimen> </resources> diff --git a/car_product/overlay/frameworks/base/core/res/res/values/styles.xml b/car_product/overlay/frameworks/base/core/res/res/values/styles.xml index de6f6ec4d6..7bcedb9ca3 100644 --- a/car_product/overlay/frameworks/base/core/res/res/values/styles.xml +++ b/car_product/overlay/frameworks/base/core/res/res/values/styles.xml @@ -64,4 +64,15 @@ <item name="fragmentFadeEnterAnimation">@animator/fragment_fade_enter</item> <item name="fragmentFadeExitAnimation">@animator/fragment_fade_exit</item> </style> + + <!-- The style for the container of media actions in a notification. --> + <style name="NotificationMediaActionContainer"> + <item name="background">@color/notification_action_list</item> + <item name="layout_width">match_parent</item> + <item name="layout_height">@dimen/notification_action_list_height</item> + <item name="layout_marginTop">0dp</item> + <item name="paddingStart">0dp</item> + <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item> + <item name="gravity">center</item> + </style> </resources> diff --git a/car-support-lib/res/values-w840dp/dimens.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml index e391d80494..3140afc419 100644 --- a/car-support-lib/res/values-w840dp/dimens.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 The Android Open Source Project +<!-- Copyright (C) 2017 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. @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. --> <resources> - <dimen name="car_drawer_button_container_width">@dimen/stream_content_keyline_1</dimen> - <dimen name="stream_margin_size">40dp</dimen> - <dimen name="stream_gutter_size">24dp</dimen> - <dimen name="stream_content_keyline_1">40dp</dimen> - <dimen name="stream_content_keyline_2">144dp</dimen> - <dimen name="stream_card_keyline_1">32dp</dimen> - <dimen name="stream_card_keyline_2">140dp</dimen> + <!-- The height of the header for a container containing child notifications. --> + <dimen name="notification_children_container_header_height">96dp</dimen> + + <!-- The top margin for the notification children container in its non-expanded form. This + value is smaller than notification_children_container_header_height to bring the first + child closer so there is less wasted space. --> + <dimen name="notification_children_container_margin_top">88dp</dimen> </resources> diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-night/colors.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-night/colors.xml new file mode 100644 index 0000000000..be66b5b66e --- /dev/null +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-night/colors.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2016, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources> + <color name="status_bar_background_color">#ff000000</color> + <color name="system_bar_background_opaque">#ff0c1013</color> +</resources> diff --git a/car-support-lib/res/values-w840dp/integers.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml index 59105198c1..7b85870d23 100644 --- a/car-support-lib/res/values-w840dp/integers.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> <resources> - <integer name="stream_num_of_columns">12</integer> - <integer name="stream_card_default_column_span">8</integer> + <!-- The width of the panel holding the notification cards. --> + <dimen name="notification_panel_width">744dp</dimen> </resources> diff --git a/car-support-lib/res/values-wheel/dimens.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml index f9bcc38b5f..932bae2dd4 100644 --- a/car-support-lib/res/values-wheel/dimens.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2017 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. @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> <resources> - <!-- stream --> - <dimen name="car_paged_list_view_scrollbar_thumb_margin">4dp</dimen> + <!-- The width of panel holding the notification card. --> + <dimen name="notification_panel_width">744dp</dimen> </resources> diff --git a/car-ui-provider/res/values-h480dp/dimens.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml index 44d9df6efd..2248352cfc 100644 --- a/car-ui-provider/res/values-h480dp/dimens.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml @@ -14,5 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> <resources> - <dimen name="lens_header_height">112dp</dimen> + <!-- The width of panel holding the notification card. --> + <dimen name="notification_panel_width">522dp</dimen> </resources> diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/colors.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/colors.xml index 7b6e5eb14c..b66ff92722 100644 --- a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/colors.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/colors.xml @@ -22,4 +22,13 @@ <color name="docked_divider_background">@color/car_grey_50</color> <color name="system_bar_background_opaque">#ff172026</color> + + <color name="status_bar_background_color">#33000000</color> + <drawable name="system_bar_background">@color/status_bar_background_color</drawable> + + <!-- The scrim color for the background of the notifications shade. --> + <color name="scrim_behind_color">#172026</color> + + <!-- The color of the dividing line between grouped notifications. --> + <color name="notification_divider_color">@*android:color/notification_action_list</color> </resources> diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml index b5aae40a47..a9f231c4cb 100644 --- a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml @@ -22,4 +22,81 @@ <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.car.CarStatusBar</string> <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.car.CarSystemUIFactory</string> <bool name="config_enableFullscreenUserSwitcher">true</bool> + + <!-- Notifications on the car should not show the gear icon. Swiping should only dismiss the + cards. --> + <bool name="config_showNotificationGear">false</bool> + + <!-- No need to draw a background around a notification because there is no gear icon. --> + <bool name="config_drawNotificationBackground">false</bool> + + <!-- No quick settings the quick settings row should not be shown.--> + <bool name="config_showQuickSettingsRow">false</bool> + + <!-- The quick settings are not available on the car and should not be editable. --> + <bool name="config_showQuickSettingsEditingIcon">false</bool> + + <!-- The multi-user switcher should always be visible because quick settings cannot be + expanded. Thus, there is no other way to access this. --> + <bool name="config_alwaysShowMultiUserSwitcher">true</bool> + + <!-- The quick settings should not be available for expansion in the car. --> + <bool name="config_showQuickSettingsExpandIndicator">false</bool> + + <!-- There are no quick settings, so it should not be revealed with scrolling. --> + <bool name="config_enableQuickSettingsOverscrollExpansion">false</bool> + + <!-- The notification shade should only be shown on a facet click and not by dragging. --> + <bool name="config_enableNotificationShadeDrag">false</bool> + + <!-- There should not be the ability to clear all notifications with a button. --> + <bool name="config_enableNotificationsClearAll">false</bool> + + <!-- Hide the notification shelf so that the cards in the notification center scroll smoothly + off-screen. --> + <bool name="config_showNotificationShelf">false</bool> + + <!-- The notifications should always fade when being dismissed. --> + <bool name="config_fadeNotificationsOnDismiss">true</bool> + + <!-- The entire notification row should be translated because the cards are smaller than the + width of the screen. If the row is not translated, then they will be clipped. --> + <bool name="config_translateNotificationContentsOnSwipe">false</bool> + + <!-- The notifications should fade as they are being swiped off screen. --> + <bool name="config_fadeDependingOnAmountSwiped">true</bool> + + <!-- The expand icon should be displayed at the top right corner of the notifications. --> + <bool name="config_showNotificationExpandButtonAtEnd">true</bool> + + <!-- A notification card that has been scrolled off screen should not be clipped in height. This + maintains the illusion that the cards are being scrolled underneath the status bar + shelf. --> + <bool name="config_clipNotificationScrollToTop">false</bool> + + <!-- The auto notification have rounded corners. Ensure that any content is clipped to these + corners. --> + <bool name="config_clipNotificationsToOutline">true</bool> + + <!-- Notifications should always be in their expanded state so that the actions are visible. + This will make it easier for a auto user to interact with them. --> + <bool name="config_alwaysExpandNonGroupedNotifications">true</bool> + + <!-- Auto does not allow notifications to be toggled to and from their expanded states to + reduce driver distraction. --> + <bool name="config_enableNonGroupedNotificationExpand">false</bool> + + <!-- There should always be a dividing line between notifications. --> + <bool name="config_showDividersWhenGroupNotificationExpanded">true</bool> + + <!--- Hide the dividing lines when the notification group is expanding. --> + <bool name="config_hideDividersDuringTransition">true</bool> + + <!-- Child notifications are displayed with no dividing space between them in auto, so disable + the shadow. --> + <bool name="config_enableShadowOnChildNotifications">false</bool> + + <!-- Keep the notification background when the container has been expanded. The children will + expand inline within the container, so it can keep its original background. --> + <bool name="config_showGroupNotificationBgWhenExpanded">true</bool> </resources> diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml index 10017842d1..5d675feb6d 100644 --- a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml +++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml @@ -17,6 +17,10 @@ */ --> <resources> + <!-- The alpha for the scrim behind the notification shade. This value is 1 so that the + scrim has no transparency. --> + <item name="scrim_behind_alpha" format="float" type="dimen">1.0</item> + <!-- The amount by which to scale up the status bar icons. --> <item name="status_bar_icon_scale_factor" format="float" type="dimen">2.3</item> @@ -70,4 +74,66 @@ <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or quick settings header --> <dimen name="max_avatar_size">128dp</dimen> + + <!-- The width of panel holding the notification card. --> + <dimen name="notification_panel_width">522dp</dimen> + + <!-- The width of the quick settings panel. -1 for match_parent. --> + <dimen name="qs_panel_width">-1px</dimen> + + <!-- Height of a small notification in the status bar--> + <dimen name="notification_min_height">600dp</dimen> + + <!-- Height of a small notification in the status bar which was used before android N --> + <dimen name="notification_min_height_legacy">600dp</dimen> + + <!-- Height of a large notification in the status bar --> + <dimen name="notification_max_height">600dp</dimen> + + <!-- Height of a heads up notification in the status bar for legacy custom views --> + <dimen name="notification_max_heads_up_height_legacy">600dp</dimen> + + <!-- Height of a heads up notification in the status bar --> + <dimen name="notification_max_heads_up_height">600dp</dimen> + + <!-- Height of the status bar header bar --> + <dimen name="status_bar_header_height">54dp</dimen> + + <!-- The height of the divider between the individual notifications. --> + <dimen name="notification_divider_height">16dp</dimen> + + <!-- The height of the divider between the individual notifications when the notification + wants it to be increased. This value is the same as notification_divider_height so that + the spacing between all notifications will always be the same. --> + <dimen name="notification_divider_height_increased">@dimen/notification_divider_height</dimen> + + <!-- The alpha of the dividing line between child notifications of a notification group. --> + <item name="notification_divider_alpha" format="float" type="dimen">1.0</item> + + <!-- The width of each individual notification card. --> + <dimen name="notification_child_width">522dp</dimen> + + <!-- The top margin of the notification panel. --> + <dimen name="notification_panel_margin_top">32dp</dimen> + + <!-- The bottom margin of the panel that holds the list of notifications. --> + <dimen name="notification_panel_margin_bottom">@dimen/notification_divider_height</dimen> + + <!-- The corner radius of the shadow behind the notification. --> + <dimen name="notification_shadow_radius">16dp</dimen> + + <!-- The amount of space below the notification list. This value is 0 so the list scrolls + all the way to the bottom. --> + <dimen name="close_handle_underlap">0dp</dimen> + + <!-- The height of the divider between the individual notifications in a notification group. --> + <dimen name="notification_children_container_divider_height">1dp</dimen> + + <!-- The height of the header for a container containing child notifications. --> + <dimen name="notification_children_container_header_height">76dp</dimen> + + <!-- The top margin for the notification children container in its non-expanded form. This + value is smaller than notification_children_container_header_height to bring the first + child closer so there is less wasted space. --> + <dimen name="notification_children_container_margin_top">68dp</dimen> </resources> diff --git a/car_product/sepolicy/evs_app.te b/car_product/sepolicy/evs_app.te new file mode 100644 index 0000000000..0e8881ea86 --- /dev/null +++ b/car_product/sepolicy/evs_app.te @@ -0,0 +1,14 @@ +# evs app +type evs_app, domain; +type evs_app_exec, exec_type, file_type; + +allow evs_app evs_app_exec:dir search; +allow evs_app evs_driver:binder call; +allow evs_app evs_mock:binder call; +allow evs_app gpu_device:chr_file ioctl; +allow evs_app hal_graphics_allocator_default:fd use; +allow evs_app hal_vehicle_default:binder call; + +init_daemon_domain(evs_app) + +binder_use(evs_app); diff --git a/car_product/sepolicy/evs_driver.te b/car_product/sepolicy/evs_driver.te new file mode 100644 index 0000000000..1307616dc8 --- /dev/null +++ b/car_product/sepolicy/evs_driver.te @@ -0,0 +1,12 @@ +# evs_driver mock hardware driver service +type evs_driver, domain; +type evs_driver_exec, exec_type, file_type; + +allow evs_driver hwservicemanager:binder { call transfer }; +allow evs_driver hwservicemanager_prop:file { getattr open read }; +allow evs_driver device:dir { open read }; +allow evs_driver surfaceflinger:binder call; + +init_daemon_domain(evs_driver) + +binder_use(evs_driver); diff --git a/car_product/sepolicy/evs_manager.te b/car_product/sepolicy/evs_manager.te new file mode 100644 index 0000000000..f5c4ba849d --- /dev/null +++ b/car_product/sepolicy/evs_manager.te @@ -0,0 +1,11 @@ +# evs manager +type evs_manager, domain; +type evs_manager_exec, exec_type, file_type; + +allow evs_manager hwservicemanager:binder { call transfer }; +allow evs_manager hwservicemanager_prop:file { getattr open read }; +allow evs_manager evs_driver:binder call; + +init_daemon_domain(evs_manager) + +binder_use(evs_manager); diff --git a/car_product/sepolicy/evs_mock.te b/car_product/sepolicy/evs_mock.te new file mode 100644 index 0000000000..b89b1baa2e --- /dev/null +++ b/car_product/sepolicy/evs_mock.te @@ -0,0 +1,11 @@ +# evs_mock mock hardware driver service +type evs_mock, domain; +type evs_mock_exec, exec_type, file_type; + +allow evs_mock hwservicemanager:binder { call transfer }; +allow evs_mock hwservicemanager_prop:file { getattr open read }; +allow evs_mock hal_graphics_allocator_default:fd use; + +init_daemon_domain(evs_mock) + +binder_use(evs_mock); diff --git a/car_product/sepolicy/file_contexts b/car_product/sepolicy/file_contexts index 25cbef2fd6..53759c7eab 100644 --- a/car_product/sepolicy/file_contexts +++ b/car_product/sepolicy/file_contexts @@ -6,4 +6,11 @@ /(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@2\.0-service u:object_r:hal_vehicle_default_exec:s0 /(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@2\.1-service u:object_r:hal_vehicle_default_exec:s0 + +/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.evs@1\.0-service u:object_r:evs_mock_exec:s0 +/system/bin/android\.hardware\.automotive\.evs@1\.0-sample u:object_r:evs_driver_exec:s0 +/system/bin/android\.automotive\.evs\.manager@1\.0 u:object_r:evs_manager_exec:s0 +/system/bin/evs_app u:object_r:evs_app_exec:s0 +/system/etc/automotive/evs(/.*)? u:object_r:evs_app_exec:s0 + ################################### diff --git a/evs/app/Android.mk b/evs/app/Android.mk index 1978616d44..f5d9b6e97a 100644 --- a/evs/app/Android.mk +++ b/evs/app/Android.mk @@ -6,37 +6,75 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ evs_app.cpp \ EvsStateControl.cpp \ - StreamHandler.cpp \ + RenderBase.cpp \ + RenderDirectView.cpp \ + RenderTopView.cpp \ ConfigManager.cpp \ - -LOCAL_C_INCLUDES += \ - frameworks/base/include \ - packages/services/Car/evs/app \ + glError.cpp \ + shader.cpp \ + TexWrapper.cpp \ + VideoTex.cpp \ + StreamHandler.cpp \ + WindowSurface.cpp \ + FormatConvert.cpp \ LOCAL_SHARED_LIBRARIES := \ libcutils \ liblog \ libutils \ libui \ - libhwbinder \ + libgui \ libhidlbase \ libhidltransport \ - libGLESv1_CM \ - libOpenSLES \ - libtinyalsa \ + libEGL \ + libGLESv2 \ libhardware \ + libpng \ android.hardware.automotive.evs@1.0 \ android.hardware.automotive.vehicle@2.0 \ LOCAL_STATIC_LIBRARIES := \ + libmath \ libjsoncpp \ LOCAL_STRIP_MODULE := keep_symbols +LOCAL_INIT_RC := evs_app.rc + LOCAL_MODULE:= evs_app LOCAL_MODULE_TAGS := optional -LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES +LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES -DLOG_TAG=\"EVSAPP\" LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code include $(BUILD_EXECUTABLE) + + +include $(CLEAR_VARS) +LOCAL_MODULE := config.json +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/automotive/evs +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := CarFromTop.png +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/automotive/evs +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := LabeledChecker.png +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/automotive/evs +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := evs_app_default_resources +LOCAL_REQUIRED_MODULES := \ + config.json \ + CarFromTop.png \ + LabeledChecker.png +include $(BUILD_PHONY_PACKAGE)
\ No newline at end of file diff --git a/evs/app/CarFromTop.png b/evs/app/CarFromTop.png Binary files differnew file mode 100644 index 0000000000..11f929e47e --- /dev/null +++ b/evs/app/CarFromTop.png diff --git a/evs/app/ConfigManager.cpp b/evs/app/ConfigManager.cpp index 48966d9ad0..07e570ddfa 100644 --- a/evs/app/ConfigManager.cpp +++ b/evs/app/ConfigManager.cpp @@ -49,24 +49,6 @@ static bool readChildNodeAsFloat(const char* groupName, } -static bool ReadChildNodeAsUint(const char* groupName, - const Json::Value& parentNode, - const char* childName, - unsigned* value) { - // Must have a place to put the value! - assert(value); - - Json::Value childNode = parentNode[childName]; - if (!childNode.isNumeric()) { - printf("Missing or invalid field %s in record %s", childName, groupName); - return false; - } - - *value = childNode.asUInt(); - return true; -} - - bool ConfigManager::initialize(const char* configFileName) { bool complete = true; @@ -110,8 +92,6 @@ bool ConfigManager::initialize(const char* configFileName) printf("Invalid configuration format -- we expect a display description\n"); return false; } - complete &= ReadChildNodeAsUint("display", displayNode, "width", &mPixelWidth); - complete &= ReadChildNodeAsUint("display", displayNode, "height", &mPixelHeight); complete &= readChildNodeAsFloat("display", displayNode, "frontRange", &mFrontRangeInCarSpace); complete &= readChildNodeAsFloat("display", displayNode, "rearRange", &mRearRangeInCarSpace); } @@ -147,7 +127,6 @@ bool ConfigManager::initialize(const char* configFileName) // Get data from the configuration file Json::Value nameNode = node.get("cameraId", "MISSING"); const char *cameraId = nameNode.asCString(); - printf("Loading camera %s\n", cameraId); Json::Value usageNode = node.get("function", ""); const char *function = usageNode.asCString(); diff --git a/evs/app/ConfigManager.h b/evs/app/ConfigManager.h index 1b61146cd3..0d24919735 100644 --- a/evs/app/ConfigManager.h +++ b/evs/app/ConfigManager.h @@ -23,8 +23,8 @@ class ConfigManager { public: struct CameraInfo { - std::string cameraId = 0; // The name of the camera from the point of view of the HAL - std::string function = 0; // The expected use for this camera ("reverse", "left", "right") + std::string cameraId = ""; // The name of the camera from the point of view of the HAL + std::string function = ""; // The expected use for this camera ("reverse", "left", "right") float position[3] = {0}; // x, y, z -> right, fwd, up in the units of car space float yaw = 0; // radians positive to the left (right hand rule about global z axis) float pitch = 0; // positive upward (ie: right hand rule about local x axis) @@ -56,7 +56,7 @@ public: }; float getDisplayRightLocation(float aspectRatio) const { // Given the display aspect ratio (width over height), how far can we see to the right? - return (getDisplayTopLocation() - getDisplayBottomLocation()) * 0.5f / aspectRatio; + return (getDisplayTopLocation() - getDisplayBottomLocation()) * 0.5f * aspectRatio; }; float getDisplayLeftLocation(float aspectRatio) const { // Given the display aspect ratio (width over height), how far can we see to the left? @@ -83,8 +83,6 @@ private: float mRearExtent; // Display information - unsigned mPixelWidth; - unsigned mPixelHeight; float mFrontRangeInCarSpace; // How far the display extends in front of the car float mRearRangeInCarSpace; // How far the display extends behind the car diff --git a/evs/app/EvsStateControl.cpp b/evs/app/EvsStateControl.cpp index ec29e39059..79533f7f76 100644 --- a/evs/app/EvsStateControl.cpp +++ b/evs/app/EvsStateControl.cpp @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#define LOG_TAG "EVSAPP" - #include "EvsStateControl.h" +#include "RenderDirectView.h" +#include "RenderTopView.h" #include <stdio.h> #include <string.h> @@ -39,6 +38,7 @@ EvsStateControl::EvsStateControl(android::sp <IVehicle> pVnet, mVehicle(pVnet), mEvs(pEvs), mDisplay(pDisplay), + mConfig(config), mCurrentState(OFF) { // Initialize the property value containers we'll be updating (they'll be zeroed by default) @@ -50,10 +50,10 @@ EvsStateControl::EvsStateControl(android::sp <IVehicle> pVnet, mGearValue.prop = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION); mTurnSignalValue.prop = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE); +#if 0 // This way we only ever deal with cameras which exist in the system // Build our set of cameras for the states we support ALOGD("Requesting camera list"); - mEvs->getCameraList([this, &config] - (hidl_vec<CameraDesc> cameraList) { + mEvs->getCameraList([this, &config](hidl_vec<CameraDesc> cameraList) { ALOGI("Camera list callback received %zu cameras", cameraList.size()); for (auto&& cam: cameraList) { @@ -63,17 +63,23 @@ EvsStateControl::EvsStateControl(android::sp <IVehicle> pVnet, // Check our configuration for information about this camera // Note that a camera can have a compound function string // such that a camera can be "right/reverse" and be used for both. + // If more than one camera is listed for a given function, we'll + // list all of them and let the UX/rendering logic use one, some + // or all of them as appropriate. for (auto&& info: config.getCameras()) { if (cam.cameraId == info.cameraId) { // We found a match! if (info.function.find("reverse") != std::string::npos) { - mCameraInfo[State::REVERSE] = info; + mCameraList[State::REVERSE].push_back(info); } if (info.function.find("right") != std::string::npos) { - mCameraInfo[State::RIGHT] = info; + mCameraList[State::RIGHT].push_back(info); } if (info.function.find("left") != std::string::npos) { - mCameraInfo[State::LEFT] = info; + mCameraList[State::LEFT].push_back(info); + } + if (info.function.find("park") != std::string::npos) { + mCameraList[State::PARKING].push_back(info); } cameraConfigFound = true; break; @@ -86,12 +92,118 @@ EvsStateControl::EvsStateControl(android::sp <IVehicle> pVnet, } } ); +#else // This way we use placeholders for cameras in the configuration but not reported by EVS + // Build our set of cameras for the states we support + ALOGD("Requesting camera list"); + for (auto&& info: config.getCameras()) { + if (info.function.find("reverse") != std::string::npos) { + mCameraList[State::REVERSE].push_back(info); + } + if (info.function.find("right") != std::string::npos) { + mCameraList[State::RIGHT].push_back(info); + } + if (info.function.find("left") != std::string::npos) { + mCameraList[State::LEFT].push_back(info); + } + if (info.function.find("park") != std::string::npos) { + mCameraList[State::PARKING].push_back(info); + } + } +#endif + ALOGD("State controller ready"); } -bool EvsStateControl::configureForVehicleState() { - ALOGD("configureForVehicleState"); +bool EvsStateControl::startUpdateLoop() { + // Create the thread and report success if it gets started + mRenderThread = std::thread([this](){ updateLoop(); }); + return mRenderThread.joinable(); +} + + +void EvsStateControl::postCommand(const Command& cmd) { + // Push the command onto the queue watched by updateLoop + mLock.lock(); + mCommandQueue.push(cmd); + mLock.unlock(); + + // Send a signal to wake updateLoop in case it is asleep + mWakeSignal.notify_all(); +} + + +void EvsStateControl::updateLoop() { + ALOGD("Starting EvsStateControl update loop"); + + bool run = true; + while (run) { + // Process incoming commands + { + std::lock_guard <std::mutex> lock(mLock); + while (!mCommandQueue.empty()) { + const Command& cmd = mCommandQueue.front(); + switch (cmd.operation) { + case Op::EXIT: + run = false; + break; + case Op::CHECK_VEHICLE_STATE: + // Just running selectStateForCurrentConditions below will take care of this + break; + case Op::TOUCH_EVENT: + // TODO: Implement this given the x/y location of the touch event + // Ignore for now + break; + } + mCommandQueue.pop(); + } + } + + // Review vehicle state and choose an appropriate renderer + if (!selectStateForCurrentConditions()) { + ALOGE("selectStateForCurrentConditions failed so we're going to die"); + break; + } + + // If we have an active renderer, give it a chance to draw + if (mCurrentRenderer) { + // Get the output buffer we'll use to display the imagery + BufferDesc tgtBuffer = {}; + mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc& buff) { + tgtBuffer = buff; + } + ); + + if (tgtBuffer.memHandle == nullptr) { + ALOGE("Didn't get requested output buffer -- skipping this frame."); + } else { + // Generate our output image + if (!mCurrentRenderer->drawFrame(tgtBuffer)) { + // If drawing failed, we want to exit quickly so an app restart can happen + run = false; + } + + // Send the finished image back for display + mDisplay->returnTargetBufferForDisplay(tgtBuffer); + } + } else { + // No active renderer, so sleep until somebody wakes us with another command + std::unique_lock<std::mutex> lock(mLock); + mWakeSignal.wait(lock); + } + } + + ALOGW("EvsStateControl update loop ending"); + + // TODO: Fix it so we can exit cleanly from the main thread instead + printf("Shutting down app due to state control loop ending\n"); + ALOGE("KILLING THE APP FROM THE EvsStateControl LOOP ON DRAW FAILURE!!!"); + exit(1); +} + + +bool EvsStateControl::selectStateForCurrentConditions() { + ALOGV("selectStateForCurrentConditions"); static int32_t sDummyGear = int32_t(VehicleGear::GEAR_REVERSE); static int32_t sDummySignal = int32_t(VehicleTurnSignal::NONE); @@ -124,6 +236,7 @@ bool EvsStateControl::configureForVehicleState() { } // Choose our desired EVS state based on the current car state + // TODO: Update this logic, and include user input when choosing if a view should be presented State desiredState = OFF; if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_REVERSE)) { desiredState = REVERSE; @@ -131,94 +244,92 @@ bool EvsStateControl::configureForVehicleState() { desiredState = RIGHT; } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::LEFT)) { desiredState = LEFT; + } else if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_PARK)) { + desiredState = PARKING; } - // Apply the desire state - ALOGV("Selected state %d.", desiredState); - configureEvsPipeline(desiredState); + ALOGD("Selected state %d.", desiredState); - // Operation was successful - return true; + // Apply the desire state + return configureEvsPipeline(desiredState); } StatusCode EvsStateControl::invokeGet(VehiclePropValue *pRequestedPropValue) { - ALOGD("invokeGet"); + ALOGV("invokeGet"); StatusCode status = StatusCode::TRY_AGAIN; - bool called = false; // Call the Vehicle HAL, which will block until the callback is complete mVehicle->get(*pRequestedPropValue, - [pRequestedPropValue, &status, &called] + [pRequestedPropValue, &status] (StatusCode s, const VehiclePropValue& v) { status = s; *pRequestedPropValue = v; - called = true; } ); - // This should be true as long as the get call is block as it should - // TODO: Once we've got some milage on this code and the underlying HIDL services, - // we should remove this belt-and-suspenders check for correct operation as unnecessary. - if (!called) { - ALOGE("VehicleNetwork query did not run as expected."); - } return status; } bool EvsStateControl::configureEvsPipeline(State desiredState) { - ALOGD("configureEvsPipeline"); + ALOGV("configureEvsPipeline"); if (mCurrentState == desiredState) { // Nothing to do here... return true; } - // See if we actually have to change cameras - if (mCameraInfo[mCurrentState].cameraId != mCameraInfo[desiredState].cameraId) { - ALOGI("Camera change required"); - ALOGD(" Current cameraId (%d) = %s", mCurrentState, - mCameraInfo[mCurrentState].cameraId.c_str()); - ALOGD(" Desired cameraId (%d) = %s", desiredState, - mCameraInfo[desiredState].cameraId.c_str()); - - // Yup, we need to change cameras, so close the previous one, if necessary. - if (mCurrentCamera != nullptr) { - mCurrentStreamHandler->blockingStopStream(); - mCurrentStreamHandler = nullptr; - mCurrentCamera = nullptr; - } + ALOGD(" Current state %d has %zu cameras", mCurrentState, + mCameraList[mCurrentState].size()); + ALOGD(" Desired state %d has %zu cameras", desiredState, + mCameraList[desiredState].size()); - // Now do we need a new camera? - if (!mCameraInfo[desiredState].cameraId.empty()) { - // Need a new camera, so open it - ALOGD("Open camera %s", mCameraInfo[desiredState].cameraId.c_str()); - mCurrentCamera = mEvs->openCamera(mCameraInfo[desiredState].cameraId); + // Since we're changing states, shut down the current renderer + if (mCurrentRenderer != nullptr) { + mCurrentRenderer->deactivate(); + mCurrentRenderer = nullptr; // It's a smart pointer, so destructs on assignment to null + } - // If we didn't get the camera we asked for, we need to bail out and try again later - if (mCurrentCamera == nullptr) { - ALOGE("Failed to open EVS camera. Skipping state change."); - return false; - } + // Do we need a new direct view renderer? + if (mCameraList[desiredState].size() > 1 || desiredState == PARKING) { + // TODO: DO we want other kinds of compound view or else sequentially selected views? + mCurrentRenderer = std::make_unique<RenderTopView>(mEvs, + mCameraList[desiredState], + mConfig); + if (!mCurrentRenderer) { + ALOGE("Failed to construct top view renderer. Skipping state change."); + return false; } + } else if (mCameraList[desiredState].size() == 1) { + // We have a camera assigned to this state for direct view + mCurrentRenderer = std::make_unique<RenderDirectView>(mEvs, + mCameraList[desiredState][0]); + if (!mCurrentRenderer) { + ALOGE("Failed to construct direct renderer. Skipping state change."); + return false; + } + } - // Now set the display state based on whether we have a camera feed to show - if (mCurrentCamera == nullptr) { - ALOGD("Turning off the display"); - mDisplay->setDisplayState(DisplayState::NOT_VISIBLE); - } else { - // Create the stream handler object to receive and forward the video frames - mCurrentStreamHandler = new StreamHandler(mCurrentCamera, mDisplay); - - // Start the camera stream - ALOGD("Starting camera stream"); - mCurrentStreamHandler->startStream(); + // Now set the display state based on whether we have a video feed to show + if (mCurrentRenderer == nullptr) { + ALOGD("Turning off the display"); + mDisplay->setDisplayState(DisplayState::NOT_VISIBLE); + } else { + // Start the camera stream + ALOGD("Starting camera stream"); + if (!mCurrentRenderer->activate()) { + ALOGE("New renderer failed to activate"); + return false; + } - // Activate the display - ALOGD("Arming the display"); - mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME); + // Activate the display + ALOGD("Arming the display"); + Return<EvsResult> result = mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME); + if (result != EvsResult::OK) { + ALOGE("setDisplayState returned an error (%d)", (EvsResult)result); + return false; } } diff --git a/evs/app/EvsStateControl.h b/evs/app/EvsStateControl.h index d8ce9e6758..cfb6833984 100644 --- a/evs/app/EvsStateControl.h +++ b/evs/app/EvsStateControl.h @@ -17,13 +17,16 @@ #ifndef CAR_EVS_APP_EVSSTATECONTROL_H #define CAR_EVS_APP_EVSSTATECONTROL_H +#include "StreamHandler.h" +#include "ConfigManager.h" +#include "RenderBase.h" + #include <android/hardware/automotive/vehicle/2.0/IVehicle.h> #include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> #include <android/hardware/automotive/evs/1.0/IEvsDisplay.h> #include <android/hardware/automotive/evs/1.0/IEvsCamera.h> -#include "StreamHandler.h" -#include "ConfigManager.h" +#include <thread> using namespace ::android::hardware::automotive::evs::V1_0; @@ -35,6 +38,11 @@ using ::android::hardware::hidl_handle; using ::android::sp; +/* + * This class runs the main update loop for the EVS application. It will sleep when it has + * nothing to do. It provides a thread safe way for other threads to wake it and pass commands + * to it. + */ class EvsStateControl { public: EvsStateControl(android::sp <IVehicle> pVnet, @@ -43,31 +51,57 @@ public: const ConfigManager& config); enum State { - REVERSE = 0, + OFF = 0, + REVERSE, LEFT, RIGHT, - OFF, + PARKING, NUM_STATES // Must come last }; - bool configureForVehicleState(); + enum class Op { + EXIT, + CHECK_VEHICLE_STATE, + TOUCH_EVENT, + }; + + struct Command { + Op operation; + uint32_t arg1; + uint32_t arg2; + }; + + // This spawns a new thread that is expected to run continuously + bool startUpdateLoop(); + + // Safe to be called from other threads + void postCommand(const Command& cmd); private: + void updateLoop(); StatusCode invokeGet(VehiclePropValue *pRequestedPropValue); - bool configureEvsPipeline(State desiredState); + bool selectStateForCurrentConditions(); + bool configureEvsPipeline(State desiredState); // Only call from one thread! sp<IVehicle> mVehicle; sp<IEvsEnumerator> mEvs; sp<IEvsDisplay> mDisplay; + const ConfigManager& mConfig; VehiclePropValue mGearValue; VehiclePropValue mTurnSignalValue; - ConfigManager::CameraInfo mCameraInfo[State::NUM_STATES]; - State mCurrentState; - sp<IEvsCamera> mCurrentCamera; + State mCurrentState = OFF; + + std::vector<ConfigManager::CameraInfo> mCameraList[NUM_STATES]; + std::unique_ptr<RenderBase> mCurrentRenderer; + + std::thread mRenderThread; // The thread that runs the main rendering loop - sp<StreamHandler> mCurrentStreamHandler; + // Other threads may want to spur us into action, so we provide a thread safe way to do that + std::mutex mLock; + std::condition_variable mWakeSignal; + std::queue<Command> mCommandQueue; }; diff --git a/evs/app/EvsVehicleListener.h b/evs/app/EvsVehicleListener.h index b15ac0db6c..2935ab06f6 100644 --- a/evs/app/EvsVehicleListener.h +++ b/evs/app/EvsVehicleListener.h @@ -19,7 +19,11 @@ #include "EvsStateControl.h" - +/* + * This class listens for asynchronous updates from the Vehicle HAL. While the EVS + * applications is active, it can poll the vehicle state directly. However, when it goes to + * sleep, we need these notifications to bring it active again. + */ class EvsVehicleListener : public IVehicleCallback { public: // Methods from ::android::hardware::automotive::vehicle::V2_0::IVehicleCallback follow. @@ -59,7 +63,12 @@ public: waitForEvents(5000); // If we were delivered an event (or it's been a while) update as necessary - pStateController->configureForVehicleState(); + EvsStateControl::Command cmd = { + .operation = EvsStateControl::Op::CHECK_VEHICLE_STATE, + .arg1 = 0, + .arg2 = 0, + }; + pStateController->postCommand(cmd); } } diff --git a/evs/app/FormatConvert.cpp b/evs/app/FormatConvert.cpp new file mode 100644 index 0000000000..bd83ba3c17 --- /dev/null +++ b/evs/app/FormatConvert.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "FormatConvert.h" + + +// Round up to the nearest multiple of the given alignment value +template<unsigned alignment> +int align(int value) { + static_assert((alignment && !(alignment & (alignment - 1))), + "alignment must be a power of 2"); + + unsigned mask = alignment - 1; + return (value + mask) & ~mask; +} + + +// Limit the given value to the provided range. :) +static inline float clamp(float v, float min, float max) { + if (v < min) return min; + if (v > max) return max; + return v; +} + + +static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin) { + // Don't use this if you want to see the best performance. :) + // Better to do this in a pixel shader if we really have to, but on actual + // embedded hardware we expect to be able to texture directly from the YUV data + float U = Uin - 128.0f; + float V = Vin - 128.0f; + + float Rf = Y + 1.140f*V; + float Gf = Y - 0.395f*U - 0.581f*V; + float Bf = Y + 2.032f*U; + unsigned char R = (unsigned char)clamp(Rf, 0.0f, 255.0f); + unsigned char G = (unsigned char)clamp(Gf, 0.0f, 255.0f); + unsigned char B = (unsigned char)clamp(Bf, 0.0f, 255.0f); + + return (R ) | + (G << 8) | + (B << 16) | + 0xFF000000; // Fill the alpha channel with ones +} + + +void copyNV21toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels) +{ + // The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved + // U/V array. It assumes an even width and height for the overall image, and a horizontal + // stride that is an even multiple of 16 bytes for both the Y and UV arrays. + unsigned strideLum = align<16>(width); + unsigned sizeY = strideLum * height; + unsigned strideColor = strideLum; // 1/2 the samples, but two interleaved channels + unsigned offsetUV = sizeY; + + uint8_t* srcY = src; + uint8_t* srcUV = src+offsetUV; + + for (unsigned r = 0; r < height; r++) { + // Note that we're walking the same UV row twice for even/odd luminance rows + uint8_t* rowY = srcY + r*strideLum; + uint8_t* rowUV = srcUV + (r/2 * strideColor); + + uint32_t* rowDest = dst + r*dstStridePixels; + + for (unsigned c = 0; c < width; c++) { + unsigned uCol = (c & ~1); // uCol is always even and repeats 1:2 with Y values + unsigned vCol = uCol | 1; // vCol is always odd + rowDest[c] = yuvToRgbx(rowY[c], rowUV[uCol], rowUV[vCol]); + } + } +} + + +void copyYV12toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels) +{ + // The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed + // by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image, + // and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U, + // and V arrays. + unsigned strideLum = align<16>(width); + unsigned sizeY = strideLum * height; + unsigned strideColor = align<16>(strideLum/2); + unsigned sizeColor = strideColor * height/2; + unsigned offsetU = sizeY; + unsigned offsetV = sizeY + sizeColor; + + uint8_t* srcY = src; + uint8_t* srcU = src+offsetU; + uint8_t* srcV = src+offsetV; + + for (unsigned r = 0; r < height; r++) { + // Note that we're walking the same U and V rows twice for even/odd luminance rows + uint8_t* rowY = srcY + r*strideLum; + uint8_t* rowU = srcU + (r/2 * strideColor); + uint8_t* rowV = srcV + (r/2 * strideColor); + + uint32_t* rowDest = dst + r*dstStridePixels; + + for (unsigned c = 0; c < width; c++) { + rowDest[c] = yuvToRgbx(rowY[c], rowU[c], rowV[c]); + } + } +} + + +void copyYUYVtoRGB32(unsigned width, unsigned height, + uint8_t* src, unsigned srcStridePixels, + uint32_t* dst, unsigned dstStridePixels) +{ + uint32_t* srcWords = (uint32_t*)src; + + const int srcRowPadding32 = srcStridePixels/2 - width/2; // 2 bytes per pixel, 4 bytes per word + const int dstRowPadding32 = dstStridePixels - width; // 4 bytes per pixel, 4 bytes per word + + for (unsigned r = 0; r < height; r++) { + for (unsigned c = 0; c < width/2; c++) { + // Note: we're walking two pixels at a time here (even/odd) + uint32_t srcPixel = *srcWords++; + + uint8_t Y1 = (srcPixel) & 0xFF; + uint8_t U = (srcPixel >> 8) & 0xFF; + uint8_t Y2 = (srcPixel >> 16) & 0xFF; + uint8_t V = (srcPixel >> 24) & 0xFF; + + // On the RGB output, we're writing one pixel at a time + *(dst+0) = yuvToRgbx(Y1, U, V); + *(dst+1) = yuvToRgbx(Y2, U, V); + dst += 2; + } + + // Skip over any extra data or end of row alignment padding + srcWords += srcRowPadding32; + dst += dstRowPadding32; + } +} + + +void copyMatchedInterleavedFormats(unsigned width, unsigned height, + void* src, unsigned srcStridePixels, + void* dst, unsigned dstStridePixels, + unsigned pixelSize) { + for (unsigned row = 0; row < height; row++) { + // Copy the entire row of pixel data + memcpy(dst, src, width * pixelSize); + + // Advance to the next row (keeping in mind that stride here is in units of pixels) + src = (uint8_t*)src + srcStridePixels * pixelSize; + dst = (uint8_t*)dst + dstStridePixels * pixelSize; + } +} diff --git a/evs/app/FormatConvert.h b/evs/app/FormatConvert.h new file mode 100644 index 0000000000..3ff1eec12d --- /dev/null +++ b/evs/app/FormatConvert.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef EVS_VTS_FORMATCONVERT_H +#define EVS_VTS_FORMATCONVERT_H + +#include <queue> +#include <stdint.h> + + +// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx values. +// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved +// U/V array. It assumes an even width and height for the overall image, and a horizontal +// stride that is an even multiple of 16 bytes for both the Y and UV arrays. +void copyNV21toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels); + + +// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx values. +// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed +// by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image, +// and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U, +// and V arrays. +void copyYV12toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels); + + +// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx values. +// The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved +// U/V array. It assumes an even width and height for the overall image, and a horizontal +// stride that is an even multiple of 16 bytes for both the Y and UV arrays. +void copyYUYVtoRGB32(unsigned width, unsigned height, + uint8_t* src, unsigned srcStrideBytes, + uint32_t* dst, unsigned dstStrideBytes); + + +// Given an simple rectangular image buffer with an integer number of bytes per pixel, +// copy the pixel values into a new rectangular buffer (potentially with a different stride). +// This is typically used to copy RGBx data into an RGBx output buffer. +void copyMatchedInterleavedFormats(unsigned width, unsigned height, + void* src, unsigned srcStridePixels, + void* dst, unsigned dstStridePixels, + unsigned pixelSize); + +#endif // EVS_VTS_FORMATCONVERT_H diff --git a/evs/app/LabeledChecker.png b/evs/app/LabeledChecker.png Binary files differnew file mode 100644 index 0000000000..02da85d816 --- /dev/null +++ b/evs/app/LabeledChecker.png diff --git a/evs/app/RenderBase.cpp b/evs/app/RenderBase.cpp new file mode 100644 index 0000000000..cb6aa93368 --- /dev/null +++ b/evs/app/RenderBase.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "RenderBase.h" +#include "glError.h" + +#include <log/log.h> +#include <ui/GraphicBuffer.h> + +// Eventually we shouldn't need this dependency, but for now the +// graphics allocator interface isn't fully supported on all platforms +// and this is our work around. +using ::android::GraphicBuffer; + + +// OpenGL state shared among all renderers +EGLDisplay RenderBase::sDisplay = EGL_NO_DISPLAY; +EGLContext RenderBase::sContext = EGL_NO_CONTEXT; +EGLSurface RenderBase::sDummySurface = EGL_NO_SURFACE; +GLuint RenderBase::sFrameBuffer = -1; +GLuint RenderBase::sColorBuffer = -1; +GLuint RenderBase::sDepthBuffer = -1; +EGLImageKHR RenderBase::sKHRimage = EGL_NO_IMAGE_KHR; +unsigned RenderBase::sWidth = 0; +unsigned RenderBase::sHeight = 0; +float RenderBase::sAspectRatio = 0.0f; + + +bool RenderBase::prepareGL() { + // Just trivially return success if we're already prepared + if (sDisplay != EGL_NO_DISPLAY) { + return true; + } + + // Hardcoded to RGBx output display + const EGLint config_attribs[] = { + // Tag Value + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_NONE + }; + + // Select OpenGL ES v 3 + const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + + + // Set up our OpenGL ES context associated with the default display (though we won't be visible) + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + ALOGE("Failed to get egl display"); + return false; + } + + EGLint major = 0; + EGLint minor = 0; + if (!eglInitialize(display, &major, &minor)) { + ALOGE("Failed to initialize EGL: %s", getEGLError()); + return false; + } else { + ALOGI("Intiialized EGL at %d.%d", major, minor); + } + + + // Select the configuration that "best" matches our desired characteristics + EGLConfig egl_config; + EGLint num_configs; + if (!eglChooseConfig(display, config_attribs, &egl_config, 1, &num_configs)) { + ALOGE("eglChooseConfig() failed with error: %s", getEGLError()); + return false; + } + + + // Create a dummy pbuffer so we have a surface to bind -- we never intend to draw to this + // because attachRenderTarget will be called first. + EGLint surface_attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; + sDummySurface = eglCreatePbufferSurface(display, egl_config, surface_attribs); + if (sDummySurface == EGL_NO_SURFACE) { + ALOGE("Failed to create OpenGL ES Dummy surface: %s", getEGLError()); + return false; + } else { + ALOGI("Dummy surface looks good! :)"); + } + + + // + // Create the EGL context + // + EGLContext context = eglCreateContext(display, egl_config, EGL_NO_CONTEXT, context_attribs); + if (context == EGL_NO_CONTEXT) { + ALOGE("Failed to create OpenGL ES Context: %s", getEGLError()); + return false; + } + + + // Activate our render target for drawing + if (!eglMakeCurrent(display, sDummySurface, sDummySurface, context)) { + ALOGE("Failed to make the OpenGL ES Context current: %s", getEGLError()); + return false; + } else { + ALOGI("We made our context current! :)"); + } + + + // Report the extensions available on this implementation + const char* gl_extensions = (const char*) glGetString(GL_EXTENSIONS); + ALOGI("GL EXTENSIONS:\n %s", gl_extensions); + + + // Reserve handles for the color and depth targets we'll be setting up + glGenRenderbuffers(1, &sColorBuffer); + glGenRenderbuffers(1, &sDepthBuffer); + + // Set up the frame buffer object we can modify and use for off screen rendering + glGenFramebuffers(1, &sFrameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, sFrameBuffer); + + + // Now that we're assured success, store object handles we constructed + sDisplay = display; + sContext = context; + + return true; +} + + +bool RenderBase::attachRenderTarget(const BufferDesc& tgtBuffer) { + // Hardcoded to RGBx for now + if (tgtBuffer.format != HAL_PIXEL_FORMAT_RGBA_8888) { + ALOGE("Unsupported target buffer format"); + return false; + } + + // create a GraphicBuffer from the existing handle + sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(tgtBuffer.memHandle, + GraphicBuffer::CLONE_HANDLE, + tgtBuffer.width, tgtBuffer.height, + tgtBuffer.format, 1, // layer count + GRALLOC_USAGE_HW_RENDER, + tgtBuffer.stride); + if (pGfxBuffer.get() == nullptr) { + ALOGE("Failed to allocate GraphicBuffer to wrap image handle"); + return false; + } + + // Get a GL compatible reference to the graphics buffer we've been given + EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; + EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer()); + sKHRimage = eglCreateImageKHR(sDisplay, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuf, + eglImageAttributes); + if (sKHRimage == EGL_NO_IMAGE_KHR) { + ALOGE("error creating EGLImage for target buffer: %s", getEGLError()); + return false; + } + + // Construct a render buffer around the external buffer + glBindRenderbuffer(GL_RENDERBUFFER, sColorBuffer); + glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, static_cast<GLeglImageOES>(sKHRimage)); + if (eglGetError() != EGL_SUCCESS) { + ALOGI("glEGLImageTargetRenderbufferStorageOES => %s", getEGLError()); + return false; + } + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sColorBuffer); + if (eglGetError() != EGL_SUCCESS) { + ALOGE("glFramebufferRenderbuffer => %s", getEGLError()); + return false; + } + + GLenum checkResult = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (checkResult != GL_FRAMEBUFFER_COMPLETE) { + ALOGE("Offscreen framebuffer not configured successfully (%d: %s)", + checkResult, getGLFramebufferError()); + return false; + } + + // Store the size of our target buffer + sWidth = tgtBuffer.width; + sHeight = tgtBuffer.height; + sAspectRatio = (float)sWidth / sHeight; + + // Set the viewport + glViewport(0, 0, sWidth, sHeight); + +#if 1 // We don't actually need the clear if we're going to cover the whole screen anyway + // Clear the color buffer + glClearColor(0.8f, 0.1f, 0.2f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); +#endif + + + return true; +} + + +void RenderBase::detachRenderTarget() { + // Drop our external render target + if (sKHRimage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(sDisplay, sKHRimage); + sKHRimage = EGL_NO_IMAGE_KHR; + } +}
\ No newline at end of file diff --git a/evs/app/RenderBase.h b/evs/app/RenderBase.h new file mode 100644 index 0000000000..25474d575a --- /dev/null +++ b/evs/app/RenderBase.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef CAR_EVS_APP_RENDERBASE_H +#define CAR_EVS_APP_RENDERBASE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> + +#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> + +using namespace ::android::hardware::automotive::evs::V1_0; +using ::android::sp; + + +/* + * Abstract base class for the workhorse classes that handle the user interaction and display for + * each mode of the EVS application. + */ +class RenderBase { +public: + virtual ~RenderBase() {}; + + virtual bool activate() = 0; + virtual void deactivate() = 0; + + virtual bool drawFrame(const BufferDesc& tgtBuffer) = 0; + +protected: + static bool prepareGL(); + + static bool attachRenderTarget(const BufferDesc& tgtBuffer); + static void detachRenderTarget(); + + // OpenGL state shared among all renderers + static EGLDisplay sDisplay; + static EGLContext sContext; + static EGLSurface sDummySurface; + static GLuint sFrameBuffer; + static GLuint sColorBuffer; + static GLuint sDepthBuffer; + + static EGLImageKHR sKHRimage; + + static unsigned sWidth; + static unsigned sHeight; + static float sAspectRatio; +}; + + +#endif //CAR_EVS_APP_RENDERBASE_H diff --git a/evs/app/RenderDirectView.cpp b/evs/app/RenderDirectView.cpp new file mode 100644 index 0000000000..24eb4854ab --- /dev/null +++ b/evs/app/RenderDirectView.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "RenderDirectView.h" +#include "VideoTex.h" +#include "glError.h" +#include "shader.h" +#include "shader_simpleTex.h" + +#include <log/log.h> +#include <math/mat4.h> + + +RenderDirectView::RenderDirectView(sp<IEvsEnumerator> enumerator, + const ConfigManager::CameraInfo& cam) { + mEnumerator = enumerator; + mCameraInfo = cam; +} + + +bool RenderDirectView::activate() { + // Ensure GL is ready to go... + if (!prepareGL()) { + ALOGE("Error initializing GL"); + return false; + } + + // Load our shader program if we don't have it already + if (!mShaderProgram) { + mShaderProgram = buildShaderProgram(vtxShader_simpleTexture, + pixShader_simpleTexture, + "simpleTexture"); + if (!mShaderProgram) { + ALOGE("Error buliding shader program"); + return false; + } + } + + // Construct our video texture + mTexture.reset(createVideoTexture(mEnumerator, mCameraInfo.cameraId.c_str(), sDisplay)); + if (!mTexture) { + ALOGE("Failed to set up video texture for %s (%s)", + mCameraInfo.cameraId.c_str(), mCameraInfo.function.c_str()); +// TODO: For production use, we may actually want to fail in this case, but not yet... +// return false; + } + + return true; +} + + +void RenderDirectView::deactivate() { + // Release our video texture + // We can't hold onto it because some other Render object might need the same camera + // TODO: If start/stop costs become a problem, we could share video textures + mTexture = nullptr; +} + + +bool RenderDirectView::drawFrame(const BufferDesc& tgtBuffer) { + // Tell GL to render to the given buffer + if (!attachRenderTarget(tgtBuffer)) { + ALOGE("Failed to attached render target"); + return false; + } + + // Select our screen space simple texture shader + glUseProgram(mShaderProgram); + + // Set up the model to clip space transform (identity matrix if we're modeling in screen space) + GLint loc = glGetUniformLocation(mShaderProgram, "cameraMat"); + if (loc < 0) { + ALOGE("Couldn't set shader parameter 'cameraMat'"); + return false; + } else { + const android::mat4 identityMatrix; + glUniformMatrix4fv(loc, 1, false, identityMatrix.asArray()); + } + + + // Bind the texture and assign it to the shader's sampler + mTexture->refresh(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mTexture->glId()); + + + GLint sampler = glGetUniformLocation(mShaderProgram, "tex"); + if (sampler < 0) { + ALOGE("Couldn't set shader parameter 'tex'"); + return false; + } else { + // Tell the sampler we looked up from the shader to use texture slot 0 as its source + glUniform1i(sampler, 0); + } + + // We want our image to show up opaque regardless of alpha values + glDisable(GL_BLEND); + + + // Draw a rectangle on the screen + GLfloat vertsCarPos[] = { -1.0, 1.0, 0.0f, // left top in window space + 1.0, 1.0, 0.0f, // right top + -1.0, -1.0, 0.0f, // left bottom + 1.0, -1.0, 0.0f // right bottom + }; + // TODO: We're flipping horizontally here, but should do it only for specified cameras! + GLfloat vertsCarTex[] = { 1.0f, 1.0f, // left top + 0.0f, 1.0f, // right top + 1.0f, 0.0f, // left bottom + 0.0f, 0.0f // right bottom + }; + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + + + // Wait for the rendering to finish + glFinish(); + + return true; +} diff --git a/evs/app/RenderDirectView.h b/evs/app/RenderDirectView.h new file mode 100644 index 0000000000..1543fce8e9 --- /dev/null +++ b/evs/app/RenderDirectView.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef CAR_EVS_APP_RENDERDIRECTVIEW_H +#define CAR_EVS_APP_RENDERDIRECTVIEW_H + + +#include "RenderBase.h" + +#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> +#include "ConfigManager.h" +#include "VideoTex.h" + + +using namespace ::android::hardware::automotive::evs::V1_0; + + +/* + * Renders the view from a single specified camera directly to the full display. + */ +class RenderDirectView: public RenderBase { +public: + RenderDirectView(sp<IEvsEnumerator> enumerator, const ConfigManager::CameraInfo& cam); + + virtual bool activate() override; + virtual void deactivate() override; + + virtual bool drawFrame(const BufferDesc& tgtBuffer); + +protected: + sp<IEvsEnumerator> mEnumerator; + ConfigManager::CameraInfo mCameraInfo; + + std::unique_ptr<VideoTex> mTexture; + + GLuint mShaderProgram = 0; +}; + + +#endif //CAR_EVS_APP_RENDERDIRECTVIEW_H diff --git a/evs/app/RenderPixelCopy.cpp b/evs/app/RenderPixelCopy.cpp new file mode 100644 index 0000000000..0a586a4b42 --- /dev/null +++ b/evs/app/RenderPixelCopy.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "RenderPixelCopy.h" +#include "FormatConvert.h" + +#include <log/log.h> + + +RenderPixelCopy::RenderPixelCopy(sp<IEvsEnumerator> enumerator, + const ConfigManager::CameraInfo& cam) { + mEnumerator = enumerator; + mCameraInfo = cam; +} + + +bool RenderPixelCopy::activate() { + // Set up the camera to feed this texture + sp<IEvsCamera> pCamera = mEnumerator->openCamera(mCameraInfo.cameraId.c_str()); + if (pCamera.get() == nullptr) { + ALOGE("Failed to allocate new EVS Camera interface"); + return false; + } + + // Initialize the stream that will help us update this texture's contents + sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera); + if (pStreamHandler.get() == nullptr) { + ALOGE("failed to allocate FrameHandler"); + return false; + } + + // Start the video stream + if (!pStreamHandler->startStream()) { + ALOGE("start stream failed"); + return false; + } + + mStreamHandler = pStreamHandler; + + return true; +} + + +void RenderPixelCopy::deactivate() { + mStreamHandler = nullptr; +} + + +bool RenderPixelCopy::drawFrame(const BufferDesc& tgtBuffer) { + bool success = true; + + sp<android::GraphicBuffer> tgt = new android::GraphicBuffer( + tgtBuffer.memHandle, android::GraphicBuffer::CLONE_HANDLE, + tgtBuffer.width, tgtBuffer.height, tgtBuffer.format, 1, tgtBuffer.usage, + tgtBuffer.stride); + + // Lock our target buffer for writing (should be RGBA8888 format) + uint32_t* tgtPixels = nullptr; + tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels); + + if (tgtPixels) { + if (tgtBuffer.format != HAL_PIXEL_FORMAT_RGBA_8888) { + // We always expect 32 bit RGB for the display output for now. Is there a need for 565? + ALOGE("Diplay buffer is always expected to be 32bit RGBA"); + success = false; + } else { + // Make sure we have the latest frame data + if (mStreamHandler->newFrameAvailable()) { + const BufferDesc& srcBuffer = mStreamHandler->getNewFrame(); + + // Lock our source buffer for reading (current expectation are for this to be NV21 format) + sp<android::GraphicBuffer> src = new android::GraphicBuffer( + srcBuffer.memHandle, android::GraphicBuffer::CLONE_HANDLE, + srcBuffer.width, srcBuffer.height, srcBuffer.format, 1, srcBuffer.usage, + srcBuffer.stride); + unsigned char* srcPixels = nullptr; + src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels); + if (!srcPixels) { + ALOGE("Failed to get pointer into src image data"); + } + + // Make sure we don't run off the end of either buffer + const unsigned width = std::min(tgtBuffer.width, + srcBuffer.width); + const unsigned height = std::min(tgtBuffer.height, + srcBuffer.height); + + if (srcBuffer.format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21 + copyNV21toRGB32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); + } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12 + copyYV12toRGB32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); + } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV + copyYUYVtoRGB32(width, height, + srcPixels, srcBuffer.stride, + tgtPixels, tgtBuffer.stride); + } else if (srcBuffer.format == tgtBuffer.format) { // 32bit RGBA + copyMatchedInterleavedFormats(width, height, + srcPixels, srcBuffer.stride, + tgtPixels, tgtBuffer.stride, + tgtBuffer.pixelSize); + } + + mStreamHandler->doneWithFrame(srcBuffer); + } + } + } else { + ALOGE("Failed to lock buffer contents for contents transfer"); + success = false; + } + + if (tgtPixels) { + tgt->unlock(); + } + + return success; +} diff --git a/evs/app/RenderPixelCopy.h b/evs/app/RenderPixelCopy.h new file mode 100644 index 0000000000..ee6eede1d6 --- /dev/null +++ b/evs/app/RenderPixelCopy.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef CAR_EVS_APP_RENDERPIXELCOPY_H +#define CAR_EVS_APP_RENDERPIXELCOPY_H + + +#include "RenderBase.h" + +#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> +#include "ConfigManager.h" +#include "VideoTex.h" + + +using namespace ::android::hardware::automotive::evs::V1_0; + + +/* + * Renders the view from a single specified camera directly to the full display. + */ +class RenderPixelCopy: public RenderBase { +public: + RenderPixelCopy(sp<IEvsEnumerator> enumerator, const ConfigManager::CameraInfo& cam); + + virtual bool activate() override; + virtual void deactivate() override; + + virtual bool drawFrame(const BufferDesc& tgtBuffer); + +protected: + sp<IEvsEnumerator> mEnumerator; + ConfigManager::CameraInfo mCameraInfo; + + sp<StreamHandler> mStreamHandler; +}; + + +#endif //CAR_EVS_APP_RENDERPIXELCOPY_H diff --git a/evs/app/RenderTopView.cpp b/evs/app/RenderTopView.cpp new file mode 100644 index 0000000000..1579a0a5de --- /dev/null +++ b/evs/app/RenderTopView.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2017 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. + */ + +#include "RenderTopView.h" +#include "VideoTex.h" +#include "glError.h" +#include "shader.h" +#include "shader_simpleTex.h" +#include "shader_projectedTex.h" + +#include <log/log.h> +#include <math/mat4.h> +#include <math/vec3.h> + + +// Simple aliases to make geometric math using vectors more readable +static const unsigned X = 0; +static const unsigned Y = 1; +static const unsigned Z = 2; +//static const unsigned W = 3; + + +// Since we assume no roll in these views, we can simplify the required math +static android::vec3 unitVectorFromPitchAndYaw(float pitch, float yaw) { + float sinPitch, cosPitch; + sincosf(pitch, &sinPitch, &cosPitch); + float sinYaw, cosYaw; + sincosf(yaw, &sinYaw, &cosYaw); + return android::vec3(cosPitch * -sinYaw, + cosPitch * cosYaw, + sinPitch); +} + + +// Helper function to set up a perspective matrix with independent horizontal and vertical +// angles of view. +static android::mat4 perspective(float hfov, float vfov, float near, float far) { + const float tanHalfFovX = tanf(hfov * 0.5f); + const float tanHalfFovY = tanf(vfov * 0.5f); + + android::mat4 p(0.0f); + p[0][0] = 1.0f / tanHalfFovX; + p[1][1] = 1.0f / tanHalfFovY; + p[2][2] = - (far + near) / (far - near); + p[2][3] = -1.0f; + p[3][2] = - (2.0f * far * near) / (far - near); + return p; +} + + +// Helper function to set up a view matrix for a camera given it's yaw & pitch & location +// Yes, with a bit of work, we could use lookAt, but it does a lot of extra work +// internally that we can short cut. +static android::mat4 cameraLookMatrix(const ConfigManager::CameraInfo& cam) { + float sinYaw, cosYaw; + sincosf(cam.yaw, &sinYaw, &cosYaw); + + // Construct principal unit vectors + android::vec3 vAt = unitVectorFromPitchAndYaw(cam.pitch, cam.yaw); + android::vec3 vRt = android::vec3(cosYaw, sinYaw, 0.0f); + android::vec3 vUp = -cross(vAt, vRt); + android::vec3 eye = android::vec3(cam.position[X], cam.position[Y], cam.position[Z]); + + android::mat4 Result(1.0f); + Result[0][0] = vRt.x; + Result[1][0] = vRt.y; + Result[2][0] = vRt.z; + Result[0][1] = vUp.x; + Result[1][1] = vUp.y; + Result[2][1] = vUp.z; + Result[0][2] =-vAt.x; + Result[1][2] =-vAt.y; + Result[2][2] =-vAt.z; + Result[3][0] =-dot(vRt, eye); + Result[3][1] =-dot(vUp, eye); + Result[3][2] = dot(vAt, eye); + return Result; +} + + +RenderTopView::RenderTopView(sp<IEvsEnumerator> enumerator, + const std::vector<ConfigManager::CameraInfo>& camList, + const ConfigManager& mConfig) : + mEnumerator(enumerator), + mConfig(mConfig) { + + // Copy the list of cameras we're to employ into our local storage. We'll create and + // associate a streaming video texture when we are activated. + mActiveCameras.reserve(camList.size()); + for (unsigned i=0; i<camList.size(); i++) { + mActiveCameras.emplace_back(camList[i]); + } +} + + +bool RenderTopView::activate() { + // Ensure GL is ready to go... + if (!prepareGL()) { + ALOGE("Error initializing GL"); + return false; + } + + // Load our shader programs + mPgmAssets.simpleTexture = buildShaderProgram(vtxShader_simpleTexture, + pixShader_simpleTexture, + "simpleTexture"); + if (!mPgmAssets.simpleTexture) { + ALOGE("Failed to build shader program"); + return false; + } + mPgmAssets.projectedTexture = buildShaderProgram(vtxShader_projectedTexture, + pixShader_projectedTexture, + "projectedTexture"); + if (!mPgmAssets.projectedTexture) { + ALOGE("Failed to build shader program"); + return false; + } + + + // Load the checkerboard text image + mTexAssets.checkerBoard.reset(createTextureFromPng( + "/system/etc/automotive/evs/LabeledChecker.png")); + if (!mTexAssets.checkerBoard->glId()) { + ALOGE("Failed to load checkerboard texture"); + return false; + } + + // Load the car image + mTexAssets.carTopView.reset(createTextureFromPng( + "/system/etc/automotive/evs/CarFromTop.png")); + if (!mTexAssets.carTopView->glId()) { + ALOGE("Failed to load carTopView texture"); + return false; + } + + + // Set up streaming video textures for our associated cameras + for (auto&& cam: mActiveCameras) { + cam.tex.reset(createVideoTexture(mEnumerator, cam.info.cameraId.c_str(), sDisplay)); + if (!cam.tex) { + ALOGE("Failed to set up video texture for %s (%s)", + cam.info.cameraId.c_str(), cam.info.function.c_str()); +// TODO: For production use, we may actually want to fail in this case, but not yet... +// return false; + } + } + + return true; +} + + +void RenderTopView::deactivate() { + // Release our video textures + // We can't hold onto it because some other Render object might need the same camera + // TODO: If start/stop costs become a problem, we could share video textures + for (auto&& cam: mActiveCameras) { + cam.tex = nullptr; + } +} + + +bool RenderTopView::drawFrame(const BufferDesc& tgtBuffer) { + // Tell GL to render to the given buffer + if (!attachRenderTarget(tgtBuffer)) { + ALOGE("Failed to attached render target"); + return false; + } + + // Set up our top down projection matrix from car space (world units, Xfwd, Yright, Zup) + // to view space (-1 to 1) + const float top = mConfig.getDisplayTopLocation(); + const float bottom = mConfig.getDisplayBottomLocation(); + const float right = mConfig.getDisplayRightLocation(sAspectRatio); + const float left = mConfig.getDisplayLeftLocation(sAspectRatio); + + const float near = 10.0f; // arbitrary top of view volume + const float far = 0.0f; // ground plane is at zero + + // We can use a simple, unrotated ortho view since the screen and car space axis are + // naturally aligned in the top down view. + // TODO: Not sure if flipping top/bottom here is "correct" or a double reverse... +// orthoMatrix = android::mat4::ortho(left, right, bottom, top, near, far); + orthoMatrix = android::mat4::ortho(left, right, top, bottom, near, far); + + + // Refresh our video texture contents. We do it all at once in hopes of getting + // better coherence among images. This does not guarantee synchronization, of course... + for (auto&& cam: mActiveCameras) { + if (cam.tex) { + cam.tex->refresh(); + } + } + + // Iterate over all the cameras and project their images onto the ground plane + for (auto&& cam: mActiveCameras) { + renderCameraOntoGroundPlane(cam); + } + + // Draw the car image + renderCarTopView(); + + // Wait for the rendering to finish + glFinish(); + + return true; +} + + +// +// Responsible for drawing the car's self image in the top down view. +// Draws in car model space (units of meters with origin at center of rear axel) +// NOTE: We probably want to eventually switch to using a VertexArray based model system. +// +void RenderTopView::renderCarTopView() { + // Compute the corners of our image footprint in car space + const float carLengthInTexels = mConfig.carGraphicRearPixel() - mConfig.carGraphicFrontPixel(); + const float carSpaceUnitsPerTexel = mConfig.getCarLength() / carLengthInTexels; + const float textureHeightInCarSpace = mTexAssets.carTopView->height() * carSpaceUnitsPerTexel; + const float textureAspectRatio = (float)mTexAssets.carTopView->width() / + mTexAssets.carTopView->height(); + const float pixelsBehindCarInImage = mTexAssets.carTopView->height() - + mConfig.carGraphicRearPixel(); + const float textureExtentBehindCarInCarSpace = pixelsBehindCarInImage * carSpaceUnitsPerTexel; + + const float btCS = mConfig.getRearLocation() - textureExtentBehindCarInCarSpace; + const float tpCS = textureHeightInCarSpace + btCS; + const float ltCS = 0.5f * textureHeightInCarSpace * textureAspectRatio; + const float rtCS = -ltCS; + + GLfloat vertsCarPos[] = { ltCS, tpCS, 0.0f, // left top in car space + rtCS, tpCS, 0.0f, // right top + ltCS, btCS, 0.0f, // left bottom + rtCS, btCS, 0.0f // right bottom + }; + // NOTE: We didn't flip the image in the texture, so V=0 is actually the top of the image + GLfloat vertsCarTex[] = { 0.0f, 0.0f, // left top + 1.0f, 0.0f, // right top + 0.0f, 1.0f, // left bottom + 1.0f, 1.0f // right bottom + }; + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUseProgram(mPgmAssets.simpleTexture); + GLint loc = glGetUniformLocation(mPgmAssets.simpleTexture, "cameraMat"); + glUniformMatrix4fv(loc, 1, false, orthoMatrix.asArray()); + glBindTexture(GL_TEXTURE_2D, mTexAssets.carTopView->glId()); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + + glDisable(GL_BLEND); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); +} + + +// NOTE: Might be worth reviewing the ideas at +// http://math.stackexchange.com/questions/1691895/inverse-of-perspective-matrix +// to see if that simplifies the math, although we'll still want to compute the actual ground +// interception points taking into account the pitchLimit as below. +void RenderTopView::renderCameraOntoGroundPlane(const ActiveCamera& cam) { + // How far is the farthest any camera should even consider projecting it's image? + const float visibleSizeV = mConfig.getDisplayTopLocation() - mConfig.getDisplayBottomLocation(); + const float visibleSizeH = visibleSizeV * sAspectRatio; + const float maxRange = (visibleSizeH > visibleSizeV) ? visibleSizeH : visibleSizeV; + + // Construct the projection matrix (View + Projection) associated with this sensor + // TODO: Consider just hard coding the far plane distance as it likely doesn't matter + const android::mat4 V = cameraLookMatrix(cam.info); + const android::mat4 P = perspective(cam.info.hfov, cam.info.vfov, cam.info.position[Z], maxRange); + const android::mat4 projectionMatix = P*V; + + // Just draw the whole darn ground plane for now -- we're wasting fill rate, but so what? + // A 2x optimization would be to draw only the 1/2 space of the window in the direction + // the sensor is facing. A more complex solution would be to construct the intersection + // of the sensor volume with the ground plane and render only that geometry. + const float top = mConfig.getDisplayTopLocation(); + const float bottom = mConfig.getDisplayBottomLocation(); + const float wsHeight = top - bottom; + const float wsWidth = wsHeight * sAspectRatio; + const float right = wsWidth * 0.5f; + const float left = -right; + + const android::vec3 topLeft(left, top, 0.0f); + const android::vec3 topRight(right, top, 0.0f); + const android::vec3 botLeft(left, bottom, 0.0f); + const android::vec3 botRight(right, bottom, 0.0f); + + GLfloat vertsPos[] = { topLeft[X], topLeft[Y], topLeft[Z], + topRight[X], topRight[Y], topRight[Z], + botLeft[X], botLeft[Y], botLeft[Z], + botRight[X], botRight[Y], botRight[Z], + }; + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsPos); + glEnableVertexAttribArray(0); + + + glDisable(GL_BLEND); + + glUseProgram(mPgmAssets.projectedTexture); + GLint locCam = glGetUniformLocation(mPgmAssets.projectedTexture, "cameraMat"); + glUniformMatrix4fv(locCam, 1, false, orthoMatrix.asArray()); + GLint locProj = glGetUniformLocation(mPgmAssets.projectedTexture, "projectionMat"); + glUniformMatrix4fv(locProj, 1, false, projectionMatix.asArray()); + + GLuint texId; + if (cam.tex) { + texId = cam.tex->glId(); + } else { + texId = mTexAssets.checkerBoard->glId(); + } + glBindTexture(GL_TEXTURE_2D, texId); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + + glDisableVertexAttribArray(0); +} diff --git a/evs/app/RenderTopView.h b/evs/app/RenderTopView.h new file mode 100644 index 0000000000..570718fd61 --- /dev/null +++ b/evs/app/RenderTopView.h @@ -0,0 +1,76 @@ + +/* + * Copyright (C) 2017 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. + */ + +#ifndef CAR_EVS_APP_RENDERTOPVIEW_H +#define CAR_EVS_APP_RENDERTOPVIEW_H + + +#include "RenderBase.h" + +#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> +#include "ConfigManager.h" +#include "VideoTex.h" +#include <math/mat4.h> + + +using namespace ::android::hardware::automotive::evs::V1_0; + + +/* + * Combines the views from all available cameras into one reprojected top down view. + */ +class RenderTopView: public RenderBase { +public: + RenderTopView(sp<IEvsEnumerator> enumerator, + const std::vector<ConfigManager::CameraInfo>& camList, + const ConfigManager& config); + + virtual bool activate() override; + virtual void deactivate() override; + + virtual bool drawFrame(const BufferDesc& tgtBuffer); + +protected: + struct ActiveCamera { + const ConfigManager::CameraInfo& info; + std::unique_ptr<VideoTex> tex; + + ActiveCamera(const ConfigManager::CameraInfo& c) : info(c) {}; + }; + + void renderCarTopView(); + void renderCameraOntoGroundPlane(const ActiveCamera& cam); + + sp<IEvsEnumerator> mEnumerator; + const ConfigManager& mConfig; + std::vector<ActiveCamera> mActiveCameras; + + struct { + std::unique_ptr<TexWrapper> checkerBoard; + std::unique_ptr<TexWrapper> carTopView; + } mTexAssets; + + struct { + GLuint simpleTexture; + GLuint projectedTexture; + } mPgmAssets; + + android::mat4 orthoMatrix; +}; + + +#endif //CAR_EVS_APP_RENDERTOPVIEW_H diff --git a/evs/app/StreamHandler.cpp b/evs/app/StreamHandler.cpp index ee50e96b17..5477642880 100644 --- a/evs/app/StreamHandler.cpp +++ b/evs/app/StreamHandler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 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. @@ -14,40 +14,52 @@ * limitations under the License. */ -#define LOG_TAG "EvsTest" +#define LOG_TAG "EVSAPP" #include "StreamHandler.h" #include <stdio.h> #include <string.h> -#include <android/log.h> +#include <log/log.h> #include <cutils/native_handle.h> -#include <ui/GraphicBuffer.h> -#include <algorithm> // std::min +StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera) : + mCamera(pCamera) +{ + // We rely on the camera having at least two buffers available since we'll hold one and + // expect the camera to be able to capture a new image in the background. + pCamera->setMaxFramesInFlight(2); +} -// For the moment, we're assuming that the underlying EVS driver we're working with -// is providing 4 byte RGBx data. This is fine for loopback testing, although -// real hardware is expected to provide YUV data -- most likly formatted as YV12 -static const unsigned kBytesPerPixel = 4; // assuming 4 byte RGBx pixels +void StreamHandler::shutdown() +{ + // Make sure we're not still streaming + blockingStopStream(); -StreamHandler::StreamHandler(android::sp<IEvsCamera> pCamera, android::sp<IEvsDisplay> pDisplay) : - mCamera(pCamera), - mDisplay(pDisplay) { + // At this point, the receiver thread is no longer running, so we can safely drop + // our remote object references so they can be freed + mCamera = nullptr; } -void StreamHandler::startStream() { - // Mark ourselves as running - mLock.lock(); - mRunning = true; - mLock.unlock(); +bool StreamHandler::startStream() { + std::unique_lock<std::mutex> lock(mLock); - // Tell the camera to start streaming - mCamera->startVideoStream(this); + if (!mRunning) { + // Tell the camera to start streaming + Return <EvsResult> result = mCamera->startVideoStream(this); + if (result != EvsResult::OK) { + return false; + } + + // Mark ourselves as running + mRunning = true; + } + + return true; } @@ -64,7 +76,9 @@ void StreamHandler::blockingStopStream() { // Wait until the stream has actually stopped std::unique_lock<std::mutex> lock(mLock); - mSignal.wait(lock, [this](){ return !mRunning; }); + if (mRunning) { + mSignal.wait(lock, [this]() { return !mRunning; }); + } } @@ -74,146 +88,80 @@ bool StreamHandler::isRunning() { } -unsigned StreamHandler::getFramesReceived() { +bool StreamHandler::newFrameAvailable() { std::unique_lock<std::mutex> lock(mLock); - return mFramesReceived; -}; + return (mReadyBuffer >= 0); +} -unsigned StreamHandler::getFramesCompleted() { +const BufferDesc& StreamHandler::getNewFrame() { std::unique_lock<std::mutex> lock(mLock); - return mFramesCompleted; -}; - -Return<void> StreamHandler::deliverFrame(const BufferDesc& bufferArg) { - ALOGD("Received a frame from the camera (%p)", bufferArg.memHandle.getNativeHandle()); - - // Local flag we use to keep track of when the stream is stopping - bool timeToStop = false; - - if (bufferArg.memHandle.getNativeHandle() == nullptr) { - // Signal that the last frame has been received and that the stream should stop - timeToStop = true; - ALOGI("End of stream signaled"); + if (mHeldBuffer >= 0) { + ALOGE("Ignored call for new frame while still holding the old one."); } else { - // Get the output buffer we'll use to display the imagery - BufferDesc tgtBuffer = {}; - mDisplay->getTargetBuffer([&tgtBuffer] - (const BufferDesc& buff) { - tgtBuffer = buff; - ALOGD("Got output buffer (%p) with id %d cloned as (%p)", - buff.memHandle.getNativeHandle(), - tgtBuffer.bufferId, - tgtBuffer.memHandle.getNativeHandle()); - } - ); - - if (tgtBuffer.memHandle == nullptr) { - printf("Didn't get target buffer - frame lost\n"); - ALOGE("Didn't get requested output buffer -- skipping this frame."); - } else { - // Copy the contents of the of buffer.memHandle into tgtBuffer - copyBufferContents(tgtBuffer, bufferArg); - - // TODO: Add a bit of overlay graphics? - // TODO: Use OpenGL to render from texture? - // NOTE: If we mess with the frame contents, we'll need to update the frame inspection - // logic in the default (test) display driver. - - // Send the target buffer back for display - ALOGD("Calling returnTargetBufferForDisplay (%p)", - tgtBuffer.memHandle.getNativeHandle()); - Return<EvsResult> result = mDisplay->returnTargetBufferForDisplay(tgtBuffer); - if (!result.isOk()) { - printf("HIDL error on display buffer (%s)- frame lost\n", - result.description().c_str()); - ALOGE("Error making the remote function call. HIDL said %s", - result.description().c_str()); - } else if (result != EvsResult::OK) { - printf("Display reported error - frame lost\n"); - ALOGE("We encountered error %d when returning a buffer to the display!", - (EvsResult)result); - } else { - // Everything looks good! Keep track so tests or watch dogs can monitor progress - mLock.lock(); - mFramesCompleted++; - mLock.unlock(); - printf("frame OK\n"); - } + if (mReadyBuffer < 0) { + ALOGE("Returning invalid buffer because we don't have any. Call newFrameAvailable first?"); + mReadyBuffer = 0; // This is a lie! } - // Send the camera buffer back now that we're done with it - ALOGD("Calling doneWithFrame"); - // TODO: Why is it that we get a HIDL crash if we pass back the cloned buffer? - mCamera->doneWithFrame(bufferArg); - - ALOGD("Frame handling complete"); + // Move the ready buffer into the held position, and clear the ready position + mHeldBuffer = mReadyBuffer; + mReadyBuffer = -1; } + return mBuffers[mHeldBuffer]; +} - // Update our received frame count and notify anybody who cares that things have changed - mLock.lock(); - if (timeToStop) { - mRunning = false; - } else { - mFramesReceived++; + +void StreamHandler::doneWithFrame(const BufferDesc& buffer) { + std::unique_lock<std::mutex> lock(mLock); + + // We better be getting back the buffer we original delivered! + if ((mHeldBuffer < 0) || (buffer.bufferId != mBuffers[mHeldBuffer].bufferId)) { + ALOGE("StreamHandler::doneWithFrame got an unexpected buffer!"); } - mLock.unlock(); - mSignal.notify_all(); + // Send the buffer back to the underlying camera + mCamera->doneWithFrame(mBuffers[mHeldBuffer]); - return Void(); + // Clear the held position + mHeldBuffer = -1; } -bool StreamHandler::copyBufferContents(const BufferDesc& tgtBuffer, - const BufferDesc& srcBuffer) { - bool success = true; - - // Make sure we don't run off the end of either buffer - const unsigned width = std::min(tgtBuffer.width, - srcBuffer.width); - const unsigned height = std::min(tgtBuffer.height, - srcBuffer.height); - - sp<android::GraphicBuffer> tgt = new android::GraphicBuffer( - tgtBuffer.memHandle, android::GraphicBuffer::CLONE_HANDLE, - tgtBuffer.width, tgtBuffer.height, tgtBuffer.format, 1, - tgtBuffer.usage, tgtBuffer.stride); - sp<android::GraphicBuffer> src = new android::GraphicBuffer( - srcBuffer.memHandle, android::GraphicBuffer::CLONE_HANDLE, - srcBuffer.width, srcBuffer.height, srcBuffer.format, 1, - srcBuffer.usage, srcBuffer.stride); - - // Lock our source buffer for reading - unsigned char* srcPixels = nullptr; - src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void **) &srcPixels); - - // Lock our target buffer for writing - unsigned char* tgtPixels = nullptr; - tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void **) &tgtPixels); - - if (srcPixels && tgtPixels) { - for (unsigned row = 0; row < height; row++) { - // Copy the entire row of pixel data - memcpy(tgtPixels, srcPixels, width * kBytesPerPixel); - - // Advance to the next row (keeping in mind that stride here is in units of pixels) - tgtPixels += tgtBuffer.stride * kBytesPerPixel; - srcPixels += srcBuffer.stride * kBytesPerPixel; +Return<void> StreamHandler::deliverFrame(const BufferDesc& buffer) { + ALOGD("Received a frame from the camera (%p)", buffer.memHandle.getNativeHandle()); + + // Take the lock to protect our frame slots and running state variable + { + std::unique_lock <std::mutex> lock(mLock); + + if (buffer.memHandle.getNativeHandle() == nullptr) { + // Signal that the last frame has been received and the stream is stopped + mRunning = false; + } else { + // Do we already have a "ready" frame? + if (mReadyBuffer >= 0) { + // Send the previously saved buffer back to the camera unused + mCamera->doneWithFrame(mBuffers[mReadyBuffer]); + + // We'll reuse the same ready buffer index + } else if (mHeldBuffer >= 0) { + // The client is holding a buffer, so use the other slot for "on deck" + mReadyBuffer = 1 - mHeldBuffer; + } else { + // This is our first buffer, so just pick a slot + mReadyBuffer = 0; + } + + // Save this frame until our client is interested in it + mBuffers[mReadyBuffer] = buffer; } - } else { - ALOGE("Failed to copy buffer contents"); - success = false; } - if (srcPixels) { - src->unlock(); - } - if (tgtPixels) { - tgt->unlock(); - } + // Notify anybody who cares that things have changed + mSignal.notify_all(); - return success; + return Void(); } diff --git a/evs/app/StreamHandler.h b/evs/app/StreamHandler.h index eb2f6ce67f..9e1d3b79d0 100644 --- a/evs/app/StreamHandler.h +++ b/evs/app/StreamHandler.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 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. @@ -14,8 +14,12 @@ * limitations under the License. */ -#ifndef CAR_EVS_APP_STREAMHANDLER_H -#define CAR_EVS_APP_STREAMHANDLER_H +#ifndef EVS_VTS_STREAMHANDLER_H +#define EVS_VTS_STREAMHANDLER_H + +#include <queue> + +#include "ui/GraphicBuffer.h" #include <android/hardware/automotive/evs/1.0/IEvsCameraStream.h> #include <android/hardware/automotive/evs/1.0/IEvsCamera.h> @@ -29,38 +33,49 @@ using ::android::hardware::hidl_handle; using ::android::sp; +/* + * StreamHandler: + * This class can be used to receive camera imagery from an IEvsCamera implementation. It will + * hold onto the most recent image buffer, returning older ones. + * Note that the video frames are delivered on a background thread, while the control interface + * is actuated from the applications foreground thread. + */ class StreamHandler : public IEvsCameraStream { public: - StreamHandler(android::sp <IEvsCamera> pCamera, - android::sp <IEvsDisplay> pDisplay); + virtual ~StreamHandler() { shutdown(); }; + + StreamHandler(android::sp <IEvsCamera> pCamera); + void shutdown(); - void startStream(); + bool startStream(); void asyncStopStream(); void blockingStopStream(); bool isRunning(); - unsigned getFramesReceived(); - unsigned getFramesCompleted(); + bool newFrameAvailable(); + const BufferDesc& getNewFrame(); + void doneWithFrame(const BufferDesc& buffer); private: // Implementation for ::android::hardware::automotive::evs::V1_0::ICarCameraStream Return<void> deliverFrame(const BufferDesc& buffer) override; - // Local implementation details - bool copyBufferContents(const BufferDesc& tgtBuffer, const BufferDesc& srcBuffer); - + // Values initialized as startup android::sp <IEvsCamera> mCamera; - android::sp <IEvsDisplay> mDisplay; + // Since we get frames delivered to us asnchronously via the ICarCameraStream interface, + // we need to protect all member variables that may be modified while we're streaming + // (ie: those below) std::mutex mLock; std::condition_variable mSignal; bool mRunning = false; - unsigned mFramesReceived = 0; // Simple counter -- rolls over eventually! - unsigned mFramesCompleted = 0; // Simple counter -- rolls over eventually! + BufferDesc mBuffers[2]; + int mHeldBuffer = -1; // Index of the one currently held by the client + int mReadyBuffer = -1; // Index of the newest available buffer }; -#endif //CAR_EVS_APP_STREAMHANDLER_H +#endif //EVS_VTS_STREAMHANDLER_H diff --git a/evs/app/TexWrapper.cpp b/evs/app/TexWrapper.cpp new file mode 100644 index 0000000000..7ec2191ff7 --- /dev/null +++ b/evs/app/TexWrapper.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2017 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. + */ +#include "TexWrapper.h" +#include "glError.h" + +#include "log/log.h" + +#include <fcntl.h> +#include <malloc.h> +#include <png.h> + + +/* Create an new empty GL texture that will be filled later */ +TexWrapper::TexWrapper() { + GLuint textureId; + glGenTextures(1, &textureId); + if (textureId <= 0) { + ALOGE("Didn't get a texture handle allocated: %s", getEGLError()); + } else { + // Store the basic texture properties + id = textureId; + w = 0; + h = 0; + } +} + + +/* Wrap a texture that already allocated. The wrapper takes ownership. */ +TexWrapper::TexWrapper(GLuint textureId, unsigned width, unsigned height) { + // Store the basic texture properties + id = textureId; + w = width; + h = height; +} + + +TexWrapper::~TexWrapper() { + // Give the texture ID back + if (id > 0) { + glDeleteTextures(1, &id); + } + id = -1; +} + + +/* Factory to build TexWrapper objects from a given PNG file */ +TexWrapper* createTextureFromPng(const char * filename) +{ + // Open the PNG file + FILE *inputFile = fopen(filename, "rb"); + if (inputFile == 0) + { + perror(filename); + return nullptr; + } + + // Read the file header and validate that it is a PNG + static const int kSigSize = 8; + png_byte header[kSigSize] = {0}; + fread(header, 1, kSigSize, inputFile); + if (png_sig_cmp(header, 0, kSigSize)) { + printf("%s is not a PNG.\n", filename); + fclose(inputFile); + return nullptr; + } + + // Set up our control structure + png_structp pngControl = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!pngControl) + { + printf("png_create_read_struct failed.\n"); + fclose(inputFile); + return nullptr; + } + + // Set up our image info structure + png_infop pngInfo = png_create_info_struct(pngControl); + if (!pngInfo) + { + printf("error: png_create_info_struct returned 0.\n"); + png_destroy_read_struct(&pngControl, nullptr, nullptr); + fclose(inputFile); + return nullptr; + } + + // Install an error handler + if (setjmp(png_jmpbuf(pngControl))) { + printf("libpng reported an error\n"); + png_destroy_read_struct(&pngControl, &pngInfo, nullptr); + fclose(inputFile); + return nullptr; + } + + // Set up the png reader and fetch the remaining bits of the header + png_init_io(pngControl, inputFile); + png_set_sig_bytes(pngControl, kSigSize); + png_read_info(pngControl, pngInfo); + + // Get basic information about the PNG we're reading + int bitDepth; + int colorFormat; + png_uint_32 width; + png_uint_32 height; + png_get_IHDR(pngControl, pngInfo, + &width, &height, + &bitDepth, &colorFormat, + NULL, NULL, NULL); + + GLint format; + switch(colorFormat) + { + case PNG_COLOR_TYPE_RGB: + format = GL_RGB; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + format = GL_RGBA; + break; + default: + printf("%s: Unknown libpng color format %d.\n", filename, colorFormat); + return nullptr; + } + + // Refresh the values in the png info struct in case any transformation shave been applied. + png_read_update_info(pngControl, pngInfo); + int stride = png_get_rowbytes(pngControl, pngInfo); + stride += 3 - ((stride-1) % 4); // glTexImage2d requires rows to be 4-byte aligned + + // Allocate storage for the pixel data + png_byte * buffer = (png_byte*)malloc(stride * height); + if (buffer == NULL) + { + printf("error: could not allocate memory for PNG image data\n"); + png_destroy_read_struct(&pngControl, &pngInfo, nullptr); + fclose(inputFile); + return nullptr; + } + + // libpng needs an array of pointers into the image data for each row + png_byte ** rowPointers = (png_byte**)malloc(height * sizeof(png_byte*)); + if (rowPointers == NULL) + { + printf("Failed to allocate temporary row pointers\n"); + png_destroy_read_struct(&pngControl, &pngInfo, nullptr); + free(buffer); + fclose(inputFile); + return nullptr; + } + for (unsigned int r = 0; r < height; r++) + { + rowPointers[r] = buffer + r*stride; + } + + + // Read in the actual image bytes + png_read_image(pngControl, rowPointers); + png_read_end(pngControl, nullptr); + + + // Set up the OpenGL texture to contain this image + GLuint textureId; + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_2D, textureId); + + // Send the image data to GL + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + // Initialize the sampling properties (it seems the sample may not work if this isn't done) + // The user of this texture may very well want to set their own filtering, but we're going + // to pay the (minor) price of setting this up for them to avoid the dreaded "black image" if + // they forget. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + // clean up + png_destroy_read_struct(&pngControl, &pngInfo, nullptr); + free(buffer); + free(rowPointers); + fclose(inputFile); + + glBindTexture(GL_TEXTURE_2D, 0); + + + // Return the texture + return new TexWrapper(textureId, width, height); +} diff --git a/evs/app/TexWrapper.h b/evs/app/TexWrapper.h new file mode 100644 index 0000000000..7c9224704c --- /dev/null +++ b/evs/app/TexWrapper.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 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. + */ +#ifndef TEXWRAPPER_H +#define TEXWRAPPER_H + +#include <GLES2/gl2.h> + + +class TexWrapper { +public: + TexWrapper(GLuint textureId, unsigned width, unsigned height); + virtual ~TexWrapper(); + + GLuint glId() { return id; }; + unsigned width() { return w; }; + unsigned height() { return h; }; + +protected: + TexWrapper(); + + GLuint id; + unsigned w; + unsigned h; +}; + + +TexWrapper* createTextureFromPng(const char* filename); + +#endif // TEXWRAPPER_H
\ No newline at end of file diff --git a/evs/app/VideoTex.cpp b/evs/app/VideoTex.cpp new file mode 100644 index 0000000000..10d54bd4eb --- /dev/null +++ b/evs/app/VideoTex.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 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. + */ +#include <vector> +#include <stdio.h> +#include <fcntl.h> +#include <alloca.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <malloc.h> +#include <png.h> + +#include "VideoTex.h" +#include "glError.h" + +#include <ui/GraphicBuffer.h> + +// Eventually we shouldn't need this dependency, but for now the +// graphics allocator interface isn't fully supported on all platforms +// and this is our work around. +using ::android::GraphicBuffer; + + +VideoTex::VideoTex(sp<IEvsEnumerator> pEnum, + sp<IEvsCamera> pCamera, + sp<StreamHandler> pStreamHandler, + EGLDisplay glDisplay) + : TexWrapper() + , mEnumerator(pEnum) + , mCamera(pCamera) + , mStreamHandler(pStreamHandler) + , mDisplay(glDisplay) { + // Nothing but initialization here... +} + +VideoTex::~VideoTex() { + // Tell the stream to stop flowing + mStreamHandler->asyncStopStream(); + + // Close the camera + mEnumerator->closeCamera(mCamera); + + // Drop our device texture image + if (mKHRimage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mDisplay, mKHRimage); + mKHRimage = EGL_NO_IMAGE_KHR; + } +} + + +// Return true if the texture contents are changed +bool VideoTex::refresh() { + if (!mStreamHandler->newFrameAvailable()) { + // No new image has been delivered, so there's nothing to do here + return false; + } + + // If we already have an image backing us, then it's time to return it + if (mImageBuffer.memHandle.getNativeHandle() != nullptr) { + // Drop our device texture image + if (mKHRimage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mDisplay, mKHRimage); + mKHRimage = EGL_NO_IMAGE_KHR; + } + + // Return it since we're done with it + mStreamHandler->doneWithFrame(mImageBuffer); + } + + // Get the new image we want to use as our contents + mImageBuffer = mStreamHandler->getNewFrame(); + + + // create a GraphicBuffer from the existing handle + sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(mImageBuffer.memHandle, + GraphicBuffer::CLONE_HANDLE, + mImageBuffer.width, mImageBuffer.height, + mImageBuffer.format, 1, // layer count + GRALLOC_USAGE_HW_TEXTURE, + mImageBuffer.stride); + if (pGfxBuffer.get() == nullptr) { + ALOGE("Failed to allocate GraphicBuffer to wrap image handle"); + // Returning "true" in this error condition because we already released the + // previous image (if any) and so the texture may change in unpredictable ways now! + return true; + } + + // Get a GL compatible reference to the graphics buffer we've been given + EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; + EGLClientBuffer clientBuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer()); + mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuf, + eglImageAttributes); + if (mKHRimage == EGL_NO_IMAGE_KHR) { + const char *msg = getEGLError(); + ALOGE("error creating EGLImage: %s", msg); + } else { + // Update the texture handle we already created to refer to this gralloc buffer + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, glId()); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(mKHRimage)); + + // Initialize the sampling properties (it seems the sample may not work if this isn't done) + // The user of this texture may very well want to set their own filtering, but we're going + // to pay the (minor) price of setting this up for them to avoid the dreaded "black image" + // if they forget. + // TODO: Can we do this once for the texture ID rather than ever refresh? + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + return true; +} + + +VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum, + const char* evsCameraId, + EGLDisplay glDisplay) { + // Set up the camera to feed this texture + sp<IEvsCamera> pCamera = pEnum->openCamera(evsCameraId); + if (pCamera.get() == nullptr) { + ALOGE("Failed to allocate new EVS Camera interface for %s", evsCameraId); + return nullptr; + } + + // Initialize the stream that will help us update this texture's contents + sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera); + if (pStreamHandler.get() == nullptr) { + ALOGE("failed to allocate FrameHandler"); + return nullptr; + } + + // Start the video stream + if (!pStreamHandler->startStream()) { + printf("Couldn't start the camera stream (%s)\n", evsCameraId); + ALOGE("start stream failed for %s", evsCameraId); + return nullptr; + } + + return new VideoTex(pEnum, pCamera, pStreamHandler, glDisplay); +} diff --git a/evs/app/VideoTex.h b/evs/app/VideoTex.h new file mode 100644 index 0000000000..0b95c1ddd5 --- /dev/null +++ b/evs/app/VideoTex.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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. + */ +#ifndef VIDEOTEX_H +#define VIDEOTEX_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> + +#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> + +#include "TexWrapper.h" +#include "StreamHandler.h" + + +using namespace ::android::hardware::automotive::evs::V1_0; + + +class VideoTex: public TexWrapper { + friend VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum, + const char * evsCameraId, + EGLDisplay glDisplay); + +public: + VideoTex() = delete; + virtual ~VideoTex(); + + bool refresh(); // returns true if the texture contents were updated + +private: + VideoTex(sp<IEvsEnumerator> pEnum, + sp<IEvsCamera> pCamera, + sp<StreamHandler> pStreamHandler, + EGLDisplay glDisplay); + + sp<IEvsEnumerator> mEnumerator; + sp<IEvsCamera> mCamera; + sp<StreamHandler> mStreamHandler; + BufferDesc mImageBuffer; + + EGLDisplay mDisplay; + EGLImageKHR mKHRimage = EGL_NO_IMAGE_KHR; +}; + + +VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum, + const char * deviceName, + EGLDisplay glDisplay); + +#endif // VIDEOTEX_H
\ No newline at end of file diff --git a/evs/app/WindowSurface.cpp b/evs/app/WindowSurface.cpp new file mode 100644 index 0000000000..a3f56bc2ca --- /dev/null +++ b/evs/app/WindowSurface.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2014 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. + */ + +#include "WindowSurface.h" + +#include <gui/SurfaceComposerClient.h> +#include <gui/ISurfaceComposer.h> +#include <gui/Surface.h> +#include <ui/DisplayInfo.h> + +using namespace android; + +WindowSurface::WindowSurface() { + status_t err; + + sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient; + err = surfaceComposerClient->initCheck(); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposerClient::initCheck error: %#x\n", err); + return; + } + + // Get main display parameters. + sp<IBinder> mainDpy = SurfaceComposerClient::getBuiltInDisplay( + ISurfaceComposer::eDisplayIdMain); + DisplayInfo mainDpyInfo; + err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: unable to get display characteristics\n"); + return; + } + + uint32_t width, height; + if (mainDpyInfo.orientation != DISPLAY_ORIENTATION_0 && + mainDpyInfo.orientation != DISPLAY_ORIENTATION_180) { + // rotated + width = mainDpyInfo.h; + height = mainDpyInfo.w; + } else { + width = mainDpyInfo.w; + height = mainDpyInfo.h; + } + + sp<SurfaceControl> sc = surfaceComposerClient->createSurface( + String8("Benchmark"), width, height, + PIXEL_FORMAT_RGBX_8888, ISurfaceComposerClient::eOpaque); + if (sc == NULL || !sc->isValid()) { + fprintf(stderr, "Failed to create SurfaceControl\n"); + return; + } + + SurfaceComposerClient::openGlobalTransaction(); + err = sc->setLayer(0x7FFFFFFF); // always on top + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposer::setLayer error: %#x\n", err); + return; + } + + err = sc->show(); + if (err != NO_ERROR) { + fprintf(stderr, "SurfaceComposer::show error: %#x\n", err); + return; + } + SurfaceComposerClient::closeGlobalTransaction(); + + mSurfaceControl = sc; +} + +EGLNativeWindowType WindowSurface::getSurface() const { + sp<ANativeWindow> anw = mSurfaceControl->getSurface(); + return (EGLNativeWindowType) anw.get(); +} + diff --git a/evs/app/WindowSurface.h b/evs/app/WindowSurface.h new file mode 100644 index 0000000000..966ea11f88 --- /dev/null +++ b/evs/app/WindowSurface.h @@ -0,0 +1,46 @@ +/* + * Copyright 2014 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. + */ + +#ifndef OPENGL_TESTS_WINDOWSURFACE_H +#define OPENGL_TESTS_WINDOWSURFACE_H + +#include <gui/SurfaceControl.h> + +#include <EGL/egl.h> + + +/* + * A window that covers the entire display surface. + * + * The window is destroyed when this object is destroyed, so don't try + * to use the surface after that point. + */ +class WindowSurface { +public: + // Creates the window. + WindowSurface(); + + // Retrieves a handle to the window. + EGLNativeWindowType getSurface() const; + +private: + WindowSurface(const WindowSurface&) = delete; + WindowSurface& operator=(const WindowSurface&) = delete; + + android::sp<android::SurfaceControl> mSurfaceControl; +}; + +#endif /* OPENGL_TESTS_WINDOWSURFACE_H */ diff --git a/evs/app/config.json b/evs/app/config.json index 791dc4ed57..5de8bb5577 100644 --- a/evs/app/config.json +++ b/evs/app/config.json @@ -6,8 +6,6 @@ "rearExtent" : 40 }, "display" : { - "width" : 640, - "height" : 480, "frontRange" : 100, "rearRange" : 100 }, @@ -17,57 +15,37 @@ }, "cameras" : [ { - "name" : "rightFront", - "x" : 36.0, - "y" : 90.0, - "z" : 36, - "yaw" : -45, - "pitch" : -25, - "hfov" : 60, - "vfov" : 40 - }, - { - "name" : "rightRear", - "function" : "right", - "x" : 36.0, - "y" : -10, - "z" : 36, - "yaw" : -135, - "pitch" : -25, - "hfov" : 60, - "vfov" : 40 - }, - { - "name" : "left", - "function" : "left", - "x" : -36.0, - "y" : 80, - "z" : 30, - "yaw" : 90, - "pitch" : -45, - "hfov" : 90, - "vfov" : 90 + "cameraId" : "/dev/video32", + "function" : "reverse,park", + "x" : 0.0, + "y" : -40.0, + "z" : 48, + "yaw" : 180, + "pitch" : -30, + "hfov" : 125, + "vfov" :103 }, { - "name" : "front", + "cameraId" : "/dev/video45", + "function" : "front,park", "x" : 0.0, "y" : 100.0, "z" : 48, "yaw" : 0, "pitch" : -10, - "hfov" : 60, - "vfov" : 42 + "hfov" : 70, + "vfov" : 43 }, { - "name" : "rear", - "function" : "rear", - "x" : 0.0, - "y" : -40, - "z" : 30, - "yaw" : 180, - "pitch" : -45, - "hfov" : 90, - "vfov" : 60 + "cameraId" : "/dev/video0", + "function" : "right,park", + "x" : 36.0, + "y" : 60.0, + "z" : 32, + "yaw" : -90, + "pitch" : -30, + "hfov" : 60, + "vfov" : 42 } ] -}
\ No newline at end of file +} diff --git a/evs/app/evs_app.cpp b/evs/app/evs_app.cpp index d17119e75a..5f8d64f652 100644 --- a/evs/app/evs_app.cpp +++ b/evs/app/evs_app.cpp @@ -40,18 +40,66 @@ using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; -// TODO: Should this somehow be a shared definition with the module itself? -const static char kEvsServiceName[] = "EvsSharedEnumerator"; +// Helper to subscribe to VHal notifications +static bool subscribeToVHal(sp<IVehicle> pVnet, + sp<IVehicleCallback> listener, + VehicleProperty propertyId) { + assert(pVnet != nullptr); + assert(listener != nullptr); + + // Register for vehicle state change callbacks we care about + // Changes in these values are what will trigger a reconfiguration of the EVS pipeline + SubscribeOptions optionsData[] = { + { + .propId = static_cast<int32_t>(propertyId), + .flags = SubscribeFlags::DEFAULT + }, + }; + hidl_vec <SubscribeOptions> options; + options.setToExternal(optionsData, arraysize(optionsData)); + StatusCode status = pVnet->subscribe(listener, options); + if (status != StatusCode::OK) { + ALOGW("VHAL subscription for property 0x%08X failed with code %d.", propertyId, status); + return false; + } + + return true; +} // Main entry point -int main(int /* argc */, char** /* argv */) +int main(int argc, char** argv) { - printf("EVS app starting\n"); + ALOGI("EVS app starting\n"); + + // Set up default behavior, then check for command line options + bool useVehicleHal = true; + bool printHelp = false; + const char* evsServiceName = "default"; + for (int i=1; i< argc; i++) { + if (strcmp(argv[i], "--test") == 0) { + useVehicleHal = false; + } else if (strcmp(argv[i], "--hw") == 0) { + evsServiceName = "EvsEnumeratorHw"; + } else if (strcmp(argv[i], "--mock") == 0) { + evsServiceName = "EvsEnumeratorHw-Mock"; + } else if (strcmp(argv[i], "--help") == 0) { + printHelp = true; + } else { + printf("Ignoring unrecognized command line arg '%s'\n", argv[i]); + printHelp = true; + } + } + if (printHelp) { + printf("Options include:\n"); + printf(" --test Do not talk to Vehicle Hal, but simulate 'reverse' instead\n"); + printf(" --hw Bypass EvsManager by connecting directly to EvsEnumeratorHw\n"); + printf(" --mock Connect directly to EvsEnumeratorHw-Mock\n"); + } // Load our configuration information ConfigManager config; - config.initialize("config.json"); + config.initialize("/system/etc/automotive/evs/config.json"); // Set thread pool size to one to avoid concurrent events from the HAL. // This pool will handle the EvsCameraStream callbacks. @@ -64,9 +112,9 @@ int main(int /* argc */, char** /* argv */) // Get the EVS manager service ALOGI("Acquiring EVS Enumerator"); - android::sp<IEvsEnumerator> pEvs = IEvsEnumerator::getService(kEvsServiceName); + android::sp<IEvsEnumerator> pEvs = IEvsEnumerator::getService(evsServiceName); if (pEvs.get() == nullptr) { - ALOGE("getService returned NULL. Exiting."); + ALOGE("getService(%s) returned NULL. Exiting.", evsServiceName); return 1; } @@ -80,52 +128,43 @@ int main(int /* argc */, char** /* argv */) } // Connect to the Vehicle HAL so we can monitor state - ALOGI("Connecting to Vehicle HAL"); - android::sp <IVehicle> pVnet = IVehicle::getService(); - if (pVnet.get() == nullptr) { -#if 0 - ALOGE("Vehicle HAL getService returned NULL. Exiting."); - return 1; -#else - // While testing, at least, we want to be able to run without a vehicle - ALOGE("getService returned NULL, but we're in test, so we'll pretend to be in reverse"); -#endif - } else { - // Register for vehicle state change callbacks we care about - // Changes in these values are what will trigger a reconfiguration of the EVS pipeline - SubscribeOptions optionsData[2] = { - { - .propId = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION), - .flags = SubscribeFlags::DEFAULT - }, - { - .propId = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE), - .flags = SubscribeFlags::DEFAULT - }, - }; - hidl_vec<SubscribeOptions> options; - options.setToExternal(optionsData, arraysize(optionsData)); - StatusCode status = pVnet->subscribe(pEvsListener, options); - if (status != StatusCode::OK) { - ALOGE("Subscription to vehicle notifications failed with code %d. Exiting.", status); + sp<IVehicle> pVnet; + if (useVehicleHal) { + ALOGI("Connecting to Vehicle HAL"); + pVnet = IVehicle::getService(); + if (pVnet.get() == nullptr) { + ALOGE("Vehicle HAL getService returned NULL. Exiting."); return 1; + } else { + // Register for vehicle state change callbacks we care about + // Changes in these values are what will trigger a reconfiguration of the EVS pipeline + if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) { + ALOGE("Without gear notification, we can't support EVS. Exiting."); + return 1; + } + if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) { + ALOGW("Didn't get turn signal notificaitons, so we'll ignore those."); + } } + } else { + ALOGW("Test mode selected, so not talking to Vehicle HAL"); } // Configure ourselves for the current vehicle state at startup ALOGI("Constructing state controller"); EvsStateControl *pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config); - if (!pStateController->configureForVehicleState()) { + if (!pStateController->startUpdateLoop()) { ALOGE("Initial configuration failed. Exiting."); return 1; - } else { - // Run forever, reacting to events as necessary - ALOGI("Entering running state"); - pEvsListener->run(pStateController); } + // Run forever, reacting to events as necessary + ALOGI("Entering running state"); + pEvsListener->run(pStateController); + // In normal operation, we expect to run forever, but in some error conditions we'll quit. // One known example is if another process preempts our registration for our service name. - printf("EVS Listener stopped. Exiting.\n"); + ALOGE("EVS Listener stopped. Exiting."); + return 0; } diff --git a/evs/app/evs_app.rc b/evs/app/evs_app.rc new file mode 100644 index 0000000000..a61edfef0b --- /dev/null +++ b/evs/app/evs_app.rc @@ -0,0 +1,5 @@ +service evs_app /system/bin/evs_app + class hal + priority -20 + user automotive_evs + group automotive_evs diff --git a/evs/app/glError.cpp b/evs/app/glError.cpp new file mode 100644 index 0000000000..53188d3843 --- /dev/null +++ b/evs/app/glError.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 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. + */ + +#include <stdio.h> +#include <EGL/egl.h> +#include <GLES3/gl3.h> + + +const char *getEGLError(void) { + switch (eglGetError()) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return "Unknown error"; + } +} + + +const char *getGLFramebufferError(void) { + switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) { + case GL_FRAMEBUFFER_COMPLETE: + return "GL_FRAMEBUFFER_COMPLETE"; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + case GL_FRAMEBUFFER_UNSUPPORTED: + return "GL_FRAMEBUFFER_UNSUPPORTED"; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + default: + return "Unknown error"; + } +} diff --git a/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java b/evs/app/glError.h index 94abeb1ab4..52c5d5a245 100644 --- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java +++ b/evs/app/glError.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2017 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. @@ -14,13 +14,11 @@ * limitations under the License. */ -package com.android.support.car.apitest; +#ifndef GLERROR_H +#define GLERROR_H -import android.support.car.app.CarProxyActivity; +const char *getEGLError(void); -public class TestCarProxyActivity extends CarProxyActivity { +const char *getGLFramebufferError(void); - public TestCarProxyActivity() { - super(TestCarActivity.class); - } -} +#endif // GLERROR_H
\ No newline at end of file diff --git a/evs/app/shader.cpp b/evs/app/shader.cpp new file mode 100644 index 0000000000..6922fbe972 --- /dev/null +++ b/evs/app/shader.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 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. + */ +#include "shader.h" + +#include <stdio.h> +#include <memory> + + +// Given shader source, load and compile it +static GLuint loadShader(GLenum type, const char *shaderSrc, const char *name) { + // Create the shader object + GLuint shader = glCreateShader (type); + if (shader == 0) { + return 0; + } + + // Load and compile the shader + glShaderSource(shader, 1, &shaderSrc, nullptr); + glCompileShader(shader); + + // Verify the compilation worked as expected + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + printf("Error compiling %s shader for %s\n", (type==GL_VERTEX_SHADER) ? "vtx":"pxl", name); + + GLint size = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size); + if (size > 0) + { + // Get and report the error message + std::unique_ptr<char> infoLog(new char[size]); + glGetShaderInfoLog(shader, size, NULL, infoLog.get()); + printf(" msg:\n%s\n", infoLog.get()); + } + + glDeleteShader(shader); + return 0; + } + + return shader; +} + + +// Create a program object given vertex and pixels shader source +GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc, const char* name) { + GLuint program = glCreateProgram(); + if (program == 0) { + printf("Failed to allocate program object\n"); + return 0; + } + + // Compile the shaders and bind them to this program + GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vtxSrc, name); + if (vertexShader == 0) { + printf("Failed to load vertex shader\n"); + glDeleteProgram(program); + return 0; + } + GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pxlSrc, name); + if (pixelShader == 0) { + printf("Failed to load pixel shader\n"); + glDeleteProgram(program); + glDeleteShader(vertexShader); + return 0; + } + glAttachShader(program, vertexShader); + glAttachShader(program, pixelShader); + + // Link the program + glLinkProgram(program); + GLint linked = 0; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (!linked) + { + printf("Error linking program.\n"); + GLint size = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &size); + if (size > 0) + { + // Get and report the error message + std::unique_ptr<char> infoLog(new char[size]); + glGetProgramInfoLog(program, size, NULL, infoLog.get()); + printf(" msg: %s\n", infoLog.get()); + } + + glDeleteProgram(program); + glDeleteShader(vertexShader); + glDeleteShader(pixelShader); + return 0; + } + + +#if 0 // Debug output to diagnose shader parameters + GLint numShaderParams; + GLchar paramName[128]; + GLint paramSize; + GLenum paramType; + const char *typeName = "?"; + printf("Shader parameters for %s:\n", name); + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numShaderParams); + for (GLint i=0; i<numShaderParams; i++) { + glGetActiveUniform(program, + i, + sizeof(paramName), + nullptr, + ¶mSize, + ¶mType, + paramName); + switch (paramType) { + case GL_FLOAT: typeName = "GL_FLOAT"; break; + case GL_FLOAT_VEC4: typeName = "GL_FLOAT_VEC4"; break; + case GL_FLOAT_MAT4: typeName = "GL_FLOAT_MAT4"; break; + case GL_SAMPLER_2D: typeName = "GL_SAMPLER_2D"; break; + } + + printf(" %2d: %s\t (%d) of type %s(%d)\n", i, paramName, paramSize, typeName, paramType); + } +#endif + + + return program; +} diff --git a/car-support-lib/src/android/support/car/app/CarAppUtil.java b/evs/app/shader.h index 2371025e8f..476a2f0e81 100644 --- a/car-support-lib/src/android/support/car/app/CarAppUtil.java +++ b/evs/app/shader.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.car.app; -import android.content.Context; +#ifndef SHADER_H +#define SHADER_H + +#include <GLES2/gl2.h> -/** - * @hide - */ -public final class CarAppUtil { - /** - * PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L. - */ - private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; +// Create a program object given vertex and pixels shader source +GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc, const char* name); - public static boolean isEmbeddedCar(Context context) { - return context.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE); - } -} +#endif // SHADER_H
\ No newline at end of file diff --git a/evs/app/shader_projectedTex.h b/evs/app/shader_projectedTex.h new file mode 100644 index 0000000000..65e9109996 --- /dev/null +++ b/evs/app/shader_projectedTex.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef SHADER_PROJECTED_TEX_H +#define SHADER_PROJECTED_TEX_H + +// This shader is used to project a sensors image onto wold space geometry +// as if it were projected from the original sensor's point of view in the world. + +const char vtxShader_projectedTexture[] = "" + "#version 300 es \n" + "layout(location = 0) in vec4 pos; \n" + "uniform mat4 cameraMat; \n" + "uniform mat4 projectionMat; \n" + "out vec4 projectionSpace; \n" + "void main() \n" + "{ \n" + " gl_Position = cameraMat * pos; \n" + " projectionSpace = projectionMat * pos; \n" + "} \n"; + +const char pixShader_projectedTexture[] = + "#version 300 es \n" + "precision mediump float; \n" + "uniform sampler2D tex; \n" + "in vec4 projectionSpace; \n" + "out vec4 color; \n" + "void main() \n" + "{ \n" + " const vec2 zero = vec2(0.0f, 0.0f); \n" + " const vec2 one = vec2(1.0f, 1.0f); \n" + " \n" + " // Compute perspective correct texture coordinates \n" + " // in the sensor map \n" + " vec2 cs = projectionSpace.xy / projectionSpace.w; \n" + " \n" + " // flip the texture! \n" + " cs.y = -cs.y; \n" + " \n" + " // scale from -1/1 clip space to 0/1 uv space \n" + " vec2 uv = (cs + 1.0f) * 0.5f; \n" + " \n" + " // Bail if we don't have a valid projection \n" + " if ((projectionSpace.w <= 0.0f) || \n" + " any(greaterThan(uv, one)) || \n" + " any(lessThan(uv, zero))) { \n" + " discard; \n" + " } \n" + " color = texture(tex, uv); \n" + "} \n"; + +#endif // SHADER_PROJECTED_TEX_H
\ No newline at end of file diff --git a/evs/app/shader_simpleTex.h b/evs/app/shader_simpleTex.h new file mode 100644 index 0000000000..0e962bdd6c --- /dev/null +++ b/evs/app/shader_simpleTex.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef SHADER_SIMPLE_TEX_H +#define SHADER_SIMPLE_TEX_H + +const char vtxShader_simpleTexture[] = "" + "#version 300 es \n" + "layout(location = 0) in vec4 pos; \n" + "layout(location = 1) in vec2 tex; \n" + "uniform mat4 cameraMat; \n" + "out vec2 uv; \n" + "void main() \n" + "{ \n" + " gl_Position = cameraMat * pos; \n" + " uv = tex; \n" + "} \n"; + +const char pixShader_simpleTexture[] = + "#version 300 es \n" + "precision mediump float; \n" + "uniform sampler2D tex; \n" + "in vec2 uv; \n" + "out vec4 color; \n" + "void main() \n" + "{ \n" + " vec4 texel = texture(tex, uv); \n" + " color = texel; \n" + "} \n"; + +#endif // SHADER_SIMPLE_TEX_H
\ No newline at end of file diff --git a/evs/manager/Android.mk b/evs/manager/Android.mk index 9522f81102..fe384d95c6 100644 --- a/evs/manager/Android.mk +++ b/evs/manager/Android.mk @@ -9,26 +9,24 @@ LOCAL_SRC_FILES := \ HalCamera.cpp \ VirtualCamera.cpp \ -LOCAL_C_INCLUDES += \ - frameworks/base/include \ - packages/services/Car/evs/manager \ LOCAL_SHARED_LIBRARIES := \ libcutils \ liblog \ libutils \ libui \ - libhwbinder \ libhidlbase \ libhidltransport \ - libtinyalsa \ libhardware \ android.hardware.automotive.evs@1.0 \ -LOCAL_STRIP_MODULE := keep_symbols + +LOCAL_INIT_RC := android.automotive.evs.manager@1.0.rc LOCAL_MODULE := android.automotive.evs.manager@1.0 + LOCAL_MODULE_TAGS := optional +LOCAL_STRIP_MODULE := keep_symbols LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code diff --git a/evs/manager/ServiceNames.h b/evs/manager/ServiceNames.h index 3d8500114d..fb875364ee 100644 --- a/evs/manager/ServiceNames.h +++ b/evs/manager/ServiceNames.h @@ -16,9 +16,12 @@ // This is the name as which we'll register ourselves -const static char kManagedEnumeratorName[] = "EvsSharedEnumerator"; +const static char kManagedEnumeratorName[] = "default"; -// This is the name of the hardware provider to which we'll bind -// TODO: How should we configure these values to target appropriate hardware? -const static char kHardwareEnumeratorName[] = "EvsEnumeratorHw-Mock"; +// This is the name of the hardware provider to which we'll bind by default +const static char kHardwareEnumeratorName[] = "EvsEnumeratorHw"; + +// This is the name of the mock hardware provider selectable via command line. +// (should match .../hardware/interfaces/automotive/evs/1.0/default/ServiceNames.h) +const static char kMockEnumeratorName[] = "EvsEnumeratorHw-Mock"; diff --git a/evs/manager/android.automotive.evs.manager@1.0.rc b/evs/manager/android.automotive.evs.manager@1.0.rc new file mode 100644 index 0000000000..8a53ba7b85 --- /dev/null +++ b/evs/manager/android.automotive.evs.manager@1.0.rc @@ -0,0 +1,6 @@ +service evs_manager /system/bin/android.automotive.evs.manager@1.0 + class hal + priority -20 + user automotive_evs + group automotive_evs + onrestart restart evs_app diff --git a/evs/manager/service.cpp b/evs/manager/service.cpp index b3b6e9a4e3..fe1136b956 100644 --- a/evs/manager/service.cpp +++ b/evs/manager/service.cpp @@ -40,14 +40,43 @@ using namespace android::automotive::evs::V1_0::implementation; using namespace android; -int main() { +int main(int argc, char** argv) { + ALOGI("EVS manager starting\n"); + + // Set up default behavior, then check for command line options + bool printHelp = false; + const char* evsHardwareServiceName = kHardwareEnumeratorName; + for (int i=1; i< argc; i++) { + if (strcmp(argv[i], "--mock") == 0) { + evsHardwareServiceName = kMockEnumeratorName; + } else if (strcmp(argv[i], "--target") == 0) { + i++; + if (i >= argc) { + ALOGE("--target <service> was not provided with a service name\n"); + } else { + evsHardwareServiceName = argv[i]; + } + } else if (strcmp(argv[i], "--help") == 0) { + printHelp = true; + } else { + printf("Ignoring unrecognized command line arg '%s'\n", argv[i]); + printHelp = true; + } + } + if (printHelp) { + printf("Options include:\n"); + printf(" --mock Connect to the mock driver at EvsEnumeratorHw-Mock\n"); + printf(" --target <service_name> Connect to the named IEvsEnumerator service"); + } + + // Prepare the RPC serving thread pool. We're configuring it with no additional // threads beyond the main thread which will "join" the pool below. configureRpcThreadpool(1, true /* callerWillJoin */); - ALOGI("EVS managed service connecting to hardware at %s", kHardwareEnumeratorName); + ALOGI("EVS managed service connecting to hardware service at %s", evsHardwareServiceName); android::sp<Enumerator> service = new Enumerator(); - if (!service->init(kHardwareEnumeratorName)) { + if (!service->init(evsHardwareServiceName)) { ALOGE("Failed to initialize"); return 1; } diff --git a/evs/sampleDriver/Android.mk b/evs/sampleDriver/Android.mk new file mode 100644 index 0000000000..231ab6e911 --- /dev/null +++ b/evs/sampleDriver/Android.mk @@ -0,0 +1,44 @@ +LOCAL_PATH:= $(call my-dir) + +################################## +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + service.cpp \ + EvsEnumerator.cpp \ + EvsV4lCamera.cpp \ + EvsGlDisplay.cpp \ + GlWrapper.cpp \ + VideoCapture.cpp \ + bufferCopy.cpp \ + + +LOCAL_SHARED_LIBRARIES := \ + android.hardware.automotive.evs@1.0 \ + libui \ + libgui \ + libEGL \ + libGLESv2 \ + libbase \ + libbinder \ + libcutils \ + libhardware \ + libhidlbase \ + libhidltransport \ + liblog \ + libutils \ + +LOCAL_INIT_RC := android.hardware.automotive.evs@1.0-sample.rc + +LOCAL_MODULE := android.hardware.automotive.evs@1.0-sample + +LOCAL_MODULE_TAGS := optional +LOCAL_STRIP_MODULE := keep_symbols + +LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES +LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code + +# NOTE: It can be helpful, while debugging, to disable optimizations +#LOCAL_CFLAGS += -O0 -g + +include $(BUILD_EXECUTABLE) diff --git a/evs/sampleDriver/EvsEnumerator.cpp b/evs/sampleDriver/EvsEnumerator.cpp new file mode 100644 index 0000000000..25b8133b7d --- /dev/null +++ b/evs/sampleDriver/EvsEnumerator.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include "EvsEnumerator.h" +#include "EvsV4lCamera.h" +#include "EvsGlDisplay.h" + +#include <dirent.h> + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +// NOTE: All members values are static so that all clients operate on the same state +// That is to say, this is effectively a singleton despite the fact that HIDL +// constructs a new instance for each client. +std::list<EvsEnumerator::CameraRecord> EvsEnumerator::sCameraList; +wp<EvsGlDisplay> EvsEnumerator::sActiveDisplay; + + +EvsEnumerator::EvsEnumerator() { + ALOGD("EvsEnumerator created"); + + unsigned videoCount = 0; + unsigned captureCount = 0; + + // For every video* entry in the dev folder, see if it reports suitable capabilities + // WARNING: Depending on the driver implementations this could be slow, especially if + // there are timeouts or round trips to hardware required to collect the needed + // information. Platform implementers should consider hard coding this list of + // known good devices to speed up the startup time of their EVS implementation. + // For example, this code might be replaced with nothing more than: + // sCameraList.emplace_back("/dev/video0"); + // sCameraList.emplace_back("/dev/video1"); + ALOGI("Starting dev/video* enumeration"); + DIR* dir = opendir("/dev"); + if (!dir) { + LOG_FATAL("Failed to open /dev folder\n"); + } + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + // We're only looking for entries starting with 'video' + if (strncmp(entry->d_name, "video", 5) == 0) { + std::string deviceName("/dev/"); + deviceName += entry->d_name; + videoCount++; + if (qualifyCaptureDevice(deviceName.c_str())) { + sCameraList.emplace_back(deviceName.c_str()); + captureCount++; + } + } + } + + ALOGI("Found %d qualified video capture devices of %d checked\n", captureCount, videoCount); +} + + +// Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow. +Return<void> EvsEnumerator::getCameraList(getCameraList_cb _hidl_cb) { + ALOGD("getCameraList"); + + const unsigned numCameras = sCameraList.size(); + + // Build up a packed array of CameraDesc for return + hidl_vec<CameraDesc> hidlCameras; + hidlCameras.resize(numCameras); + unsigned i = 0; + for (const auto& cam : sCameraList) { + hidlCameras[i++] = cam.desc; + } + + // Send back the results + ALOGD("reporting %zu cameras available", hidlCameras.size()); + _hidl_cb(hidlCameras); + + // HIDL convention says we return Void if we sent our result back via callback + return Void(); +} + + +Return<sp<IEvsCamera>> EvsEnumerator::openCamera(const hidl_string& cameraId) { + ALOGD("openCamera"); + + // Is this a recognized camera id? + CameraRecord *pRecord = findCameraById(cameraId); + if (!pRecord) { + ALOGE("Requested camera %s not found", cameraId.c_str()); + return nullptr; + } + + // Has this camera already been instantiated by another caller? + sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote(); + if (pActiveCamera != nullptr) { + ALOGW("Killing previous camera because of new caller"); + closeCamera(pActiveCamera); + } + + // Construct a camera instance for the caller + pActiveCamera = new EvsV4lCamera(cameraId.c_str()); + pRecord->activeInstance = pActiveCamera; + if (pActiveCamera == nullptr) { + ALOGE("Failed to allocate new EvsV4lCamera object for %s\n", cameraId.c_str()); + } + + return pActiveCamera; +} + + +Return<void> EvsEnumerator::closeCamera(const ::android::sp<IEvsCamera>& pCamera) { + ALOGD("closeCamera"); + + if (pCamera == nullptr) { + ALOGE("Ignoring call to closeCamera with null camera ptr"); + return Void(); + } + + // Get the camera id so we can find it in our list + std::string cameraId; + pCamera->getCameraInfo([&cameraId](CameraDesc desc) { + cameraId = desc.cameraId; + } + ); + + // Find the named camera + CameraRecord *pRecord = findCameraById(cameraId); + + // Is the display being destroyed actually the one we think is active? + if (!pRecord) { + ALOGE("Asked to close a camera whose name isn't recognized"); + } else { + sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote(); + + if (pActiveCamera == nullptr) { + ALOGE("Somehow a camera is being destroyed when the enumerator didn't know one existed"); + } else if (pActiveCamera != pCamera) { + // This can happen if the camera was aggressively reopened, orphaning this previous instance + ALOGW("Ignoring close of previously orphaned camera - why did a client steal?"); + } else { + // Drop the active camera + pActiveCamera->shutdown(); + pRecord->activeInstance = nullptr; + } + } + + return Void(); +} + + +Return<sp<IEvsDisplay>> EvsEnumerator::openDisplay() { + ALOGD("openDisplay"); + + // If we already have a display active, then we need to shut it down so we can + // give exclusive access to the new caller. + sp<EvsGlDisplay> pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay != nullptr) { + ALOGW("Killing previous display because of new caller"); + closeDisplay(pActiveDisplay); + } + + // Create a new display interface and return it + pActiveDisplay = new EvsGlDisplay(); + sActiveDisplay = pActiveDisplay; + + ALOGD("Returning new EvsGlDisplay object %p", pActiveDisplay.get()); + return pActiveDisplay; +} + + +Return<void> EvsEnumerator::closeDisplay(const ::android::sp<IEvsDisplay>& pDisplay) { + ALOGD("closeDisplay"); + + // Do we still have a display object we think should be active? + sp<EvsGlDisplay> pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay == nullptr) { + ALOGE("Somehow a display is being destroyed when the enumerator didn't know one existed"); + } else if (sActiveDisplay != pDisplay) { + ALOGW("Ignoring close of previously orphaned display - why did a client steal?"); + } else { + // Drop the active display + pActiveDisplay->forceShutdown(); + sActiveDisplay = nullptr; + } + + return Void(); +} + + +Return<DisplayState> EvsEnumerator::getDisplayState() { + ALOGD("getDisplayState"); + + // Do we still have a display object we think should be active? + sp<IEvsDisplay> pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay != nullptr) { + return pActiveDisplay->getDisplayState(); + } else { + return DisplayState::NOT_OPEN; + } +} + + +bool EvsEnumerator::qualifyCaptureDevice(const char* deviceName) { + class FileHandleWrapper { + public: + FileHandleWrapper(int fd) { mFd = fd; } + ~FileHandleWrapper() { if (mFd > 0) close(mFd); } + operator int() const { return mFd; } + private: + int mFd = -1; + }; + + + FileHandleWrapper fd = open(deviceName, O_RDWR, 0); + if (fd < 0) { + return false; + } + + v4l2_capability caps; + int result = ioctl(fd, VIDIOC_QUERYCAP, &caps); + if (result < 0) { + return false; + } + if (((caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) || + ((caps.capabilities & V4L2_CAP_STREAMING) == 0)) { + return false; + } + + // Enumerate the available capture formats (if any) + v4l2_fmtdesc formatDescription; + formatDescription.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + for (int i=0; true; i++) { + formatDescription.index = i; + if (ioctl(fd, VIDIOC_ENUM_FMT, &formatDescription) == 0) { + switch (formatDescription.pixelformat) + { + case V4L2_PIX_FMT_YUYV: return true; + case V4L2_PIX_FMT_NV21: return true; + case V4L2_PIX_FMT_NV16: return true; + case V4L2_PIX_FMT_YVU420: return true; + case V4L2_PIX_FMT_RGB32: return true; +#ifdef V4L2_PIX_FMT_ARGB32 // introduced with kernel v3.17 + case V4L2_PIX_FMT_ARGB32: return true; + case V4L2_PIX_FMT_XRGB32: return true; +#endif // V4L2_PIX_FMT_ARGB32 + default: break; + } + } else { + // No more formats available + break; + } + } + + // If we get here, we didn't find a usable output format + return false; +} + + +EvsEnumerator::CameraRecord* EvsEnumerator::findCameraById(const std::string& cameraId) { + // Find the named camera + for (auto &&cam : sCameraList) { + if (cam.desc.cameraId == cameraId) { + // Found a match! + return &cam; + } + } + + // We didn't find a match + return nullptr; +} + + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/evs/sampleDriver/EvsEnumerator.h b/evs/sampleDriver/EvsEnumerator.h new file mode 100644 index 0000000000..d8d7b36d3c --- /dev/null +++ b/evs/sampleDriver/EvsEnumerator.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSCAMERAENUMERATOR_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSCAMERAENUMERATOR_H + +#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> +#include <android/hardware/automotive/evs/1.0/IEvsCamera.h> + +#include <list> + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +class EvsV4lCamera; // from EvsCamera.h +class EvsGlDisplay; // from EvsGlDisplay.h + + +class EvsEnumerator : public IEvsEnumerator { +public: + // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow. + Return<void> getCameraList(getCameraList_cb _hidl_cb) override; + Return<sp<IEvsCamera>> openCamera(const hidl_string& cameraId) override; + Return<void> closeCamera(const ::android::sp<IEvsCamera>& carCamera) override; + Return<sp<IEvsDisplay>> openDisplay() override; + Return<void> closeDisplay(const ::android::sp<IEvsDisplay>& display) override; + Return<DisplayState> getDisplayState() override; + + // Implementation details + EvsEnumerator(); + +private: + struct CameraRecord { + CameraDesc desc; + wp<EvsV4lCamera> activeInstance; + + CameraRecord(const char *cameraId) : desc() { desc.cameraId = cameraId; } + }; + + + static bool qualifyCaptureDevice(const char* deviceName); + static CameraRecord* findCameraById(const std::string& cameraId); + + + // NOTE: All members values are static so that all clients operate on the same state + // That is to say, this is effectively a singleton despite the fact that HIDL + // constructs a new instance for each client. + // Because our server has a single thread in the thread pool, these values are + // never accessed concurrently despite potentially having multiple instance objects + // using them. + static std::list<CameraRecord> sCameraList; + + static wp<EvsGlDisplay> sActiveDisplay; // Weak pointer. Object destructs if client dies. +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSCAMERAENUMERATOR_H diff --git a/evs/sampleDriver/EvsGlDisplay.cpp b/evs/sampleDriver/EvsGlDisplay.cpp new file mode 100644 index 0000000000..0f62e64f3d --- /dev/null +++ b/evs/sampleDriver/EvsGlDisplay.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include "EvsGlDisplay.h" + +#include <ui/GraphicBufferAllocator.h> +#include <ui/GraphicBufferMapper.h> + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +EvsGlDisplay::EvsGlDisplay() { + ALOGD("EvsGlDisplay instantiated"); + + // Set up our self description + // NOTE: These are arbitrary values chosen for testing + mInfo.displayId = "Mock Display"; + mInfo.vendorFlags = 3870; +} + + +EvsGlDisplay::~EvsGlDisplay() { + ALOGD("EvsGlDisplay being destroyed"); + forceShutdown(); +} + + +/** + * This gets called if another caller "steals" ownership of the display + */ +void EvsGlDisplay::forceShutdown() +{ + ALOGD("EvsGlDisplay forceShutdown"); + std::lock_guard<std::mutex> lock(mAccessLock); + + // If the buffer isn't being held by a remote client, release it now as an + // optimization to release the resources more quickly than the destructor might + // get called. + if (mBuffer.memHandle) { + // Report if we're going away while a buffer is outstanding + if (mFrameBusy) { + ALOGE("EvsGlDisplay going down while client is holding a buffer"); + } + + // Drop the graphics buffer we've been using + GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + alloc.free(mBuffer.memHandle); + mBuffer.memHandle = nullptr; + + mGlWrapper.shutdown(); + } + + // Put this object into an unrecoverable error state since somebody else + // is going to own the display now. + mRequestedState = DisplayState::DEAD; +} + + +/** + * Returns basic information about the EVS display provided by the system. + * See the description of the DisplayDesc structure for details. + */ +Return<void> EvsGlDisplay::getDisplayInfo(getDisplayInfo_cb _hidl_cb) { + ALOGD("getDisplayInfo"); + + // Send back our self description + _hidl_cb(mInfo); + return Void(); +} + + +/** + * Clients may set the display state to express their desired state. + * The HAL implementation must gracefully accept a request for any state + * while in any other state, although the response may be to ignore the request. + * The display is defined to start in the NOT_VISIBLE state upon initialization. + * The client is then expected to request the VISIBLE_ON_NEXT_FRAME state, and + * then begin providing video. When the display is no longer required, the client + * is expected to request the NOT_VISIBLE state after passing the last video frame. + */ +Return<EvsResult> EvsGlDisplay::setDisplayState(DisplayState state) { + ALOGD("setDisplayState"); + std::lock_guard<std::mutex> lock(mAccessLock); + + if (mRequestedState == DisplayState::DEAD) { + // This object no longer owns the display -- it's been superceeded! + return EvsResult::OWNERSHIP_LOST; + } + + // Ensure we recognize the requested state so we don't go off the rails + if (state >= DisplayState::NUM_STATES) { + return EvsResult::INVALID_ARG; + } + + switch (state) { + case DisplayState::NOT_VISIBLE: + mGlWrapper.hideWindow(); + break; + case DisplayState::VISIBLE: + mGlWrapper.showWindow(); + break; + default: + break; + } + + // Record the requested state + mRequestedState = state; + + return EvsResult::OK; +} + + +/** + * The HAL implementation should report the actual current state, which might + * transiently differ from the most recently requested state. Note, however, that + * the logic responsible for changing display states should generally live above + * the device layer, making it undesirable for the HAL implementation to + * spontaneously change display states. + */ +Return<DisplayState> EvsGlDisplay::getDisplayState() { + ALOGD("getDisplayState"); + std::lock_guard<std::mutex> lock(mAccessLock); + + return mRequestedState; +} + + +/** + * This call returns a handle to a frame buffer associated with the display. + * This buffer may be locked and written to by software and/or GL. This buffer + * must be returned via a call to returnTargetBufferForDisplay() even if the + * display is no longer visible. + */ +Return<void> EvsGlDisplay::getTargetBuffer(getTargetBuffer_cb _hidl_cb) { + ALOGV("getTargetBuffer"); + std::lock_guard<std::mutex> lock(mAccessLock); + + if (mRequestedState == DisplayState::DEAD) { + ALOGE("Rejecting buffer request from object that lost ownership of the display."); + BufferDesc nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } + + // If we don't already have a buffer, allocate one now + if (!mBuffer.memHandle) { + // Initialize our display window + // NOTE: This will cause the display to become "VISIBLE" before a frame is actually + // returned, which is contrary to the spec and will likely result in a black frame being + // (briefly) shown. + if (!mGlWrapper.initialize()) { + // Report the failure + ALOGE("Failed to initialize GL display"); + BufferDesc nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } + + // Assemble the buffer description we'll use for our render target + mBuffer.width = mGlWrapper.getWidth(); + mBuffer.height = mGlWrapper.getHeight(); + mBuffer.format = HAL_PIXEL_FORMAT_RGBA_8888; + mBuffer.usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER; + mBuffer.bufferId = 0x3870; // Arbitrary magic number for self recognition + mBuffer.pixelSize = 4; + + // Allocate the buffer that will hold our displayable image + buffer_handle_t handle = nullptr; + GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + status_t result = alloc.allocate(mBuffer.width, mBuffer.height, + mBuffer.format, 1, + mBuffer.usage, &handle, + &mBuffer.stride, + 0, "EvsGlDisplay"); + if (result != NO_ERROR) { + ALOGE("Error %d allocating %d x %d graphics buffer", + result, mBuffer.width, mBuffer.height); + BufferDesc nullBuff = {}; + _hidl_cb(nullBuff); + mGlWrapper.shutdown(); + return Void(); + } + if (!handle) { + ALOGE("We didn't get a buffer handle back from the allocator"); + BufferDesc nullBuff = {}; + _hidl_cb(nullBuff); + mGlWrapper.shutdown(); + return Void(); + } + + mBuffer.memHandle = handle; + ALOGD("Allocated new buffer %p with stride %u", + mBuffer.memHandle.getNativeHandle(), mBuffer.stride); + mFrameBusy = false; + } + + // Do we have a frame available? + if (mFrameBusy) { + // This means either we have a 2nd client trying to compete for buffers + // (an unsupported mode of operation) or else the client hasn't returned + // a previously issued buffer yet (they're behaving badly). + // NOTE: We have to make the callback even if we have nothing to provide + ALOGE("getTargetBuffer called while no buffers available."); + BufferDesc nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } else { + // Mark our buffer as busy + mFrameBusy = true; + + // Send the buffer to the client + ALOGV("Providing display buffer handle %p as id %d", + mBuffer.memHandle.getNativeHandle(), mBuffer.bufferId); + _hidl_cb(mBuffer); + return Void(); + } +} + + +/** + * This call tells the display that the buffer is ready for display. + * The buffer is no longer valid for use by the client after this call. + */ +Return<EvsResult> EvsGlDisplay::returnTargetBufferForDisplay(const BufferDesc& buffer) { + ALOGV("returnTargetBufferForDisplay %p", buffer.memHandle.getNativeHandle()); + std::lock_guard<std::mutex> lock(mAccessLock); + + // Nobody should call us with a null handle + if (!buffer.memHandle.getNativeHandle()) { + ALOGE ("returnTargetBufferForDisplay called without a valid buffer handle.\n"); + return EvsResult::INVALID_ARG; + } + if (buffer.bufferId != mBuffer.bufferId) { + ALOGE ("Got an unrecognized frame returned.\n"); + return EvsResult::INVALID_ARG; + } + if (!mFrameBusy) { + ALOGE ("A frame was returned with no outstanding frames.\n"); + return EvsResult::BUFFER_NOT_AVAILABLE; + } + + mFrameBusy = false; + + // If we've been displaced by another owner of the display, then we can't do anything else + if (mRequestedState == DisplayState::DEAD) { + return EvsResult::OWNERSHIP_LOST; + } + + // If we were waiting for a new frame, this is it! + if (mRequestedState == DisplayState::VISIBLE_ON_NEXT_FRAME) { + mRequestedState = DisplayState::VISIBLE; + mGlWrapper.showWindow(); + } + + // Validate we're in an expected state + if (mRequestedState != DisplayState::VISIBLE) { + // Not sure why a client would send frames back when we're not visible. + ALOGW ("Got a frame returned while not visible - ignoring.\n"); + } else { + // Update the texture contents with the provided data +// TODO: Why doesn't it work to pass in the buffer handle we got from HIDL? +// if (!mGlWrapper.updateImageTexture(buffer)) { + if (!mGlWrapper.updateImageTexture(mBuffer)) { + return EvsResult::UNDERLYING_SERVICE_ERROR; + } + + // Put the image on the screen + mGlWrapper.renderImageToScreen(); + } + + return EvsResult::OK; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/evs/sampleDriver/EvsGlDisplay.h b/evs/sampleDriver/EvsGlDisplay.h new file mode 100644 index 0000000000..7adbac9396 --- /dev/null +++ b/evs/sampleDriver/EvsGlDisplay.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSGLDISPLAY_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSGLDISPLAY_H + +#include <android/hardware/automotive/evs/1.0/IEvsDisplay.h> +#include <ui/GraphicBuffer.h> + +#include "GlWrapper.h" + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +class EvsGlDisplay : public IEvsDisplay { +public: + // Methods from ::android::hardware::automotive::evs::V1_0::IEvsDisplay follow. + Return<void> getDisplayInfo(getDisplayInfo_cb _hidl_cb) override; + Return<EvsResult> setDisplayState(DisplayState state) override; + Return<DisplayState> getDisplayState() override; + Return<void> getTargetBuffer(getTargetBuffer_cb _hidl_cb) override; + Return<EvsResult> returnTargetBufferForDisplay(const BufferDesc& buffer) override; + + // Implementation details + EvsGlDisplay(); + virtual ~EvsGlDisplay() override; + + void forceShutdown(); // This gets called if another caller "steals" ownership of the display + +private: + DisplayDesc mInfo = {}; + BufferDesc mBuffer = {}; // A graphics buffer into which we'll store images + + bool mFrameBusy = false; // A flag telling us our buffer is in use + DisplayState mRequestedState = DisplayState::NOT_VISIBLE; + + GlWrapper mGlWrapper; + + std::mutex mAccessLock; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSGLDISPLAY_H diff --git a/evs/sampleDriver/EvsV4lCamera.cpp b/evs/sampleDriver/EvsV4lCamera.cpp new file mode 100644 index 0000000000..045d7abace --- /dev/null +++ b/evs/sampleDriver/EvsV4lCamera.cpp @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include "EvsV4lCamera.h" +#include "EvsEnumerator.h" +#include "bufferCopy.h" + +#include <ui/GraphicBufferAllocator.h> +#include <ui/GraphicBufferMapper.h> + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +// Arbitrary limit on number of graphics buffers allowed to be allocated +// Safeguards against unreasonable resource consumption and provides a testable limit +static const unsigned MAX_BUFFERS_IN_FLIGHT = 100; + + +EvsV4lCamera::EvsV4lCamera(const char *deviceName) : + mFramesAllowed(0), + mFramesInUse(0) { + ALOGD("EvsV4lCamera instantiated"); + + mDescription.cameraId = deviceName; + + // Initialize the video device + if (!mVideo.open(deviceName)) { + ALOGE("Failed to open v4l device %s\n", deviceName); + } + + // NOTE: Our current spec says only support NV21 -- can we stick to that with software + // conversion? Will this work with the hardware texture units? + // TODO: Settle on the one official format that works on all platforms + // TODO: Get NV21 working? It is scrambled somewhere along the way right now. +// mFormat = HAL_PIXEL_FORMAT_YCRCB_420_SP; // 420SP == NV21 +// mFormat = HAL_PIXEL_FORMAT_RGBA_8888; + mFormat = HAL_PIXEL_FORMAT_YCBCR_422_I; + + // How we expect to use the gralloc buffers we'll exchange with our client + mUsage = GRALLOC_USAGE_HW_TEXTURE | + GRALLOC_USAGE_SW_READ_RARELY | + GRALLOC_USAGE_SW_WRITE_OFTEN; +} + + +EvsV4lCamera::~EvsV4lCamera() { + ALOGD("EvsV4lCamera being destroyed"); + shutdown(); +} + + +// +// This gets called if another caller "steals" ownership of the camera +// +void EvsV4lCamera::shutdown() +{ + ALOGD("EvsV4lCamera shutdown"); + + // Make sure our output stream is cleaned up + // (It really should be already) + stopVideoStream(); + + // Note: Since stopVideoStream is blocking, no other threads can now be running + + // Close our video capture device + mVideo.close(); + + // Drop all the graphics buffers we've been using + if (mBuffers.size() > 0) { + GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + for (auto&& rec : mBuffers) { + if (rec.inUse) { + ALOGW("Error - releasing buffer despite remote ownership"); + } + alloc.free(rec.handle); + rec.handle = nullptr; + } + mBuffers.clear(); + } +} + + +// Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow. +Return<void> EvsV4lCamera::getCameraInfo(getCameraInfo_cb _hidl_cb) { + ALOGD("getCameraInfo"); + + // Send back our self description + _hidl_cb(mDescription); + return Void(); +} + + +Return<EvsResult> EvsV4lCamera::setMaxFramesInFlight(uint32_t bufferCount) { + ALOGD("setMaxFramesInFlight"); + std::lock_guard<std::mutex> lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (!mVideo.isOpen()) { + ALOGW("ignoring setMaxFramesInFlight call when camera has been lost."); + return EvsResult::OWNERSHIP_LOST; + } + + // We cannot function without at least one video buffer to send data + if (bufferCount < 1) { + ALOGE("Ignoring setMaxFramesInFlight with less than one buffer requested"); + return EvsResult::INVALID_ARG; + } + + // Update our internal state + if (setAvailableFrames_Locked(bufferCount)) { + return EvsResult::OK; + } else { + return EvsResult::BUFFER_NOT_AVAILABLE; + } +} + + +Return<EvsResult> EvsV4lCamera::startVideoStream(const ::android::sp<IEvsCameraStream>& stream) { + ALOGD("startVideoStream"); + std::lock_guard<std::mutex> lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (!mVideo.isOpen()) { + ALOGW("ignoring startVideoStream call when camera has been lost."); + return EvsResult::OWNERSHIP_LOST; + } + if (mStream.get() != nullptr) { + ALOGE("ignoring startVideoStream call when a stream is already running."); + return EvsResult::STREAM_ALREADY_RUNNING; + } + + // If the client never indicated otherwise, configure ourselves for a single streaming buffer + if (mFramesAllowed < 1) { + if (!setAvailableFrames_Locked(1)) { + ALOGE("Failed to start stream because we couldn't get a graphics buffer"); + return EvsResult::BUFFER_NOT_AVAILABLE; + } + } + + // Choose which image transfer function we need + // Map from V4L2 to Android graphic buffer format + const uint32_t videoSrcFormat = mVideo.getV4LFormat(); + ALOGI("Configuring to accept %4.4s camera data and convert to %4.4s", + (char*)&videoSrcFormat, (char*)&mFormat); + + // TODO: Simplify this by supporting only ONE fixed output format + switch (mFormat) { + case HAL_PIXEL_FORMAT_YCRCB_420_SP: + switch (videoSrcFormat) { + case V4L2_PIX_FMT_NV21: mFillBufferFromVideo = fillNV21FromNV21; break; + // case V4L2_PIX_FMT_YV12: mFillBufferFromVideo = fillNV21FromYV12; break; + case V4L2_PIX_FMT_YUYV: mFillBufferFromVideo = fillNV21FromYUYV; break; + // case V4L2_PIX_FORMAT_NV16: mFillBufferFromVideo = fillNV21FromNV16; break; + default: + // TODO: Are there other V4L2 formats we must support? + ALOGE("Unhandled camera output format %c%c%c%c (0x%8X)\n", + ((char*)&videoSrcFormat)[0], + ((char*)&videoSrcFormat)[1], + ((char*)&videoSrcFormat)[2], + ((char*)&videoSrcFormat)[3], + videoSrcFormat); + } + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + switch (videoSrcFormat) { + case V4L2_PIX_FMT_YUYV: mFillBufferFromVideo = fillRGBAFromYUYV; break; + default: + // TODO: Are there other V4L2 formats we must support? + ALOGE("Unhandled camera format %4.4s", (char*)&videoSrcFormat); + } + break; + case HAL_PIXEL_FORMAT_YCBCR_422_I: + switch (videoSrcFormat) { + case V4L2_PIX_FMT_YUYV: mFillBufferFromVideo = fillYUYVFromYUYV; break; + case V4L2_PIX_FMT_UYVY: mFillBufferFromVideo = fillYUYVFromUYVY; break; + default: + // TODO: Are there other V4L2 formats we must support? + ALOGE("Unhandled camera format %4.4s", (char*)&videoSrcFormat); + } + break; + default: + // TODO: Why have we told ourselves to output something we don't understand!? + ALOGE("Unhandled output format %4.4s", (char*)&mFormat); + } + + + // Record the user's callback for use when we have a frame ready + mStream = stream; + + // Set up the video stream with a callback to our member function forwardFrame() + if (!mVideo.startStream([this](VideoCapture*, imageBuffer* tgt, void* data) { + this->forwardFrame(tgt, data); + }) + ) { + mStream = nullptr; // No need to hold onto this if we failed to start + ALOGE("underlying camera start stream failed"); + return EvsResult::UNDERLYING_SERVICE_ERROR; + } + + return EvsResult::OK; +} + + +Return<void> EvsV4lCamera::doneWithFrame(const BufferDesc& buffer) { + ALOGD("doneWithFrame"); + std::lock_guard <std::mutex> lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (!mVideo.isOpen()) { + ALOGW("ignoring doneWithFrame call when camera has been lost."); + } else { + if (buffer.memHandle == nullptr) { + ALOGE("ignoring doneWithFrame called with null handle"); + } else if (buffer.bufferId >= mBuffers.size()) { + ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)", + buffer.bufferId, mBuffers.size()-1); + } else if (!mBuffers[buffer.bufferId].inUse) { + ALOGE("ignoring doneWithFrame called on frame %d which is already free", + buffer.bufferId); + } else { + // Mark the frame as available + mBuffers[buffer.bufferId].inUse = false; + mFramesInUse--; + + // If this frame's index is high in the array, try to move it down + // to improve locality after mFramesAllowed has been reduced. + if (buffer.bufferId >= mFramesAllowed) { + // Find an empty slot lower in the array (which should always exist in this case) + for (auto&& rec : mBuffers) { + if (rec.handle == nullptr) { + rec.handle = mBuffers[buffer.bufferId].handle; + mBuffers[buffer.bufferId].handle = nullptr; + break; + } + } + } + } + } + + return Void(); +} + + +Return<void> EvsV4lCamera::stopVideoStream() { + ALOGD("stopVideoStream"); + + // Tell the capture device to stop (and block until it does) + mVideo.stopStream(); + + if (mStream != nullptr) { + std::unique_lock <std::mutex> lock(mAccessLock); + + // Send one last NULL frame to signal the actual end of stream + BufferDesc nullBuff = {}; + auto result = mStream->deliverFrame(nullBuff); + if (!result.isOk()) { + ALOGE("Error delivering end of stream marker"); + } + + // Drop our reference to the client's stream receiver + mStream = nullptr; + } + + return Void(); +} + + +Return<int32_t> EvsV4lCamera::getExtendedInfo(uint32_t /*opaqueIdentifier*/) { + ALOGD("getExtendedInfo"); + // Return zero by default as required by the spec + return 0; +} + + +Return<EvsResult> EvsV4lCamera::setExtendedInfo(uint32_t /*opaqueIdentifier*/, + int32_t /*opaqueValue*/) { + ALOGD("setExtendedInfo"); + std::lock_guard<std::mutex> lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (!mVideo.isOpen()) { + ALOGW("ignoring setExtendedInfo call when camera has been lost."); + return EvsResult::OWNERSHIP_LOST; + } + + // We don't store any device specific information in this implementation + return EvsResult::INVALID_ARG; +} + + +bool EvsV4lCamera::setAvailableFrames_Locked(unsigned bufferCount) { + if (bufferCount < 1) { + ALOGE("Ignoring request to set buffer count to zero"); + return false; + } + if (bufferCount > MAX_BUFFERS_IN_FLIGHT) { + ALOGE("Rejecting buffer request in excess of internal limit"); + return false; + } + + // Is an increase required? + if (mFramesAllowed < bufferCount) { + // An increase is required + unsigned needed = bufferCount - mFramesAllowed; + ALOGI("Allocating %d buffers for camera frames", needed); + + unsigned added = increaseAvailableFrames_Locked(needed); + if (added != needed) { + // If we didn't add all the frames we needed, then roll back to the previous state + ALOGE("Rolling back to previous frame queue size"); + decreaseAvailableFrames_Locked(added); + return false; + } + } else if (mFramesAllowed > bufferCount) { + // A decrease is required + unsigned framesToRelease = mFramesAllowed - bufferCount; + ALOGI("Returning %d camera frame buffers", framesToRelease); + + unsigned released = decreaseAvailableFrames_Locked(framesToRelease); + if (released != framesToRelease) { + // This shouldn't happen with a properly behaving client because the client + // should only make this call after returning sufficient outstanding buffers + // to allow a clean resize. + ALOGE("Buffer queue shrink failed -- too many buffers currently in use?"); + } + } + + return true; +} + + +unsigned EvsV4lCamera::increaseAvailableFrames_Locked(unsigned numToAdd) { + // Acquire the graphics buffer allocator + GraphicBufferAllocator &alloc(GraphicBufferAllocator::get()); + + unsigned added = 0; + + + while (added < numToAdd) { + unsigned pixelsPerLine; + buffer_handle_t memHandle = nullptr; + status_t result = alloc.allocate(mVideo.getWidth(), mVideo.getHeight(), + mFormat, 1, + mUsage, + &memHandle, &pixelsPerLine, 0, "EvsV4lCamera"); + if (result != NO_ERROR) { + ALOGE("Error %d allocating %d x %d graphics buffer", + result, + mVideo.getWidth(), + mVideo.getHeight()); + break; + } + if (!memHandle) { + ALOGE("We didn't get a buffer handle back from the allocator"); + break; + } + if (mStride) { + if (mStride != pixelsPerLine) { + ALOGE("We did not expect to get buffers with different strides!"); + } + } else { + // Gralloc defines stride in terms of pixels per line + mStride = pixelsPerLine; + } + + // Find a place to store the new buffer + bool stored = false; + for (auto&& rec : mBuffers) { + if (rec.handle == nullptr) { + // Use this existing entry + rec.handle = memHandle; + rec.inUse = false; + stored = true; + break; + } + } + if (!stored) { + // Add a BufferRecord wrapping this handle to our set of available buffers + mBuffers.emplace_back(memHandle); + } + + mFramesAllowed++; + added++; + } + + return added; +} + + +unsigned EvsV4lCamera::decreaseAvailableFrames_Locked(unsigned numToRemove) { + // Acquire the graphics buffer allocator + GraphicBufferAllocator &alloc(GraphicBufferAllocator::get()); + + unsigned removed = 0; + + for (auto&& rec : mBuffers) { + // Is this record not in use, but holding a buffer that we can free? + if ((rec.inUse == false) && (rec.handle != nullptr)) { + // Release buffer and update the record so we can recognize it as "empty" + alloc.free(rec.handle); + rec.handle = nullptr; + + mFramesAllowed--; + removed++; + + if (removed == numToRemove) { + break; + } + } + } + + return removed; +} + + +// This is the async callback from the video camera that tells us a frame is ready +void EvsV4lCamera::forwardFrame(imageBuffer* /*pV4lBuff*/, void* pData) { + bool readyForFrame = false; + size_t idx = 0; + + // Lock scope for updating shared state + { + std::lock_guard<std::mutex> lock(mAccessLock); + + // Are we allowed to issue another buffer? + if (mFramesInUse >= mFramesAllowed) { + // Can't do anything right now -- skip this frame + ALOGW("Skipped a frame because too many are in flight\n"); + } else { + // Identify an available buffer to fill + for (idx = 0; idx < mBuffers.size(); idx++) { + if (!mBuffers[idx].inUse) { + if (mBuffers[idx].handle != nullptr) { + // Found an available record, so stop looking + break; + } + } + } + if (idx >= mBuffers.size()) { + // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed + ALOGE("Failed to find an available buffer slot\n"); + } else { + // We're going to make the frame busy + mBuffers[idx].inUse = true; + mFramesInUse++; + readyForFrame = true; + } + } + } + + if (!readyForFrame) { + // We need to return the vide buffer so it can capture a new frame + mVideo.markFrameConsumed(); + } else { + // Assemble the buffer description we'll transmit below + BufferDesc buff = {}; + buff.width = mVideo.getWidth(); + buff.height = mVideo.getHeight(); + buff.stride = mStride; + buff.format = mFormat; + buff.usage = mUsage; + buff.bufferId = idx; + buff.memHandle = mBuffers[idx].handle; + + // Lock our output buffer for writing + void *targetPixels = nullptr; + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + mapper.lock(buff.memHandle, + GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER, + android::Rect(buff.width, buff.height), + (void **) &targetPixels); + + // If we failed to lock the pixel buffer, we're about to crash, but log it first + if (!targetPixels) { + ALOGE("Camera failed to gain access to image buffer for writing"); + } + + // Transfer the video image into the output buffer, making any needed + // format conversion along the way + mFillBufferFromVideo(buff, (uint8_t*)targetPixels, pData, mVideo.getStride()); + + // Unlock the output buffer + mapper.unlock(buff.memHandle); + + + // Give the video frame back to the underlying device for reuse + // Note that we do this before making the client callback to give the underlying + // camera more time to capture the next frame. + mVideo.markFrameConsumed(); + + // Issue the (asynchronous) callback to the client -- can't be holding the lock + auto result = mStream->deliverFrame(buff); + if (result.isOk()) { + ALOGD("Delivered %p as id %d", buff.memHandle.getNativeHandle(), buff.bufferId); + } else { + // This can happen if the client dies and is likely unrecoverable. + // To avoid consuming resources generating failing calls, we stop sending + // frames. Note, however, that the stream remains in the "STREAMING" state + // until cleaned up on the main thread. + ALOGE("Frame delivery call failed in the transport layer."); + + // Since we didn't actually deliver it, mark the frame as available + std::lock_guard<std::mutex> lock(mAccessLock); + mBuffers[idx].inUse = false; + mFramesInUse--; + } + } +} + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/evs/sampleDriver/EvsV4lCamera.h b/evs/sampleDriver/EvsV4lCamera.h new file mode 100644 index 0000000000..3d351b9e48 --- /dev/null +++ b/evs/sampleDriver/EvsV4lCamera.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSV4LCAMERA_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSV4LCAMERA_H + +#include <android/hardware/automotive/evs/1.0/types.h> +#include <android/hardware/automotive/evs/1.0/IEvsCamera.h> +#include <ui/GraphicBuffer.h> + +#include <thread> +#include <functional> + +#include "VideoCapture.h" + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +// From EvsEnumerator.h +class EvsEnumerator; + + +class EvsV4lCamera : public IEvsCamera { +public: + // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow. + Return<void> getCameraInfo(getCameraInfo_cb _hidl_cb) override; + Return <EvsResult> setMaxFramesInFlight(uint32_t bufferCount) override; + Return <EvsResult> startVideoStream(const ::android::sp<IEvsCameraStream>& stream) override; + Return<void> doneWithFrame(const BufferDesc& buffer) override; + Return<void> stopVideoStream() override; + Return <int32_t> getExtendedInfo(uint32_t opaqueIdentifier) override; + Return <EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override; + + // Implementation details + EvsV4lCamera(const char *deviceName); + virtual ~EvsV4lCamera() override; + void shutdown(); + + const CameraDesc& getDesc() { return mDescription; }; + +private: + // These three functions are expected to be called while mAccessLock is held + bool setAvailableFrames_Locked(unsigned bufferCount); + unsigned increaseAvailableFrames_Locked(unsigned numToAdd); + unsigned decreaseAvailableFrames_Locked(unsigned numToRemove); + + void forwardFrame(imageBuffer* tgt, void* data); + + sp <IEvsCameraStream> mStream = nullptr; // The callback used to deliver each frame + + VideoCapture mVideo; // Interface to the v4l device + + CameraDesc mDescription = {}; // The properties of this camera + uint32_t mFormat = 0; // Values from android_pixel_format_t + uint32_t mUsage = 0; // Values from from Gralloc.h + uint32_t mStride = 0; // Pixels per row (may be greater than image width) + + struct BufferRecord { + buffer_handle_t handle; + bool inUse; + + explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {}; + }; + + std::vector <BufferRecord> mBuffers; // Graphics buffers to transfer images + unsigned mFramesAllowed; // How many buffers are we currently using + unsigned mFramesInUse; // How many buffers are currently outstanding + + // Which format specific function we need to use to move camera imagery into our output buffers + void(*mFillBufferFromVideo)(const BufferDesc& tgtBuff, uint8_t* tgt, + void* imgData, unsigned imgStride); + + // Synchronization necessary to deconflict the capture thread from the main service thread + // Note that the service interface remains single threaded (ie: not reentrant) + std::mutex mAccessLock; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_EVSV4LCAMERA_H diff --git a/evs/sampleDriver/GlWrapper.cpp b/evs/sampleDriver/GlWrapper.cpp new file mode 100644 index 0000000000..a49eb20b95 --- /dev/null +++ b/evs/sampleDriver/GlWrapper.cpp @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2017 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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include "GlWrapper.h" + +#include <stdio.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include <ui/DisplayInfo.h> +#include <ui/GraphicBuffer.h> +#include <ui/GraphicBufferAllocator.h> +#include <ui/GraphicBufferMapper.h> + + +using namespace android; + + +// TODO: Consider dropping direct use of GraphicsBufferAllocator and Mapper? +using android::GraphicBuffer; +using android::GraphicBufferAllocator; +using android::GraphicBufferMapper; +using android::sp; + + +const char vertexShaderSource[] = "" + "#version 300 es \n" + "layout(location = 0) in vec4 pos; \n" + "layout(location = 1) in vec2 tex; \n" + "out vec2 uv; \n" + "void main() \n" + "{ \n" + " gl_Position = pos; \n" + " uv = tex; \n" + "} \n"; + +const char pixelShaderSource[] = + "#version 300 es \n" + "precision mediump float; \n" + "uniform sampler2D tex; \n" + "in vec2 uv; \n" + "out vec4 color; \n" + "void main() \n" + "{ \n" + " vec4 texel = texture(tex, uv); \n" + " color = texel; \n" + "} \n"; + + +static const char *getEGLError(void) { + switch (eglGetError()) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return "Unknown error"; + } +} + + +// Given shader source, load and compile it +static GLuint loadShader(GLenum type, const char *shaderSrc) { + // Create the shader object + GLuint shader = glCreateShader (type); + if (shader == 0) { + return 0; + } + + // Load and compile the shader + glShaderSource(shader, 1, &shaderSrc, nullptr); + glCompileShader(shader); + + // Verify the compilation worked as expected + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + ALOGE("Error compiling shader\n"); + + GLint size = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size); + if (size > 0) + { + // Get and report the error message + char *infoLog = (char*)malloc(size); + glGetShaderInfoLog(shader, size, nullptr, infoLog); + ALOGE(" msg:\n%s\n", infoLog); + free(infoLog); + } + + glDeleteShader(shader); + return 0; + } + + return shader; +} + + +// Create a program object given vertex and pixels shader source +static GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc) { + GLuint program = glCreateProgram(); + if (program == 0) { + ALOGE("Failed to allocate program object\n"); + return 0; + } + + // Compile the shaders and bind them to this program + GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vtxSrc); + if (vertexShader == 0) { + ALOGE("Failed to load vertex shader\n"); + glDeleteProgram(program); + return 0; + } + GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pxlSrc); + if (pixelShader == 0) { + ALOGE("Failed to load pixel shader\n"); + glDeleteProgram(program); + glDeleteShader(vertexShader); + return 0; + } + glAttachShader(program, vertexShader); + glAttachShader(program, pixelShader); + + // Link the program + glLinkProgram(program); + GLint linked = 0; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (!linked) + { + ALOGE("Error linking program.\n"); + GLint size = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &size); + if (size > 0) + { + // Get and report the error message + char *infoLog = (char*)malloc(size); + glGetProgramInfoLog(program, size, nullptr, infoLog); + ALOGE(" msg: %s\n", infoLog); + free(infoLog); + } + + glDeleteProgram(program); + glDeleteShader(vertexShader); + glDeleteShader(pixelShader); + return 0; + } + + return program; +} + + +// Main entry point +bool GlWrapper::initialize() { + // + // Create the native full screen window and get a suitable configuration to match it + // + status_t err; + + mFlinger = new SurfaceComposerClient(); + if (mFlinger == nullptr) { + ALOGE("SurfaceComposerClient couldn't be allocated"); + return false; + } + err = mFlinger->initCheck(); + if (err != NO_ERROR) { + ALOGE("SurfaceComposerClient::initCheck error: %#x", err); + return false; + } + + // Get main display parameters. + sp <IBinder> mainDpy = SurfaceComposerClient::getBuiltInDisplay( + ISurfaceComposer::eDisplayIdMain); + DisplayInfo mainDpyInfo; + err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo); + if (err != NO_ERROR) { + ALOGE("ERROR: unable to get display characteristics"); + return false; + } + + if (mainDpyInfo.orientation != DISPLAY_ORIENTATION_0 && + mainDpyInfo.orientation != DISPLAY_ORIENTATION_180) { + // rotated + mWidth = mainDpyInfo.h; + mHeight = mainDpyInfo.w; + } else { + mWidth = mainDpyInfo.w; + mHeight = mainDpyInfo.h; + } + + mFlingerSurfaceControl = mFlinger->createSurface( + String8("Evs Display"), mWidth, mHeight, + PIXEL_FORMAT_RGBX_8888, ISurfaceComposerClient::eOpaque); + if (mFlingerSurfaceControl == nullptr || !mFlingerSurfaceControl->isValid()) { + ALOGE("Failed to create SurfaceControl"); + return false; + } + mFlingerSurface = mFlingerSurfaceControl->getSurface(); + + + // Set up our OpenGL ES context associated with the default display + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mDisplay == EGL_NO_DISPLAY) { + ALOGE("Failed to get egl display"); + return false; + } + + EGLint major = 3; + EGLint minor = 0; + if (!eglInitialize(mDisplay, &major, &minor)) { + ALOGE("Failed to initialize EGL: %s", getEGLError()); + return false; + } + + + const EGLint config_attribs[] = { + // Tag Value + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_NONE + }; + + // Pick the default configuration without constraints (is this good enough?) + EGLConfig egl_config = {0}; + EGLint numConfigs = -1; + eglChooseConfig(mDisplay, config_attribs, &egl_config, 1, &numConfigs); + if (numConfigs != 1) { + ALOGE("Didn't find a suitable format for our display window"); + return false; + } + + // Create the EGL render target surface + mSurface = eglCreateWindowSurface(mDisplay, egl_config, mFlingerSurface.get(), nullptr); + if (mSurface == EGL_NO_SURFACE) { + ALOGE("gelCreateWindowSurface failed."); + return false; + } + + // Create the EGL context + // NOTE: Our shader is (currently at least) written to require version 3, so this + // is required. + const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + mContext = eglCreateContext(mDisplay, egl_config, EGL_NO_CONTEXT, context_attribs); + if (mContext == EGL_NO_CONTEXT) { + ALOGE("Failed to create OpenGL ES Context: %s", getEGLError()); + return false; + } + + + // Activate our render target for drawing + if (!eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) { + ALOGE("Failed to make the OpenGL ES Context current: %s", getEGLError()); + return false; + } + + + // Create the shader program for our simple pipeline + mShaderProgram = buildShaderProgram(vertexShaderSource, pixelShaderSource); + if (!mShaderProgram) { + ALOGE("Failed to build shader program: %s", getEGLError()); + return false; + } + + // Create a GL texture that will eventually wrap our externally created texture surface(s) + glGenTextures(1, &mTextureMap); + if (mTextureMap <= 0) { + ALOGE("Didn't get a texture handle allocated: %s", getEGLError()); + return false; + } + + + return true; +} + + +void GlWrapper::shutdown() { + + // Drop our device textures + if (mKHRimage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mDisplay, mKHRimage); + mKHRimage = EGL_NO_IMAGE_KHR; + } + + // Release all GL resources + eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(mDisplay, mSurface); + eglDestroyContext(mDisplay, mContext); + eglTerminate(mDisplay); + mSurface = EGL_NO_SURFACE; + mContext = EGL_NO_CONTEXT; + mDisplay = EGL_NO_DISPLAY; + + // Let go of our SurfaceComposer resources + mFlingerSurface.clear(); + mFlingerSurfaceControl.clear(); + mFlinger.clear(); +} + + +void GlWrapper::showWindow() { + if (mFlingerSurfaceControl != nullptr) { + SurfaceComposerClient::openGlobalTransaction(); + mFlingerSurfaceControl->setLayer(0x7FFFFFFF); // always on top + mFlingerSurfaceControl->show(); + SurfaceComposerClient::closeGlobalTransaction(); + } +} + + +void GlWrapper::hideWindow() { + if (mFlingerSurfaceControl != nullptr) { + SurfaceComposerClient::openGlobalTransaction(); + mFlingerSurfaceControl->hide(); + SurfaceComposerClient::closeGlobalTransaction(); + } +} + + +bool GlWrapper::updateImageTexture(const BufferDesc& buffer) { + + // If we haven't done it yet, create an "image" object to wrap the gralloc buffer + if (mKHRimage == EGL_NO_IMAGE_KHR) { + // create a temporary GraphicBuffer to wrap the provided handle + sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer( + buffer.width, + buffer.height, + buffer.format, + 1, /* layer count */ + buffer.usage, + buffer.stride, + const_cast<native_handle_t*>(buffer.memHandle.getNativeHandle()), + false /* keep ownership */ + ); + if (pGfxBuffer.get() == nullptr) { + ALOGE("Failed to allocate GraphicsBuffer to wrap our native handle"); + return false; + } + + + // Get a GL compatible reference to the graphics buffer we've been given + EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; + EGLClientBuffer cbuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer()); +// TODO: If we pass in a context, we get "bad context" back +#if 0 + mKHRimage = eglCreateImageKHR(mDisplay, mContext, + EGL_NATIVE_BUFFER_ANDROID, cbuf, + eglImageAttributes); +#else + mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, cbuf, + eglImageAttributes); +#endif + if (mKHRimage == EGL_NO_IMAGE_KHR) { + ALOGE("error creating EGLImage: %s", getEGLError()); + return false; + } + + + // Update the texture handle we already created to refer to this gralloc buffer + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mTextureMap); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(mKHRimage)); + + } + + return true; +} + + +void GlWrapper::renderImageToScreen() { + // Set the viewport + glViewport(0, 0, mWidth, mHeight); + + // Clear the color buffer + glClearColor(0.1f, 0.5f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Select our screen space simple texture shader + glUseProgram(mShaderProgram); + + // Bind the texture and assign it to the shader's sampler + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mTextureMap); + GLint sampler = glGetUniformLocation(mShaderProgram, "tex"); + glUniform1i(sampler, 0); + + // We want our image to show up opaque regardless of alpha values + glDisable(GL_BLEND); + + + // Draw a rectangle on the screen + // TODO: We pulled in from the edges for now for diagnostic purposes... +#if 0 + GLfloat vertsCarPos[] = { -1.0, 1.0, 0.0f, // left top in window space + 1.0, 1.0, 0.0f, // right top + -1.0, -1.0, 0.0f, // left bottom + 1.0, -1.0, 0.0f // right bottom + }; +#else + GLfloat vertsCarPos[] = { -0.8, 0.8, 0.0f, // left top in window space + 0.8, 0.8, 0.0f, // right top + -0.8, -0.8, 0.0f, // left bottom + 0.8, -0.8, 0.0f // right bottom + }; +#endif + // NOTE: We didn't flip the image in the texture, so V=0 is actually the top of the image + GLfloat vertsCarTex[] = { 0.0f, 0.0f, // left top + 1.0f, 0.0f, // right top + 0.0f, 1.0f, // left bottom + 1.0f, 1.0f // right bottom + }; + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + + // Clean up and flip the rendered result to the front so it is visible + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + + glFinish(); + + eglSwapBuffers(mDisplay, mSurface); +} + diff --git a/evs/sampleDriver/GlWrapper.h b/evs/sampleDriver/GlWrapper.h new file mode 100644 index 0000000000..07b5525d79 --- /dev/null +++ b/evs/sampleDriver/GlWrapper.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_DISPLAY_GLWRAPPER_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_DISPLAY_GLWRAPPER_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> + +#include <gui/ISurfaceComposer.h> +#include <gui/Surface.h> +#include <gui/SurfaceComposerClient.h> + +#include <android/hardware/automotive/evs/1.0/types.h> + + +using ::android::sp; +using ::android::SurfaceComposerClient; +using ::android::SurfaceControl; +using ::android::Surface; +using ::android::hardware::automotive::evs::V1_0::BufferDesc; + + +class GlWrapper { +public: + bool initialize(); + void shutdown(); + + bool updateImageTexture(const BufferDesc& buffer); + void renderImageToScreen(); + + void showWindow(); + void hideWindow(); + + unsigned getWidth() { return mWidth; }; + unsigned getHeight() { return mHeight; }; + +private: + sp<SurfaceComposerClient> mFlinger; + sp<SurfaceControl> mFlingerSurfaceControl; + sp<Surface> mFlingerSurface; + EGLDisplay mDisplay; + EGLSurface mSurface; + EGLContext mContext; + + unsigned mWidth = 0; + unsigned mHeight = 0; + + EGLImageKHR mKHRimage = EGL_NO_IMAGE_KHR; + + GLuint mTextureMap = 0; + GLuint mShaderProgram = 0; +}; + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_DISPLAY_GLWRAPPER_H diff --git a/car-support-lib/src/android/support/car/app/menu/SearchBoxEditListener.java b/evs/sampleDriver/ServiceNames.h index c8e67bcb4a..1178da5a9c 100644 --- a/car-support-lib/src/android/support/car/app/menu/SearchBoxEditListener.java +++ b/evs/sampleDriver/ServiceNames.h @@ -13,20 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.car.app.menu; -/** - * A listener that listens the user input to the search box. - * @hide - */ -public abstract class SearchBoxEditListener { - /** - * The user hit enter on the keyboard. - */ - public abstract void onSearch(String text); - - /** - * The user changed the text in the search box with the keyboard. - */ - public abstract void onEdit(String text); -}
\ No newline at end of file +const static char kEnumeratorServiceName[] = "EvsEnumeratorHw"; diff --git a/evs/sampleDriver/VideoCapture.cpp b/evs/sampleDriver/VideoCapture.cpp new file mode 100644 index 0000000000..2122a2c643 --- /dev/null +++ b/evs/sampleDriver/VideoCapture.cpp @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2016 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. + */ +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include <stdio.h> +#include <stdlib.h> +#include <error.h> +#include <errno.h> +#include <memory.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <cutils/log.h> + +#include "assert.h" + +#include "VideoCapture.h" + + +// NOTE: This developmental code does not properly clean up resources in case of failure +// during the resource setup phase. Of particular note is the potential to leak +// the file descriptor. This must be fixed before using this code for anything but +// experimentation. +bool VideoCapture::open(const char* deviceName) { + // If we want a polling interface for getting frames, we would use O_NONBLOCK +// int mDeviceFd = open(deviceName, O_RDWR | O_NONBLOCK, 0); + mDeviceFd = ::open(deviceName, O_RDWR, 0); + if (mDeviceFd < 0) { + ALOGE("failed to open device %s (%d = %s)", deviceName, errno, strerror(errno)); + return false; + } + + v4l2_capability caps; + { + int result = ioctl(mDeviceFd, VIDIOC_QUERYCAP, &caps); + if (result < 0) { + ALOGE("failed to get device caps for %s (%d = %s)", deviceName, errno, strerror(errno)); + return false; + } + } + + // Report device properties + ALOGI("Open Device: %s (fd=%d)", deviceName, mDeviceFd); + ALOGI(" Driver: %s", caps.driver); + ALOGI(" Card: %s", caps.card); + ALOGI(" Version: %u.%u.%u", + (caps.version >> 16) & 0xFF, + (caps.version >> 8) & 0xFF, + (caps.version) & 0xFF); + ALOGI(" All Caps: %08X", caps.capabilities); + ALOGI(" Dev Caps: %08X", caps.device_caps); + + // Enumerate the available capture formats (if any) + ALOGI("Supported capture formats:"); + v4l2_fmtdesc formatDescriptions; + formatDescriptions.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + for (int i=0; true; i++) { + formatDescriptions.index = i; + if (ioctl(mDeviceFd, VIDIOC_ENUM_FMT, &formatDescriptions) == 0) { + ALOGI(" %2d: %s 0x%08X 0x%X", + i, + formatDescriptions.description, + formatDescriptions.pixelformat, + formatDescriptions.flags + ); + } else { + // No more formats available + break; + } + } + + // Verify we can use this device for video capture + if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) || + !(caps.capabilities & V4L2_CAP_STREAMING)) { + // Can't do streaming capture. + ALOGE("Streaming capture not supported by %s.", deviceName); + return false; + } + + // Set our desired output format + v4l2_format format; + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + format.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; // Could/should we request V4L2_PIX_FMT_NV21? + format.fmt.pix.width = 720; // TODO: Can we avoid hard coding dimensions? + format.fmt.pix.height = 240; // For now, this works with available hardware + format.fmt.pix.field = V4L2_FIELD_ALTERNATE; // TODO: Do we need to specify this? + ALOGI("Requesting format %c%c%c%c (0x%08X)", + ((char*)&format.fmt.pix.pixelformat)[0], + ((char*)&format.fmt.pix.pixelformat)[1], + ((char*)&format.fmt.pix.pixelformat)[2], + ((char*)&format.fmt.pix.pixelformat)[3], + format.fmt.pix.pixelformat); + if (ioctl(mDeviceFd, VIDIOC_S_FMT, &format) < 0) { + ALOGE("VIDIOC_S_FMT: %s", strerror(errno)); + } + + // Report the current output format + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(mDeviceFd, VIDIOC_G_FMT, &format) == 0) { + + mFormat = format.fmt.pix.pixelformat; + mWidth = format.fmt.pix.width; + mHeight = format.fmt.pix.height; + mStride = format.fmt.pix.bytesperline; + + ALOGI("Current output format: fmt=0x%X, %dx%d, pitch=%d", + format.fmt.pix.pixelformat, + format.fmt.pix.width, + format.fmt.pix.height, + format.fmt.pix.bytesperline + ); + } else { + ALOGE("VIDIOC_G_FMT: %s", strerror(errno)); + return false; + } + + // Make sure we're initialized to the STOPPED state + mRunMode = STOPPED; + mFrameReady = false; + + // Ready to go! + return true; +} + + +void VideoCapture::close() { + ALOGD("VideoCapture::close"); + // Stream should be stopped first! + assert(mRunMode == STOPPED); + + if (isOpen()) { + ALOGD("closing video device file handled %d", mDeviceFd); + ::close(mDeviceFd); + mDeviceFd = -1; + } +} + + +bool VideoCapture::startStream(std::function<void(VideoCapture*, imageBuffer*, void*)> callback) { + // Set the state of our background thread + int prevRunMode = mRunMode.fetch_or(RUN); + if (prevRunMode & RUN) { + // The background thread is already running, so we can't start a new stream + ALOGE("Already in RUN state, so we can't start a new streaming thread"); + return false; + } + + // Tell the L4V2 driver to prepare our streaming buffers + v4l2_requestbuffers bufrequest; + bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + bufrequest.memory = V4L2_MEMORY_MMAP; + bufrequest.count = 1; + if (ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest) < 0) { + ALOGE("VIDIOC_REQBUFS: %s", strerror(errno)); + return false; + } + + // Get the information on the buffer that was created for us + memset(&mBufferInfo, 0, sizeof(mBufferInfo)); + mBufferInfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + mBufferInfo.memory = V4L2_MEMORY_MMAP; + mBufferInfo.index = 0; + if (ioctl(mDeviceFd, VIDIOC_QUERYBUF, &mBufferInfo) < 0) { + ALOGE("VIDIOC_QUERYBUF: %s", strerror(errno)); + return false; + } + + ALOGI("Buffer description:"); + ALOGI(" offset: %d", mBufferInfo.m.offset); + ALOGI(" length: %d", mBufferInfo.length); + + // Get a pointer to the buffer contents by mapping into our address space + mPixelBuffer = mmap( + NULL, + mBufferInfo.length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + mDeviceFd, + mBufferInfo.m.offset + ); + if( mPixelBuffer == MAP_FAILED) { + ALOGE("mmap: %s", strerror(errno)); + return false; + } + memset(mPixelBuffer, 0, mBufferInfo.length); + ALOGI("Buffer mapped at %p", mPixelBuffer); + + // Queue the first capture buffer + if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) { + ALOGE("VIDIOC_QBUF: %s", strerror(errno)); + return false; + } + + // Start the video stream + int type = mBufferInfo.type; + if (ioctl(mDeviceFd, VIDIOC_STREAMON, &type) < 0) { + ALOGE("VIDIOC_STREAMON: %s", strerror(errno)); + return false; + } + + // Remember who to tell about new frames as they arrive + mCallback = callback; + + // Fire up a thread to receive and dispatch the video frames + mCaptureThread = std::thread([this](){ collectFrames(); }); + + ALOGD("Stream started."); + return true; +} + + +void VideoCapture::stopStream() { + // Tell the background thread to stop + int prevRunMode = mRunMode.fetch_or(STOPPING); + if (prevRunMode == STOPPED) { + // The background thread wasn't running, so set the flag back to STOPPED + mRunMode = STOPPED; + } else if (prevRunMode & STOPPING) { + ALOGE("stopStream called while stream is already stopping. Reentrancy is not supported!"); + return; + } else { + // Block until the background thread is stopped + if (mCaptureThread.joinable()) { + mCaptureThread.join(); + } + + // Stop the underlying video stream (automatically empties the buffer queue) + int type = mBufferInfo.type; + if (ioctl(mDeviceFd, VIDIOC_STREAMOFF, &type) < 0) { + ALOGE("VIDIOC_STREAMOFF: %s", strerror(errno)); + } + + ALOGD("Capture thread stopped."); + } + + // Unmap the buffers we allocated + munmap(mPixelBuffer, mBufferInfo.length); + + // Tell the L4V2 driver to release our streaming buffers + v4l2_requestbuffers bufrequest; + bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + bufrequest.memory = V4L2_MEMORY_MMAP; + bufrequest.count = 0; + ioctl(mDeviceFd, VIDIOC_REQBUFS, &bufrequest); + + // Drop our reference to the frame delivery callback interface + mCallback = nullptr; +} + + +void VideoCapture::markFrameReady() { + mFrameReady = true; +}; + + +bool VideoCapture::returnFrame() { + // We're giving the frame back to the system, so clear the "ready" flag + mFrameReady = false; + + // Requeue the buffer to capture the next available frame + if (ioctl(mDeviceFd, VIDIOC_QBUF, &mBufferInfo) < 0) { + ALOGE("VIDIOC_QBUF: %s", strerror(errno)); + return false; + } + + return true; +} + + +// This runs on a background thread to receive and dispatch video frames +void VideoCapture::collectFrames() { + // Run until our atomic signal is cleared + while (mRunMode == RUN) { + // Wait for a buffer to be ready + if (ioctl(mDeviceFd, VIDIOC_DQBUF, &mBufferInfo) < 0) { + ALOGE("VIDIOC_DQBUF: %s", strerror(errno)); + break; + } + + markFrameReady(); + + // If a callback was requested per frame, do that now + if (mCallback) { + mCallback(this, &mBufferInfo, mPixelBuffer); + } + } + + // Mark ourselves stopped + ALOGD("VideoCapture thread ending"); + mRunMode = STOPPED; +} diff --git a/evs/sampleDriver/VideoCapture.h b/evs/sampleDriver/VideoCapture.h new file mode 100644 index 0000000000..f2d11752d7 --- /dev/null +++ b/evs/sampleDriver/VideoCapture.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 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. + */ +#include <atomic> +#include <thread> +#include <functional> +#include <linux/videodev2.h> + + +typedef v4l2_buffer imageBuffer; + + +class VideoCapture { +public: + bool open(const char* deviceName); + void close(); + + bool startStream(std::function<void(VideoCapture*, imageBuffer*, void*)> callback = nullptr); + void stopStream(); + + // Valid only after open() + __u32 getWidth() { return mWidth; }; + __u32 getHeight() { return mHeight; }; + __u32 getStride() { return mStride; }; + __u32 getV4LFormat() { return mFormat; }; + + // NULL until stream is started + void* getLatestData() { return mPixelBuffer; }; + + bool isFrameReady() { return mFrameReady; }; + void markFrameConsumed() { returnFrame(); }; + + bool isOpen() { return mDeviceFd >= 0; }; + +private: + void collectFrames(); + void markFrameReady(); + bool returnFrame(); + + int mDeviceFd = -1; + + v4l2_buffer mBufferInfo = {}; + void* mPixelBuffer = nullptr; + + __u32 mFormat = 0; + __u32 mWidth = 0; + __u32 mHeight = 0; + __u32 mStride = 0; + + std::function<void(VideoCapture*, imageBuffer*, void*)> mCallback; + + std::thread mCaptureThread; // The thread we'll use to dispatch frames + std::atomic<int> mRunMode; // Used to signal the frame loop (see RunModes below) + std::atomic<bool> mFrameReady; // Set when a frame has been delivered + + // Careful changing these -- we're using bit-wise ops to manipulate these + enum RunModes { + STOPPED = 0, + RUN = 1, + STOPPING = 2, + }; +}; + diff --git a/evs/sampleDriver/android.hardware.automotive.evs@1.0-sample.rc b/evs/sampleDriver/android.hardware.automotive.evs@1.0-sample.rc new file mode 100644 index 0000000000..81fe33ab78 --- /dev/null +++ b/evs/sampleDriver/android.hardware.automotive.evs@1.0-sample.rc @@ -0,0 +1,6 @@ +service evs_driver /system/bin/android.hardware.automotive.evs@1.0-sample + class hal + priority -20 + user graphics + group automotive_evs + onrestart restart evs_manager diff --git a/evs/sampleDriver/bufferCopy.cpp b/evs/sampleDriver/bufferCopy.cpp new file mode 100644 index 0000000000..a585040e36 --- /dev/null +++ b/evs/sampleDriver/bufferCopy.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include "bufferCopy.h" + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +// Round up to the nearest multiple of the given alignment value +template<unsigned alignment> +int align(int value) { + static_assert((alignment && !(alignment & (alignment - 1))), + "alignment must be a power of 2"); + + unsigned mask = alignment - 1; + return (value + mask) & ~mask; +} + + +// Limit the given value to the provided range. :) +static inline float clamp(float v, float min, float max) { + if (v < min) return min; + if (v > max) return max; + return v; +} + + +static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin) { + // Don't use this if you want to see the best performance. :) + // Better to do this in a pixel shader if we really have to, but on actual + // embedded hardware we expect to be able to texture directly from the YUV data + float U = Uin - 128.0f; + float V = Vin - 128.0f; + + float Rf = Y + 1.140f*V; + float Gf = Y - 0.395f*U - 0.581f*V; + float Bf = Y + 2.032f*U; + unsigned char R = (unsigned char)clamp(Rf, 0.0f, 255.0f); + unsigned char G = (unsigned char)clamp(Gf, 0.0f, 255.0f); + unsigned char B = (unsigned char)clamp(Bf, 0.0f, 255.0f); + + return ((R & 0xFF)) | + ((G & 0xFF) << 8) | + ((B & 0xFF) << 16) | + 0xFF000000; // Fill the alpha channel with ones +} + + +void fillNV21FromNV21(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned) { + // The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleave U/V array. + // It assumes an even width and height for the overall image, and a horizontal stride that is + // an even multiple of 16 bytes for both the Y and UV arrays. + + // Target and source image layout properties (They match since the formats match!) + const unsigned strideLum = align<16>(tgtBuff.width); + const unsigned sizeY = strideLum * tgtBuff.height; + const unsigned strideColor = strideLum; // 1/2 the samples, but two interleaved channels + const unsigned sizeColor = strideColor * tgtBuff.height/2; + const unsigned totalBytes = sizeY + sizeColor; + + // Simply copy the data byte for byte + memcpy(tgt, imgData, totalBytes); +} + + +void fillNV21FromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) { + // The YUYV format provides an interleaved array of pixel values with U and V subsampled in + // the horizontal direction only. Also known as interleaved 422 format. A 4 byte + // "macro pixel" provides the Y value for two adjacent pixels and the U and V values shared + // between those two pixels. The width of the image must be an even number. + // We need to down sample the UV values and collect them together after all the packed Y values + // to construct the NV21 format. + // NV21 requires even width and height, so we assume that is the case for the incomming image + // as well. + uint32_t *srcDataYUYV = (uint32_t*)imgData; + struct YUYVpixel { + uint8_t Y1; + uint8_t U; + uint8_t Y2; + uint8_t V; + }; + + // Target image layout properties + const unsigned strideLum = align<16>(tgtBuff.width); + const unsigned sizeY = strideLum * tgtBuff.height; + const unsigned strideColor = strideLum; // 1/2 the samples, but two interleaved channels + + // Source image layout properties + const unsigned srcRowPixels = imgStride/4; // imgStride is in units of bytes + const unsigned srcRowDoubleStep = srcRowPixels * 2; + uint32_t* topSrcRow = srcDataYUYV; + uint32_t* botSrcRow = srcDataYUYV + srcRowPixels; + + // We're going to work on one 2x2 cell in the output image at at time + for (unsigned cellRow = 0; cellRow < tgtBuff.height/2; cellRow++) { + + // Set up the output pointers + uint8_t* yTopRow = tgt + (cellRow*2) * strideLum; + uint8_t* yBotRow = yTopRow + strideLum; + uint8_t* uvRow = (tgt + sizeY) + cellRow * strideColor; + + for (unsigned cellCol = 0; cellCol < tgtBuff.width/2; cellCol++) { + // Collect the values from the YUYV interleaved data + const YUYVpixel* pTopMacroPixel = (YUYVpixel*)&topSrcRow[cellCol]; + const YUYVpixel* pBotMacroPixel = (YUYVpixel*)&botSrcRow[cellCol]; + + // Down sample the U/V values by linear average between rows + const uint8_t uValue = (pTopMacroPixel->U + pBotMacroPixel->U) >> 1; + const uint8_t vValue = (pTopMacroPixel->V + pBotMacroPixel->V) >> 1; + + // Store the values into the NV21 layout + yTopRow[cellCol*2] = pTopMacroPixel->Y1; + yTopRow[cellCol*2+1] = pTopMacroPixel->Y2; + yBotRow[cellCol*2] = pBotMacroPixel->Y1; + yBotRow[cellCol*2+1] = pBotMacroPixel->Y2; + uvRow[cellCol*2] = uValue; + uvRow[cellCol*2+1] = vValue; + } + + // Skipping two rows to get to the next set of two source rows + topSrcRow += srcRowDoubleStep; + botSrcRow += srcRowDoubleStep; + } +} + + +void fillRGBAFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) { + unsigned width = tgtBuff.width; + unsigned height = tgtBuff.height; + uint32_t* src = (uint32_t*)imgData; + uint32_t* dst = (uint32_t*)tgt; + unsigned srcStridePixels = imgStride / 2; + unsigned dstStridePixels = tgtBuff.stride; + + const int srcRowPadding32 = srcStridePixels/2 - width/2; // 2 bytes per pixel, 4 bytes per word + const int dstRowPadding32 = dstStridePixels - width; // 4 bytes per pixel, 4 bytes per word + + for (unsigned r=0; r<height; r++) { + for (unsigned c=0; c<width/2; c++) { + // Note: we're walking two pixels at a time here (even/odd) + uint32_t srcPixel = *src++; + + uint8_t Y1 = (srcPixel) & 0xFF; + uint8_t U = (srcPixel >> 8) & 0xFF; + uint8_t Y2 = (srcPixel >> 16) & 0xFF; + uint8_t V = (srcPixel >> 24) & 0xFF; + + // On the RGB output, we're writing one pixel at a time + *(dst+0) = yuvToRgbx(Y1, U, V); + *(dst+1) = yuvToRgbx(Y2, U, V); + dst += 2; + } + + // Skip over any extra data or end of row alignment padding + src += srcRowPadding32; + dst += dstRowPadding32; + } +} + + +void fillYUYVFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) { + unsigned width = tgtBuff.width; + unsigned height = tgtBuff.height; + uint8_t* src = (uint8_t*)imgData; + uint8_t* dst = (uint8_t*)tgt; + unsigned srcStrideBytes = imgStride; + unsigned dstStrideBytes = tgtBuff.stride * 2; + + for (unsigned r=0; r<height; r++) { + // Copy a pixel row at a time (2 bytes per pixel, averaged over a YUYV macro pixel) + memcpy(dst+r*dstStrideBytes, src+r*srcStrideBytes, width*2); + } +} + + +void fillYUYVFromUYVY(const BufferDesc& tgtBuff, uint8_t* tgt, void* imgData, unsigned imgStride) { + unsigned width = tgtBuff.width; + unsigned height = tgtBuff.height; + uint32_t* src = (uint32_t*)imgData; + uint32_t* dst = (uint32_t*)tgt; + unsigned srcStridePixels = imgStride / 2; + unsigned dstStridePixels = tgtBuff.stride; + + const int srcRowPadding32 = srcStridePixels/2 - width/2; // 2 bytes per pixel, 4 bytes per word + const int dstRowPadding32 = dstStridePixels/2 - width/2; // 2 bytes per pixel, 4 bytes per word + + for (unsigned r=0; r<height; r++) { + for (unsigned c=0; c<width/2; c++) { + // Note: we're walking two pixels at a time here (even/odd) + uint32_t srcPixel = *src++; + + uint8_t Y1 = (srcPixel) & 0xFF; + uint8_t U = (srcPixel >> 8) & 0xFF; + uint8_t Y2 = (srcPixel >> 16) & 0xFF; + uint8_t V = (srcPixel >> 24) & 0xFF; + + // Now we write back the pair of pixels with the components swizzled + *dst++ = (U) | + (Y1 << 8) | + (V << 16) | + (Y2 << 24); + } + + // Skip over any extra data or end of row alignment padding + src += srcRowPadding32; + dst += dstRowPadding32; + } +} + + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/evs/sampleDriver/bufferCopy.h b/evs/sampleDriver/bufferCopy.h new file mode 100644 index 0000000000..9f68f2bcac --- /dev/null +++ b/evs/sampleDriver/bufferCopy.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_BUFFERCOPY_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_BUFFERCOPY_H + +#include <android/hardware/automotive/evs/1.0/types.h> + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_0 { +namespace implementation { + + +void fillNV21FromNV21(const BufferDesc& tgtBuff, uint8_t* tgt, + void* imgData, unsigned imgStride); + +void fillNV21FromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, + void* imgData, unsigned imgStride); + +void fillRGBAFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, + void* imgData, unsigned imgStride); + +void fillYUYVFromYUYV(const BufferDesc& tgtBuff, uint8_t* tgt, + void* imgData, unsigned imgStride); + +void fillYUYVFromUYVY(const BufferDesc& tgtBuff, uint8_t* tgt, + void* imgData, unsigned imgStride); + +} // namespace implementation +} // namespace V1_0 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_0_BUFFERCOPY_H diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp new file mode 100644 index 0000000000..d73f758ee7 --- /dev/null +++ b/evs/sampleDriver/service.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.0-display" + +#include <unistd.h> + +#include <hidl/HidlTransportSupport.h> +#include <utils/Errors.h> +#include <utils/StrongPointer.h> +#include <utils/Log.h> + +#include "ServiceNames.h" +#include "EvsEnumerator.h" +#include "EvsGlDisplay.h" + + +// libhidl: +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +// Generated HIDL files +using android::hardware::automotive::evs::V1_0::IEvsEnumerator; +using android::hardware::automotive::evs::V1_0::IEvsDisplay; + +// The namespace in which all our implementation code lives +using namespace android::hardware::automotive::evs::V1_0::implementation; +using namespace android; + + +int main() { + ALOGI("EVS Hardware Enumerator service is starting"); + android::sp<IEvsEnumerator> service = new EvsEnumerator(); + + configureRpcThreadpool(1, true /* callerWillJoin */); + + // Register our service -- if somebody is already registered by our name, + // they will be killed (their thread pool will throw an exception). + status_t status = service->registerAsService(kEnumeratorServiceName); + if (status == OK) { + ALOGD("%s is ready.", kEnumeratorServiceName); + joinRpcThreadpool(); + } else { + ALOGE("Could not register service %s (%d).", kEnumeratorServiceName, status); + } + + // In normal operation, we don't expect the thread pool to exit + ALOGE("EVS Hardware Enumerator is shutting down"); + return 1; +} diff --git a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java index 56f64f772c..4768cebc6e 100644 --- a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java +++ b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java @@ -43,6 +43,10 @@ public class IntegerArrayStream { return mData.length - mIndex; } + public boolean isEmpty() { + return residualLength() == 0; + } + public boolean hasAtLeast(int n) { return residualLength() >= n; } diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Command.java b/obd2-lib/src/com/android/car/obd2/Obd2Command.java index e3366cfd08..30fca0c1d1 100644 --- a/obd2-lib/src/com/android/car/obd2/Obd2Command.java +++ b/obd2-lib/src/com/android/car/obd2/Obd2Command.java @@ -172,7 +172,7 @@ public abstract class Obd2Command<ValueType> { * @param <ValueType> The Java type that represents the command's result type. */ public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> { - private static final int RESPONSE_MARKER = 0x2; + private static final int RESPONSE_MARKER = 0x42; private int mFrameId; diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java index bfdb9c0f2c..f7a2d3695a 100644 --- a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java +++ b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java @@ -20,12 +20,16 @@ import android.util.Log; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; /** This class represents a connection between Java code and a "vehicle" that talks OBD2. */ public class Obd2Connection { + private static final String TAG = Obd2Connection.class.getSimpleName(); + private static final boolean DBG = false; /** * The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It @@ -72,6 +76,10 @@ public class Obd2Connection { return true; } + public boolean isConnected() { + return mConnection.isConnected(); + } + static int toDigitValue(char c) { if ((c >= '0') && (c <= '9')) return c - '0'; switch (c) { @@ -112,6 +120,10 @@ public class Obd2Connection { InputStream in = Objects.requireNonNull(mConnection.getInputStream()); OutputStream out = Objects.requireNonNull(mConnection.getOutputStream()); + if (DBG) { + Log.i(TAG, "runImpl(" + command + ")"); + } + out.write((command + "\r").getBytes()); out.flush(); @@ -127,6 +139,11 @@ public class Obd2Connection { } String responseValue = response.toString(); + + if (DBG) { + Log.i(TAG, "runImpl() returned " + responseValue); + } + return responseValue; } @@ -137,11 +154,30 @@ public class Obd2Connection { return response; } + String unpackLongFrame(String response) { + // long frames come back to us containing colon separated portions + if (response.indexOf(':') < 0) return response; + + // remove everything until the first colon + response = response.substring(response.indexOf(':') + 1); + + // then remove the <digit>: portions (sequential frame parts) + //TODO(egranata): maybe validate the sequence of digits is progressive + return response.replaceAll("[0-9]:", ""); + } + public int[] run(String command) throws IOException, InterruptedException { String responseValue = runImpl(command); String originalResponseValue = responseValue; - if (responseValue.startsWith(command)) - responseValue = responseValue.substring(command.length()); + String unspacedCommand = command.replaceAll(" ", ""); + if (responseValue.startsWith(unspacedCommand)) + responseValue = responseValue.substring(unspacedCommand.length()); + responseValue = unpackLongFrame(responseValue); + + if (DBG) { + Log.i(TAG, "post-processed response " + responseValue); + } + //TODO(egranata): should probably handle these intelligently responseValue = removeSideData( @@ -157,11 +193,12 @@ public class Obd2Connection { if (responseValue.equals("?")) return new int[] {0}; if (responseValue.equals("NODATA")) return new int[] {}; if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure"); + if (responseValue.equals("CANERROR")) throw new IOException("CAN bus error"); try { return toHexValues(responseValue); } catch (IllegalArgumentException e) { Log.e( - "OBD2", + TAG, String.format( "conversion error: command: '%s', original response: '%s'" + ", processed response: '%s'", @@ -224,7 +261,7 @@ public class Obd2Connection { public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException { Set<Integer> result = new HashSet<>(); String[] pids = new String[] {"0100", "0120", "0140", "0160"}; - int basePid = 0; + int basePid = 1; for (String pid : pids) { int[] responseData = run(pid); if (responseData.length >= 6) { @@ -232,11 +269,19 @@ public class Obd2Connection { byte byte1 = (byte) (responseData[3] & 0xFF); byte byte2 = (byte) (responseData[4] & 0xFF); byte byte3 = (byte) (responseData[5] & 0xFF); + if (DBG) { + Log.i(TAG, String.format("supported PID at base %d payload %02X%02X%02X%02X", + basePid, byte0, byte1, byte2, byte3)); + } FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3); for (int byteIndex = 0; byteIndex < 4; ++byteIndex) { for (int bitIndex = 7; bitIndex >= 0; --bitIndex) { if (fourByteBitSet.getBit(byteIndex, bitIndex)) { - result.add(basePid + 8 * byteIndex + 7 - bitIndex); + int command = basePid + 8 * byteIndex + 7 - bitIndex; + if (DBG) { + Log.i(TAG, "command " + command + " found supported"); + } + result.add(command); } } } @@ -246,4 +291,46 @@ public class Obd2Connection { return result; } + + String getDiagnosticTroubleCode(IntegerArrayStream source) { + final char[] components = new char[] {'P', 'C', 'B', 'U'}; + final char[] firstDigits = new char[] {'0', '1', '2', '3'}; + final char[] otherDigits = + new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + StringBuilder builder = new StringBuilder(5); + + int byte0 = source.consume(); + int byte1 = source.consume(); + + int componentMask = (byte0 & 0xC0) >> 6; + int firstDigitMask = (byte0 & 0x30) >> 4; + int secondDigitMask = (byte0 & 0x0F); + int thirdDigitMask = (byte1 & 0xF0) >> 4; + int fourthDigitMask = (byte1 & 0x0F); + + builder.append(components[componentMask]); + builder.append(firstDigits[firstDigitMask]); + builder.append(otherDigits[secondDigitMask]); + builder.append(otherDigits[thirdDigitMask]); + builder.append(otherDigits[fourthDigitMask]); + + return builder.toString(); + } + + public List<String> getDiagnosticTroubleCodes() throws IOException, InterruptedException { + List<String> result = new ArrayList<>(); + int[] response = run("03"); + IntegerArrayStream stream = new IntegerArrayStream(response); + if (stream.isEmpty()) return result; + if (!stream.expect(0x43)) + throw new IllegalArgumentException("data from remote end not a mode 3 response"); + int count = stream.consume(); + for (int i = 0; i < count; ++i) { + result.add(getDiagnosticTroubleCode(stream)); + } + return result; + } } diff --git a/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java b/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java new file mode 100644 index 0000000000..3de48638df --- /dev/null +++ b/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2017 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.obd2; + +import android.os.SystemClock; +import android.util.JsonWriter; +import android.util.Log; +import com.android.car.obd2.Obd2Command.FreezeFrameCommand; +import com.android.car.obd2.Obd2Command.OutputSemanticHandler; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class Obd2FreezeFrameGenerator { + public static final String FRAME_TYPE_FREEZE = "freeze"; + public static final String TAG = Obd2FreezeFrameGenerator.class.getSimpleName(); + + private final Obd2Connection mConnection; + private final List<OutputSemanticHandler<Integer>> mIntegerCommands = new ArrayList<>(); + private final List<OutputSemanticHandler<Float>> mFloatCommands = new ArrayList<>(); + + private List<String> mPreviousDtcs = new ArrayList<>(); + + public Obd2FreezeFrameGenerator(Obd2Connection connection) + throws IOException, InterruptedException { + mConnection = connection; + Set<Integer> connectionPids = connection.getSupportedPIDs(); + Set<Integer> apiIntegerPids = Obd2Command.getSupportedIntegerCommands(); + Set<Integer> apiFloatPids = Obd2Command.getSupportedFloatCommands(); + apiIntegerPids + .stream() + .filter(connectionPids::contains) + .forEach((Integer pid) -> mIntegerCommands.add(Obd2Command.getIntegerCommand(pid))); + apiFloatPids + .stream() + .filter(connectionPids::contains) + .forEach((Integer pid) -> mFloatCommands.add(Obd2Command.getFloatCommand(pid))); + Log.i( + TAG, + String.format( + "connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n" + + "mIntegerCommands = %s\nmFloatCommands = %s\n", + connectionPids, + apiIntegerPids, + apiFloatPids, + mIntegerCommands, + mFloatCommands)); + } + + public JsonWriter generate(JsonWriter jsonWriter) throws IOException, InterruptedException { + return generate(jsonWriter, SystemClock.elapsedRealtimeNanos()); + } + + // OBD2 does not have a notion of timestamping the fault codes + // As such, we need to perform additional magic in order to figure out + // whether a fault code we retrieved is the same as a fault code we already + // saw in a past iteration. The logic goes as follows: + // for every position i in currentDtcs, if mPreviousDtcs[i] is the same + // fault code, then assume they are identical. If they are not the same fault code, + // then everything in currentDtcs[i...size()) is assumed to be a new fault code as + // something in the list must have moved around; if currentDtcs is shorter than + // mPreviousDtcs then obviously exit at the end of currentDtcs; if currentDtcs + // is longer, however, anything in currentDtcs past the end of mPreviousDtcs is a new + // fault code and will be included + private final class FreezeFrameIdentity { + public final String dtc; + public final int id; + + FreezeFrameIdentity(String dtc, int id) { + this.dtc = dtc; + this.id = id; + } + } + + private List<FreezeFrameIdentity> discoverNewDtcs(List<String> currentDtcs) { + List<FreezeFrameIdentity> newDtcs = new ArrayList<>(); + int currentIndex = 0; + boolean inCopyAllMode = false; + + for (; currentIndex < currentDtcs.size(); ++currentIndex) { + if (currentIndex == mPreviousDtcs.size()) { + // we have more current DTCs than previous DTCs, copy everything + inCopyAllMode = true; + break; + } + if (!currentDtcs.get(currentIndex).equals(mPreviousDtcs.get(currentIndex))) { + // we found a different DTC, copy everything + inCopyAllMode = true; + break; + } + // same DTC, not at end of either list yet, keep looping + } + + if (inCopyAllMode) { + for (; currentIndex < currentDtcs.size(); ++currentIndex) { + newDtcs.add(new FreezeFrameIdentity(currentDtcs.get(currentIndex), currentIndex)); + } + } + + return newDtcs; + } + + public JsonWriter generate(JsonWriter jsonWriter, long timestamp) + throws IOException, InterruptedException { + List<String> currentDtcs = mConnection.getDiagnosticTroubleCodes(); + List<FreezeFrameIdentity> newDtcs = discoverNewDtcs(currentDtcs); + mPreviousDtcs = currentDtcs; + for (FreezeFrameIdentity freezeFrame : newDtcs) { + jsonWriter.beginObject(); + jsonWriter.name("type").value(FRAME_TYPE_FREEZE); + jsonWriter.name("timestamp").value(timestamp); + jsonWriter.name("stringValue").value(freezeFrame.dtc); + jsonWriter.name("intValues").beginArray(); + for (OutputSemanticHandler<Integer> handler : mIntegerCommands) { + FreezeFrameCommand<Integer> command = + Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id); + try { + Optional<Integer> result = command.run(mConnection); + if (result.isPresent()) { + jsonWriter.beginObject(); + jsonWriter.name("id").value(command.getPid()); + jsonWriter.name("value").value(result.get()); + jsonWriter.endObject(); + } + } catch (IOException | InterruptedException e) { + Log.w( + TAG, + String.format( + "unable to retrieve OBD2 pid %d due to exception: %s", + command.getPid(), e)); + // skip this entry + } + } + jsonWriter.endArray(); + jsonWriter.name("floatValues").beginArray(); + for (OutputSemanticHandler<Float> handler : mFloatCommands) { + FreezeFrameCommand<Float> command = + Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id); + try { + Optional<Float> result = command.run(mConnection); + if (result.isPresent()) { + jsonWriter.beginObject(); + jsonWriter.name("id").value(command.getPid()); + jsonWriter.name("value").value(result.get()); + jsonWriter.endObject(); + } + } catch (IOException | InterruptedException e) { + Log.w( + TAG, + String.format( + "unable to retrieve OBD2 pid %d due to exception: %s", + command.getPid(), e)); + // skip this entry + } + } + jsonWriter.endArray(); + jsonWriter.endObject(); + } + return jsonWriter; + } +} diff --git a/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java b/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java index 121b54aae9..26f408ffee 100644 --- a/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java +++ b/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java @@ -27,7 +27,7 @@ import java.util.Optional; import java.util.Set; public class Obd2LiveFrameGenerator { - public static final int FRAME_TYPE_LIVE = 1; + public static final String FRAME_TYPE_LIVE = "live"; public static final String TAG = Obd2LiveFrameGenerator.class.getSimpleName(); private final Obd2Connection mConnection; @@ -56,6 +56,16 @@ public class Obd2LiveFrameGenerator { mFloatCommands.add( Obd2Command.getLiveFrameCommand( Obd2Command.getFloatCommand(pid)))); + Log.i( + TAG, + String.format( + "connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n" + + "mIntegerCommands = %s\nmFloatCommands = %s\n", + connectionPids, + apiIntegerPids, + apiFloatPids, + mIntegerCommands, + mFloatCommands)); } public JsonWriter generate(JsonWriter jsonWriter) throws IOException { diff --git a/obd2-lib/src/com/android/car/obd2/commands/RPM.java b/obd2-lib/src/com/android/car/obd2/commands/RPM.java index b277abfc42..a062e9781c 100644 --- a/obd2-lib/src/com/android/car/obd2/commands/RPM.java +++ b/obd2-lib/src/com/android/car/obd2/commands/RPM.java @@ -30,7 +30,7 @@ public class RPM implements Obd2Command.OutputSemanticHandler<Integer> { public Optional<Integer> consume(IntegerArrayStream data) { return data.hasAtLeast( 2, - theData -> Optional.of(theData.consume() * 256 + theData.consume() / 4), + theData -> Optional.of((theData.consume() * 256 + theData.consume()) / 4), theData -> Optional.<Integer>empty()); } } diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml index 5d474cd050..175a8bb091 100644 --- a/service/AndroidManifest.xml +++ b/service/AndroidManifest.xml @@ -92,7 +92,7 @@ android:description="@string/car_permission_desc_diag_read" /> <permission android:name="android.car.permission.DIAGNOSTIC_CLEAR" - android:protectionLevel="dangerous" + android:protectionLevel="system|signature" android:label="@string/car_permission_label_diag_clear" android:description="@string/car_permission_desc_diag_clear" /> <permission @@ -139,6 +139,7 @@ <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> @@ -167,12 +168,6 @@ </intent-filter> </service> <service android:name=".PerUserCarService" android:exported="false" /> - <receiver android:name=".BootReceiver"> - <intent-filter android:priority="1000"> - <action android:name="android.intent.action.PRE_BOOT_COMPLETED"/> - <action android:name="android.intent.action.BOOT_COMPLETED"/> - </intent-filter> - </receiver> <activity android:name="com.android.car.pm.ActivityBlockingActivity" android:excludeFromRecents="true" android:exported="false"> diff --git a/service/res/values/config.xml b/service/res/values/config.xml index 088b4ed199..fd4a8414c9 100644 --- a/service/res/values/config.xml +++ b/service/res/values/config.xml @@ -76,6 +76,9 @@ <string name="defaultHomeActivity">com.android.car.overview/com.android.car.overview.StreamOverviewActivity</string> <!-- The com.android.car.VmsPublisherService will bind to this list of clients --> <string-array translatable="false" name="vmsPublisherClients"> - <item>"com.google.android.car.vms.publisher/.VmsPublisherClientSampleService"</item> + </string-array> + <!-- Permissions that the com.android.car.VmsPublisherService is allowed to grant to publishers --> + <string-array translatable="false" name="vmsSafePermissions"> + <item>"android.permission.ACCESS_FINE_LOCATION"</item> </string-array> </resources> diff --git a/service/src/com/android/car/BootReceiver.java b/service/src/com/android/car/BootReceiver.java deleted file mode 100644 index 10c6f2fd81..0000000000 --- a/service/src/com/android/car/BootReceiver.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import android.car.Car; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.UserHandle; -import android.util.Log; - - -/** - * When system boots up, start car service. - */ -public class BootReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Log.w(CarLog.TAG_SERVICE, "Starting..."); - Intent carServiceIntent = new Intent(); - carServiceIntent.setPackage(context.getPackageName()); - carServiceIntent.setAction(Car.CAR_SERVICE_INTERFACE_NAME); - context.startServiceAsUser(carServiceIntent, UserHandle.SYSTEM); - } -} diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java index 29012da8d0..d1618835e5 100644 --- a/service/src/com/android/car/CarAudioService.java +++ b/service/src/com/android/car/CarAudioService.java @@ -83,6 +83,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, */ private static final long NO_FOCUS_PLAY_WAIT_TIME_MS = 100; + private static final String RADIO_ROUTING_SOURCE_PREFIX = "RADIO_"; + private final AudioHalService mAudioHal; private final Context mContext; private final HandlerThread mFocusHandlerThread; @@ -102,10 +104,10 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, @GuardedBy("mLock") private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>(); @GuardedBy("mLock") - private AudioFocusInfo mTopFocusInfo = null; + private AudioFocusInfo mPrimaryFocusInfo = null; /** previous top which may be in ducking state */ @GuardedBy("mLock") - private AudioFocusInfo mSecondFocusInfo = null; + private AudioFocusInfo mSecondaryFocusInfo = null; private AudioRoutingPolicy mAudioRoutingPolicy; private final AudioManager mAudioManager; @@ -118,12 +120,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, new MediaMuteAudioFocusListener(); @GuardedBy("mLock") - private int mBottomFocusState; - @GuardedBy("mLock") private boolean mRadioOrExtSourceActive = false; @GuardedBy("mLock") - private boolean mCallActive = false; - @GuardedBy("mLock") private int mCurrentAudioContexts = 0; @GuardedBy("mLock") private int mCurrentPrimaryAudioContext = 0; @@ -209,11 +207,6 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom, AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); synchronized (mLock) { - if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN; - } else { - mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; - } mCurrentFocusState = currentState; mCurrentAudioContexts = 0; } @@ -237,7 +230,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mAudioHal.getExternalAudioRoutingTypes(); if (externalRoutingTypes != null) { for (String routingType : externalRoutingTypes.keySet()) { - if (routingType.startsWith("RADIO_")) { + if (routingType.startsWith(RADIO_ROUTING_SOURCE_PREFIX)) { externalRadioRoutingTypes.add(routingType); } else { externalNonRadioRoutingTypes.add(routingType); @@ -270,7 +263,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mAudioPolicy = audioPolicy; } mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream( - CarAudioManager.CAR_AUDIO_USAGE_RADIO);; + CarAudioManager.CAR_AUDIO_USAGE_RADIO); mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream( CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND); mSystemSoundPhysicalStreamActive = false; @@ -418,7 +411,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mAudioParamKeys = null; mCurrentFocusState = FocusState.STATE_LOSS; mLastFocusRequestToCar = null; - mTopFocusInfo = null; + mPrimaryFocusInfo = null; mPendingFocusChanges.clear(); mRadioOrExtSourceActive = false; if (mCarAudioContextChangeHandler != null) { @@ -458,7 +451,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, " mLastFocusRequestToCar:" + mLastFocusRequestToCar); writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts)); - writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" + + writer.println(" mRadioOrExtSourceActive:" + mRadioOrExtSourceActive); writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext + " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream); @@ -731,7 +724,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived); } systemSoundActive = mSystemSoundPhysicalStreamActive; - topInfo = mTopFocusInfo; + topInfo = mPrimaryFocusInfo; if (!mFocusReceived.equals(mCurrentFocusState.focusState)) { newFocusState = mFocusReceived.focusState; } @@ -766,7 +759,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { // clear second one as there can be no such item in these LOSS. - mSecondFocusInfo = null; + mSecondaryFocusInfo = null; } } switch (newFocusState) { @@ -878,50 +871,50 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, doHandleAndroidFocusChange(true /*triggeredByStreamChange*/); } - private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) { + private boolean checkFocusUsage(AudioFocusInfo info, int expectedUsage) { if (info == null) { return false; } - AudioAttributes attrib = info.getAttributes(); - if (info.getPackageName().equals(mContext.getOpPackageName()) && - CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == - CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) { - return true; - } - return false; - } - private boolean isFocusFromCarProxy(AudioFocusInfo info) { - if (info == null) { + AudioAttributes attributes = info.getAttributes(); + if (attributes == null) { return false; } - AudioAttributes attrib = info.getAttributes(); - if (info.getPackageName().equals(mContext.getOpPackageName()) && - attrib != null && - CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == - CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) { - return true; + + int actualUsage = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attributes); + if (actualUsage == expectedUsage) { + return info.getPackageName().equals(mContext.getOpPackageName()); } return false; } + private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) { + return checkFocusUsage(info, CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM); + } + + private boolean isFocusFromCarProxy(AudioFocusInfo info) { + return checkFocusUsage(info, CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY); + } + private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) { if (info == null) { return false; } - AudioAttributes attrib = info.getAttributes(); - if (attrib == null) { + + AudioAttributes attributes = info.getAttributes(); + if (attributes == null) { return false; } - // if radio is not external, no special handling of radio is necessary. - if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == - CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) { - return true; - } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == - CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) { - return true; + + int focusUsage = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attributes); + switch (focusUsage) { + case CarAudioManager.CAR_AUDIO_USAGE_RADIO: + return mIsRadioExternal; + case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE: + return true; + default: + return false; } - return false; } /** @@ -929,7 +922,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, * @return true if focus change was requested to car. */ private boolean reevaluateCarAudioFocusAndSendFocusLocked() { - if (mTopFocusInfo == null) { + if (mPrimaryFocusInfo == null) { if (mSystemSoundPhysicalStreamActive) { return requestFocusForSystemSoundOnlyCaseLocked(); } else { @@ -937,14 +930,14 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, return false; } } - if (mTopFocusInfo.getLossReceived() != 0) { + if (mPrimaryFocusInfo.getLossReceived() != 0) { // top one got loss. This should not happen. - Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo)); + Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mPrimaryFocusInfo)); return false; } - if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) { + if (isFocusFromCarServiceBottom(mPrimaryFocusInfo) || isFocusFromCarProxy(mPrimaryFocusInfo)) { // allow system sound only when car is not holding focus. - if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mTopFocusInfo)) { + if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mPrimaryFocusInfo)) { return requestFocusForSystemSoundOnlyCaseLocked(); } switch (mCurrentFocusState.focusState) { @@ -954,7 +947,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mFocusHandler.handleFocusReleaseRequest(); break; case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: - doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo); + doHandleFocusLossFromCar(mCurrentFocusState, mPrimaryFocusInfo); break; case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: doHandleFocusLossTransientFromCar(mCurrentFocusState); @@ -970,7 +963,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, return false; } mFocusHandler.cancelFocusReleaseRequest(); - AudioAttributes attrib = mTopFocusInfo.getAttributes(); + AudioAttributes attrib = mPrimaryFocusInfo.getAttributes(); int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib); int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) @@ -985,20 +978,14 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) { muteMedia = true; } - if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) { - mCallActive = true; - } else { - mCallActive = false; - } // other apps having focus int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; int streamsToRequest = 0x1 << physicalStreamTypeForTop; - boolean primaryIsExternal = false; - if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) { + boolean primaryIsExternal = isFocusFromExternalRadioOrExternalSource(mPrimaryFocusInfo); + if (primaryIsExternal) { streamsToRequest = 0; mRadioOrExtSourceActive = true; - primaryIsExternal = true; if (fixExtSourceAndContext( mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) { primaryExtSource = mExtSourceInfoScratch.source; @@ -1018,7 +1005,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, boolean secondaryIsExternal = false; int secondaryContext = 0; String secondaryExtSource = null; - switch (mTopFocusInfo.getGainRequest()) { + switch (mPrimaryFocusInfo.getGainRequest()) { case AudioManager.AUDIOFOCUS_GAIN: focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; break; @@ -1029,10 +1016,10 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; - if (mSecondFocusInfo == null) { + if (mSecondaryFocusInfo == null) { break; } - AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes(); + AudioAttributes secondAttrib = mSecondaryFocusInfo.getAttributes(); if (secondAttrib == null) { break; } @@ -1043,8 +1030,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, muteMedia = true; break; } - if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) { - secondaryIsExternal = true; + secondaryIsExternal = isFocusFromExternalRadioOrExternalSource(mSecondaryFocusInfo); + if (secondaryIsExternal) { secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib); secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( logicalStreamTypeForSecond, secondaryExtSource); @@ -1089,7 +1076,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, streamsToRequest = 0; break; } - int audioContexts = 0; + int audioContexts = primaryContext | secondaryContext; if (muteMedia) { boolean addMute = true; if (primaryIsExternal) { @@ -1106,7 +1093,6 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } else { mRadioOrExtSourceActive = false; } - audioContexts = primaryContext | secondaryContext; if (addMute) { audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG | AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG | @@ -1116,7 +1102,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, streamsToRequest &= ~(0x1 << mRadioPhysicalStream); } } else if (mRadioOrExtSourceActive) { - boolean addExtFocusFlag = true; + boolean shouldDropSecondaryContext = false; if (primaryIsExternal) { int primaryExtPhysicalStreamFlag = getPhysicalStreamFlagForExtSourceLocked(primaryExtSource); @@ -1125,23 +1111,23 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource); if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) { // overlap, drop secondary - audioContexts &= ~secondaryContext; - secondaryContext = 0; + shouldDropSecondaryContext = true; secondaryExtSource = null; } streamsToRequest = 0; } else { // primary only if (streamsToRequest == primaryExtPhysicalStreamFlag) { // cannot keep secondary - secondaryContext = 0; + shouldDropSecondaryContext = true; } streamsToRequest &= ~primaryExtPhysicalStreamFlag; } } - if (addExtFocusFlag) { - extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; + extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; + if (shouldDropSecondaryContext) { + audioContexts &= ~secondaryContext; + secondaryContext = 0; } - audioContexts = primaryContext | secondaryContext; } else if (streamsToRequest == 0) { if (mSystemSoundPhysicalStreamActive) { return requestFocusForSystemSoundOnlyCaseLocked(); @@ -1150,8 +1136,6 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mFocusHandler.handleFocusReleaseRequest(); return false; } - } else { - audioContexts = primaryContext | secondaryContext; } if (mSystemSoundPhysicalStreamActive) { boolean addSystemStream = true; @@ -1198,7 +1182,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, return true; } if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG && - !extSourceInfo.source.startsWith("RADIO_")) { + !extSourceInfo.source.startsWith(RADIO_ROUTING_SOURCE_PREFIX)) { Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source); extSourceInfo.source = mDefaultRadioRoutingType; return true; @@ -1207,13 +1191,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } private int getPhysicalStreamFlagForExtSourceLocked(String extSource) { - AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get( - extSource); - if (info != null) { - return 0x1 << info.physicalStreamNumber; - } else { - return 0x1 << mRadioPhysicalStream; - } + return 0x1 << getPhysicalStreamNumberForExtSourceLocked(extSource); } private int getPhysicalStreamNumberForExtSourceLocked(String extSource) { @@ -1293,6 +1271,29 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } } + private void doSendFocusRequestToCarLocked(int focusToRequest, + int streamsToRequest, int extFocus, int audioContexts) { + if (DBG) { + Log.d(TAG_FOCUS, String.format("audio focus request. focusToRequest = %d, " + + "streamsToRequest = 0x%x, extFocus = 0x%x, audioContexts = 0x%x", + focusToRequest, streamsToRequest, extFocus, audioContexts)); + } + try { + mAudioHal.requestAudioFocusChange( + focusToRequest, + streamsToRequest, + extFocus, + audioContexts); + } catch (IllegalArgumentException e) { + // can happen when mocking ends. ignore. timeout will handle it properly. + } + try { + mLock.wait(mFocusResponseWaitTimeoutMs); + } catch (InterruptedException e) { + // ignore + } + } + private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) { if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus, @@ -1313,17 +1314,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" + Integer.toHexString(audioContexts)); } - try { - mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus, - audioContexts); - } catch (IllegalArgumentException e) { - // can happen when mocking ends. ignore. timeout will handle it properly. - } - try { - mLock.wait(mFocusResponseWaitTimeoutMs); - } catch (InterruptedException e) { - //ignore - } + doSendFocusRequestToCarLocked(focusToRequest, streamsToRequest, extFocus, + audioContexts); return true; } return false; @@ -1384,15 +1376,15 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } else { newTopInfo = mPendingFocusChanges.getFirst(); mPendingFocusChanges.clear(); - if (mTopFocusInfo != null && - newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) && - newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() && + if (mPrimaryFocusInfo != null && + newTopInfo.getClientId().equals(mPrimaryFocusInfo.getClientId()) && + newTopInfo.getGainRequest() == mPrimaryFocusInfo.getGainRequest() && isAudioAttributesSame( - newTopInfo.getAttributes(), mTopFocusInfo.getAttributes()) && + newTopInfo.getAttributes(), mPrimaryFocusInfo.getAttributes()) && !triggeredByStreamChange) { if (DBG) { Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" + - dumpAudioFocusInfo(mTopFocusInfo)); + dumpAudioFocusInfo(mPrimaryFocusInfo)); } // already in top somehow, no need to make any change return; @@ -1401,14 +1393,14 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, if (newTopInfo != null) { if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { - mSecondFocusInfo = mTopFocusInfo; + mSecondaryFocusInfo = mPrimaryFocusInfo; } else { - mSecondFocusInfo = null; + mSecondaryFocusInfo = null; } if (DBG) { Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo)); } - mTopFocusInfo = newTopInfo; + mPrimaryFocusInfo = newTopInfo; } focusRequested = handleCarFocusRequestAndResponseLocked(); } @@ -1423,7 +1415,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, if (DBG) { if (!focusRequested) { Log.i(TAG_FOCUS, "focus not requested for top focus:" + - dumpAudioFocusInfo(mTopFocusInfo) + " currentState:" + mCurrentFocusState); + dumpAudioFocusInfo(mPrimaryFocusInfo) + " currentState:" + mCurrentFocusState); } } if (focusRequested) { @@ -1459,20 +1451,11 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; sent = true; - try { - if (mExternalRoutingHintSupported) { - mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease); - } - mAudioHal.requestAudioFocusChange( - AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); - } catch (IllegalArgumentException e) { - // can happen when mocking ends. ignore. timeout will handle it properly. - } - try { - mLock.wait(mFocusResponseWaitTimeoutMs); - } catch (InterruptedException e) { - //ignore + if (mExternalRoutingHintSupported) { + mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease); } + doSendFocusRequestToCarLocked(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, + 0, 0, 0); mCurrentPrimaryAudioContext = 0; mCurrentPrimaryPhysicalStream = 0; if (mCarAudioContextChangeHandler != null) { @@ -1578,9 +1561,6 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { @Override public void onAudioFocusChange(int focusChange) { - synchronized (mLock) { - mBottomFocusState = focusChange; - } } } diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java index c14a3851ee..88e7e349f1 100644 --- a/service/src/com/android/car/CarDiagnosticService.java +++ b/service/src/com/android/car/CarDiagnosticService.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; +import com.android.car.hal.DiagnosticHalService.DiagnosticCapabilities; import com.android.car.internal.CarPermission; import com.android.car.Listeners.ClientWithRate; import com.android.car.hal.DiagnosticHalService; @@ -161,10 +162,10 @@ public class CarDiagnosticService extends ICarDiagnostic.Stub if (event.isLiveFrame()) { // record recent-most live frame information setRecentmostLiveFrame(event); - listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE); + listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_LIVE); } else if (event.isFreezeFrame()) { setRecentmostFreezeFrame(event); - listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE); + listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FREEZE); } else { Log.w( CarLog.TAG_DIAGNOSTIC, @@ -198,24 +199,6 @@ public class CarDiagnosticService extends ICarDiagnostic.Stub processDiagnosticData(events); } - private List<CarDiagnosticEvent> getCachedEventsLocked(int frameType) { - ArrayList<CarDiagnosticEvent> events = new ArrayList<>(); - switch (frameType) { - case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE: - mLiveFrameDiagnosticRecord.lock(); - events.add(mLiveFrameDiagnosticRecord.getLastEvent()); - mLiveFrameDiagnosticRecord.unlock(); - break; - case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE: - mFreezeFrameDiagnosticRecords.lock(); - mFreezeFrameDiagnosticRecords.getEvents().forEach(events::add); - mFreezeFrameDiagnosticRecords.unlock(); - break; - default: break; - } - return events; - } - @Override public boolean registerOrUpdateDiagnosticListener(int frameType, int rate, ICarDiagnosticEventListener listener) { @@ -242,8 +225,6 @@ public class CarDiagnosticService extends ICarDiagnostic.Stub } mClients.add(diagnosticClient); } - // If we have a cached event for this diagnostic, send the event. - diagnosticClient.dispatchDiagnosticUpdate(getCachedEventsLocked(frameType)); diagnosticListeners = mDiagnosticListeners.get(frameType); if (diagnosticListeners == null) { diagnosticListeners = new Listeners<>(rate); @@ -306,21 +287,21 @@ public class CarDiagnosticService extends ICarDiagnostic.Stub return false; } switch (frameType) { - case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE: + case CarDiagnosticManager.FRAME_TYPE_LIVE: if (mLiveFrameDiagnosticRecord.isEnabled()) { return true; } - if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_LIVE, rate)) { mLiveFrameDiagnosticRecord.enable(); return true; } break; - case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE: + case CarDiagnosticManager.FRAME_TYPE_FREEZE: if (mFreezeFrameDiagnosticRecords.isEnabled()) { return true; } - if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE, + if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FREEZE, rate)) { mFreezeFrameDiagnosticRecords.enable(); return true; @@ -394,13 +375,13 @@ public class CarDiagnosticService extends ICarDiagnostic.Stub return; } switch (frameType) { - case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE: + case CarDiagnosticManager.FRAME_TYPE_LIVE: if (mLiveFrameDiagnosticRecord.disableIfNeeded()) - diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE); + diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_LIVE); break; - case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE: + case CarDiagnosticManager.FRAME_TYPE_FREEZE: if (mFreezeFrameDiagnosticRecords.disableIfNeeded()) - diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE); + diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FREEZE); break; } } @@ -409,6 +390,29 @@ public class CarDiagnosticService extends ICarDiagnostic.Stub return mDiagnosticHal; } + // Expose DiagnosticCapabilities + public boolean isLiveFrameSupported() { + return getDiagnosticHal().getDiagnosticCapabilities().isLiveFrameSupported(); + } + + public boolean isFreezeFrameSupported() { + return getDiagnosticHal().getDiagnosticCapabilities().isFreezeFrameSupported(); + } + + public boolean isFreezeFrameTimestampSupported() { + DiagnosticCapabilities diagnosticCapabilities = + getDiagnosticHal().getDiagnosticCapabilities(); + return diagnosticCapabilities.isFreezeFrameInfoSupported() && + diagnosticCapabilities.isFreezeFrameSupported(); + } + + public boolean isFreezeFrameClearSupported() { + DiagnosticCapabilities diagnosticCapabilities = + getDiagnosticHal().getDiagnosticCapabilities(); + return diagnosticCapabilities.isFreezeFrameClearSupported() && + diagnosticCapabilities.isFreezeFrameSupported(); + } + // ICarDiagnostic implementations @Override diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java index 2d65c0a073..298290b425 100644 --- a/service/src/com/android/car/CarService.java +++ b/service/src/com/android/car/CarService.java @@ -16,7 +16,6 @@ package com.android.car; import static android.os.SystemClock.elapsedRealtime; -import static com.android.car.internal.FeatureConfiguration.ENABLE_VEHICLE_HAL_V2_1; import android.annotation.Nullable; import android.app.Service; @@ -178,7 +177,7 @@ public class CarService extends Service { try { boolean anyVersion = interfaceName == null || interfaceName.isEmpty(); IVehicle vehicle = null; - if (ENABLE_VEHICLE_HAL_V2_1 && (anyVersion || IVHAL_21.equals(interfaceName))) { + if (anyVersion || IVHAL_21.equals(interfaceName)) { vehicle = android.hardware.automotive.vehicle.V2_1.IVehicle .getService(); } diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java index a53771f740..f23f10c440 100644 --- a/service/src/com/android/car/ICarImpl.java +++ b/service/src/com/android/car/ICarImpl.java @@ -26,7 +26,9 @@ import android.content.pm.PackageManager; import android.hardware.automotive.vehicle.V2_0.IVehicle; import android.hardware.automotive.vehicle.V2_0.VehicleAreaDoor; import android.hardware.automotive.vehicle.V2_0.VehicleProperty; +import android.os.Binder; import android.os.IBinder; +import android.os.Process; import android.util.Log; import com.android.car.cluster.InstrumentClusterService; @@ -35,6 +37,7 @@ import com.android.car.internal.FeatureConfiguration; import com.android.car.internal.FeatureUtil; import com.android.car.pm.CarPackageManagerService; import com.android.internal.annotations.GuardedBy; +import com.android.internal.car.ICarServiceHelper; import java.io.PrintWriter; import java.util.ArrayList; @@ -82,6 +85,9 @@ public class ICarImpl extends ICar.Stub { @GuardedBy("this") private CarTestService mCarTestService; + @GuardedBy("this") + private ICarServiceHelper mICarServiceHelper; + public ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface, CanBusErrorNotifier errorNotifier) { mContext = serviceContext; @@ -116,10 +122,7 @@ public class ICarImpl extends ICar.Stub { mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal()); mVmsPublisherService = new VmsPublisherService(serviceContext, mHal.getVmsHal()); } - if (FeatureConfiguration.ENABLE_DIAGNOSTIC) { - mCarDiagnosticService = new CarDiagnosticService(serviceContext, - mHal.getDiagnosticHal()); - } + mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal()); // Be careful with order. Service depending on other service should be inited later. List<CarServiceBase> allServices = new ArrayList<>(Arrays.asList( @@ -141,15 +144,13 @@ public class ICarImpl extends ICar.Stub { mSystemStateControllerService, mCarVendorExtensionService, mCarBluetoothService, + mCarDiagnosticService, mPerUserCarServiceHelper )); if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) { allServices.add(mVmsSubscriberService); allServices.add(mVmsPublisherService); } - if (FeatureConfiguration.ENABLE_DIAGNOSTIC) { - allServices.add(mCarDiagnosticService); - } mAllServices = allServices.toArray(new CarServiceBase[0]); } @@ -176,6 +177,17 @@ public class ICarImpl extends ICar.Stub { } @Override + public void setCarServiceHelper(IBinder helper) { + int uid = Binder.getCallingUid(); + if (uid != Process.SYSTEM_UID) { + throw new SecurityException("Only allowed from system"); + } + synchronized (this) { + mICarServiceHelper = ICarServiceHelper.Stub.asInterface(helper); + } + } + + @Override public IBinder getCarService(String serviceName) { switch (serviceName) { case Car.AUDIO_SERVICE: @@ -192,11 +204,8 @@ public class ICarImpl extends ICar.Stub { assertCabinPermission(mContext); return mCarCabinService; case Car.DIAGNOSTIC_SERVICE: - FeatureUtil.assertFeature(FeatureConfiguration.ENABLE_DIAGNOSTIC); - if (FeatureConfiguration.ENABLE_DIAGNOSTIC) { - assertAnyDiagnosticPermission(mContext); - return mCarDiagnosticService; - } + assertAnyDiagnosticPermission(mContext); + return mCarDiagnosticService; case Car.HVAC_SERVICE: assertHvacPermission(mContext); return mCarHvacService; @@ -483,4 +492,4 @@ public class ICarImpl extends ICar.Stub { } } -}
\ No newline at end of file +} diff --git a/service/src/com/android/car/VmsLayersAvailability.java b/service/src/com/android/car/VmsLayersAvailability.java index 5f5ac3085e..d6e89f2bc0 100644 --- a/service/src/com/android/car/VmsLayersAvailability.java +++ b/service/src/com/android/car/VmsLayersAvailability.java @@ -83,7 +83,7 @@ public class VmsLayersAvailability { /** * Returns a collection of all the layers which may be published. */ - public Collection<VmsLayer> getAvailableLayers() { + public Set<VmsLayer> getAvailableLayers() { synchronized (mLock) { return mAvailableLayers; } @@ -93,7 +93,7 @@ public class VmsLayersAvailability { * Returns a collection of all the layers which publishers could have published if the * dependencies were satisfied. */ - public Collection<VmsLayer> getUnavailableLayers() { + public Set<VmsLayer> getUnavailableLayers() { synchronized (mLock) { return mUnavailableLayers; } diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java index 8f7fba353b..37d265a548 100644 --- a/service/src/com/android/car/VmsPublisherService.java +++ b/service/src/com/android/car/VmsPublisherService.java @@ -27,9 +27,12 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import com.android.car.hal.VmsHalService; @@ -37,7 +40,9 @@ import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -56,7 +61,7 @@ public class VmsPublisherService extends IVmsPublisherService.Stub private final Context mContext; private final VmsHalService mHal; private final VmsPublisherManager mPublisherManager; - private final Map<IBinder, VmsLayersOffering> mRawOffering = new HashMap<>(); + private Set<String> mSafePermissions; public VmsPublisherService(Context context, VmsHalService hal) { mContext = context; @@ -68,6 +73,9 @@ public class VmsPublisherService extends IVmsPublisherService.Stub @Override public void init() { mHal.addPublisherListener(this); + // Load permissions that can be granted to publishers. + mSafePermissions = new HashSet<>( + Arrays.asList(mContext.getResources().getStringArray(R.array.vmsSafePermissions))); // Launch publishers. String[] publisherNames = mContext.getResources().getStringArray( R.array.vmsPublisherClients); @@ -96,12 +104,7 @@ public class VmsPublisherService extends IVmsPublisherService.Stub @Override public void setLayersOffering(IBinder token, VmsLayersOffering offering) { - // Store the raw dependencies - mRawOffering.put(token, offering); - - //TODO(asafro): Calculate the new available layers - - //TODO(asafro): Notify the subscribers that there is a change in availability + mHal.setPublisherLayersOffering(token, offering); } // Implements IVmsPublisherService interface. @@ -141,6 +144,12 @@ public class VmsPublisherService extends IVmsPublisherService.Stub return mHal.getSubscriptionState(); } + @Override + public int getPublisherStaticId(byte[] publisherInfo) { + ICarImpl.assertVmsPublisherPermission(mContext); + return mHal.getPublisherStaticId(publisherInfo); + } + // Implements VmsHalListener interface /** * This method is only invoked by VmsHalService.notifyPublishers which is synchronized. @@ -193,11 +202,12 @@ public class VmsPublisherService extends IVmsPublisherService.Stub // Already registered, nothing to do. return; } + grantPermissions(name); Intent intent = new Intent(); intent.setComponent(name); PublisherConnection connection = new PublisherConnection(); - if (publisherService.mContext.bindService(intent, connection, - Context.BIND_AUTO_CREATE)) { + if (publisherService.mContext.bindServiceAsUser(intent, connection, + Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) { mPublisherConnectionMap.put(publisherName, connection); } else { Log.e(TAG, "unable to bind to: " + publisherName); @@ -250,6 +260,39 @@ public class VmsPublisherService extends IVmsPublisherService.Stub mPublisherMap.clear(); } + private void grantPermissions(ComponentName component) { + VmsPublisherService publisherService = mPublisherService.get(); + if (publisherService == null) return; + final PackageManager packageManager = publisherService.mContext.getPackageManager(); + final String packageName = component.getPackageName(); + PackageInfo packageInfo; + try { + packageInfo = packageManager.getPackageInfo(packageName, + PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Error getting package info for " + packageName, e); + return; + } + if (packageInfo.requestedPermissions == null) return; + for (String permission : packageInfo.requestedPermissions) { + if (!publisherService.mSafePermissions.contains(permission)) { + continue; + } + if (packageManager.checkPermission(permission, packageName) + == PackageManager.PERMISSION_GRANTED) { + continue; + } + try { + packageManager.grantRuntimePermission(packageName, permission, + UserHandle.SYSTEM); + Log.d(TAG, "Permission " + permission + " granted to " + packageName); + } catch (SecurityException | IllegalArgumentException e) { + Log.e(TAG, "Error while trying to grant " + permission + " to " + packageName, + e); + } + } + } + class PublisherConnection implements ServiceConnection { private final IBinder mToken = new Binder(); diff --git a/service/src/com/android/car/VmsPublishersInfo.java b/service/src/com/android/car/VmsPublishersInfo.java new file mode 100644 index 0000000000..04ee82f78d --- /dev/null +++ b/service/src/com/android/car/VmsPublishersInfo.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 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; + + +import android.car.annotation.FutureFeature; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import com.android.internal.annotations.GuardedBy; +import android.util.Log; + +@FutureFeature +public class VmsPublishersInfo { + private static final String TAG = "VmsPublishersInfo"; + private static final boolean DBG = true; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final Map<InfoWrapper, Integer> mPublishersIds = new HashMap(); + @GuardedBy("mLock") + private final Map<Integer, byte[]> mPublishersInfo = new HashMap(); + + private static class InfoWrapper { + private final byte[] mInfo; + + public InfoWrapper(byte[] info) { + mInfo = info; + } + + public byte[] getInfo() { + return mInfo; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof InfoWrapper)) { + return false; + } + InfoWrapper p = (InfoWrapper) o; + return Arrays.equals(this.mInfo, p.mInfo); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mInfo); + } + } + + /** + * Returns the ID associated with the publisher info. When called for the first time for a + * publisher info will store the info and assign an ID + */ + public int getIdForInfo(byte[] publisherInfo) { + Integer publisherId; + InfoWrapper wrappedPublisherInfo = new InfoWrapper(publisherInfo); + synchronized (mLock) { + maybeAddPublisherInfoLocked(wrappedPublisherInfo); + publisherId = mPublishersIds.get(wrappedPublisherInfo); + } + if (DBG) { + Log.i(TAG, "Publisher ID is: " + publisherId); + } + return publisherId; + } + + public byte[] getPublisherInfo(int publisherId) { + synchronized (mLock) { + return mPublishersInfo.get(publisherId).clone(); + } + } + + private void maybeAddPublisherInfoLocked(InfoWrapper wrappedPublisherInfo) { + if (!mPublishersIds.containsKey(wrappedPublisherInfo)) { + // Assign ID to the info + Integer publisherId = mPublishersIds.size(); + + mPublishersIds.put(wrappedPublisherInfo, publisherId); + mPublishersInfo.put(publisherId, wrappedPublisherInfo.getInfo()); + } + } +} + diff --git a/service/src/com/android/car/VmsRouting.java b/service/src/com/android/car/VmsRouting.java index fc2cbaca7b..2829cc0623 100644 --- a/service/src/com/android/car/VmsRouting.java +++ b/service/src/com/android/car/VmsRouting.java @@ -20,16 +20,14 @@ import android.car.annotation.FutureFeature; import android.car.vms.IVmsSubscriberClient; import android.car.vms.VmsLayer; import android.car.vms.VmsSubscriptionState; - +import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; -import java.util.HashSet; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import com.android.internal.annotations.GuardedBy; - /** * Manages all the VMS subscriptions: * + Subscriptions to data messages of individual layer + version. @@ -62,6 +60,7 @@ public class VmsRouting { * @param layer the layer subscribing to. */ public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer) { + //TODO(b/36902947): revise if need to sync, and return value. synchronized (mLock) { ++mSequenceNumber; // Get or create the list of listeners for layer and version. @@ -142,8 +141,8 @@ public class VmsRouting { } /** - * Returns all the listeners for a layer and version. This include the subscribers which - * explicitly subscribed to this layer and version and the promiscuous subscribers. + * Returns a list with all the listeners for a layer and version. This include the subscribers + * which explicitly subscribed to this layer and version and the promiscuous subscribers. * * @param layer to get listeners to. * @return a list of the listeners. @@ -162,6 +161,21 @@ public class VmsRouting { } /** + * Returns a list with all the listeners. + */ + public Set<IVmsSubscriberClient> getAllListeners() { + Set<IVmsSubscriberClient> listeners = new HashSet<>(); + synchronized (mLock) { + for (VmsLayer layer : mLayerSubscriptions.keySet()) { + listeners.addAll(mLayerSubscriptions.get(layer)); + } + // Add the promiscuous subscribers. + listeners.addAll(mPromiscuousSubscribers); + } + return listeners; + } + + /** * Checks if a listener is subscribed to any messages. * @param listener that may have subscription. * @return true if the listener uis subscribed to messages. diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java index 97ed27ff8c..fc0a88557e 100644 --- a/service/src/com/android/car/VmsSubscriberService.java +++ b/service/src/com/android/car/VmsSubscriberService.java @@ -262,6 +262,13 @@ public class VmsSubscriberService extends IVmsSubscriberService.Stub } @Override + public byte[] getPublisherInfo(int publisherId) { + synchronized (mSubscriberServiceLock) { + return mHal.getPublisherInfo(publisherId); + } + } + + @Override public List<VmsLayer> getAvailableLayers() { //TODO(asafro): return the list of available layers once logic is implemented. return Collections.emptyList(); @@ -269,18 +276,13 @@ public class VmsSubscriberService extends IVmsSubscriberService.Stub // Implements VmsHalSubscriberListener interface @Override - public void onChange(VmsLayer layer, byte[] payload) { + public void onDataMessage(VmsLayer layer, byte[] payload) { if(DBG) { Log.d(TAG, "Publishing a message for layer: " + layer); } Set<IVmsSubscriberClient> listeners = mHal.getListeners(layer); - // If there are no listeners we're done. - if ((listeners == null)) { - return; - } - for (IVmsSubscriberClient subscriber : listeners) { try { subscriber.onVmsMessageReceived(layer, payload); @@ -290,6 +292,24 @@ public class VmsSubscriberService extends IVmsSubscriberService.Stub Log.e(TAG, "onVmsMessageReceived calling failed: ", e); } } + } + + @Override + public void onLayersAvaiabilityChange(List<VmsLayer> availableLayers) { + if(DBG) { + Log.d(TAG, "Publishing layers availability change: " + availableLayers); + } + + Set<IVmsSubscriberClient> listeners = mHal.getAllListeners(); + for (IVmsSubscriberClient subscriber : listeners) { + try { + subscriber.onLayersAvailabilityChange(availableLayers); + } catch (RemoteException e) { + // If we could not send a record, its likely the connection snapped. Let the binder + // death handle the situation. + Log.e(TAG, "onLayersAvailabilityChange calling failed: ", e); + } + } } } diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java index 84e36785a8..98a3b3d404 100644 --- a/service/src/com/android/car/hal/DiagnosticHalService.java +++ b/service/src/com/android/car/hal/DiagnosticHalService.java @@ -95,13 +95,13 @@ public class DiagnosticHalService extends SensorHalServiceBase { mVehiclePropertyToConfig.put(propConfig.prop, propConfig); Log.i(CarLog.TAG_DIAGNOSTIC, String.format("configArray for OBD2_LIVE_FRAME is %s", propConfig.configArray)); - return CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE; + return CarDiagnosticManager.FRAME_TYPE_LIVE; case VehicleProperty.OBD2_FREEZE_FRAME: mDiagnosticCapabilities.setSupported(propConfig.prop); mVehiclePropertyToConfig.put(propConfig.prop, propConfig); Log.i(CarLog.TAG_DIAGNOSTIC, String.format("configArray for OBD2_FREEZE_FRAME is %s", propConfig.configArray)); - return CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE; + return CarDiagnosticManager.FRAME_TYPE_FREEZE; case VehicleProperty.OBD2_FREEZE_FRAME_INFO: mDiagnosticCapabilities.setSupported(propConfig.prop); return propConfig.prop; @@ -310,7 +310,7 @@ public class DiagnosticHalService extends SensorHalServiceBase { VehiclePropValue value = mHal.get(builder.build()); return createCarDiagnosticEvent(value); } catch (PropertyTimeoutException e) { - Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_DTC_INFO"); + Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_FREEZE_FRAME"); return null; } catch (IllegalArgumentException e) { Log.e(CarLog.TAG_DIAGNOSTIC, diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java index 5bf4e64cfd..c60d16db87 100644 --- a/service/src/com/android/car/hal/VehicleHal.java +++ b/service/src/com/android/car/hal/VehicleHal.java @@ -111,9 +111,7 @@ public class VehicleHal extends IVehicleCallback.Stub { if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) { mVmsHal = new VmsHalService(this); } - if(FeatureConfiguration.ENABLE_DIAGNOSTIC) { - mDiagnosticHal = new DiagnosticHalService(this); - } + mDiagnosticHal = new DiagnosticHalService(this); mAllServices.addAll(Arrays.asList(mPowerHal, mSensorHal, mInfoHal, @@ -122,13 +120,11 @@ public class VehicleHal extends IVehicleCallback.Stub { mRadioHal, mHvacHal, mInputHal, - mVendorExtensionHal)); + mVendorExtensionHal, + mDiagnosticHal)); if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) { mAllServices.add(mVmsHal); } - if(FeatureConfiguration.ENABLE_DIAGNOSTIC) { - mAllServices.add(mDiagnosticHal); - } mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/); } @@ -148,14 +144,11 @@ public class VehicleHal extends IVehicleCallback.Stub { mHvacHal = hvacHal; mInputHal = null; mVendorExtensionHal = null; + mDiagnosticHal = null; if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) { - // TODO(antoniocortes): do we need a test version of VmsHalService? mVmsHal = null; } - if(FeatureConfiguration.ENABLE_DIAGNOSTIC) { - mDiagnosticHal = null; - } mHalClient = halClient; } @@ -177,7 +170,6 @@ public class VehicleHal extends IVehicleCallback.Stub { mHvacHal = hvacHal; mInputHal = null; mVendorExtensionHal = null; - // TODO(antoniocortes): do we need a test version of VmsHalService? mVmsHal = null; mHalClient = halClient; mDiagnosticHal = diagnosticHal; diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java index c23f36a7e7..8ab5427a39 100644 --- a/service/src/com/android/car/hal/VmsHalService.java +++ b/service/src/com/android/car/hal/VmsHalService.java @@ -22,24 +22,33 @@ import android.car.VehicleAreaType; import android.car.annotation.FutureFeature; import android.car.vms.IVmsSubscriberClient; import android.car.vms.VmsLayer; +import android.car.vms.VmsLayerDependency; +import android.car.vms.VmsLayersOffering; import android.car.vms.VmsSubscriptionState; import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; import android.hardware.automotive.vehicle.V2_1.VehicleProperty; -import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex; +import android.hardware.automotive.vehicle.V2_1.VmsBaseMessageIntegerValuesIndex; import android.hardware.automotive.vehicle.V2_1.VmsMessageType; -import android.os.SystemClock; +import android.hardware.automotive.vehicle.V2_1.VmsOfferingMessageIntegerValuesIndex; +import android.hardware.automotive.vehicle.V2_1.VmsSimpleMessageIntegerValuesIndex; +import android.os.Binder; +import android.os.IBinder; import android.util.Log; import com.android.car.CarLog; +import com.android.car.VmsLayersAvailability; +import com.android.car.VmsPublishersInfo; import com.android.car.VmsRouting; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -49,25 +58,27 @@ import java.util.concurrent.CopyOnWriteArrayList; */ @FutureFeature public class VmsHalService extends HalServiceBase { + private static final boolean DBG = true; private static final int HAL_PROPERTY_ID = VehicleProperty.VEHICLE_MAP_SERVICE; private static final String TAG = "VmsHalService"; - private static final Set<Integer> SUPPORTED_MESSAGE_TYPES = - new HashSet<Integer>( - Arrays.asList( - VmsMessageType.SUBSCRIBE, - VmsMessageType.UNSUBSCRIBE, - VmsMessageType.DATA)); private boolean mIsSupported = false; private CopyOnWriteArrayList<VmsHalPublisherListener> mPublisherListeners = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList<VmsHalSubscriberListener> mSubscriberListeners = new CopyOnWriteArrayList<>(); + + private final IBinder mHalPublisherToken = new Binder(); private final VehicleHal mVehicleHal; - @GuardedBy("mLock") - private VmsRouting mRouting = new VmsRouting(); + private final Object mLock = new Object(); + private final VmsRouting mRouting = new VmsRouting(); + @GuardedBy("mLock") + private final Map<IBinder, VmsLayersOffering> mOfferings = new HashMap<>(); + @GuardedBy("mLock") + private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability(); + private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo(); /** * The VmsPublisherService implements this interface to receive data from the HAL. @@ -80,7 +91,11 @@ public class VmsHalService extends HalServiceBase { * The VmsSubscriberService implements this interface to receive data from the HAL. */ public interface VmsHalSubscriberListener { - void onChange(VmsLayer layer, byte[] payload); + // Notify listener on a data Message. + void onDataMessage(VmsLayer layer, byte[] payload); + + // Notify listener on a change in available layers. + void onLayersAvaiabilityChange(List<VmsLayer> availableLayers); } /** @@ -110,21 +125,21 @@ public class VmsHalService extends HalServiceBase { } public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer) { + boolean firstSubscriptionForLayer = false; synchronized (mLock) { // Check if publishers need to be notified about this change in subscriptions. - boolean firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer); + firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer); // Add the listeners subscription to the layer mRouting.addSubscription(listener, layer); - - // Notify the publishers - if (firstSubscriptionForLayer) { - notifyPublishers(layer, true); - } + } + if (firstSubscriptionForLayer) { + notifyPublishers(layer, true); } } public void removeSubscription(IVmsSubscriberClient listener, VmsLayer layer) { + boolean layerHasSubscribers = true; synchronized (mLock) { if (!mRouting.hasLayerSubscriptions(layer)) { Log.i(TAG, "Trying to remove a layer with no subscription: " + layer); @@ -135,12 +150,10 @@ public class VmsHalService extends HalServiceBase { mRouting.removeSubscription(listener, layer); // Check if publishers need to be notified about this change in subscriptions. - boolean layerHasSubscribers = mRouting.hasLayerSubscriptions(layer); - - // Notify the publishers - if (!layerHasSubscribers) { - notifyPublishers(layer, false); - } + layerHasSubscribers = mRouting.hasLayerSubscriptions(layer); + } + if (!layerHasSubscribers) { + notifyPublishers(layer, false); } } @@ -168,6 +181,12 @@ public class VmsHalService extends HalServiceBase { } } + public Set<IVmsSubscriberClient> getAllListeners() { + synchronized (mLock) { + return mRouting.getAllListeners(); + } + } + public boolean isHalSubscribed(VmsLayer layer) { synchronized (mLock) { return mRouting.isHalSubscribed(layer); @@ -180,21 +199,46 @@ public class VmsHalService extends HalServiceBase { } } + /** + * Assigns an idempotent ID for publisherInfo and stores it. The idempotency in this case means + * that the same publisherInfo will always, within a trip of the vehicle, return the same ID. + * The publisherInfo should be static for a binary and should only change as part of a software + * update. The publisherInfo is a serialized proto message which VMS clients can interpret. + */ + public int getPublisherStaticId(byte[] publisherInfo) { + if (DBG) { + Log.i(TAG, "Getting publisher static ID"); + } + synchronized (mLock) { + return mPublishersInfo.getIdForInfo(publisherInfo); + } + } + + public byte[] getPublisherInfo(int publisherId) { + if (DBG) { + Log.i(TAG, "Getting information for publisher ID: " + publisherId); + } + synchronized (mLock) { + return mPublishersInfo.getPublisherInfo(publisherId); + } + } + public void addHalSubscription(VmsLayer layer) { + boolean firstSubscriptionForLayer = true; synchronized (mLock) { // Check if publishers need to be notified about this change in subscriptions. - boolean firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer); + firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer); // Add the listeners subscription to the layer mRouting.addHalSubscription(layer); - - if (firstSubscriptionForLayer) { - notifyPublishers(layer, true); - } + } + if (firstSubscriptionForLayer) { + notifyPublishers(layer, true); } } public void removeHalSubscription(VmsLayer layer) { + boolean layerHasSubscribers = true; synchronized (mLock) { if (!mRouting.hasLayerSubscriptions(layer)) { Log.i(TAG, "Trying to remove a layer with no subscription: " + layer); @@ -205,12 +249,10 @@ public class VmsHalService extends HalServiceBase { mRouting.removeHalSubscription(layer); // Check if publishers need to be notified about this change in subscriptions. - boolean layerHasSubscribers = mRouting.hasLayerSubscriptions(layer); - - // Notify the publishers - if (!layerHasSubscribers) { - notifyPublishers(layer, false); - } + layerHasSubscribers = mRouting.hasLayerSubscriptions(layer); + } + if (!layerHasSubscribers) { + notifyPublishers(layer, false); } } @@ -220,6 +262,22 @@ public class VmsHalService extends HalServiceBase { } } + public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering){ + Set<VmsLayer> availableLayers = Collections.EMPTY_SET; + synchronized (mLock) { + updateOffering(publisherToken, offering); + availableLayers = mAvailableLayers.getAvailableLayers(); + } + notifySubscribers(availableLayers); + } + + public Set<VmsLayer> getAvailableLayers() { + //TODO(b/36872877): wrap available layers in VmsAvailabilityState similar to VmsSubscriptionState. + synchronized (mLock) { + return mAvailableLayers.getAvailableLayers(); + } + } + /** * Notify all the publishers and the HAL on subscription changes regardless of who triggered * the change. @@ -227,18 +285,31 @@ public class VmsHalService extends HalServiceBase { * @param layer layer which is being subscribed to or unsubscribed from. * @param hasSubscribers indicates if the notification is for subscription or unsubscription. */ - public void notifyPublishers(VmsLayer layer, boolean hasSubscribers) { - synchronized (mLock) { - // notify the HAL - setSubscriptionRequest(layer, hasSubscribers); - - // Notify the App publishers - for (VmsHalPublisherListener listener : mPublisherListeners) { - // Besides the list of layers, also a timestamp is provided to the clients. - // They should ignore any notification with a timestamp that is older than the most - // recent timestamp they have seen. - listener.onChange(getSubscriptionState()); - } + private void notifyPublishers(VmsLayer layer, boolean hasSubscribers) { + // notify the HAL + setSubscriptionRequest(layer, hasSubscribers); + + // Notify the App publishers + for (VmsHalPublisherListener listener : mPublisherListeners) { + // Besides the list of layers, also a timestamp is provided to the clients. + // They should ignore any notification with a timestamp that is older than the most + // recent timestamp they have seen. + listener.onChange(getSubscriptionState()); + } + } + + /** + * Notify all the subscribers and the HAL on layers availability change. + * + * @param availableLayers the layers which publishers claim they made publish. + */ + private void notifySubscribers(Set<VmsLayer> availableLayers) { + // notify the HAL + setAvailableLayers(availableLayers); + + // Notify the App subscribers + for (VmsHalSubscriberListener listener : mSubscriberListeners) { + listener.onLayersAvaiabilityChange(new ArrayList<>(availableLayers)); } } @@ -281,6 +352,10 @@ public class VmsHalService extends HalServiceBase { return taken; } + /** + * Consumes/produces HAL messages. The format of these messages is defined in: + * hardware/interfaces/automotive/vehicle/2.1/types.hal + */ @Override public void handleHalEvents(List<VehiclePropValue> values) { if (DBG) { @@ -288,40 +363,204 @@ public class VmsHalService extends HalServiceBase { } for (VehiclePropValue v : values) { ArrayList<Integer> vec = v.value.int32Values; - int messageType = vec.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE); - int layerId = vec.get(VmsMessageIntegerValuesIndex.VMS_LAYER_ID); - int layerVersion = vec.get(VmsMessageIntegerValuesIndex.VMS_LAYER_VERSION); - - // Check if message type is supported. - if (!SUPPORTED_MESSAGE_TYPES.contains(messageType)) { - throw new IllegalArgumentException("Unexpected message type. " + - "Expecting: " + SUPPORTED_MESSAGE_TYPES + - ". Got: " + messageType); - - } + int messageType = vec.get(VmsBaseMessageIntegerValuesIndex.VMS_MESSAGE_TYPE); if (DBG) { - Log.d(TAG, - "Received message for Type: " + messageType + - " Layer Id: " + layerId + - "Version: " + layerVersion); + Log.d(TAG, "Handling VMS message type: " + messageType); } - // This is a data message intended for subscribers. - if (messageType == VmsMessageType.DATA) { - // Get the payload. - byte[] payload = toByteArray(v.value.bytes); - - // Send the message. - for (VmsHalSubscriberListener listener : mSubscriberListeners) { - listener.onChange(new VmsLayer(layerId, layerVersion), payload); - } - } else if (messageType == VmsMessageType.SUBSCRIBE) { - addHalSubscription(new VmsLayer(layerId, layerVersion)); + switch(messageType) { + case VmsMessageType.DATA: + handleDataEvent(vec, toByteArray(v.value.bytes)); + break; + case VmsMessageType.SUBSCRIBE: + handleSubscribeEvent(vec); + break; + case VmsMessageType.UNSUBSCRIBE: + handleUnsubscribeEvent(vec); + break; + case VmsMessageType.OFFERING: + handleOfferingEvent(vec); + break; + case VmsMessageType.AVAILABILITY_REQUEST: + handleAvailabilityEvent(); + break; + case VmsMessageType.SUBSCRIPTION_REQUEST: + handleSubscriptionRequestEvent(); + break; + default: + throw new IllegalArgumentException("Unexpected message type: " + messageType); + } + } + } + + /** + * Data message format: + * <ul> + * <li>Message type. + * <li>Layer id. + * <li>Layer version. + * <li>Payload. + * </ul> + */ + private void handleDataEvent(List<Integer> integerValues, byte[] payload) { + int layerId = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID); + int layerVersion = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION); + if (DBG) { + Log.d(TAG, + "Handling a data event for Layer Id: " + layerId + + " Version: " + layerVersion); + } + + // Send the message. + for (VmsHalSubscriberListener listener : mSubscriberListeners) { + listener.onDataMessage(new VmsLayer(layerId, layerVersion), payload); + } + } + + /** + * Subscribe message format: + * <ul> + * <li>Message type. + * <li>Layer id. + * <li>Layer version. + * </ul> + */ + private void handleSubscribeEvent(List<Integer> integerValues) { + int layerId = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID); + int layerVersion = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION); + if (DBG) { + Log.d(TAG, + "Handling a subscribe event for Layer Id: " + layerId + + " Version: " + layerVersion); + } + addHalSubscription(new VmsLayer(layerId, layerVersion)); + } + + /** + * Unsubscribe message format: + * <ul> + * <li>Message type. + * <li>Layer id. + * <li>Layer version. + * </ul> + */ + private void handleUnsubscribeEvent(List<Integer> integerValues) { + int layerId = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID); + int layerVersion = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION); + if (DBG) { + Log.d(TAG, + "Handling an unsubscribe event for Layer Id: " + layerId + + " Version: " + layerVersion); + } + removeHalSubscription(new VmsLayer(layerId, layerVersion)); + } + + /** + * Offering message format: + * <ul> + * <li>Message type. + * <li>Number of offerings. + * <li>Each offering consists of: + * <ul> + * <li>Layer id. + * <li>Layer version. + * <li>Number of layer dependencies. + * <li>Layer type/version pairs. + * </ul> + * </ul> + */ + private void handleOfferingEvent(List<Integer> integerValues) { + int numLayersDependencies = + integerValues.get(VmsOfferingMessageIntegerValuesIndex.VMS_NUMBER_OF_LAYERS_DEPENDENCIES); + int idx = VmsOfferingMessageIntegerValuesIndex.FIRST_DEPENDENCIES_INDEX; + + List<VmsLayerDependency> offeredLayers = new ArrayList<>(); + + // An offering is layerId, LayerVersion, NumDeps, <LayerId, LayerVersion> X NumDeps. + for (int i = 0; i < numLayersDependencies; i++) { + int layerId = integerValues.get(idx++); + int layerVersion = integerValues.get(idx++); + VmsLayer offeredLayer = new VmsLayer(layerId, layerVersion); + + int numDependenciesForLayer = integerValues.get(idx++); + if (numDependenciesForLayer == 0) { + offeredLayers.add(new VmsLayerDependency(offeredLayer)); } else { - // messageType == VmsMessageType.UNSUBSCRIBE - removeHalSubscription(new VmsLayer(layerId, layerVersion)); + Set<VmsLayer> dependencies = new HashSet<>(); + + for (int j = 0; j < numDependenciesForLayer; j++) { + int dependantLayerId = integerValues.get(idx++); + int dependantLayerVersion = integerValues.get(idx++); + + VmsLayer dependantLayer = new VmsLayer(dependantLayerId, dependantLayerVersion); + dependencies.add(dependantLayer); + } + offeredLayers.add(new VmsLayerDependency(offeredLayer, dependencies)); } } + // Store the HAL offering. + VmsLayersOffering offering = new VmsLayersOffering(offeredLayers); + synchronized (mLock) { + updateOffering(mHalPublisherToken, offering); + } + } + + /** + * Availability message format: + * <ul> + * <li>Message type. + * <li>Number of layers. + * <li>Layer type/version pairs. + * </ul> + */ + private void handleAvailabilityEvent() { + synchronized (mLock) { + Collection<VmsLayer> availableLayers = mAvailableLayers.getAvailableLayers(); + VehiclePropValue vehiclePropertyValue = toVehiclePropValue( + VmsMessageType.AVAILABILITY_RESPONSE, availableLayers); + setPropertyValue(vehiclePropertyValue); + } + } + + /** + * VmsSubscriptionRequestFormat: + * <ul> + * <li>Message type. + * </ul> + * + * VmsSubscriptionResponseFormat: + * <ul> + * <li>Message type. + * <li>Sequence number. + * <li>Number of layers. + * <li>Layer type/version pairs. + * </ul> + */ + private void handleSubscriptionRequestEvent() { + VmsSubscriptionState subscription = getSubscriptionState(); + VehiclePropValue vehicleProp = toVehiclePropValue(VmsMessageType.SUBSCRIPTION_RESPONSE); + VehiclePropValue.RawValue v = vehicleProp.value; + v.int32Values.add(subscription.getSequenceNumber()); + List<VmsLayer> layers = subscription.getLayers(); + v.int32Values.add(layers.size()); + for (VmsLayer layer : layers) { + v.int32Values.add(layer.getId()); + v.int32Values.add(layer.getVersion()); + } + setPropertyValue(vehicleProp); + } + + private void updateOffering(IBinder publisherToken, VmsLayersOffering offering) { + Set<VmsLayer> availableLayers = Collections.EMPTY_SET; + synchronized (mLock) { + mOfferings.put(publisherToken, offering); + + // Update layers availability. + mAvailableLayers.setPublishersOffering(mOfferings.values()); + + availableLayers = mAvailableLayers.getAvailableLayers(); + } + notifySubscribers(availableLayers); } @Override @@ -339,14 +578,22 @@ public class VmsHalService extends HalServiceBase { */ public boolean setSubscriptionRequest(VmsLayer layer, boolean hasSubscribers) { VehiclePropValue vehiclePropertyValue = toVehiclePropValue( - hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, layer); + hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, layer); return setPropertyValue(vehiclePropertyValue); } public boolean setDataMessage(VmsLayer layer, byte[] payload) { VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.DATA, - layer, - payload); + layer, + payload); + return setPropertyValue(vehiclePropertyValue); + } + + public boolean setAvailableLayers(Collection<VmsLayer> availableLayers) { + VehiclePropValue vehiclePropertyValue = + toVehiclePropValue(VmsMessageType.AVAILABILITY_RESPONSE, + availableLayers); + return setPropertyValue(vehiclePropertyValue); } @@ -361,13 +608,20 @@ public class VmsHalService extends HalServiceBase { } /** Creates a {@link VehiclePropValue} */ - private static VehiclePropValue toVehiclePropValue(int messageType, VmsLayer layer) { + private static VehiclePropValue toVehiclePropValue(int messageType) { VehiclePropValue vehicleProp = new VehiclePropValue(); vehicleProp.prop = HAL_PROPERTY_ID; vehicleProp.areaId = VehicleAreaType.VEHICLE_AREA_TYPE_NONE; VehiclePropValue.RawValue v = vehicleProp.value; v.int32Values.add(messageType); + return vehicleProp; + } + + /** Creates a {@link VehiclePropValue} */ + private static VehiclePropValue toVehiclePropValue(int messageType, VmsLayer layer) { + VehiclePropValue vehicleProp = toVehiclePropValue(messageType); + VehiclePropValue.RawValue v = vehicleProp.value; v.int32Values.add(layer.getId()); v.int32Values.add(layer.getVersion()); return vehicleProp; @@ -375,8 +629,8 @@ public class VmsHalService extends HalServiceBase { /** Creates a {@link VehiclePropValue} with payload */ private static VehiclePropValue toVehiclePropValue(int messageType, - VmsLayer layer, - byte[] payload) { + VmsLayer layer, + byte[] payload) { VehiclePropValue vehicleProp = toVehiclePropValue(messageType, layer); VehiclePropValue.RawValue v = vehicleProp.value; v.bytes.ensureCapacity(payload.length); @@ -385,4 +639,18 @@ public class VmsHalService extends HalServiceBase { } return vehicleProp; } -}
\ No newline at end of file + + /** Creates a {@link VehiclePropValue} with payload */ + private static VehiclePropValue toVehiclePropValue(int messageType, + Collection<VmsLayer> layers) { + VehiclePropValue vehicleProp = toVehiclePropValue(messageType); + VehiclePropValue.RawValue v = vehicleProp.value; + int numLayers = layers.size(); + v.int32Values.add(numLayers); + for (VmsLayer layer : layers) { + v.int32Values.add(layer.getId()); + v.int32Values.add(layer.getVersion()); + } + return vehicleProp; + } +} diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/diagnostic.xml b/tests/EmbeddedKitchenSinkApp/res/layout/diagnostic.xml new file mode 100644 index 0000000000..7fd234ccdb --- /dev/null +++ b/tests/EmbeddedKitchenSinkApp/res/layout/diagnostic.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +android:layout_width="match_parent" +android:layout_height="match_parent" +android:orientation="vertical" > +<!-- dummy one for top area --> +<LinearLayout + android:layout_width="match_parent" + android:layout_height="50dp" + android:orientation="vertical" + android:layout_weight="1" /> +<LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1" > + <TextView + android:id="@+id/live_diagnostic_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Live Information" + android:minLines="10"/> + <TextView + android:id="@+id/freeze_diagnostic_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Freeze Information" + android:minLines="10"/> +</LinearLayout> +</LinearLayout> diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml index 6a3d9cac57..75545088ed 100644 --- a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml +++ b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml @@ -2,7 +2,7 @@ <!-- We use this container to place kitchen app fragments. It insets the fragment contents --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/kitchen_content" - android:background="@android:color/black" + android:background="#A8A9AA" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingStart="56dp" diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java index 884d982f3e..e68c3fafec 100644 --- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java +++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java @@ -41,6 +41,7 @@ import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment; import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment; import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment; import com.google.android.car.kitchensink.cube.CubesTestFragment; +import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment; import com.google.android.car.kitchensink.hvac.HvacTestFragment; import com.google.android.car.kitchensink.input.InputTestFragment; import com.google.android.car.kitchensink.job.JobSchedulerFragment; @@ -50,47 +51,122 @@ import com.google.android.car.kitchensink.sensor.SensorsTestFragment; import com.google.android.car.kitchensink.setting.CarServiceSettingsActivity; import com.google.android.car.kitchensink.touch.TouchTestFragment; import com.google.android.car.kitchensink.volume.VolumeTestFragment; +import java.util.ArrayList; +import java.util.List; public class KitchenSinkActivity extends CarDrawerActivity { private static final String TAG = "KitchenSinkActivity"; - private static final String MENU_AUDIO = "audio"; - private static final String MENU_ASSISTANT = "assistant"; - private static final String MENU_HVAC = "hvac"; - private static final String MENU_QUIT = "quit"; - private static final String MENU_JOB = "job_scheduler"; - private static final String MENU_CLUSTER = "inst cluster"; - private static final String MENU_INPUT_TEST = "input test"; - private static final String MENU_RADIO = "radio"; - private static final String MENU_SENSORS = "sensors"; - private static final String MENU_VOLUME_TEST = "volume test"; - private static final String MENU_TOUCH_TEST = "touch test"; - private static final String MENU_CUBES_TEST = "cubes test"; - private static final String MENU_CAR_SETTINGS = "car service settings"; - private static final String MENU_ORIENTATION = "orientation test"; - private static final String MENU_BLUETOOTH_HEADSET = "bluetooth headset"; - private static final String MENU_MAP_MESSAGING = "bluetooth messaging test"; + private interface ClickHandler { + void onClick(); + } + + private static abstract class MenuEntry implements ClickHandler { + abstract String getText(); + } + + private final class OnClickMenuEntry extends MenuEntry { + private final String mText; + private final ClickHandler mClickHandler; + OnClickMenuEntry(String text, ClickHandler clickHandler) { + mText = text; + mClickHandler = clickHandler; + } + + @Override + String getText() { + return mText; + } + + @Override + public void onClick() { + mClickHandler.onClick(); + } + } + + private final class FragmentMenuEntry<T extends Fragment> extends MenuEntry { + private final class FragmentClassOrInstance<T extends Fragment> { + final Class<T> mClazz; + T mFragment = null; + + FragmentClassOrInstance(Class<T> clazz) { + mClazz = clazz; + } + + T getFragment() { + if (mFragment == null) { + try { + mFragment = mClazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + Log.e(TAG, "unable to create fragment", e); + } + } + return mFragment; + } + } + + private final String mText; + private final FragmentClassOrInstance<T> mFragment; + + FragmentMenuEntry(String text, Class<T> clazz) { + mText = text; + mFragment = new FragmentClassOrInstance<>(clazz); + } + + @Override + String getText() { + return mText; + } + + @Override + public void onClick() { + Fragment fragment = mFragment.getFragment(); + if (fragment != null) { + KitchenSinkActivity.this.showFragment(fragment); + } else { + Log.e(TAG, "cannot show fragment for " + getText()); + } + } + } + + private final List<MenuEntry> mMenuEntries = new ArrayList<MenuEntry>() { + { + add("audio", AudioTestFragment.class); + add("hvac", HvacTestFragment.class); + add("job scheduler", JobSchedulerFragment.class); + add("inst cluster", InstrumentClusterFragment.class); + add("input test", InputTestFragment.class); + add("radio", RadioTestFragment.class); + add("assistant", CarAssistantFragment.class); + add("sensors", SensorsTestFragment.class); + add("diagnostic", DiagnosticTestFragment.class); + add("volume test", VolumeTestFragment.class); + add("touch test", TouchTestFragment.class); + add("cubes test", CubesTestFragment.class); + add("orientation test", OrientationTestFragment.class); + add("bluetooth headset",BluetoothHeadsetFragment.class); + add("bluetooth messaging test", MapMceTestFragment.class); + add("car service settings", () -> { + Intent intent = new Intent(KitchenSinkActivity.this, + CarServiceSettingsActivity.class); + startActivity(intent); + }); + add("quit", KitchenSinkActivity.this::finish); + } + + <T extends Fragment> void add(String text, Class<T> clazz) { + add(new FragmentMenuEntry(text, clazz)); + } + void add(String text, ClickHandler onClick) { + add(new OnClickMenuEntry(text, onClick)); + } + }; private Car mCarApi; private CarHvacManager mHvacManager; private CarSensorManager mCarSensorManager; private CarAppFocusManager mCarAppFocusManager; - private AudioTestFragment mAudioTestFragment; - private RadioTestFragment mRadioTestFragment; - private SensorsTestFragment mSensorsTestFragment; - private HvacTestFragment mHvacTestFragment; - private JobSchedulerFragment mJobFragment; - private InstrumentClusterFragment mInstrumentClusterFragment; - private InputTestFragment mInputTestFragment; - private VolumeTestFragment mVolumeTestFragment; - private TouchTestFragment mTouchTestFragment; - private CubesTestFragment mCubesTestFragment; - private OrientationTestFragment mOrientationFragment; - private MapMceTestFragment mMapMceTestFragment; - private BluetoothHeadsetFragment mBluetoothHeadsetFragement; - private CarAssistantFragment mAssistantFragment; - private final CarSensorManager.OnSensorChangedListener mListener = (manager, event) -> { switch (event.sensorType) { case CarSensorManager.SENSOR_TYPE_DRIVING_STATUS: @@ -99,6 +175,10 @@ public class KitchenSinkActivity extends CarDrawerActivity { } }; + public CarHvacManager getHvacManager() { + return mHvacManager; + } + @Override protected CarDrawerAdapter getRootAdapter() { return new DrawerAdapter(); @@ -194,132 +274,30 @@ public class KitchenSinkActivity extends CarDrawerActivity { private final class DrawerAdapter extends CarDrawerAdapter { - private final String mAllMenus[] = { - MENU_AUDIO, MENU_ASSISTANT, MENU_RADIO, MENU_HVAC, MENU_JOB, - MENU_CLUSTER, MENU_INPUT_TEST, MENU_SENSORS, MENU_VOLUME_TEST, - MENU_TOUCH_TEST, MENU_CUBES_TEST, MENU_CAR_SETTINGS, MENU_ORIENTATION, - MENU_BLUETOOTH_HEADSET, MENU_MAP_MESSAGING, MENU_QUIT - }; - public DrawerAdapter() { - super(KitchenSinkActivity.this, true /* showDisabledOnListOnEmpty */, - true /* smallLayout */); + super(KitchenSinkActivity.this, true /* showDisabledOnListOnEmpty */); setTitle(getString(R.string.app_title)); } @Override protected int getActualItemCount() { - return mAllMenus.length; + return mMenuEntries.size(); } @Override protected void populateViewHolder(DrawerItemViewHolder holder, int position) { - holder.getTitle().setText(mAllMenus[position]); + holder.getTitle().setText(mMenuEntries.get(position).getText()); } @Override public void onItemClick(int position) { - - switch (mAllMenus[position]) { - case MENU_AUDIO: - if (mAudioTestFragment == null) { - mAudioTestFragment = new AudioTestFragment(); - } - showFragment(mAudioTestFragment); - break; - case MENU_ASSISTANT: - if (mAssistantFragment == null) { - mAssistantFragment = new CarAssistantFragment(); - } - showFragment(mAssistantFragment); - break; - case MENU_RADIO: - if (mRadioTestFragment == null) { - mRadioTestFragment = new RadioTestFragment(); - } - showFragment(mRadioTestFragment); - break; - case MENU_SENSORS: - if (mSensorsTestFragment == null) { - mSensorsTestFragment = new SensorsTestFragment(); - } - showFragment(mSensorsTestFragment); - break; - case MENU_HVAC: - if (mHvacManager != null) { - if (mHvacTestFragment == null) { - mHvacTestFragment = new HvacTestFragment(); - mHvacTestFragment.setHvacManager(mHvacManager); - } - // Don't allow HVAC fragment to start if we don't have a manager. - showFragment(mHvacTestFragment); - } - break; - case MENU_JOB: - if (mJobFragment == null) { - mJobFragment = new JobSchedulerFragment(); - } - showFragment(mJobFragment); - break; - case MENU_CLUSTER: - if (mInstrumentClusterFragment == null) { - mInstrumentClusterFragment = new InstrumentClusterFragment(); - } - showFragment(mInstrumentClusterFragment); - break; - case MENU_INPUT_TEST: - if (mInputTestFragment == null) { - mInputTestFragment = new InputTestFragment(); - } - showFragment(mInputTestFragment); - break; - case MENU_VOLUME_TEST: - if (mVolumeTestFragment == null) { - mVolumeTestFragment = new VolumeTestFragment(); - } - showFragment(mVolumeTestFragment); - break; - case MENU_TOUCH_TEST: - if (mTouchTestFragment == null) { - mTouchTestFragment = new TouchTestFragment(); - } - showFragment(mTouchTestFragment); - break; - case MENU_CUBES_TEST: - if (mCubesTestFragment == null) { - mCubesTestFragment = new CubesTestFragment(); - } - showFragment(mCubesTestFragment); - break; - case MENU_CAR_SETTINGS: - Intent intent = new Intent(KitchenSinkActivity.this, - CarServiceSettingsActivity.class); - startActivity(intent); - break; - case MENU_ORIENTATION: - if (mOrientationFragment == null) { - mOrientationFragment = new OrientationTestFragment(); - } - showFragment(mOrientationFragment); - break; - case MENU_BLUETOOTH_HEADSET: - if (mBluetoothHeadsetFragement == null) { - mBluetoothHeadsetFragement = new BluetoothHeadsetFragment(); - } - showFragment(mBluetoothHeadsetFragement); - break; - case MENU_MAP_MESSAGING: - if (mMapMceTestFragment == null) { - mMapMceTestFragment = new MapMceTestFragment(); - } - showFragment(mMapMceTestFragment); - break; - case MENU_QUIT: - finish(); - break; - default: - Log.wtf(TAG, "Unknown menu item: " + mAllMenus[position]); + if ((position < 0) || (position >= mMenuEntries.size())) { + Log.wtf(TAG, "Unknown menu item: " + position); + return; } + + mMenuEntries.get(position).onClick(); + closeDrawer(); } } diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java new file mode 100644 index 0000000000..71deee8512 --- /dev/null +++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/diagnostic/DiagnosticTestFragment.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 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.kitchensink.diagnostic; + +import android.annotation.Nullable; +import android.car.Car; +import android.car.hardware.CarDiagnosticEvent; +import android.car.hardware.CarDiagnosticManager; +import android.car.hardware.CarDiagnosticManager.OnDiagnosticEventListener; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.support.car.hardware.CarSensorManager; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.google.android.car.kitchensink.KitchenSinkActivity; +import com.google.android.car.kitchensink.R; +import java.util.Objects; + + +public class DiagnosticTestFragment extends Fragment { + private static final String TAG = "CAR.DIAGNOSTIC.KS"; + + private KitchenSinkActivity mActivity; + private TextView mLiveDiagnosticInfo; + private TextView mFreezeDiagnosticInfo; + private CarDiagnosticManager mDiagnosticManager; + + private final class TestListener implements OnDiagnosticEventListener { + private final TextView mTextView; + + TestListener(TextView view) { + mTextView = Objects.requireNonNull(view); + } + + @Override + public void onDiagnosticEvent(CarDiagnosticEvent carDiagnosticEvent) { + mTextView.post(() -> mTextView.setText(carDiagnosticEvent.toString())); + } + } + + private OnDiagnosticEventListener mLiveListener; + private OnDiagnosticEventListener mFreezeListener; + + @Nullable + @Override + public View onCreateView( + LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.diagnostic, container, false); + mActivity = (KitchenSinkActivity) getHost(); + + mLiveDiagnosticInfo = (TextView) view.findViewById(R.id.live_diagnostic_info); + mLiveDiagnosticInfo.setTextColor(Color.RED); + mLiveListener = new TestListener(mLiveDiagnosticInfo); + + mFreezeDiagnosticInfo = (TextView) view.findViewById(R.id.freeze_diagnostic_info); + mFreezeDiagnosticInfo.setTextColor(Color.RED); + mFreezeListener = new TestListener(mFreezeDiagnosticInfo); + + return view; + } + + @Override + public void onResume() { + super.onResume(); + resumeDiagnosticManager(); + } + + @Override + public void onPause() { + super.onPause(); + pauseDiagnosticManager(); + } + + private void resumeDiagnosticManager() { + try { + mDiagnosticManager = + (CarDiagnosticManager)mActivity.getCar().getCarManager(Car.DIAGNOSTIC_SERVICE); + if (mLiveListener != null) { + mDiagnosticManager.registerListener(mLiveListener, + CarDiagnosticManager.FRAME_TYPE_LIVE, + CarSensorManager.SENSOR_RATE_NORMAL); + } + if (mFreezeListener != null) { + mDiagnosticManager.registerListener(mFreezeListener, + CarDiagnosticManager.FRAME_TYPE_FREEZE, + CarSensorManager.SENSOR_RATE_NORMAL); + } + } catch (android.car.CarNotConnectedException|android.support.car.CarNotConnectedException e) { + Log.e(TAG, "Car not connected or not supported", e); + } + } + + private void pauseDiagnosticManager() { + if (mDiagnosticManager != null) { + if (mLiveListener != null) { + mDiagnosticManager.unregisterListener(mLiveListener); + } + if (mFreezeListener != null) { + mDiagnosticManager.unregisterListener(mFreezeListener); + } + } + } +} diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java index 05e33e8ecd..3f5ef86aef 100644 --- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java +++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java @@ -18,6 +18,7 @@ package com.google.android.car.kitchensink.hvac; import static java.lang.Integer.toHexString; +import com.google.android.car.kitchensink.KitchenSinkActivity; import com.google.android.car.kitchensink.R; import android.car.CarNotConnectedException; @@ -166,6 +167,10 @@ public class HvacTestFragment extends Fragment { } }; + public HvacTestFragment() { + setHvacManager( ((KitchenSinkActivity)getActivity()).getHvacManager() ); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/tests/VmsPublisherClientSample/Android.mk b/tests/VmsPublisherClientSample/Android.mk index 6bb5bf7554..2aa6c40194 100644 --- a/tests/VmsPublisherClientSample/Android.mk +++ b/tests/VmsPublisherClientSample/Android.mk @@ -28,7 +28,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_PRIVILEGED_MODULE := true -LOCAL_CERTIFICATE := platform +LOCAL_CERTIFICATE := testkey LOCAL_PROGUARD_ENABLED := disabled diff --git a/tests/VmsPublisherClientSample/AndroidManifest.xml b/tests/VmsPublisherClientSample/AndroidManifest.xml index d3ac1952cf..fdc1a318f3 100644 --- a/tests/VmsPublisherClientSample/AndroidManifest.xml +++ b/tests/VmsPublisherClientSample/AndroidManifest.xml @@ -15,8 +15,11 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.car.vms.publisher" - android:sharedUserId="android.uid.system"> + package="com.google.android.car.vms.publisher"> + + <uses-permission android:name="android.car.permission.VMS_PUBLISHER" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> + <uses-permission android:name="android.permission.CAMERA"/> <uses-sdk android:minSdkVersion="25" android:targetSdkVersion='25'/> @@ -24,7 +27,8 @@ android:icon="@mipmap/ic_launcher" android:directBootAware="true"> <service android:name=".VmsPublisherClientSampleService" - android:exported="false"> + android:exported="true" + android:singleUser="true"> </service> </application> </manifest> diff --git a/tests/VmsPublisherClientSample/src/com/google/android/car/vms/publisher/VmsPublisherClientSampleService.java b/tests/VmsPublisherClientSample/src/com/google/android/car/vms/publisher/VmsPublisherClientSampleService.java index c3104645c6..08d37cd261 100644 --- a/tests/VmsPublisherClientSample/src/com/google/android/car/vms/publisher/VmsPublisherClientSampleService.java +++ b/tests/VmsPublisherClientSample/src/com/google/android/car/vms/publisher/VmsPublisherClientSampleService.java @@ -22,7 +22,6 @@ import android.car.vms.VmsSubscriptionState; import android.os.Handler; import android.os.Message; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -31,7 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class VmsPublisherClientSampleService extends VmsPublisherClientService { public static final int PUBLISH_EVENT = 0; - public static final VmsLayer TEST_LAYER = new VmsLayer(0,0); + public static final VmsLayer TEST_LAYER = new VmsLayer(0, 0); private byte mCounter = 0; private AtomicBoolean mInitialized = new AtomicBoolean(false); @@ -39,7 +38,7 @@ public class VmsPublisherClientSampleService extends VmsPublisherClientService { private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - if (msg.what == PUBLISH_EVENT) { + if (msg.what == PUBLISH_EVENT && mInitialized.get()) { periodicPublish(); } } @@ -51,6 +50,8 @@ public class VmsPublisherClientSampleService extends VmsPublisherClientService { */ @Override public void onVmsPublisherServiceReady() { + VmsSubscriptionState subscriptionState = getSubscriptions(); + onVmsSubscriptionChange(subscriptionState); } @Override @@ -64,6 +65,13 @@ public class VmsPublisherClientSampleService extends VmsPublisherClientService { } } + @Override + public void onDestroy() { + super.onDestroy(); + mInitialized.set(false); + mHandler.removeMessages(PUBLISH_EVENT); + } + private void periodicPublish() { publish(TEST_LAYER, new byte[]{mCounter}); ++mCounter; diff --git a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java index 4ee95a131d..fe32ab9359 100644 --- a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java +++ b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java @@ -108,5 +108,10 @@ public class VmsSubscriberClientSampleActivity extends Activity { public void onLayersAvailabilityChange(List<VmsLayer> availableLayers) { mTextView.setText(String.valueOf(availableLayers)); } + + @Override + public void onCarDisconnected() { + + } }; } diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java index 4385e415a9..21fb5e00ac 100644 --- a/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java +++ b/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java @@ -25,15 +25,11 @@ import android.os.IBinder; import android.os.Looper; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; -import com.android.car.internal.FeatureConfiguration; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @MediumTest public class CarDiagnosticManagerTest extends AndroidTestCase { - private static final String TAG = CarDiagnosticManagerTest.class.getSimpleName(); - private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000; private final Semaphore mConnectionWait = new Semaphore(0); @@ -64,25 +60,14 @@ public class CarDiagnosticManagerTest extends AndroidTestCase { mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); } - private boolean isFeatureEnabled() { - return FeatureConfiguration.ENABLE_DIAGNOSTIC; - } - @Override protected void setUp() throws Exception { super.setUp(); mCar = Car.createCar(getContext(), mConnectionListener); mCar.connect(); waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); - - if (isFeatureEnabled()) { - Log.i(TAG, "attempting to get DIAGNOSTIC_SERVICE"); - mCarDiagnosticManager = - (CarDiagnosticManager) mCar.getCarManager(Car.DIAGNOSTIC_SERVICE); - assertNotNull(mCarDiagnosticManager); - } else { - Log.i(TAG, "skipping diagnostic tests as ENABLE_DIAGNOSTIC flag is false"); - } + mCarDiagnosticManager = (CarDiagnosticManager) mCar.getCarManager(Car.DIAGNOSTIC_SERVICE); + assertNotNull(mCarDiagnosticManager); } @Override @@ -97,11 +82,6 @@ public class CarDiagnosticManagerTest extends AndroidTestCase { * @throws Exception */ public void testLiveFrame() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testLiveFrame as diagnostics API is not enabled"); - return; - } - CarDiagnosticEvent liveFrame = mCarDiagnosticManager.getLatestLiveFrame(); if (null != liveFrame) { assertTrue(liveFrame.isLiveFrame()); @@ -115,11 +95,6 @@ public class CarDiagnosticManagerTest extends AndroidTestCase { * @throws Exception */ public void testFreezeFrames() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testFreezeFrames as diagnostics API is not enabled"); - return; - } - long[] timestamps = mCarDiagnosticManager.getFreezeFrameTimestamps(); if (null != timestamps) { for (long timestamp : timestamps) { diff --git a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarActivityTest.java b/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarActivityTest.java deleted file mode 100644 index 0fc8dc6439..0000000000 --- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarActivityTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 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.support.car.apitest; - -import android.support.car.Car; -import android.test.ActivityInstrumentationTestCase2; -import android.test.suitebuilder.annotation.MediumTest; - -@MediumTest -public class CarActivityTest extends ActivityInstrumentationTestCase2<TestCarProxyActivity> { - private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000; - - private TestCarProxyActivity mActivity; - - public CarActivityTest() { - super(TestCarProxyActivity.class); - } - - public CarActivityTest(Class<TestCarProxyActivity> activityClass) { - super(activityClass); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - TestCarActivity.testCleanup(); - } - - public void testCycle() throws Throwable { - TestCarActivity.sCreateTestAction = new TestAction<TestCarActivity>() { - @Override - public void run(TestCarActivity param) { - // TODO: Add tests - } - }; - TestCarActivity.sStartTestAction = new TestAction<TestCarActivity>() { - @Override - public void run(TestCarActivity param) { - // TODO: Add tests - } - }; - TestCarActivity.sResumeTestAction = new TestAction<TestCarActivity>() { - @Override - public void run(TestCarActivity param) { - // TODO: Add tests - } - }; - TestCarActivity.sPauseTestAction = new TestAction<TestCarActivity>() { - @Override - public void run(TestCarActivity param) { - // TODO: Add tests - } - }; - TestCarActivity.sStopTestAction = new TestAction<TestCarActivity>() { - @Override - public void run(TestCarActivity param) { - // TODO: Add tests - } - }; - TestCarActivity.sDestroyTestAction = new TestAction<TestCarActivity>() { - @Override - public void run(TestCarActivity param) { - // TODO: Add tests - } - }; - mActivity = getActivity(); - TestCarActivity.sCreateTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS); - TestCarActivity.sStartTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS); - TestCarActivity.sResumeTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS); - mActivity.finish(); - TestCarActivity.sPauseTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS); - TestCarActivity.sStopTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS); - TestCarActivity.sDestroyTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS); - } - -} diff --git a/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarActivity.java b/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarActivity.java deleted file mode 100644 index da19b72e5b..0000000000 --- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarActivity.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 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.support.car.apitest; - -import android.content.Context; -import android.os.Bundle; -import android.support.car.Car; -import android.support.car.app.CarActivity; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -@MediumTest -public class TestCarActivity extends CarActivity { - private static final String TAG = TestCarActivity.class.getSimpleName(); - - public static volatile TestAction<TestCarActivity> sCreateTestAction; - public static volatile TestAction<TestCarActivity> sStartTestAction; - public static volatile TestAction<TestCarActivity> sResumeTestAction; - public static volatile TestAction<TestCarActivity> sPauseTestAction; - public static volatile TestAction<TestCarActivity> sStopTestAction; - public static volatile TestAction<TestCarActivity> sDestroyTestAction; - - public TestCarActivity(CarActivity.Proxy proxy, Context context, Car car) { - super(proxy, context, car); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - Log.d(TAG, "TestAction " + sCreateTestAction); - doRunTest(sCreateTestAction); - } - - - @Override - protected void onStart() { - Log.d(TAG, "onStart"); - super.onStart(); - doRunTest(sStartTestAction); - } - - @Override - protected void onResume() { - Log.d(TAG, "onResume"); - super.onResume(); - doRunTest(sResumeTestAction); - - } - - @Override - protected void onPause() { - Log.d(TAG, "onPause"); - super.onPause(); - doRunTest(sPauseTestAction); - } - - - @Override - protected void onStop() { - Log.d(TAG, "onStop"); - super.onStop(); - doRunTest(sStopTestAction); - } - - @Override - protected void onDestroy() { - Log.d(TAG, "onDestroy"); - super.onDestroy(); - doRunTest(sDestroyTestAction); - } - - private void doRunTest(TestAction<TestCarActivity> test) { - Log.d(TAG, "doRunTest " + test); - if (test != null) { - test.doRun(this); - } - } - - public static void testCleanup() { - sCreateTestAction = null; - sStartTestAction = null; - sResumeTestAction = null; - sPauseTestAction = null; - sStopTestAction = null; - sDestroyTestAction = null; - } -} diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml index 6a1e2bfcbf..6f7ba349aa 100644 --- a/tests/carservice_test/AndroidManifest.xml +++ b/tests/carservice_test/AndroidManifest.xml @@ -43,5 +43,9 @@ android:process="com.android.car.carservicetest.activityC"/> <activity android:name="com.android.car.test.SystemActivityMonitoringServiceTest$BlockingActivity" android:taskAffinity="com.android.car.carservicetest.block"/> + <service android:name=".SimpleVmsPublisherClientService" + android:exported="true" + /> + <service android:name=".VmsPublisherClientMockService" android:exported="true" /> </application> </manifest> diff --git a/tests/carservice_test/src/com/android/car/test/AudioTestUtils.java b/tests/carservice_test/src/com/android/car/test/AudioTestUtils.java new file mode 100644 index 0000000000..8c3de7c4c7 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/AudioTestUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 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.test; + +import android.media.AudioAttributes; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.media.AudioManager.OnAudioFocusChangeListener; + +final class AudioTestUtils { + private AudioTestUtils() {} + + static int doRequestFocus( + AudioManager audioManager, + OnAudioFocusChangeListener listener, + int streamType, + int androidFocus) { + AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); + attributesBuilder.setLegacyStreamType(streamType); + return doRequestFocus(audioManager, listener, attributesBuilder.build(), androidFocus); + } + + static int doRequestFocus( + AudioManager audioManager, + OnAudioFocusChangeListener listener, + AudioAttributes attributes, + int androidFocus) { + return doRequestFocus(audioManager, listener, attributes, androidFocus, false); + } + + static int doRequestFocus( + AudioManager audioManager, + OnAudioFocusChangeListener listener, + AudioAttributes attributes, + int androidFocus, + boolean acceptsDelayedFocus) { + AudioFocusRequest.Builder focusBuilder = new AudioFocusRequest.Builder(androidFocus); + focusBuilder.setOnAudioFocusChangeListener(listener).setAcceptsDelayedFocusGain( + acceptsDelayedFocus); + focusBuilder.setAudioAttributes(attributes); + + return audioManager.requestAudioFocus(focusBuilder.build()); + } +} diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java index 047ad41a23..71e0d04e2c 100644 --- a/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java +++ b/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java @@ -16,6 +16,7 @@ package com.android.car.test; import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS; +import static com.android.car.test.AudioTestUtils.doRequestFocus; import static java.lang.Integer.toHexString; import android.car.Car; @@ -226,7 +227,7 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { public void testMediaNavFocus() throws Exception { //music start AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -248,8 +249,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); assertEquals(0x3, request[1]); @@ -292,7 +293,7 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { public void testMediaExternalMediaNavFocus() throws Exception { // android music AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -322,8 +323,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK, request[0]); @@ -368,8 +369,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { assertNotNull(carAudioManager); AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage( CarAudioManager.CAR_AUDIO_USAGE_RADIO); - int res = mAudioManager.requestAudioFocus(listenerRadio, - radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + int res = doRequestFocus(mAudioManager, listenerRadio, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -388,8 +389,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { AudioFocusListener listenerNav = new AudioFocusListener(); AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource( CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); - res = mAudioManager.requestAudioFocus(listenerNav, - extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerNav, + extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, @@ -437,7 +438,7 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { public void testMediaExternalNav() throws Exception { // android music AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -457,8 +458,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { AudioFocusListener listenerNav = new AudioFocusListener(); AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource( CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); - res = mAudioManager.requestAudioFocus(listenerNav, - extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerNav, + extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, @@ -513,8 +514,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { AudioFocusListener listenerIntNav = new AudioFocusListener(); AudioAttributes intNavAttributes = mCarAudioManager.getAudioAttributesForCarUsage( CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE); - int res = mAudioManager.requestAudioFocus(listenerIntNav, intNavAttributes, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + int res = doRequestFocus(mAudioManager, listenerIntNav, intNavAttributes, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK, @@ -533,8 +534,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { AudioFocusListener listenerExtNav = new AudioFocusListener(); AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource( CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); - res = mAudioManager.requestAudioFocus(listenerExtNav, - extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerExtNav, + extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, @@ -581,7 +582,7 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { public void testMediaExternalRadioNavMediaFocus() throws Exception { // android music AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -604,8 +605,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { assertNotNull(carAudioManager); AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage( CarAudioManager.CAR_AUDIO_USAGE_RADIO); - res = mAudioManager.requestAudioFocus(listenerRadio, - radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + res = doRequestFocus(mAudioManager, listenerRadio, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -626,8 +627,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -762,8 +763,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { assertNotNull(carAudioManager); AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(mediaUsage); Log.i(TAG, "request media Focus"); - int res = mAudioManager.requestAudioFocus(listenerMedia, - radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + int res = doRequestFocus(mAudioManager, listenerMedia, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -806,8 +807,8 @@ public class CarAudioExtFocusTest extends MockedCarTestBase { setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); Log.i(TAG, "request nav Focus"); - res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK, request[0]); diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java index 99958cc6ac..afafb28fc4 100644 --- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java +++ b/tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java @@ -17,6 +17,7 @@ package com.android.car.test; import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS; import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_STREAM_STATE; +import static com.android.car.test.AudioTestUtils.doRequestFocus; import com.google.android.collect.Lists; @@ -136,8 +137,8 @@ public class CarAudioFocusSystemSoundTest extends MockedCarTestBase { assertNotNull(carAudioManager); AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage( CarAudioManager.CAR_AUDIO_USAGE_RADIO); - int res = mAudioManager.requestAudioFocus(listenerRadio, - radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + int res = doRequestFocus(mAudioManager, listenerRadio, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -190,7 +191,7 @@ public class CarAudioFocusSystemSoundTest extends MockedCarTestBase { public void testMusicSystemSound() throws Exception { // music start AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -246,8 +247,8 @@ public class CarAudioFocusSystemSoundTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - int res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + int res = doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK, diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java index 9ead75dd5f..6f5da5f928 100644 --- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java +++ b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java @@ -16,6 +16,7 @@ package com.android.car.test; import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS; +import static com.android.car.test.AudioTestUtils.doRequestFocus; import android.car.Car; import android.car.media.CarAudioManager; @@ -135,7 +136,7 @@ public class CarAudioFocusTest extends MockedCarTestBase { public void testMediaNavFocus() throws Exception { //music start AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -155,8 +156,8 @@ public class CarAudioFocusTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); assertEquals(0x3, request[1]); @@ -193,7 +194,7 @@ public class CarAudioFocusTest extends MockedCarTestBase { public void testMediaExternalMediaNavFocus() throws Exception { // android music AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -221,8 +222,8 @@ public class CarAudioFocusTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK, request[0]); @@ -258,7 +259,7 @@ public class CarAudioFocusTest extends MockedCarTestBase { public void testMediaExternalRadioNavMediaFocus() throws Exception { // android music AudioFocusListener listenerMusic = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -279,8 +280,8 @@ public class CarAudioFocusTest extends MockedCarTestBase { assertNotNull(carAudioManager); AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage( CarAudioManager.CAR_AUDIO_USAGE_RADIO); - res = mAudioManager.requestAudioFocus(listenerRadio, - radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + res = doRequestFocus(mAudioManager, listenerRadio, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -299,8 +300,8 @@ public class CarAudioFocusTest extends MockedCarTestBase { setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); - res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -360,7 +361,7 @@ public class CarAudioFocusTest extends MockedCarTestBase { int context) throws Exception { AudioFocusListener lister = new AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(lister, + int res = doRequestFocus(mAudioManager, lister, streamType, androidFocus); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -426,8 +427,8 @@ public class CarAudioFocusTest extends MockedCarTestBase { assertNotNull(carAudioManager); AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(mediaUsage); Log.i(TAG, "request media Focus"); - int res = mAudioManager.requestAudioFocus(listenerMedia, - radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + int res = doRequestFocus(mAudioManager, listenerMedia, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]); @@ -461,8 +462,8 @@ public class CarAudioFocusTest extends MockedCarTestBase { setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). build(); Log.i(TAG, "request nav Focus"); - res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK, request[0]); diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java index 656254244c..d316ba6f82 100644 --- a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java +++ b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java @@ -33,11 +33,16 @@ import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; import android.hardware.automotive.vehicle.V2_1.VehicleProperty; import android.os.SystemClock; import android.test.suitebuilder.annotation.MediumTest; +import android.util.JsonReader; +import android.util.JsonWriter; import android.util.Log; import com.android.car.internal.FeatureConfiguration; import com.android.car.vehiclehal.DiagnosticEventBuilder; +import com.android.car.vehiclehal.DiagnosticJson; import com.android.car.vehiclehal.VehiclePropValueBuilder; import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; +import java.io.StringReader; +import java.io.StringWriter; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -198,10 +203,6 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { mFreezeFrameProperties.mFreezeFrameClearHandler); } - private boolean isFeatureEnabled() { - return FeatureConfiguration.ENABLE_DIAGNOSTIC; - } - @Override protected void setUp() throws Exception { mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE, 30); @@ -225,21 +226,12 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { super.setUp(); - if (isFeatureEnabled()) { - Log.i(TAG, "attempting to get DIAGNOSTIC_SERVICE"); - mCarDiagnosticManager = - (CarDiagnosticManager) getCar().getCarManager(Car.DIAGNOSTIC_SERVICE); - } else { - Log.i(TAG, "skipping diagnostic tests as ENABLE_DIAGNOSTIC flag is false"); - } + Log.i(TAG, "attempting to get DIAGNOSTIC_SERVICE"); + mCarDiagnosticManager = + (CarDiagnosticManager) getCar().getCarManager(Car.DIAGNOSTIC_SERVICE); } public void testLiveFrameRead() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testLiveFrameRead as diagnostics API is not enabled"); - return; - } - CarDiagnosticEvent liveFrame = mCarDiagnosticManager.getLatestLiveFrame(); assertNotNull(liveFrame); @@ -273,15 +265,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testLiveFrameEvent() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testLiveFrameEvent as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); listener.reset(); @@ -302,15 +289,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testMissingSensorRead() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testMissingSensorRead as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build()); @@ -341,15 +323,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testFuelSystemStatus() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testFuelSystemStatus as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build()); @@ -369,15 +346,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testSecondaryAirStatus() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testSecondaryAirStatus as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); mLiveFrameEventBuilder.addIntSensor( @@ -403,15 +375,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testIgnitionMonitors() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testIgnitionMonitors as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); // cfr. CarDiagnosticEvent for the meaning of the several bits @@ -505,15 +472,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testFuelType() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testFuelType as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); mLiveFrameEventBuilder.addIntSensor( @@ -532,22 +494,65 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { assertEquals(FuelType.BIFUEL_RUNNING_LPG, liveFrame.getFuelType().intValue()); } - public void testMultipleListeners() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testMultipleListeners as diagnostics API is not enabled"); - return; - } + public void testDiagnosticJson() throws Exception { + Listener listener = new Listener(); + mCarDiagnosticManager.registerListener( + listener, + CarDiagnosticManager.FRAME_TYPE_LIVE, + android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); + + mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.ENGINE_OIL_TEMPERATURE, 74); + mLiveFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.OXYGEN_SENSOR1_VOLTAGE, 0.125f); + + long timestamp = SystemClock.elapsedRealtimeNanos(); + getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp)); + + assertTrue(listener.waitForEvent(timestamp)); + + CarDiagnosticEvent liveFrame = listener.getLastEvent(); + assertNotNull(liveFrame); + + assertEquals( + 74, + liveFrame + .getSystemIntegerSensor(Obd2IntegerSensorIndex.ENGINE_OIL_TEMPERATURE) + .intValue()); + assertEquals( + 0.125f, + liveFrame.getSystemFloatSensor(Obd2FloatSensorIndex.OXYGEN_SENSOR1_VOLTAGE)); + + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + + liveFrame.writeToJson(jsonWriter); + jsonWriter.flush(); + StringReader stringReader = new StringReader(stringWriter.toString()); + JsonReader jsonReader = new JsonReader(stringReader); + DiagnosticJson diagnosticJson = DiagnosticJson.build(jsonReader); + + assertEquals( + 74, + diagnosticJson + .intValues + .get(Obd2IntegerSensorIndex.ENGINE_OIL_TEMPERATURE) + .intValue()); + assertEquals( + 0.125f, + diagnosticJson.floatValues.get(Obd2FloatSensorIndex.OXYGEN_SENSOR1_VOLTAGE)); + } + + public void testMultipleListeners() throws Exception { Listener listener1 = new Listener(); Listener listener2 = new Listener(); mCarDiagnosticManager.registerListener( listener1, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); mCarDiagnosticManager.registerListener( listener2, - CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE, + CarDiagnosticManager.FRAME_TYPE_LIVE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); listener1.reset(); @@ -560,6 +565,15 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { CarDiagnosticEvent event1 = listener1.getLastEvent(); CarDiagnosticEvent event2 = listener2.getLastEvent(); + + assertTrue(event1.equals(event1)); + assertTrue(event2.equals(event2)); + assertTrue(event1.equals(event2)); + assertTrue(event2.equals(event1)); + + assertTrue(event1.hashCode() == event1.hashCode()); + assertTrue(event1.hashCode() == event2.hashCode()); + assertEquals( 5000, event1.getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START) @@ -583,6 +597,8 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { event2 = listener2.getLastEvent(); assertTrue(event1.isEarlierThan(event2)); + assertFalse(event1.equals(event2)); + assertFalse(event2.equals(event1)); assertEquals( 5000, @@ -591,15 +607,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testFreezeFrameEvent() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testFreezeFrameEvent as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE, + CarDiagnosticManager.FRAME_TYPE_FREEZE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); listener.reset(); @@ -634,15 +645,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testFreezeFrameTimestamps() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testFreezeFrameTimestamps as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE, + CarDiagnosticManager.FRAME_TYPE_FREEZE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); Set<Long> generatedTimestamps = new HashSet<>(); @@ -668,15 +674,10 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { } public void testClearFreezeFrameTimestamps() throws Exception { - if (!isFeatureEnabled()) { - Log.i(TAG, "skipping testClearFreezeFrameTimestamps as diagnostics API is not enabled"); - return; - } - Listener listener = new Listener(); mCarDiagnosticManager.registerListener( listener, - CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE, + CarDiagnosticManager.FRAME_TYPE_FREEZE, android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); VehiclePropValue injectedEvent = @@ -689,6 +690,51 @@ public class CarDiagnosticManagerTest extends MockedCarTestBase { assertNull(mCarDiagnosticManager.getFreezeFrame(injectedEvent.timestamp)); } + public void testListenerUnregister() throws Exception { + Listener listener1 = new Listener(); + Listener listener2 = new Listener(); + mCarDiagnosticManager.registerListener( + listener1, + CarDiagnosticManager.FRAME_TYPE_LIVE, + android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); + mCarDiagnosticManager.registerListener( + listener1, + CarDiagnosticManager.FRAME_TYPE_FREEZE, + android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); + + mCarDiagnosticManager.unregisterListener(listener1); + + // you need a listener to be registered before MockedVehicleHal will actually dispatch + // your events - add one, but do it *after* unregistering the first listener + mCarDiagnosticManager.registerListener( + listener2, + CarDiagnosticManager.FRAME_TYPE_LIVE, + android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); + mCarDiagnosticManager.registerListener( + listener2, + CarDiagnosticManager.FRAME_TYPE_FREEZE, + android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL); + + VehiclePropValue injectedEvent = + mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder); + long time = injectedEvent.timestamp; + getMockedVehicleHal().injectEvent(injectedEvent); + assertFalse(listener1.waitForEvent(time)); + assertTrue(listener2.waitForEvent(time)); + + time += 1000; + getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time)); + assertFalse(listener1.waitForEvent(time)); + assertTrue(listener2.waitForEvent(time)); + } + + public void testIsSupportedApiCalls() throws Exception { + assertTrue(mCarDiagnosticManager.isLiveFrameSupported()); + assertTrue(mCarDiagnosticManager.isFreezeFrameSupported()); + assertTrue(mCarDiagnosticManager.isFreezeFrameTimestampSupported()); + assertTrue(mCarDiagnosticManager.isFreezeFrameClearSupported()); + } + class Listener implements CarDiagnosticManager.OnDiagnosticEventListener { private final Object mSync = new Object(); diff --git a/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java b/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java index cdd8838510..df2b5326ff 100644 --- a/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java +++ b/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java @@ -15,6 +15,8 @@ */ package com.android.car.test; +import static com.android.car.test.AudioTestUtils.doRequestFocus; + import com.google.android.collect.Lists; import android.car.Car; @@ -121,7 +123,7 @@ public class CarVolumeServiceTest extends MockedCarTestBase { // first give focus to system sound CarAudioFocusTest.AudioFocusListener listenerMusic = new CarAudioFocusTest.AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_SYSTEM, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -137,8 +139,8 @@ public class CarVolumeServiceTest extends MockedCarTestBase { AudioAttributes callAttrib = (new AudioAttributes.Builder()). setUsage(AudioAttributes.USAGE_ALARM). build(); - res = mAudioManager.requestAudioFocus(listenerAlarm, callAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + res = doRequestFocus(mAudioManager, listenerAlarm, callAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); mAudioFocusPropertyHandler.sendAudioFocusState( @@ -169,7 +171,7 @@ public class CarVolumeServiceTest extends MockedCarTestBase { CarAudioFocusTest.AudioFocusListener listenerMusic = new CarAudioFocusTest.AudioFocusListener(); - int res = mAudioManager.requestAudioFocus(listenerMusic, + int res = doRequestFocus(mAudioManager, listenerMusic, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); @@ -191,8 +193,8 @@ public class CarVolumeServiceTest extends MockedCarTestBase { AudioAttributes callAttrib = (new AudioAttributes.Builder()). setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION). build(); - mAudioManager.requestAudioFocus(listenerCall, callAttrib, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + doRequestFocus(mAudioManager, listenerCall, callAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); mAudioFocusPropertyHandler.sendAudioFocusState( VehicleAudioFocusState.STATE_GAIN, request[1], diff --git a/tests/carservice_test/src/com/android/car/test/SimpleVmsPublisherClientService.java b/tests/carservice_test/src/com/android/car/test/SimpleVmsPublisherClientService.java new file mode 100644 index 0000000000..c8badf2ad2 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/SimpleVmsPublisherClientService.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 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.test; + +import android.car.annotation.FutureFeature; +import android.car.vms.VmsPublisherClientService; +import android.car.vms.VmsSubscriptionState; + +/** + * This service is launched during the tests in VmsPublisherClientServiceTest. + */ +@FutureFeature +public class SimpleVmsPublisherClientService extends VmsPublisherClientService { + @Override + public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) { + + } + + @Override + public void onVmsPublisherServiceReady() { + // Publish a property that is going to be verified in the test. + publish(VmsPublisherClientServiceTest.MOCK_PUBLISHER_LAYER, + VmsPublisherClientServiceTest.PAYLOAD); + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsHalServiceSubscriptionEventTest.java b/tests/carservice_test/src/com/android/car/test/VmsHalServiceSubscriptionEventTest.java new file mode 100644 index 0000000000..240598a6f9 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsHalServiceSubscriptionEventTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2017 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.test; + +import static org.junit.Assume.assumeTrue; + +import android.car.VehicleAreaType; +import android.car.annotation.FutureFeature; +import android.car.vms.VmsLayer; +import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; +import android.hardware.automotive.vehicle.V2_1.VehicleProperty; +import android.hardware.automotive.vehicle.V2_1.VmsSimpleMessageIntegerValuesIndex; +import android.hardware.automotive.vehicle.V2_1.VmsMessageType; +import android.hardware.automotive.vehicle.V2_1.VmsSubscriptionResponseFormat; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.car.vehiclehal.VehiclePropValueBuilder; +import com.android.car.vehiclehal.test.MockedVehicleHal; +import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@FutureFeature +@MediumTest +public class VmsHalServiceSubscriptionEventTest extends MockedCarTestBase { + private static final String TAG = "VmsHalServiceTest"; + + private HalHandler mHalHandler; + private MockedVehicleHal mHal; + // Used to block until the HAL property is updated in HalHandler.onPropertySet. + private Semaphore mHalHandlerSemaphore; + + @Override + protected synchronized void configureMockedHal() { + mHalHandler = new HalHandler(); + addProperty(VehicleProperty.VEHICLE_MAP_SERVICE, mHalHandler) + .setChangeMode(VehiclePropertyChangeMode.ON_CHANGE) + .setAccess(VehiclePropertyAccess.READ_WRITE) + .setSupportedAreas(VehicleAreaType.VEHICLE_AREA_TYPE_NONE); + } + + @Override + protected void setUp() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.setUp(); + mHal = getMockedVehicleHal(); + mHalHandlerSemaphore = new Semaphore(0); + } + + @Override + protected synchronized void tearDown() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.tearDown(); + } + + public void testEmptySubscriptions() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + List<VmsLayer> layers = new ArrayList<>(); + subscriptionTestLogic(layers); + } + + public void testOneSubscription() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + List<VmsLayer> layers = Arrays.asList(new VmsLayer(8, 3)); + subscriptionTestLogic(layers); + } + + public void testManySubscriptions() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + List<VmsLayer> layers = Arrays.asList( + new VmsLayer(8, 3), + new VmsLayer(5, 1), + new VmsLayer(3, 9), + new VmsLayer(2, 7), + new VmsLayer(9, 3)); + subscriptionTestLogic(layers); + } + + /** + * First, it subscribes to the given layers. Then it validates that a subscription request + * responds with the same layers. + */ + private void subscriptionTestLogic(List<VmsLayer> layers) throws Exception { + for (VmsLayer layer : layers) { + subscribeViaHal(layer); + } + // Send subscription request. + mHal.injectEvent(createHalSubscriptionRequest()); + // Wait for response. + assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + // Validate response. + ArrayList<Integer> v = mHalHandler.getValues(); + int messageType = v.get(VmsSubscriptionResponseFormat.VMS_MESSAGE_TYPE); + int sequenceNumber = v.get(VmsSubscriptionResponseFormat.SEQUENCE_NUMBER); + int numberLayers = v.get(VmsSubscriptionResponseFormat.NUMBER_OF_LAYERS); + assertEquals(VmsMessageType.SUBSCRIPTION_RESPONSE, messageType); + assertEquals(layers.size(), sequenceNumber); + assertEquals(layers.size(), numberLayers); + List<VmsLayer> receivedLayers = new ArrayList<>(); + int start = VmsSubscriptionResponseFormat.FIRST_LAYER; + int end = VmsSubscriptionResponseFormat.FIRST_LAYER + 2 * numberLayers; + while (start < end) { + int id = v.get(start++); + int version = v.get(start++); + receivedLayers.add(new VmsLayer(id, version)); + } + assertEquals(new HashSet<>(layers), new HashSet<>(receivedLayers)); + } + + /** + * Subscribes to a layer, waits for the event to propagate back to the HAL layer and validates + * the propagated message. + */ + private void subscribeViaHal(VmsLayer layer) throws Exception { + // Send subscribe request. + mHal.injectEvent(createHalSubscribeRequest(layer)); + // Wait for response. + assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + // Validate response. + ArrayList<Integer> v = mHalHandler.getValues(); + int messsageType = v.get(VmsSimpleMessageIntegerValuesIndex.VMS_MESSAGE_TYPE); + int layerId = v.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID); + int layerVersion = v.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION); + assertEquals(VmsMessageType.SUBSCRIBE, messsageType); + assertEquals(layer.getId(), layerId); + assertEquals(layer.getVersion(), layerVersion); + } + + private VehiclePropValue createHalSubscribeRequest(VmsLayer layer) { + return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .addIntValue(VmsMessageType.SUBSCRIBE) + .addIntValue(layer.getId()) + .addIntValue(layer.getVersion()) + .build(); + } + + private VehiclePropValue createHalSubscriptionRequest() { + return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .addIntValue(VmsMessageType.SUBSCRIPTION_REQUEST) + .build(); + } + + private class HalHandler implements VehicleHalPropertyHandler { + private ArrayList<Integer> mValues; + + @Override + public synchronized void onPropertySet(VehiclePropValue value) { + mValues = value.value.int32Values; + mHalHandlerSemaphore.release(); + } + + public ArrayList<Integer> getValues() { + return mValues; + } + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java new file mode 100644 index 0000000000..d8e344b4be --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 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.test; + +import android.car.annotation.FutureFeature; +import android.car.vms.VmsLayer; +import android.car.vms.VmsLayerDependency; +import android.car.vms.VmsLayersOffering; +import android.car.vms.VmsPublisherClientService; +import android.car.vms.VmsSubscriptionState; +import android.util.Log; +import java.util.List; +import java.util.ArrayList; + +/** + * This service is launched during the tests in VmsPublisherSubscriberTest. It publishes a property + * that is going to be verified in the test. + * + * Note that the subscriber can subscribe before the publisher finishes initialization. To cover + * both potential scenarios, this service publishes the test message in onVmsSubscriptionChange + * and in onVmsPublisherServiceReady. See comments below. + */ +@FutureFeature +public class VmsPublisherClientMockService extends VmsPublisherClientService { + private static final String TAG = "VmsPublisherClientMockService"; + + @Override + public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) { + // Case when the publisher finished initialization before the subscription request. + initializeMockPublisher(subscriptionState); + } + + @Override + public void onVmsPublisherServiceReady() { + // Case when the subscription request was sent before the publisher was ready. + VmsSubscriptionState subscriptionState = getSubscriptions(); + initializeMockPublisher(subscriptionState); + } + + private void initializeMockPublisher(VmsSubscriptionState subscriptionState) { + Log.d(TAG, "Initializing Mock publisher"); + getPublisherStaticId(VmsPublisherSubscriberTest.PAYLOAD); + publishIfNeeded(subscriptionState); + declareOffering(subscriptionState); + } + + private void publishIfNeeded(VmsSubscriptionState subscriptionState) { + for (VmsLayer layer : subscriptionState.getLayers()) { + if (layer.equals(VmsPublisherSubscriberTest.LAYER)) { + publish(VmsPublisherSubscriberTest.LAYER, VmsPublisherSubscriberTest.PAYLOAD); + } + } + } + + private void declareOffering(VmsSubscriptionState subscriptionState) { + List<VmsLayerDependency> dependencies = new ArrayList<>(); + for( VmsLayer layer : subscriptionState.getLayers()) { + dependencies.add(new VmsLayerDependency(layer)); + } + VmsLayersOffering offering = new VmsLayersOffering(dependencies); + setLayersOffering(offering); + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java new file mode 100644 index 0000000000..c22f63abe6 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 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.test; + +import android.annotation.ArrayRes; +import android.car.VehicleAreaType; +import android.car.annotation.FutureFeature; +import android.car.vms.VmsLayer; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; +import android.hardware.automotive.vehicle.V2_1.VehicleProperty; +import android.hardware.automotive.vehicle.V2_1.VmsBaseMessageIntegerValuesIndex; +import android.hardware.automotive.vehicle.V2_1.VmsSimpleMessageIntegerValuesIndex; +import android.hardware.automotive.vehicle.V2_1.VmsMessageType; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.car.R; +import com.android.car.vehiclehal.VehiclePropValueBuilder; +import com.android.car.vehiclehal.test.MockedVehicleHal; +import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@FutureFeature +@MediumTest +public class VmsPublisherClientServiceTest extends MockedCarTestBase { + private static final String TAG = "VmsPublisherTest"; + private static final int MOCK_PUBLISHER_LAYER_ID = 12; + private static final int MOCK_PUBLISHER_LAYER_VERSION = 34; + public static final VmsLayer MOCK_PUBLISHER_LAYER = new VmsLayer(MOCK_PUBLISHER_LAYER_ID, + MOCK_PUBLISHER_LAYER_VERSION); + public static final byte[] PAYLOAD = new byte[]{1, 1, 2, 3, 5, 8, 13}; + + private HalHandler mHalHandler; + // Used to block until the HAL property is updated in HalHandler.onPropertySet. + private Semaphore mHalHandlerSemaphore; + + @Override + protected synchronized void configureMockedHal() { + mHalHandler = new HalHandler(); + addProperty(VehicleProperty.VEHICLE_MAP_SERVICE, mHalHandler) + .setChangeMode(VehiclePropertyChangeMode.ON_CHANGE) + .setAccess(VehiclePropertyAccess.READ_WRITE) + .setSupportedAreas(VehicleAreaType.VEHICLE_AREA_TYPE_NONE); + } + + /** + * Creates a context with the resource vmsPublisherClients overridden. The overridden value + * contains the name of the test service defined also in this test package. + */ + @Override + protected Context getCarServiceContext() throws PackageManager.NameNotFoundException { + Context context = getContext() + .createPackageContext("com.android.car", Context.CONTEXT_IGNORE_SECURITY); + Resources resources = new Resources(context.getAssets(), + context.getResources().getDisplayMetrics(), + context.getResources().getConfiguration()) { + @Override + public String[] getStringArray(@ArrayRes int id) throws NotFoundException { + if (id == R.array.vmsPublisherClients) { + return new String[]{"com.android.car.test/.SimpleVmsPublisherClientService"}; + } + return super.getStringArray(id); + } + }; + ContextWrapper wrapper = new ContextWrapper(context) { + @Override + public Resources getResources() { + return resources; + } + }; + return wrapper; + } + + private VehiclePropValue getHalSubscriptionRequest() { + return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .addIntValue(VmsMessageType.SUBSCRIBE) + .addIntValue(MOCK_PUBLISHER_LAYER_ID) + .addIntValue(MOCK_PUBLISHER_LAYER_VERSION) + .build(); + } + + @Override + protected void setUp() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + /** + * First init the semaphore, setUp will start a series of events that will ultimately + * update the HAL layer and release this semaphore. + */ + mHalHandlerSemaphore = new Semaphore(0); + super.setUp(); + + // Inject a subscribe event which simulates the HAL is subscribed to the Mock Publisher. + MockedVehicleHal mHal = getMockedVehicleHal(); + mHal.injectEvent(getHalSubscriptionRequest()); + } + + @Override + protected synchronized void tearDown() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.tearDown(); + } + + /** + * The method setUp initializes all the Car services, including the VmsPublisherService. + * The VmsPublisherService will start and configure its list of clients. This list was + * overridden in the method getCarServiceContext. + * Therefore, only SimpleVmsPublisherClientService will be started. + * The service SimpleVmsPublisherClientService will publish one message, which is validated in + * this test. + */ + public void testPublish() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + //TODO: This test is using minial synchronisation between clients. + // If more complexity is added this may result in publisher + // publishing before the subscriber subscribed, in which case + // the semaphore will not be released. + assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + VehiclePropValue.RawValue rawValue = mHalHandler.getValue().value; + int messageType = rawValue.int32Values.get(VmsSimpleMessageIntegerValuesIndex.VMS_MESSAGE_TYPE); + int layerId = rawValue.int32Values.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID); + int layerVersion = rawValue.int32Values.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION); + byte[] payload = new byte[rawValue.bytes.size()]; + for (int i = 0; i < rawValue.bytes.size(); ++i) { + payload[i] = rawValue.bytes.get(i); + } + assertEquals(VmsMessageType.DATA, messageType); + assertEquals(MOCK_PUBLISHER_LAYER_ID, layerId); + assertEquals(MOCK_PUBLISHER_LAYER_VERSION, layerVersion); + assertTrue(Arrays.equals(PAYLOAD, payload)); + } + + private class HalHandler implements VehicleHalPropertyHandler { + private VehiclePropValue mValue; + + @Override + public synchronized void onPropertySet(VehiclePropValue value) { + mValue = value; + + // If this is the data message release the semaphone so the test can continue. + ArrayList<Integer> int32Values = value.value.int32Values; + if (int32Values.get(VmsBaseMessageIntegerValuesIndex.VMS_MESSAGE_TYPE) == + VmsMessageType.DATA) { + mHalHandlerSemaphore.release(); + } + } + + @Override + public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) { + return mValue != null ? mValue : value; + } + + @Override + public synchronized void onPropertySubscribe(int property, int zones, float sampleRate) { + Log.d(TAG, "onPropertySubscribe property " + property + " sampleRate " + sampleRate); + } + + @Override + public synchronized void onPropertyUnsubscribe(int property) { + Log.d(TAG, "onPropertyUnSubscribe property " + property); + } + + public VehiclePropValue getValue() { + return mValue; + } + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherPermissionsTest.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherPermissionsTest.java new file mode 100644 index 0000000000..739f5d00b5 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherPermissionsTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2017 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.test; + +import android.annotation.ArrayRes; +import android.car.VehicleAreaType; +import android.car.annotation.FutureFeature; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; +import android.hardware.automotive.vehicle.V2_1.VehicleProperty; +import android.hardware.automotive.vehicle.V2_1.VmsBaseMessageIntegerValuesIndex; +import android.hardware.automotive.vehicle.V2_1.VmsMessageType; + +import com.android.car.R; +import com.android.car.vehiclehal.VehiclePropValueBuilder; +import com.android.car.vehiclehal.test.MockedVehicleHal; +import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; + +import java.util.ArrayList; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@FutureFeature +public class VmsPublisherPermissionsTest extends MockedCarTestBase { + private static final String TAG = "VmsPublisherTest"; + private static final int MOCK_PUBLISHER_LAYER_ID = 0; + private static final int MOCK_PUBLISHER_LAYER_VERSION = 0; + + private HalHandler mHalHandler; + // Used to block until the HAL property is updated in HalHandler.onPropertySet. + private Semaphore mHalHandlerSemaphore; + + @Override + protected synchronized void configureMockedHal() { + mHalHandler = new HalHandler(); + addProperty(VehicleProperty.VEHICLE_MAP_SERVICE, mHalHandler) + .setChangeMode(VehiclePropertyChangeMode.ON_CHANGE) + .setAccess(VehiclePropertyAccess.READ_WRITE) + .setSupportedAreas(VehicleAreaType.VEHICLE_AREA_TYPE_NONE); + } + + /** + * Creates a context with the resource vmsPublisherClients overridden. The overridden value + * contains the name of the test service defined also in this test package. + */ + @Override + protected Context getCarServiceContext() throws PackageManager.NameNotFoundException { + Context context = getContext() + .createPackageContext("com.android.car", Context.CONTEXT_IGNORE_SECURITY); + Resources resources = new Resources(context.getAssets(), + context.getResources().getDisplayMetrics(), + context.getResources().getConfiguration()) { + @Override + public String[] getStringArray(@ArrayRes int id) throws NotFoundException { + if (id == R.array.vmsPublisherClients) { + return new String[]{ + "com.google.android.car.vms.publisher/" + + ".VmsPublisherClientSampleService"}; + } else if (id == R.array.vmsSafePermissions) { + return new String[]{"android.permission.ACCESS_FINE_LOCATION"}; + } + return super.getStringArray(id); + } + }; + ContextWrapper wrapper = new ContextWrapper(context) { + @Override + public Resources getResources() { + return resources; + } + }; + return wrapper; + } + + private VehiclePropValue getHalSubscriptionRequest() { + return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .addIntValue(VmsMessageType.SUBSCRIBE) + .addIntValue(MOCK_PUBLISHER_LAYER_ID) + .addIntValue(MOCK_PUBLISHER_LAYER_VERSION) + .build(); + } + + @Override + protected void setUp() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + /** + * First init the semaphore, setUp will start a series of events that will ultimately + * update the HAL layer and release this semaphore. + */ + mHalHandlerSemaphore = new Semaphore(0); + super.setUp(); + + // Inject a subscribe event which simulates the HAL is subscribed to the Sample Publisher. + MockedVehicleHal mHal = getMockedVehicleHal(); + mHal.injectEvent(getHalSubscriptionRequest()); + } + + @Override + protected synchronized void tearDown() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.tearDown(); + } + + /** + * The method setUp initializes all the Car services, including the VmsPublisherService. + * The VmsPublisherService will start and configure its list of clients. This list was + * overridden in the method getCarServiceContext. + * Therefore, only VmsPublisherClientSampleService will be started. + * The service VmsPublisherClientSampleService will publish one message, which is validated in + * this test. + */ + public void testPermissions() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + // At this point the client initialization finished. Let's validate the permissions. + // The VMS service is only allowed to grant ACCESS_FINE_LOCATION but not CAMERA. + assertTrue( + getContext().getPackageManager().checkPermission( + "android.permission.ACCESS_FINE_LOCATION", + "com.google.android.car.vms.publisher") + == PackageManager.PERMISSION_GRANTED); + assertFalse(getContext().getPackageManager().checkPermission( + "android.permission.CAMERA", "com.google.android.car.vms.publisher") + == PackageManager.PERMISSION_GRANTED); + } + + private class HalHandler implements VehicleHalPropertyHandler { + @Override + public synchronized void onPropertySet(VehiclePropValue value) { + // If this is the data message release the semaphore so the test can continue. + ArrayList<Integer> int32Values = value.value.int32Values; + if (int32Values.get(VmsBaseMessageIntegerValuesIndex.VMS_MESSAGE_TYPE) == + VmsMessageType.DATA) { + mHalHandlerSemaphore.release(); + } + } + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java new file mode 100644 index 0000000000..3b3a94fc48 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2017 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.test; + +import android.annotation.ArrayRes; +import android.car.Car; +import android.car.VehicleAreaType; +import android.car.annotation.FutureFeature; +import android.car.vms.VmsLayer; +import android.car.vms.VmsSubscriberManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; +import android.hardware.automotive.vehicle.V2_1.VehicleProperty; +import com.android.car.vehiclehal.test.MockedVehicleHal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@FutureFeature +public class VmsPublisherSubscriberTest extends MockedCarTestBase { + private static final int LAYER_ID = 88; + private static final int LAYER_VERSION = 19; + private static final int EXPECTED_PUBLISHER_ID = 0; + private static final String TAG = "VmsPubSubTest"; + + public static final VmsLayer LAYER = new VmsLayer(LAYER_ID, LAYER_VERSION); + public static final byte[] PAYLOAD = new byte[]{2, 3, 5, 7, 11, 13, 17}; + private static final List<VmsLayer> AVAILABLE_LAYERS = new ArrayList<>(Arrays.asList(LAYER)); + + private HalHandler mHalHandler; + // Used to block until a value is propagated to the TestListener.onVmsMessageReceived. + private Semaphore mSubscriberSemaphore; + private Semaphore mAvailabilitySemaphore; + + @Override + protected synchronized void configureMockedHal() { + mHalHandler = new HalHandler(); + addProperty(VehicleProperty.VEHICLE_MAP_SERVICE, mHalHandler) + .setChangeMode(VehiclePropertyChangeMode.ON_CHANGE) + .setAccess(VehiclePropertyAccess.READ_WRITE) + .setSupportedAreas(VehicleAreaType.VEHICLE_AREA_TYPE_NONE); + } + + /** + * Creates a context with the resource vmsPublisherClients overridden. The overridden value + * contains the name of the test service defined also in this test package. + */ + @Override + protected Context getCarServiceContext() throws PackageManager.NameNotFoundException { + Context context = getContext() + .createPackageContext("com.android.car", Context.CONTEXT_IGNORE_SECURITY); + Resources resources = new Resources(context.getAssets(), + context.getResources().getDisplayMetrics(), + context.getResources().getConfiguration()) { + @Override + public String[] getStringArray(@ArrayRes int id) throws NotFoundException { + if (id == com.android.car.R.array.vmsPublisherClients) { + return new String[]{"com.android.car.test/.VmsPublisherClientMockService"}; + } + return super.getStringArray(id); + } + }; + ContextWrapper wrapper = new ContextWrapper(context) { + @Override + public Resources getResources() { + return resources; + } + }; + return wrapper; + } + + @Override + protected void setUp() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.setUp(); + mSubscriberSemaphore = new Semaphore(0); + mAvailabilitySemaphore = new Semaphore(0); + } + + @Override + protected synchronized void tearDown() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.tearDown(); + } + + /** + * The method setUp initializes all the Car services, including the VmsPublisherService. + * The VmsPublisherService will start and configure its list of clients. This list was + * overridden in the method getCarServiceContext. Therefore, only VmsPublisherClientMockService + * will be started. This test method subscribes to a layer and triggers + * VmsPublisherClientMockService.onVmsSubscriptionChange. In turn, the mock service will publish + * a message, which is validated in this test. + */ + public void testPublisherToSubscriber() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribe(LAYER); + + assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + assertEquals(LAYER, listener.getLayer()); + assertTrue(Arrays.equals(PAYLOAD, listener.getPayload())); + } + + /** + * The Mock service will get a publisher ID by sending its information when it will get + * ServiceReady as well as on SubscriptionChange. Since clients are not notified when + * publishers are assigned IDs, this test waits until the availability is changed which indicates + * that the Mock service has gotten its ServiceReady and publisherId. + */ + public void testPublisherInfo() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + // Subscribe to layer as a way to make sure the mock client completed setting the information. + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribe(LAYER); + + assertTrue(mAvailabilitySemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + + byte[] info = vmsSubscriberManager.getPublisherInfo(EXPECTED_PUBLISHER_ID); + assertTrue(Arrays.equals(PAYLOAD, info)); + } + + /** + * The Mock service offers all the subscribed layers as available layers, so in this + * test the listener subscribes to a layer and verifies that it gets the notification that it + * is available. + */ + public void testAvailability() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribe(LAYER); + + assertTrue(mAvailabilitySemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + assertEquals(AVAILABLE_LAYERS, listener.getAvailalbeLayers()); + } + + private class HalHandler implements MockedVehicleHal.VehicleHalPropertyHandler { + } + + private class TestListener implements VmsSubscriberManager.VmsSubscriberClientListener { + private VmsLayer mLayer; + private byte[] mPayload; + private List<VmsLayer> mAvailableLayers; + + @Override + public void onVmsMessageReceived(VmsLayer layer, byte[] payload) { + assertEquals(LAYER, layer); + assertTrue(Arrays.equals(PAYLOAD, payload)); + mLayer = layer; + mPayload = payload; + mSubscriberSemaphore.release(); + } + + @Override + public void onLayersAvailabilityChange(List<VmsLayer> availableLayers) { + assertEquals(AVAILABLE_LAYERS, availableLayers); + mAvailableLayers = availableLayers; + mAvailabilitySemaphore.release(); + } + + @Override + public void onCarDisconnected() { + + } + + public VmsLayer getLayer() { + return mLayer; + } + + public byte[] getPayload() { + return mPayload; + } + + public List<VmsLayer> getAvailalbeLayers() { + return mAvailableLayers; + } + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java new file mode 100644 index 0000000000..063d5cff32 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2017 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.test; + +import android.car.Car; +import android.car.VehicleAreaType; +import android.car.annotation.FutureFeature; +import android.car.vms.VmsLayer; +import android.car.vms.VmsSubscriberManager; +import android.car.vms.VmsSubscriberManager.VmsSubscriberClientListener; +import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; +import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; +import android.hardware.automotive.vehicle.V2_1.VehicleProperty; +import android.hardware.automotive.vehicle.V2_1.VmsMessageType; +import android.os.SystemClock; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; +import com.android.car.vehiclehal.VehiclePropValueBuilder; +import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@FutureFeature +@MediumTest +public class VmsSubscriberManagerTest extends MockedCarTestBase { + private static final String TAG = "VmsSubscriberManagerTest"; + private static final int SUBSCRIPTION_LAYER_ID = 2; + private static final int SUBSCRIPTION_LAYER_VERSION = 3; + private static final VmsLayer SUBSCRIPTION_LAYER = new VmsLayer(SUBSCRIPTION_LAYER_ID, + SUBSCRIPTION_LAYER_VERSION); + + private static final int SUBSCRIPTION_DEPENDANT_LAYER_ID_1 = 4; + private static final int SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1 = 5; + private static final VmsLayer SUBSCRIPTION_DEPENDANT_LAYER_1 = + new VmsLayer(SUBSCRIPTION_DEPENDANT_LAYER_ID_1, SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1); + + private static final int SUBSCRIPTION_DEPENDANT_LAYER_ID_2 = 6; + private static final int SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2 = 7; + private static final VmsLayer SUBSCRIPTION_DEPENDANT_LAYER_2 = + new VmsLayer(SUBSCRIPTION_DEPENDANT_LAYER_ID_2, SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2); + + private static final int SUBSCRIPTION_UNSUPPORTED_LAYER_ID = 100; + private static final int SUBSCRIPTION_UNSUPPORTED_LAYER_VERSION = 200; + + + private HalHandler mHalHandler; + // Used to block until the HAL property is updated in HalHandler.onPropertySet. + private Semaphore mHalHandlerSemaphore; + // Used to block until a value is propagated to the TestListener.onVmsMessageReceived. + private Semaphore mSubscriberSemaphore; + + @Override + protected synchronized void configureMockedHal() { + mHalHandler = new HalHandler(); + addProperty(VehicleProperty.VEHICLE_MAP_SERVICE, mHalHandler) + .setChangeMode(VehiclePropertyChangeMode.ON_CHANGE) + .setAccess(VehiclePropertyAccess.READ_WRITE) + .setSupportedAreas(VehicleAreaType.VEHICLE_AREA_TYPE_NONE); + } + + @Override + protected void setUp() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.setUp(); + mSubscriberSemaphore = new Semaphore(0); + mHalHandlerSemaphore = new Semaphore(0); + } + + @Override + protected synchronized void tearDown() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + super.tearDown(); + } + + // Test injecting a value in the HAL and verifying it propagates to a subscriber. + public void testSubscribe() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER); + + // Inject a value and wait for its callback in TestListener.onVmsMessageReceived. + VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE) + .setTimestamp(SystemClock.elapsedRealtimeNanos()) + .build(); + v.value.int32Values.add(VmsMessageType.DATA); // MessageType + v.value.int32Values.add(SUBSCRIPTION_LAYER_ID); + v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION); + v.value.bytes.add((byte) 0xa); + v.value.bytes.add((byte) 0xb); + assertEquals(0, mSubscriberSemaphore.availablePermits()); + + getMockedVehicleHal().injectEvent(v); + assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + assertEquals(SUBSCRIPTION_LAYER, listener.getLayer()); + byte[] expectedPayload = {(byte) 0xa, (byte) 0xb}; + assertTrue(Arrays.equals(expectedPayload, listener.getPayload())); + } + + + // Test injecting a value in the HAL and verifying it propagates to a subscriber. + public void testSubscribeAll() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribeAll(); + + // Inject a value and wait for its callback in TestListener.onVmsMessageReceived. + VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE) + .setTimestamp(SystemClock.elapsedRealtimeNanos()) + .build(); + v.value.int32Values.add(VmsMessageType.DATA); // MessageType + v.value.int32Values.add(SUBSCRIPTION_LAYER_ID); + v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION); + v.value.bytes.add((byte) 0xa); + v.value.bytes.add((byte) 0xb); + assertEquals(0, mSubscriberSemaphore.availablePermits()); + + getMockedVehicleHal().injectEvent(v); + assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + assertEquals(SUBSCRIPTION_LAYER, listener.getLayer()); + byte[] expectedPayload = {(byte) 0xa, (byte) 0xb}; + assertTrue(Arrays.equals(expectedPayload, listener.getPayload())); + } + + + // Test injecting a value in the HAL and verifying it propagates to a subscriber. + public void testSimpleAvailableLayers() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER); + + // Inject a value and wait for its callback in TestListener.onLayersAvailabilityChange. + VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE) + .setTimestamp(SystemClock.elapsedRealtimeNanos()) + .build(); + /* + Offering: + Layer | Dependency + ==================== + (2, 3) | {} + + Expected availability: + {(2, 3)} + */ + v.value.int32Values.addAll( + Arrays.asList( + VmsMessageType.OFFERING, // MessageType + 1, // Number of offered layers + + SUBSCRIPTION_LAYER_ID, + SUBSCRIPTION_LAYER_VERSION, + 0 // number of dependencies for layer + ) + ); + + assertEquals(0, mSubscriberSemaphore.availablePermits()); + + List<VmsLayer> expectedAvailableLayers = new ArrayList<>(Arrays.asList(SUBSCRIPTION_LAYER)); + + getMockedVehicleHal().injectEvent(v); + assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + assertEquals(expectedAvailableLayers, listener.getAvailableLayers()); + } + + // Test injecting a value in the HAL and verifying it propagates to a subscriber. + public void testComplexAvailableLayers() throws Exception { + if (!VmsTestUtils.canRunTest(TAG)) return; + VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager( + Car.VMS_SUBSCRIBER_SERVICE); + TestListener listener = new TestListener(); + vmsSubscriberManager.setListener(listener); + vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER); + + // Inject a value and wait for its callback in TestListener.onLayersAvailabilityChange. + VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE) + .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE) + .setTimestamp(SystemClock.elapsedRealtimeNanos()) + .build(); + /* + Offering: + Layer | Dependency + ==================== + (2, 3) | {} + (4, 5) | {(2, 3)} + (6, 7) | {(2, 3), (4, 5)} + (6, 7) | {(100, 200)} + + Expected availability: + {(2, 3), (4, 5), (6, 7)} + */ + + v.value.int32Values.addAll( + Arrays.asList( + VmsMessageType.OFFERING, // MessageType + 4, // Number of offered layers + + SUBSCRIPTION_LAYER_ID, + SUBSCRIPTION_LAYER_VERSION, + 0, // number of dependencies for layer + + SUBSCRIPTION_DEPENDANT_LAYER_ID_1, + SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1, + 1, // number of dependencies for layer + SUBSCRIPTION_LAYER_ID, + SUBSCRIPTION_LAYER_VERSION, + + SUBSCRIPTION_DEPENDANT_LAYER_ID_2, + SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2, + 2, // number of dependencies for layer + SUBSCRIPTION_LAYER_ID, + SUBSCRIPTION_LAYER_VERSION, + SUBSCRIPTION_DEPENDANT_LAYER_ID_1, + SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1, + + SUBSCRIPTION_DEPENDANT_LAYER_ID_2, + SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2, + 1, // number of dependencies for layer + SUBSCRIPTION_UNSUPPORTED_LAYER_ID, + SUBSCRIPTION_UNSUPPORTED_LAYER_VERSION + ) + ); + + assertEquals(0, mSubscriberSemaphore.availablePermits()); + + List<VmsLayer> expectedAvailableLayers = + new ArrayList<>(Arrays.asList(SUBSCRIPTION_LAYER, + SUBSCRIPTION_DEPENDANT_LAYER_1, + SUBSCRIPTION_DEPENDANT_LAYER_2)); + + getMockedVehicleHal().injectEvent(v); + assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS)); + assertEquals(expectedAvailableLayers, listener.getAvailableLayers()); + } + + private class HalHandler implements VehicleHalPropertyHandler { + private VehiclePropValue mValue; + + @Override + public synchronized void onPropertySet(VehiclePropValue value) { + mValue = value; + mHalHandlerSemaphore.release(); + } + + @Override + public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) { + return mValue != null ? mValue : value; + } + + @Override + public synchronized void onPropertySubscribe(int property, int zones, float sampleRate) { + Log.d(TAG, "onPropertySubscribe property " + property + " sampleRate " + sampleRate); + } + + @Override + public synchronized void onPropertyUnsubscribe(int property) { + Log.d(TAG, "onPropertyUnSubscribe property " + property); + } + + public VehiclePropValue getValue() { + return mValue; + } + } + + + private class TestListener implements VmsSubscriberClientListener{ + private VmsLayer mLayer; + private byte[] mPayload; + private List<VmsLayer> mAvailableLayers = new ArrayList<>(); + + @Override + public void onVmsMessageReceived(VmsLayer layer, byte[] payload) { + Log.d(TAG, "onVmsMessageReceived: layer: " + layer + " Payload: " + payload); + mLayer = layer; + mPayload = payload; + mSubscriberSemaphore.release(); + } + + @Override + public void onLayersAvailabilityChange(List<VmsLayer> availableLayers) { + Log.d(TAG, "onLayersAvailabilityChange: Layers: " + availableLayers); + mAvailableLayers.addAll(availableLayers); + mSubscriberSemaphore.release(); + } + + @Override + public void onCarDisconnected() { + + } + + public VmsLayer getLayer() { + return mLayer; + } + + public byte[] getPayload() { + return mPayload; + } + + public List<VmsLayer> getAvailableLayers() { + return mAvailableLayers; + } + } +} diff --git a/tests/carservice_test/src/com/android/car/test/VmsTestUtils.java b/tests/carservice_test/src/com/android/car/test/VmsTestUtils.java new file mode 100644 index 0000000000..2f3af52a09 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/VmsTestUtils.java @@ -0,0 +1,16 @@ +package com.android.car.test; + +import android.car.annotation.FutureFeature; +import android.util.Log; + +import com.android.car.internal.FeatureConfiguration; + +@FutureFeature +public class VmsTestUtils { + public static boolean canRunTest(String tag) { + if (!FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) { + Log.i(tag, "Skipping test because ENABLE_VEHICLE_MAP_SERVICE = false"); + } + return FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE; + } +} diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java b/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java index ac9654c4f3..d6fd68d831 100644 --- a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java +++ b/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java @@ -23,6 +23,7 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -64,6 +65,15 @@ public class VmsLayersAvailabilityTest extends AndroidTestCase { super.setUp(); } + public void testNoOffering() { + assertTrue(mLayersAvailability.getAvailableLayers().isEmpty()); + } + + public void testEmptyOffering() { + mLayersAvailability.setPublishersOffering(Collections.EMPTY_LIST); + assertTrue(mLayersAvailability.getAvailableLayers().isEmpty()); + } + public void testSingleLayerNoDeps() throws Exception { Set<VmsLayer> expectedAvailableLayers = new HashSet<>(); expectedAvailableLayers.add(LAYER_X); diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java new file mode 100644 index 0000000000..2b75012950 --- /dev/null +++ b/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 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; + +import android.car.annotation.FutureFeature; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.Arrays; +import java.util.Map; + +@FutureFeature +@SmallTest +public class VmsPublishersInfoTest extends AndroidTestCase { + public static final byte[] MOCK_INFO_0 = new byte[]{2, 3, 5, 7, 11, 13, 17}; + public static final byte[] SAME_MOCK_INFO_0 = new byte[]{2, 3, 5, 7, 11, 13, 17}; + public static final byte[] MOCK_INFO_1 = new byte[]{2, 3, 5, 7, 11, 13, 17, 19}; + + private VmsPublishersInfo mVmsPublishersInfo; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mVmsPublishersInfo = new VmsPublishersInfo(); + } + + // Test one info sanity + public void testSingleInfo() throws Exception { + int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0); + assertEquals(0, id); + + byte[] info = mVmsPublishersInfo.getPublisherInfo(id); + assertTrue(Arrays.equals(MOCK_INFO_0, info)); + } + + // Test one info sanity - wrong ID fails. + public void testSingleInfoWrongId() throws Exception { + int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0); + assertEquals(0, id); + + try { + byte[] info = mVmsPublishersInfo.getPublisherInfo(id + 1); + } + catch (NullPointerException e) { + return; + } + fail(); + } + + // Test two infos. + public void testTwoInfos() throws Exception { + int id0 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0); + int id1 = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_1); + assertEquals(0, id0); + assertEquals(1, id1); + + byte[] info0 = mVmsPublishersInfo.getPublisherInfo(id0); + byte[] info1 = mVmsPublishersInfo.getPublisherInfo(id1); + assertTrue(Arrays.equals(MOCK_INFO_0, info0)); + assertTrue(Arrays.equals(MOCK_INFO_1, info1)); + } + + // Test same info twice get the same ID. + public void testSingleInfoInsertedTwice() throws Exception { + int id = mVmsPublishersInfo.getIdForInfo(MOCK_INFO_0); + assertEquals(0, id); + + int sameId = mVmsPublishersInfo.getIdForInfo(SAME_MOCK_INFO_0); + assertEquals(sameId, id); + } +} diff --git a/car-ui-provider/Android.mk b/tests/obd2_app/Android.mk index f63674aa34..7e1e22fa39 100644 --- a/car-ui-provider/Android.mk +++ b/tests/obd2_app/Android.mk @@ -1,4 +1,4 @@ -# Copyright (C) 2015 The Android Open Source Project +# Copyright (C) 2017 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. @@ -14,23 +14,27 @@ # # -#disble build in PDK, should add prebuilts/fullsdk to make this work -ifneq ($(TARGET_BUILD_PDK),true) - LOCAL_PATH:= $(call my-dir) + include $(CLEAR_VARS) -LOCAL_CERTIFICATE := platform -LOCAL_MODULE_TAGS := optional -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_PACKAGE_NAME := CarUiProvider +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := Obd2App + +LOCAL_AAPT_FLAGS := --auto-add-overlay + +LOCAL_MODULE_TAGS := optional + LOCAL_PROGUARD_ENABLED := disabled -LOCAL_DEX_PREOPT := nostripping -include packages/services/Car/car-support-lib/car-support.mk +LOCAL_DEX_PREOPT := false -include $(BUILD_PACKAGE) +LOCAL_STATIC_JAVA_LIBRARIES += \ + com.android.car.obd2 \ + +LOCAL_JAVA_VERSION := 1.8 -endif #TARGET_BUILD_PDK +include $(BUILD_PACKAGE) diff --git a/tests/obd2_app/AndroidManifest.xml b/tests/obd2_app/AndroidManifest.xml new file mode 100644 index 0000000000..184d939c52 --- /dev/null +++ b/tests/obd2_app/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright (C) 2017 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 + + + 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.google.android.car.obd2app"> + + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <application + android:allowBackup="true" + android:debuggable="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".SettingsActivity"/> + </application> + +</manifest> diff --git a/tests/obd2_app/res/drawable/ic_info_black_24dp.xml b/tests/obd2_app/res/drawable/ic_info_black_24dp.xml new file mode 100644 index 0000000000..b9139d16f1 --- /dev/null +++ b/tests/obd2_app/res/drawable/ic_info_black_24dp.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/> +</vector> diff --git a/tests/obd2_app/res/drawable/ic_notifications_black_24dp.xml b/tests/obd2_app/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000000..486956c505 --- /dev/null +++ b/tests/obd2_app/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp"> + <path + android:fillColor="#FF000000" + android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/> +</vector> diff --git a/tests/obd2_app/res/drawable/ic_sync_black_24dp.xml b/tests/obd2_app/res/drawable/ic_sync_black_24dp.xml new file mode 100644 index 0000000000..8511efac80 --- /dev/null +++ b/tests/obd2_app/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp"> + <path + android:fillColor="#FF000000" + android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z"/> +</vector>
\ No newline at end of file diff --git a/tests/obd2_app/res/layout/activity_main.xml b/tests/obd2_app/res/layout/activity_main.xml new file mode 100644 index 0000000000..29d7bd2f6d --- /dev/null +++ b/tests/obd2_app/res/layout/activity_main.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/activity_main" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + tools:context="com.google.android.car.obd2app.MainActivity"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" + android:id="@+id/statusBar" + android:layout_alignParentEnd="true" + android:text="Nothing to say" + android:minLines="10"/> + <Button + android:text="Connect" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:id="@+id/connection"/> + <Button + android:text="Settings" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/settings" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:onClick="doSettings"/> +</RelativeLayout> diff --git a/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..cde69bccce --- /dev/null +++ b/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png diff --git a/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..c133a0cbd3 --- /dev/null +++ b/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png diff --git a/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..bfa42f0e7b --- /dev/null +++ b/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png diff --git a/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..324e72cdd7 --- /dev/null +++ b/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png diff --git a/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..aee44e1384 --- /dev/null +++ b/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/tests/obd2_app/res/values-w820dp/dimens.xml b/tests/obd2_app/res/values-w820dp/dimens.xml new file mode 100644 index 0000000000..1da9658933 --- /dev/null +++ b/tests/obd2_app/res/values-w820dp/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/tests/obd2_app/res/values/arrays.xml b/tests/obd2_app/res/values/arrays.xml new file mode 100644 index 0000000000..2bd7ce3895 --- /dev/null +++ b/tests/obd2_app/res/values/arrays.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string-array name="scan_delay_entries"> + <item>2 seconds</item> + <item>5 seconds</item> + <item>10 seconds</item> + </string-array> + <string-array name="scan_delay_entryValues"> + <item>2</item> + <item>5</item> + <item>10</item> + </string-array> +</resources> diff --git a/car-ui-provider/AndroidManifest.xml b/tests/obd2_app/res/values/colors.xml index ffbdfed3ee..49a370ab86 100644 --- a/car-ui-provider/AndroidManifest.xml +++ b/tests/obd2_app/res/values/colors.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2017 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. @@ -14,10 +14,8 @@ limitations under the License. --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - package="android.car.ui.provider" > - <uses-sdk android:minSdkVersion="23" - android:targetSdkVersion="23" /> - <application android:label="@string/app_name" /> -</manifest> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources> diff --git a/tests/obd2_app/res/values/dimens.xml b/tests/obd2_app/res/values/dimens.xml new file mode 100644 index 0000000000..261477ecb5 --- /dev/null +++ b/tests/obd2_app/res/values/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/tests/obd2_app/res/values/strings.xml b/tests/obd2_app/res/values/strings.xml new file mode 100644 index 0000000000..c615c4f2da --- /dev/null +++ b/tests/obd2_app/res/values/strings.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="app_name">Obd2App</string> + <string name="title_activity_settings">Settings</string> + + <!-- Strings related to Settings --> + + <!-- Example General settings --> + <string name="pref_header_general">General</string> + + <string name="pref_title_social_recommendations">Enable social recommendations</string> + <string name="pref_description_social_recommendations">Recommendations for people to contact based + on your message history + </string> + + <string name="pref_title_display_name">Display name</string> + <string name="pref_default_display_name">John Smith</string> + + <string name="pref_title_add_friends_to_messages">Add friends to messages</string> + <string-array name="pref_example_list_titles"> + <item>Always</item> + <item>When possible</item> + <item>Never</item> + </string-array> + <string-array name="pref_example_list_values"> + <item>1</item> + <item>0</item> + <item>-1</item> + </string-array> + + <!-- Example settings for Data & Sync --> + <string name="pref_header_data_sync">Data & sync</string> + + <string name="pref_title_sync_frequency">Sync frequency</string> + <string-array name="pref_sync_frequency_titles"> + <item>15 minutes</item> + <item>30 minutes</item> + <item>1 hour</item> + <item>3 hours</item> + <item>6 hours</item> + <item>Never</item> + </string-array> + <string-array name="pref_sync_frequency_values"> + <item>15</item> + <item>30</item> + <item>60</item> + <item>180</item> + <item>360</item> + <item>-1</item> + </string-array> + + <string-array name="list_preference_entries"> + <item>Entry 1</item> + <item>Entry 2</item> + <item>Entry 3</item> + </string-array> + + <string-array name="list_preference_entry_values"> + <item>1</item> + <item>2</item> + <item>3</item> + </string-array> + + <string-array name="multi_select_list_preference_default_value"/> + + <string name="pref_title_system_sync_settings">System sync settings</string> + + <!-- Example settings for Notifications --> + <string name="pref_header_notifications">Notifications</string> + + <string name="pref_title_new_message_notifications">New message notifications</string> + + <string name="pref_title_ringtone">Ringtone</string> + <string name="pref_ringtone_silent">Silent</string> + + <string name="pref_title_vibrate">Vibrate</string> +</resources> diff --git a/tests/obd2_app/res/values/styles.xml b/tests/obd2_app/res/values/styles.xml new file mode 100644 index 0000000000..c21c0ac43e --- /dev/null +++ b/tests/obd2_app/res/values/styles.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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> + <style name="AppTheme" parent="android:Theme.Material"> + </style> + +</resources> diff --git a/tests/obd2_app/res/xml/preferences.xml b/tests/obd2_app/res/xml/preferences.xml new file mode 100644 index 0000000000..0c6f53400f --- /dev/null +++ b/tests/obd2_app/res/xml/preferences.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <com.google.android.car.obd2app.BluetoothPreference + android:key="bluetooth_mac" + android:title="OBD2 Dongle" + android:dialogTitle="Select OBD2 Scanner" /> + <com.google.android.car.obd2app.IntegerListPreference + android:key="scan_delay" + android:title="Time between queries" + android:dialogTitle="Select Delay" + android:entries="@array/scan_delay_entries" + android:entryValues="@array/scan_delay_entryValues" + android:defaultValue="2"/> +</PreferenceScreen> diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/BluetoothPreference.java b/tests/obd2_app/src/com/google/android/car/obd2app/BluetoothPreference.java new file mode 100644 index 0000000000..59da4c07cb --- /dev/null +++ b/tests/obd2_app/src/com/google/android/car/obd2app/BluetoothPreference.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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.obd2app; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.preference.ListPreference; +import android.util.AttributeSet; + +import java.util.ArrayList; +import java.util.List; + +public class BluetoothPreference extends ListPreference { + private static final class DeviceEntry { + private final String mName; + private final String mAddress; + + DeviceEntry(BluetoothDevice device) { + mAddress = device.getAddress(); + if (device.getName() == null) { + mName = mAddress; + } else { + mName = device.getName(); + } + } + + String getName() { + return mName; + } + + String getAddress() { + return mAddress; + } + } + + public BluetoothPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter(); + List<DeviceEntry> pairedDevices = new ArrayList<>(); + defaultAdapter + .getBondedDevices() + .forEach((BluetoothDevice device) -> pairedDevices.add(new DeviceEntry(device))); + setEntries(pairedDevices.stream().map(DeviceEntry::getName).toArray(String[]::new)); + setEntryValues(pairedDevices.stream().map(DeviceEntry::getAddress).toArray(String[]::new)); + } + + public BluetoothPreference(Context context) { + this(context, null); + } +} diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/IntegerListPreference.java b/tests/obd2_app/src/com/google/android/car/obd2app/IntegerListPreference.java new file mode 100644 index 0000000000..6e9e9dceeb --- /dev/null +++ b/tests/obd2_app/src/com/google/android/car/obd2app/IntegerListPreference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 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.obd2app; + +import android.content.Context; +import android.preference.ListPreference; +import android.util.AttributeSet; + +public class IntegerListPreference extends ListPreference { + public IntegerListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public IntegerListPreference(Context context) { + super(context); + } + + @Override + protected boolean persistString(String value) { + return value != null && persistInt(Integer.valueOf(value)); + } + + @Override + protected String getPersistedString(String defaultReturnValue) { + if (getSharedPreferences().contains(getKey())) { + return String.valueOf(getPersistedInt(2)); + } else { + return defaultReturnValue; + } + } +} diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/MainActivity.java b/tests/obd2_app/src/com/google/android/car/obd2app/MainActivity.java new file mode 100644 index 0000000000..dc38b5c604 --- /dev/null +++ b/tests/obd2_app/src/com/google/android/car/obd2app/MainActivity.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 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.obd2app; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; +import java.util.Timer; + +public class MainActivity extends Activity implements StatusNotification { + public static final String TAG = MainActivity.class.getSimpleName(); + + private static final String BLUETOOTH_MAC_PREFERENCE_ID = "bluetooth_mac"; + private static final String SCAN_DELAY_PREFERENCE_ID = "scan_delay"; + + private Obd2CollectionTask mCollectionTask = null; + private final Timer mTimer = new Timer("com.google.android.car.obd2app.collection"); + + private String getBluetoothDongleMacFromPreferences(String defaultValue) { + SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(this); + return appPreferences.getString(BLUETOOTH_MAC_PREFERENCE_ID, defaultValue); + } + + private int getScanDelayFromPreferences(int defaultValue) { + SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(this); + return appPreferences.getInt(SCAN_DELAY_PREFERENCE_ID, defaultValue); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); + String bluetoothDongleMac = getBluetoothDongleMacFromPreferences(""); + if (TextUtils.isEmpty(bluetoothDongleMac)) { + notifyNoDongle(); + } else { + notifyPaired(bluetoothDongleMac); + } + findViewById(R.id.connection) + .setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + handleConnection(v); + } + }); + Log.i(TAG, "I did all the things"); + } + + private void stopConnection() { + mCollectionTask.cancel(); + mTimer.purge(); + mCollectionTask = null; + } + + @Override + protected void onDestroy() { + stopConnection(); + } + + public void doSettings(View view) { + Intent launchSettings = new Intent(this, SettingsActivity.class); + startActivity(launchSettings); + } + + @Override + public void notify(String status) { + Log.i(TAG, status); + runOnUiThread(() -> ((TextView) findViewById(R.id.statusBar)).setText(status)); + } + + public void handleConnection(View view) { + String deviceAddress = getBluetoothDongleMacFromPreferences(""); + Log.i(TAG, "Considering a connection to " + deviceAddress); + if (TextUtils.isEmpty(deviceAddress)) { + notifyNoDongle(); + } + if (mCollectionTask == null) { + mCollectionTask = Obd2CollectionTask.create(this, this, deviceAddress); + if (null == mCollectionTask) { + notifyConnectionFailed(); + return; + } + final int delay = 1000 * getScanDelayFromPreferences(2); + mTimer.scheduleAtFixedRate(mCollectionTask, delay, delay); + ((Button) view).setText("Disconnect"); + } else { + stopConnection(); + ((Button) view).setText("Connect"); + } + } +} diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java b/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java new file mode 100644 index 0000000000..b38cf30b32 --- /dev/null +++ b/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 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.obd2app; + +import android.annotation.Nullable; +import android.content.Context; +import android.os.Environment; +import android.os.SystemClock; +import android.util.JsonWriter; +import android.util.Log; +import com.android.car.obd2.Obd2Connection; +import com.android.car.obd2.Obd2FreezeFrameGenerator; +import com.android.car.obd2.Obd2LiveFrameGenerator; +import com.android.car.obd2.connections.BluetoothConnection; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Objects; +import java.util.TimerTask; + +public class Obd2CollectionTask extends TimerTask { + private final Obd2Connection mConnection; + private final Obd2LiveFrameGenerator mLiveFrameGenerator; + private final Obd2FreezeFrameGenerator mFreezeFrameGenerator; + private final StatusNotification mStatusNotification; + private final JsonWriter mJsonWriter; + + public static @Nullable Obd2CollectionTask create( + Context context, StatusNotification statusNotification, String deviceAddress) { + try { + return new Obd2CollectionTask( + Objects.requireNonNull(context), + Objects.requireNonNull(statusNotification), + Objects.requireNonNull(deviceAddress)); + } catch (IOException | InterruptedException | IllegalStateException e) { + Log.i(MainActivity.TAG, "Connection failed due to exception", e); + return null; + } + } + + @Override + public boolean cancel() { + synchronized (mJsonWriter) { + try { + mJsonWriter.endArray(); + mJsonWriter.flush(); + mJsonWriter.close(); + } catch (IOException e) { + Log.w(MainActivity.TAG, "IOException during close", e); + } + return super.cancel(); + } + } + + @Override + public void run() { + if (!mConnection.isConnected()) { + if (!mConnection.reconnect()) { + mStatusNotification.notifyDisconnected(); + return; + } + } + + try { + synchronized (mJsonWriter) { + mLiveFrameGenerator.generate(mJsonWriter); + mFreezeFrameGenerator.generate(mJsonWriter); + mJsonWriter.flush(); + } + mStatusNotification.notifyDataCapture(); + } catch (Exception e) { + mStatusNotification.notifyException(e); + } + } + + Obd2CollectionTask(Context context, StatusNotification statusNotification, String deviceAddress) + throws IOException, InterruptedException { + if (!isExternalStorageWriteable()) + throw new IOException("Cannot write data to external storage"); + mStatusNotification = statusNotification; + BluetoothConnection bluetoothConnection = new BluetoothConnection(deviceAddress); + if (!bluetoothConnection.isConnected()) { + statusNotification.notifyConnectionFailed(); + throw new IllegalStateException("Unable to connect to remote end."); + } + mConnection = new Obd2Connection(bluetoothConnection); + mLiveFrameGenerator = new Obd2LiveFrameGenerator(mConnection); + mFreezeFrameGenerator = new Obd2FreezeFrameGenerator(mConnection); + mJsonWriter = + new JsonWriter( + new OutputStreamWriter( + new FileOutputStream(getFilenameForStorage(context)))); + mJsonWriter.beginArray(); + } + + private static boolean isExternalStorageWriteable() { + String state = Environment.getExternalStorageState(); + return (Environment.MEDIA_MOUNTED.equals(state)); + } + + private static File getFilenameForStorage(Context context) { + String basename = String.format("obd2app.capture.%d", SystemClock.elapsedRealtimeNanos()); + return new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), basename); + } +} diff --git a/car-support-lib/src/android/support/car/ui/PathClippingView.java b/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java index 812977a4e5..23f120e2a9 100644 --- a/car-support-lib/src/android/support/car/ui/PathClippingView.java +++ b/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.car.ui; -import android.graphics.Path; +package com.google.android.car.obd2app; -/** - * Interface for a view that can apply a clip given to it in the form of a {@link android.graphics.Path}. - * @hide - */ -public interface PathClippingView { - /** - * Notify listener of a new clip path. - * @param clipPath Clipping path. If {@code null}, clip should no longer be performed. - */ - void setClipPath(Path clipPath); +import android.os.Bundle; +import android.preference.PreferenceActivity; +public class SettingsActivity extends PreferenceActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } } diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/StatusNotification.java b/tests/obd2_app/src/com/google/android/car/obd2app/StatusNotification.java new file mode 100644 index 0000000000..185c384fa1 --- /dev/null +++ b/tests/obd2_app/src/com/google/android/car/obd2app/StatusNotification.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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.obd2app; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +interface StatusNotification { + void notify(String status); + + default void notifyNoDongle() { + notify("No OBD2 dongle paired. Go to Settings."); + } + + default void notifyPaired(String deviceAddress) { + notify("Paired to " + deviceAddress + ". Ready to capture data."); + } + + default void notifyConnectionFailed() { + notify("Unable to connect."); + } + + default void notifyConnected(String deviceAddress) { + notify("Connected to " + deviceAddress + ". Starting data capture."); + } + + default void notifyDataCapture() { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MMMM dd yyyy hh:mm:ssa"); + notify("Successfully captured data at " + now.format(dateTimeFormatter)); + } + + default void notifyException(Exception e) { + StringWriter stringWriter = new StringWriter(1024); + e.printStackTrace(new PrintWriter(stringWriter)); + notify("Exception occurred.\n" + stringWriter.toString()); + } + + default void notifyDisconnected() { + notify("Lost connection to remote end. Will try to reconnect."); + } +} diff --git a/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java b/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java index 3c3b1e6e90..e9bf98b85d 100644 --- a/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java +++ b/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java @@ -59,4 +59,14 @@ public class IntegerArrayStreamTest { assertFalse(stream.expect(4, 6)); assertEquals(5, stream.peek()); } + + @Test + public void testIsEmpty() { + IntegerArrayStream stream = new IntegerArrayStream(DATA_SET); + assertFalse(stream.isEmpty()); + stream.expect(1, 2, 3, 4, 5); + assertFalse(stream.isEmpty()); + stream.consume(); + assertTrue(stream.isEmpty()); + } } diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java index b342cbc8c6..f4ac13dbb7 100644 --- a/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java +++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java @@ -106,7 +106,7 @@ public class Obd2CommandTest { String[] commandToSend = new String[] {String.format("02%02X 01\r", pid)}; String[] responseToGet = - new String[] {String.format("02 %02X 01 %s", pid, responseBytes), OBD2_PROMPT}; + new String[] {String.format("42 %02X 01 %s", pid, responseBytes), OBD2_PROMPT}; MockObd2UnderlyingTransport transport = new MockObd2UnderlyingTransport( @@ -134,7 +134,7 @@ public class Obd2CommandTest { String[] commandToSend = new String[] {String.format("02%02X 01\r", pid)}; String[] responseToGet = - new String[] {String.format("02 %02X 01 %s", pid, responseBytes), OBD2_PROMPT}; + new String[] {String.format("42 %02X 01 %s", pid, responseBytes), OBD2_PROMPT}; MockObd2UnderlyingTransport transport = new MockObd2UnderlyingTransport( @@ -213,7 +213,7 @@ public class Obd2CommandTest { @Test public void testRpm() { - checkCommand(0x0C, "12 0F", 4611); + checkCommand(0x0C, "12 0F", 1155); } @Test diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2FreezeFrameGeneratorTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2FreezeFrameGeneratorTest.java new file mode 100644 index 0000000000..20919a14eb --- /dev/null +++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2FreezeFrameGeneratorTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 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.obd2.test; + +import static android.hardware.automotive.vehicle.V2_1.VehicleProperty.OBD2_FREEZE_FRAME; +import static com.android.car.obd2.test.Utils.concatIntArrays; +import static com.android.car.obd2.test.Utils.stringsToIntArray; +import static org.junit.Assert.*; + +import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; +import android.util.JsonReader; +import android.util.JsonWriter; +import com.android.car.obd2.Obd2Connection; +import com.android.car.obd2.Obd2FreezeFrameGenerator; +import com.android.car.vehiclehal.DiagnosticJsonReader; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.Test; + +public class Obd2FreezeFrameGeneratorTest { + private static final String[] EXPECTED_INIT_COMMANDS = + new String[] { + "ATD\r", "ATZ\r", "AT E0\r", "AT L0\r", "AT S0\r", "AT H0\r", "AT SP 0\r" + }; + + private static final String OBD2_PROMPT = ">"; + + private static final String[] EXPECTED_INIT_RESPONSES = + new String[] { + OBD2_PROMPT, + OBD2_PROMPT, + OBD2_PROMPT, + OBD2_PROMPT, + OBD2_PROMPT, + OBD2_PROMPT, + OBD2_PROMPT + }; + + private static final String[] EXPECTED_DISCOVERY_COMMANDS = + new String[] {"0100\r", "0120\r", "0140\r", "0160\r"}; + + private static final String[] EXPECTED_DISCOVERY_RESPONSES = + new String[] {"00 00 00 18 00 00", OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT}; + + private static final String[] EXPECTED_MODE3_COMMANDS = new String[] {"03\r"}; + + private static final String[] EXPECTED_MODE3_RESPONSES = + new String[] { + "0300E0:4306010002001:030043008200C12:0000000000000043010101", OBD2_PROMPT + }; + + private static final String[] EXPECTED_FRAME_COMMANDS = + new String[] {"020C 00\r", "020D 00\r", "020C 01\r", "020D 01\r"}; + + private static final String[] EXPECTED_FRAME_RESPONSES = + new String[] { + "42 0C 00 12 0F", + OBD2_PROMPT, + "42 0D 00 82", + OBD2_PROMPT, + "42 0C 01 12 0F", + OBD2_PROMPT, + "42 0D 01 83", + OBD2_PROMPT + }; + + @Test + public void testObd2FreezeFrameGeneration() throws Exception { + MockObd2UnderlyingTransport transport = + new MockObd2UnderlyingTransport( + concatIntArrays( + stringsToIntArray(EXPECTED_INIT_COMMANDS), + stringsToIntArray(EXPECTED_DISCOVERY_COMMANDS), + stringsToIntArray(EXPECTED_MODE3_COMMANDS), + stringsToIntArray(EXPECTED_FRAME_COMMANDS)), + concatIntArrays( + stringsToIntArray(EXPECTED_INIT_RESPONSES), + stringsToIntArray(EXPECTED_DISCOVERY_RESPONSES), + stringsToIntArray(EXPECTED_MODE3_RESPONSES), + stringsToIntArray(EXPECTED_FRAME_RESPONSES))); + Obd2Connection obd2Connection = new Obd2Connection(transport); + Obd2FreezeFrameGenerator obd2Generator = new Obd2FreezeFrameGenerator(obd2Connection); + StringWriter stringWriter = new StringWriter(1024); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.beginArray(); + obd2Generator.generate(jsonWriter); + jsonWriter.endArray(); + JsonReader jsonReader = new JsonReader(new StringReader(stringWriter.toString())); + DiagnosticJsonReader diagnosticJsonReader = new DiagnosticJsonReader(); + jsonReader.beginArray(); + VehiclePropValue vehiclePropValue = diagnosticJsonReader.build(jsonReader); + assertEquals(OBD2_FREEZE_FRAME, vehiclePropValue.prop); + assertEquals(1155, (long) vehiclePropValue.value.int32Values.get(0xC)); + assertEquals(130, (long) vehiclePropValue.value.int32Values.get(0xD)); + vehiclePropValue = diagnosticJsonReader.build(jsonReader); + assertEquals(OBD2_FREEZE_FRAME, vehiclePropValue.prop); + assertEquals(1155, (long) vehiclePropValue.value.int32Values.get(0xC)); + assertEquals(131, (long) vehiclePropValue.value.int32Values.get(0xD)); + } +} diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java index e2022c1f8a..ba3dbb81fe 100644 --- a/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java +++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java @@ -54,7 +54,7 @@ public class Obd2LiveFrameGeneratorTest { new String[] {"0100\r", "0120\r", "0140\r", "0160\r"}; private static final String[] EXPECTED_DISCOVERY_RESPONSES = - new String[] {"00 00 00 0C 00 00", OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT}; + new String[] {"00 00 00 18 00 00", OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT}; private static final String[] EXPECTED_FRAME_COMMANDS = new String[] {"010C\r", "010D\r"}; @@ -82,7 +82,7 @@ public class Obd2LiveFrameGeneratorTest { DiagnosticJsonReader diagnosticJsonReader = new DiagnosticJsonReader(); VehiclePropValue vehiclePropValue = diagnosticJsonReader.build(jsonReader); assertEquals(OBD2_LIVE_FRAME, vehiclePropValue.prop); - assertEquals(4611, (long) vehiclePropValue.value.int32Values.get(0xC)); + assertEquals(1155, (long) vehiclePropValue.value.int32Values.get(0xC)); assertEquals(130, (long) vehiclePropValue.value.int32Values.get(0xD)); } } diff --git a/tools/bootanalyze/bootanalyze.py b/tools/bootanalyze/bootanalyze.py index da874ce8f1..480c9d43f1 100755 --- a/tools/bootanalyze/bootanalyze.py +++ b/tools/bootanalyze/bootanalyze.py @@ -522,7 +522,7 @@ def collect_events(search_events, command, timings, stop_events, disable_timing_ print "timeout waiting for event, continue", time_left break read_r = read_poll.poll(time_left * 1000.0) - if read_r: + if len(read_r) > 0 and read_r[0][1] == select.POLLIN: line = process.stdout.readline() else: print "poll timeout waiting for event, continue", time_left diff --git a/tools/emulator/__init__.py b/tools/emulator/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/emulator/__init__.py diff --git a/tools/emulator/diagjson.example b/tools/emulator/diagjson.example new file mode 100644 index 0000000000..1029dfc7f7 --- /dev/null +++ b/tools/emulator/diagjson.example @@ -0,0 +1,12011 @@ +[ + { + "timestamp": 72375175786629, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 18 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 64 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.22222 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72377177593287, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 20 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 64 + }, + { + "id": 8, + "value": 3084 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.22222 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72379176544788, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 22 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 64 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72381179373780, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 24 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 64 + }, + { + "id": 8, + "value": 2873 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72383179413967, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 26 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 65 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72385179454210, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 28 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 65 + }, + { + "id": 8, + "value": 2869 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72387176856679, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 30 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 66 + }, + { + "id": 8, + "value": 3090 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 52 + }, + { + "id": 2, + "value": 230.46875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72389180204642, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 32 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 66 + }, + { + "id": 8, + "value": 4110 + }, + { + "id": 9, + "value": 1 + }, + { + "id": 0, + "value": 68.44444 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72391178997905, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 34 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 66 + }, + { + "id": 8, + "value": 3633 + }, + { + "id": 9, + "value": 5 + }, + { + "id": 0, + "value": 51.11111 + }, + { + "id": 2, + "value": 223.4375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72393179466175, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 36 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 67 + }, + { + "id": 8, + "value": 3135 + }, + { + "id": 9, + "value": 4 + }, + { + "id": 0, + "value": 50.666668 + }, + { + "id": 2, + "value": 230.46875 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72395177442840, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 38 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 67 + }, + { + "id": 8, + "value": 3125 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 48.444443 + }, + { + "id": 2, + "value": 228.125 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72397179771686, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 40 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 68 + }, + { + "id": 8, + "value": 2857 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 55.555557 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72399177882285, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 42 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 68 + }, + { + "id": 8, + "value": 4899 + }, + { + "id": 9, + "value": 6 + }, + { + "id": 0, + "value": 82.666664 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 17.254902 + } + ] + }, + { + "timestamp": 72401178271883, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 44 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 68 + }, + { + "id": 8, + "value": 8483 + }, + { + "id": 9, + "value": 17 + }, + { + "id": 0, + "value": 45.333332 + }, + { + "id": 2, + "value": 237.5 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72403179389890, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 46 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 69 + }, + { + "id": 8, + "value": 5158 + }, + { + "id": 9, + "value": 18 + }, + { + "id": 0, + "value": 28 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72405178339774, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 48 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 69 + }, + { + "id": 8, + "value": 6184 + }, + { + "id": 9, + "value": 20 + }, + { + "id": 0, + "value": 36 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72407178346527, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 50 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 70 + }, + { + "id": 8, + "value": 6207 + }, + { + "id": 9, + "value": 21 + }, + { + "id": 0, + "value": 31.11111 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72409177960280, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 52 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 70 + }, + { + "id": 8, + "value": 5409 + }, + { + "id": 9, + "value": 19 + }, + { + "id": 0, + "value": 28.88889 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 36.47059 + } + ] + }, + { + "timestamp": 72411176941375, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 54 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 71 + }, + { + "id": 8, + "value": 5653 + }, + { + "id": 9, + "value": 18 + }, + { + "id": 0, + "value": 92.888885 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 24.705883 + } + ] + }, + { + "timestamp": 72413177348486, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 56 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 71 + }, + { + "id": 8, + "value": 7988 + }, + { + "id": 9, + "value": 26 + }, + { + "id": 0, + "value": 31.555555 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72415176509329, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 58 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 72 + }, + { + "id": 8, + "value": 4134 + }, + { + "id": 9, + "value": 21 + }, + { + "id": 0, + "value": 33.333332 + }, + { + "id": 2, + "value": 176.5625 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72417178548720, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 60 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 72 + }, + { + "id": 8, + "value": 3109 + }, + { + "id": 9, + "value": 14 + }, + { + "id": 0, + "value": 49.333332 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72417178548720, + "type": "freeze", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 60 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 72 + }, + { + "id": 8, + "value": 3109 + }, + { + "id": 9, + "value": 14 + }, + { + "id": 0, + "value": 49.333332 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 30.19608 + } + ], + "stringValue": "P0420" + }, + { + "timestamp": 72419178377617, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 62 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 73 + }, + { + "id": 8, + "value": 3635 + }, + { + "id": 9, + "value": 16 + }, + { + "id": 0, + "value": 44.88889 + }, + { + "id": 2, + "value": 223.4375 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72421177803572, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 64 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 73 + }, + { + "id": 8, + "value": 3619 + }, + { + "id": 9, + "value": 11 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 228.125 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72423176710009, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 66 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 74 + }, + { + "id": 8, + "value": 4637 + }, + { + "id": 9, + "value": 10 + }, + { + "id": 0, + "value": 36 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 35.686275 + } + ] + }, + { + "timestamp": 72425176560994, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 68 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 74 + }, + { + "id": 8, + "value": 7199 + }, + { + "id": 9, + "value": 15 + }, + { + "id": 0, + "value": 36 + }, + { + "id": 2, + "value": 228.125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72427175927486, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 70 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 75 + }, + { + "id": 8, + "value": 3894 + }, + { + "id": 9, + "value": 11 + }, + { + "id": 0, + "value": 47.555557 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 24.705883 + } + ] + }, + { + "timestamp": 72429178132978, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 72 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 75 + }, + { + "id": 8, + "value": 3108 + }, + { + "id": 9, + "value": 1 + }, + { + "id": 0, + "value": 48 + }, + { + "id": 2, + "value": 218.75 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72431177381669, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 74 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 75 + }, + { + "id": 8, + "value": 3087 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72433178039132, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 76 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 76 + }, + { + "id": 8, + "value": 3091 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72435178023153, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 78 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 76 + }, + { + "id": 8, + "value": 3090 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72437177860993, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 80 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 77 + }, + { + "id": 8, + "value": 3083 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72439177936939, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 82 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 77 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.333332 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72441178444751, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 84 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 77 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72443176930748, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 86 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 78 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72445178255526, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 88 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 78 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72447175742708, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 90 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 78 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 31.37255 + } + ] + }, + { + "timestamp": 72449178336056, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 92 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 79 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.333332 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72451176649982, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 94 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 79 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 30.588236 + } + ] + }, + { + "timestamp": 72453177388904, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 96 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 79 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72455178049520, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 98 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 79 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72457178189969, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 100 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 80 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72459177706926, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 102 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 80 + }, + { + "id": 8, + "value": 2877 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72461176388948, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 104 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 80 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72463178194570, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 106 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 81 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72465178204165, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 108 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 81 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72467178005994, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 110 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 81 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72469178443939, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 112 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 82 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72471176771874, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 114 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 82 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72473179942657, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 116 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 82 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72475177449291, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 118 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 82 + }, + { + "id": 8, + "value": 2877 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72477179209812, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 120 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 83 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72479178421641, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 122 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 83 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72481179539631, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 124 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 83 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.666668 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72483178480110, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 126 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 83 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72485180096548, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 128 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 84 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72487178424406, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 130 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 84 + }, + { + "id": 8, + "value": 3075 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 47.11111 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72489180054622, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 132 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 84 + }, + { + "id": 8, + "value": 3081 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72491179557027, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 134 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 2878 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.22222 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72493177727317, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 136 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.666668 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72495177794472, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 138 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.666668 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72497177210035, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 140 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 47.11111 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72499180474808, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 142 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 3085 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 46.666668 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72501178578880, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 144 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 47.11111 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72503179731578, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 146 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 7177 + }, + { + "id": 9, + "value": 11 + }, + { + "id": 0, + "value": 49.77778 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72505177292755, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 148 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 4126 + }, + { + "id": 9, + "value": 15 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72507176773477, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 150 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 6438 + }, + { + "id": 9, + "value": 20 + }, + { + "id": 0, + "value": 77.333336 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72509177195082, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 152 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 8746 + }, + { + "id": 9, + "value": 30 + }, + { + "id": 0, + "value": 112.888885 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 8.627451 + } + ] + }, + { + "timestamp": 72511175989969, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 154 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 14082 + }, + { + "id": 9, + "value": 44 + }, + { + "id": 0, + "value": 103.111115 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 20.784313 + } + ] + }, + { + "timestamp": 72513179658544, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 156 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 15639 + }, + { + "id": 9, + "value": 54 + }, + { + "id": 0, + "value": 73.333336 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72515179414407, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 158 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 10268 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 63.555557 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72517176297337, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 160 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 5928 + }, + { + "id": 9, + "value": 60 + }, + { + "id": 0, + "value": 24 + }, + { + "id": 2, + "value": 146.09375 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72519176826185, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 162 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 6418 + }, + { + "id": 9, + "value": 58 + }, + { + "id": 0, + "value": 59.555557 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72521179790904, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 164 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 6179 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 34.22222 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72523180627127, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 166 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 85 + }, + { + "id": 8, + "value": 7483 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 99.55556 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72525177789023, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 168 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 6660 + }, + { + "id": 9, + "value": 63 + }, + { + "id": 0, + "value": 96.888885 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72527175560417, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 170 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 8460 + }, + { + "id": 9, + "value": 66 + }, + { + "id": 0, + "value": 54.666668 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72529179499137, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 172 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 5431 + }, + { + "id": 9, + "value": 56 + }, + { + "id": 0, + "value": 20 + }, + { + "id": 2, + "value": 167.1875 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72531178292369, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 174 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 4868 + }, + { + "id": 9, + "value": 48 + }, + { + "id": 0, + "value": 15.111111 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72533178253439, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 176 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 7195 + }, + { + "id": 9, + "value": 47 + }, + { + "id": 0, + "value": 83.55556 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72535179199711, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 178 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 6423 + }, + { + "id": 9, + "value": 48 + }, + { + "id": 0, + "value": 104 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72537177243028, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 180 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 11582 + }, + { + "id": 9, + "value": 55 + }, + { + "id": 0, + "value": 112.888885 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72539180216273, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 182 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 13336 + }, + { + "id": 9, + "value": 67 + }, + { + "id": 0, + "value": 113.333336 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72541176144311, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 184 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 12304 + }, + { + "id": 9, + "value": 73 + }, + { + "id": 0, + "value": 24 + }, + { + "id": 2, + "value": 174.21875 + }, + { + "id": 42, + "value": 24.705883 + } + ] + }, + { + "timestamp": 72543178195294, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 186 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 6717 + }, + { + "id": 9, + "value": 68 + }, + { + "id": 0, + "value": 20.88889 + }, + { + "id": 2, + "value": 181.25 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72545176834995, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 188 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 4654 + }, + { + "id": 9, + "value": 47 + }, + { + "id": 0, + "value": 20.444445 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72547177907339, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 190 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 86 + }, + { + "id": 8, + "value": 3330 + }, + { + "id": 9, + "value": 30 + }, + { + "id": 0, + "value": 29.333334 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72549179735735, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 192 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 3097 + }, + { + "id": 9, + "value": 13 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72551181754526, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 194 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 3088 + }, + { + "id": 9, + "value": 2 + }, + { + "id": 0, + "value": 45.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72553177986304, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 196 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3075 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72555176967736, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 198 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72557177160577, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 200 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72559179745844, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 202 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 2873 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.22222 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 33.333332 + } + ] + }, + { + "timestamp": 72561176203928, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 204 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 2878 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72563177799351, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 206 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72565177953894, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 208 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 2870 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72567176653777, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 210 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 3075 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72569178043717, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 212 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 2872 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72571178251056, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 214 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72573176628436, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 216 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72575177840420, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 218 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72577176563646, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 220 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72579179164504, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 222 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 2876 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72581177838286, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 224 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72583178358480, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 226 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72585177901670, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 228 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72587177734144, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 230 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72589179333460, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 232 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72591178129065, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 234 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2878 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72593177596819, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 236 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72595178384378, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 238 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44.444443 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72597178056865, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 240 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3075 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72599179833740, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 243 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2872 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72601176651309, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 244 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72603177566065, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 246 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3083 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72605178128004, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 248 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72607179599532, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 250 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72609179437284, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 252 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3081 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72611179630474, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 254 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72613179774544, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 256 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72615178404973, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 258 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72617179059428, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 260 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44.444443 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72619180105086, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 262 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 44.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72621179098902, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 264 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3104 + }, + { + "id": 9, + "value": 1 + }, + { + "id": 0, + "value": 59.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72623179359108, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 266 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 8255 + }, + { + "id": 9, + "value": 15 + }, + { + "id": 0, + "value": 100.888885 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 9.411765 + } + ] + }, + { + "timestamp": 72625179856484, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 268 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 11276 + }, + { + "id": 9, + "value": 33 + }, + { + "id": 0, + "value": 99.55556 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 15.686275 + } + ] + }, + { + "timestamp": 72627176310119, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 270 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 15618 + }, + { + "id": 9, + "value": 50 + }, + { + "id": 0, + "value": 90.666664 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72629177201984, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 272 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 13109 + }, + { + "id": 9, + "value": 63 + }, + { + "id": 0, + "value": 111.55556 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72631177723851, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 274 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 14343 + }, + { + "id": 9, + "value": 74 + }, + { + "id": 0, + "value": 111.55556 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 17.647058 + } + ] + }, + { + "timestamp": 72633179257550, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 276 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 15921 + }, + { + "id": 9, + "value": 83 + }, + { + "id": 0, + "value": 21.777779 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 9.803922 + } + ] + }, + { + "timestamp": 72635179273140, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 278 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 4400 + }, + { + "id": 9, + "value": 55 + }, + { + "id": 0, + "value": 34.666668 + }, + { + "id": 2, + "value": 183.59375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72637179409130, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 280 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 3117 + }, + { + "id": 9, + "value": 32 + }, + { + "id": 0, + "value": 30.666666 + }, + { + "id": 2, + "value": 183.59375 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72639179800268, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 282 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 3108 + }, + { + "id": 9, + "value": 26 + }, + { + "id": 0, + "value": 29.333334 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72641179405070, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 284 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 3106 + }, + { + "id": 9, + "value": 21 + }, + { + "id": 0, + "value": 32.88889 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72643179866403, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 286 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 4115 + }, + { + "id": 9, + "value": 17 + }, + { + "id": 0, + "value": 111.111115 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 17.254902 + } + ] + }, + { + "timestamp": 72645178430456, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 288 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 8991 + }, + { + "id": 9, + "value": 30 + }, + { + "id": 0, + "value": 112.44444 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72647178451017, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 290 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 9513 + }, + { + "id": 9, + "value": 43 + }, + { + "id": 0, + "value": 95.55556 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72649179500927, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 292 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 9518 + }, + { + "id": 9, + "value": 50 + }, + { + "id": 0, + "value": 27.555555 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 34.509804 + } + ] + }, + { + "timestamp": 72651179611409, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 294 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 6418 + }, + { + "id": 9, + "value": 49 + }, + { + "id": 0, + "value": 19.555555 + }, + { + "id": 2, + "value": 178.90625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72653178456810, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 296 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 5173 + }, + { + "id": 9, + "value": 40 + }, + { + "id": 0, + "value": 20.88889 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72655180014824, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 298 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 4112 + }, + { + "id": 9, + "value": 32 + }, + { + "id": 0, + "value": 28.444445 + }, + { + "id": 2, + "value": 185.9375 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72657180944053, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 300 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 8720 + }, + { + "id": 9, + "value": 37 + }, + { + "id": 0, + "value": 113.333336 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72659179332880, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 302 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 16412 + }, + { + "id": 9, + "value": 52 + }, + { + "id": 0, + "value": 109.333336 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 12.54902 + } + ] + }, + { + "timestamp": 72661177765487, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 304 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 13828 + }, + { + "id": 9, + "value": 61 + }, + { + "id": 0, + "value": 22.666666 + }, + { + "id": 2, + "value": 178.90625 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72663177689968, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 306 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 6447 + }, + { + "id": 9, + "value": 61 + }, + { + "id": 0, + "value": 28.88889 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72665177450571, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 308 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 5657 + }, + { + "id": 9, + "value": 56 + }, + { + "id": 0, + "value": 16.88889 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72667179692696, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 310 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 4873 + }, + { + "id": 9, + "value": 48 + }, + { + "id": 0, + "value": 19.11111 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 35.686275 + } + ] + }, + { + "timestamp": 72669179293955, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 312 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 87 + }, + { + "id": 8, + "value": 3613 + }, + { + "id": 9, + "value": 34 + }, + { + "id": 0, + "value": 27.11111 + }, + { + "id": 2, + "value": 185.9375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72671177593842, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 314 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3131 + }, + { + "id": 9, + "value": 19 + }, + { + "id": 0, + "value": 30.222221 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 36.862747 + } + ] + }, + { + "timestamp": 72673177057067, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 316 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3844 + }, + { + "id": 9, + "value": 8 + }, + { + "id": 0, + "value": 36.444443 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72675179630131, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 318 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 43.555557 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72677179714055, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 320 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 2866 + }, + { + "id": 9, + "value": 1 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72679179288433, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 322 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 7457 + }, + { + "id": 9, + "value": 13 + }, + { + "id": 0, + "value": 76 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 10.588236 + } + ] + }, + { + "timestamp": 72681176480810, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 324 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 9272 + }, + { + "id": 9, + "value": 27 + }, + { + "id": 0, + "value": 105.77778 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 15.686275 + } + ] + }, + { + "timestamp": 72683179662780, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 326 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 13585 + }, + { + "id": 9, + "value": 43 + }, + { + "id": 0, + "value": 87.55556 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72685177751997, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 328 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 11024 + }, + { + "id": 9, + "value": 53 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 190.625 + }, + { + "id": 42, + "value": 20.784313 + } + ] + }, + { + "timestamp": 72687177061206, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 330 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 6927 + }, + { + "id": 9, + "value": 51 + }, + { + "id": 0, + "value": 18.222221 + }, + { + "id": 2, + "value": 155.46875 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72689176965248, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 332 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 5148 + }, + { + "id": 9, + "value": 38 + }, + { + "id": 0, + "value": 21.333334 + }, + { + "id": 2, + "value": 157.8125 + }, + { + "id": 42, + "value": 34.11765 + } + ] + }, + { + "timestamp": 72691180692310, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 334 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3594 + }, + { + "id": 9, + "value": 29 + }, + { + "id": 0, + "value": 26.222221 + }, + { + "id": 2, + "value": 139.0625 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72693178299558, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 336 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 3350 + }, + { + "id": 9, + "value": 25 + }, + { + "id": 0, + "value": 48.88889 + }, + { + "id": 2, + "value": 185.9375 + }, + { + "id": 42, + "value": 18.82353 + } + ] + }, + { + "timestamp": 72695178266771, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 338 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 88 + }, + { + "id": 8, + "value": 8456 + }, + { + "id": 9, + "value": 34 + }, + { + "id": 0, + "value": 79.55556 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72697178378315, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 340 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 6163 + }, + { + "id": 9, + "value": 40 + }, + { + "id": 0, + "value": 30.666666 + }, + { + "id": 2, + "value": 185.9375 + }, + { + "id": 42, + "value": 36.47059 + } + ] + }, + { + "timestamp": 72699179440945, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 342 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 4886 + }, + { + "id": 9, + "value": 32 + }, + { + "id": 0, + "value": 24 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 30.588236 + } + ] + }, + { + "timestamp": 72701179783621, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 344 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 5130 + }, + { + "id": 9, + "value": 29 + }, + { + "id": 0, + "value": 54.22222 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72703178384562, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 346 + }, + { + "id": 13, + "value": 21 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 9525 + }, + { + "id": 9, + "value": 38 + }, + { + "id": 0, + "value": 112.888885 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72705179707825, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 348 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 10513 + }, + { + "id": 9, + "value": 49 + }, + { + "id": 0, + "value": 99.55556 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72707177436366, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 350 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 8248 + }, + { + "id": 9, + "value": 54 + }, + { + "id": 0, + "value": 76.888885 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72709176949975, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 352 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 8484 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 59.11111 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72711179509943, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 354 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 5945 + }, + { + "id": 9, + "value": 61 + }, + { + "id": 0, + "value": 74.22222 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72713179908608, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 356 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 6197 + }, + { + "id": 9, + "value": 61 + }, + { + "id": 0, + "value": 23.11111 + }, + { + "id": 2, + "value": 178.90625 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72715178040463, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 358 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 5904 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 20.88889 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72717179826935, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 360 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 6154 + }, + { + "id": 9, + "value": 60 + }, + { + "id": 0, + "value": 35.11111 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72719178168768, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 362 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 5636 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 16.88889 + }, + { + "id": 2, + "value": 181.25 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72721179101986, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 4 + }, + { + "id": 7, + "value": 364 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 5411 + }, + { + "id": 9, + "value": 54 + }, + { + "id": 0, + "value": 54.666668 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 24.705883 + } + ] + }, + { + "timestamp": 72723180021513, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 366 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 5688 + }, + { + "id": 9, + "value": 55 + }, + { + "id": 0, + "value": 24.444445 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72725179738209, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 368 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 5903 + }, + { + "id": 9, + "value": 55 + }, + { + "id": 0, + "value": 48.88889 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72727179292538, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 370 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 5162 + }, + { + "id": 9, + "value": 51 + }, + { + "id": 0, + "value": 20.88889 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72729178027625, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 372 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 4124 + }, + { + "id": 9, + "value": 44 + }, + { + "id": 0, + "value": 24 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72731178148147, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 374 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3132 + }, + { + "id": 9, + "value": 37 + }, + { + "id": 0, + "value": 23.555555 + }, + { + "id": 2, + "value": 181.25 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72733176368495, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 376 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3128 + }, + { + "id": 9, + "value": 24 + }, + { + "id": 0, + "value": 28 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 34.901962 + } + ] + }, + { + "timestamp": 72735179502626, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 378 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 3089 + }, + { + "id": 9, + "value": 15 + }, + { + "id": 0, + "value": 38.666668 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 39.607845 + } + ] + }, + { + "timestamp": 72737179297296, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 380 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 2869 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.666668 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72739177718891, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 382 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 2877 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72741177981830, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 384 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2871 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.11111 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72743176174851, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 387 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2872 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.11111 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72745329912826, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 389 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2870 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.11111 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72747355080893, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 391 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72749176618991, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 392 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.555557 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72751176696798, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 394 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2867 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72753176964078, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 396 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72755178349568, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 398 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72757178508591, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 400 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 2875 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 30.588236 + } + ] + }, + { + "timestamp": 72759176500609, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 402 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72761176683571, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 404 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 2878 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72763178363414, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 406 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3081 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72765177437003, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 408 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3085 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72767178204902, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 410 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 2877 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72769177266732, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 412 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72771176734323, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 414 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72773176997295, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 416 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72775176636900, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 418 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72777176589987, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 420 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72779178389314, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 422 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3075 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72781178485901, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 424 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72783177805524, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 426 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72785177696746, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 428 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3081 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72787177943035, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 430 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3085 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72789177892167, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 432 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3900 + }, + { + "id": 9, + "value": 2 + }, + { + "id": 0, + "value": 84 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 11.372549 + } + ] + }, + { + "timestamp": 72791176960551, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 434 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 6937 + }, + { + "id": 9, + "value": 15 + }, + { + "id": 0, + "value": 73.333336 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 17.254902 + } + ] + }, + { + "timestamp": 72793179080142, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 436 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 10253 + }, + { + "id": 9, + "value": 30 + }, + { + "id": 0, + "value": 100.888885 + }, + { + "id": 2, + "value": 211.71875 + }, + { + "id": 42, + "value": 10.588236 + } + ] + }, + { + "timestamp": 72795177144042, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 438 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 14653 + }, + { + "id": 9, + "value": 46 + }, + { + "id": 0, + "value": 104 + }, + { + "id": 2, + "value": 214.0625 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72797177311878, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 440 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 17934 + }, + { + "id": 9, + "value": 59 + }, + { + "id": 0, + "value": 55.555557 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 24.705883 + } + ] + }, + { + "timestamp": 72799176281873, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 442 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 8757 + }, + { + "id": 9, + "value": 62 + }, + { + "id": 0, + "value": 28.444445 + }, + { + "id": 2, + "value": 190.625 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72801179010275, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 444 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 5642 + }, + { + "id": 9, + "value": 58 + }, + { + "id": 0, + "value": 17.777779 + }, + { + "id": 2, + "value": 188.28125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72803176720724, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 446 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 3630 + }, + { + "id": 9, + "value": 38 + }, + { + "id": 0, + "value": 25.333334 + }, + { + "id": 2, + "value": 171.875 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72805176371184, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 448 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 89 + }, + { + "id": 8, + "value": 3130 + }, + { + "id": 9, + "value": 27 + }, + { + "id": 0, + "value": 25.777779 + }, + { + "id": 2, + "value": 178.90625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72807176264873, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 450 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 3091 + }, + { + "id": 9, + "value": 13 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72809177130217, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 452 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 90 + }, + { + "id": 8, + "value": 3340 + }, + { + "id": 9, + "value": 4 + }, + { + "id": 0, + "value": 43.11111 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72811178240749, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 454 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 2874 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 30.19608 + } + ] + }, + { + "timestamp": 72813176674429, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 456 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 2877 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.555557 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72815177036139, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 458 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.11111 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72817178504749, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 460 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 2866 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.555557 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72819175954203, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 462 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.11111 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72821177378507, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 464 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 2873 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 39.555557 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72823177069331, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 466 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3076 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72825179501699, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 468 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72827176421710, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 470 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72829176386701, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 472 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72831176764503, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 474 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 2873 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72833177319130, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 476 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72835180519900, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 478 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72837178223527, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 480 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72839177312748, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 482 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72841177797843, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 484 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72843179579857, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 486 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3074 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.444443 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72845180133072, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 488 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72847176774274, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 490 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72849177384942, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 492 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72851178359655, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 494 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3080 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72853178455898, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 496 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 40.88889 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72855177993333, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 498 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3084 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72857177079489, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 500 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3072 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72859176801251, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 502 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2878 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72861178465404, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 504 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3082 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72863179481995, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 506 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3073 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72865177785963, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 508 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72867176113089, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 510 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3075 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72869176834314, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 512 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2874 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 190.625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72871178569429, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 514 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.333332 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72873176252437, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 516 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.22222 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72875177079512, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 518 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2872 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72877177740208, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 520 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72879180125173, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 522 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2872 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 190.625 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72881178525713, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 524 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3079 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 195.3125 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72883176875986, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 526 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2875 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72885176324726, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 528 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 2879 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72887176793512, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 530 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3078 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 42.22222 + }, + { + "id": 2, + "value": 197.65625 + }, + { + "id": 42, + "value": 31.764706 + } + ] + }, + { + "timestamp": 72889177026044, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 532 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 3077 + }, + { + "id": 9, + "value": 0 + }, + { + "id": 0, + "value": 41.77778 + }, + { + "id": 2, + "value": 192.96875 + }, + { + "id": 42, + "value": 32.156864 + } + ] + }, + { + "timestamp": 72891177972355, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 534 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 95 + }, + { + "id": 8, + "value": 5426 + }, + { + "id": 9, + "value": 5 + }, + { + "id": 0, + "value": 51.555557 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 22.745098 + } + ] + }, + { + "timestamp": 72893178557124, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 536 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 96 + }, + { + "id": 8, + "value": 9000 + }, + { + "id": 9, + "value": 18 + }, + { + "id": 0, + "value": 66.22222 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72895179323906, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 538 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 95 + }, + { + "id": 8, + "value": 7996 + }, + { + "id": 9, + "value": 24 + }, + { + "id": 0, + "value": 53.333332 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 18.82353 + } + ] + }, + { + "timestamp": 72897179514717, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 540 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 95 + }, + { + "id": 8, + "value": 6195 + }, + { + "id": 9, + "value": 29 + }, + { + "id": 0, + "value": 109.333336 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 10.588236 + } + ] + }, + { + "timestamp": 72899178457633, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 542 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 12839 + }, + { + "id": 9, + "value": 40 + }, + { + "id": 0, + "value": 61.77778 + }, + { + "id": 2, + "value": 204.6875 + }, + { + "id": 42, + "value": 23.529411 + } + ] + }, + { + "timestamp": 72901179477573, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 544 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 94 + }, + { + "id": 8, + "value": 10757 + }, + { + "id": 9, + "value": 49 + }, + { + "id": 0, + "value": 111.55556 + }, + { + "id": 2, + "value": 207.03125 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72903179086670, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 546 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 93 + }, + { + "id": 8, + "value": 12836 + }, + { + "id": 9, + "value": 61 + }, + { + "id": 0, + "value": 85.77778 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 28.62745 + } + ] + }, + { + "timestamp": 72905177385416, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 548 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 10042 + }, + { + "id": 9, + "value": 65 + }, + { + "id": 0, + "value": 34.22222 + }, + { + "id": 2, + "value": 200 + }, + { + "id": 42, + "value": 32.941177 + } + ] + }, + { + "timestamp": 72907178215527, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 550 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 92 + }, + { + "id": 8, + "value": 9733 + }, + { + "id": 9, + "value": 66 + }, + { + "id": 0, + "value": 111.55556 + }, + { + "id": 2, + "value": 216.40625 + }, + { + "id": 42, + "value": 26.27451 + } + ] + }, + { + "timestamp": 72909179246647, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 552 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 10302 + }, + { + "id": 9, + "value": 70 + }, + { + "id": 0, + "value": 56.88889 + }, + { + "id": 2, + "value": 202.34375 + }, + { + "id": 42, + "value": 27.450981 + } + ] + }, + { + "timestamp": 72911180048703, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 554 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 15104 + }, + { + "id": 9, + "value": 73 + }, + { + "id": 0, + "value": 111.111115 + }, + { + "id": 2, + "value": 209.375 + }, + { + "id": 42, + "value": 19.215687 + } + ] + }, + { + "timestamp": 72913178558121, + "type": "live", + "intValues": [ + { + "id": 0, + "value": 2 + }, + { + "id": 7, + "value": 556 + }, + { + "id": 13, + "value": 22 + } + ], + "floatValues": [ + { + "id": 1, + "value": 91 + }, + { + "id": 8, + "value": 16423 + }, + { + "id": 9, + "value": 86 + }, + { + "id": 0, + "value": 75.111115 + } + ] + }, + { + "timestamp": 72915176591525, + "type": "live" + }, + { + "timestamp": 72917180004744, + "type": "live" + }, + { + "timestamp": 72919178449026, + "type": "live" + }, + { + "timestamp": 72921179933335, + "type": "live" + }, + { + "timestamp": 72923179387974, + "type": "live" + }, + { + "timestamp": 72925179338880, + "type": "live" + }, + { + "timestamp": 72927176345108, + "type": "live" + }, + { + "timestamp": 72929179848799, + "type": "live" + }, + { + "timestamp": 72931179314858, + "type": "live" + }, + { + "timestamp": 72933180632674, + "type": "live" + }, + { + "timestamp": 72935179793063, + "type": "live" + }, + { + "timestamp": 72937178822539, + "type": "live" + }, + { + "timestamp": 72939177286298, + "type": "live" + }, + { + "timestamp": 72941177772999, + "type": "live" + }, + { + "timestamp": 72943177666251, + "type": "live" + }, + { + "timestamp": 72945180047718, + "type": "live" + }, + { + "timestamp": 72947179391597, + "type": "live" + }, + { + "timestamp": 72949176920841, + "type": "live" + }, + { + "timestamp": 72951177850208, + "type": "live" + }, + { + "timestamp": 72953176750100, + "type": "live" + }, + { + "timestamp": 72955175939724, + "type": "live" + }, + { + "timestamp": 72957177389603, + "type": "live" + }, + { + "timestamp": 72959178499462, + "type": "live" + } +] diff --git a/tools/emulator/diagnostic_builder.py b/tools/emulator/diagnostic_builder.py new file mode 100644 index 0000000000..b205a5b4ca --- /dev/null +++ b/tools/emulator/diagnostic_builder.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3.4 +# +# Copyright (C) 2017 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. +# + +# A helper class to generate COMPLEX property values that can be +# set as the value for a diagnostic frame +# Spritually, the same as DiagnosticEventBuilder.java + +from diagnostic_sensors import OBD2_SENSOR_INTEGER_LAST_SYSTEM_INDEX +from diagnostic_sensors import OBD2_SENSOR_FLOAT_LAST_SYSTEM_INDEX + +class DiagnosticEventBuilder(object): + class ByteArray(object): + def __init__(self, numElements): + self.count = numElements + if 0 == (numElements % 8): + self.data = bytearray(numElements/8) + else: + # if not a multiple of 8, add one extra byte + self.data = bytearray(1+numElements/8) + + def _getIndices(self, bit): + if (bit < 0) or (bit >= self.count): + raise IndexError("index %d not in range [0,%d)" % (bit, self.count)) + byteIdx = bit / 8 + bitIdx = (bit % 8) + return byteIdx, bitIdx + + def setBit(self, bit): + byteIdx, bitIdx = self._getIndices(bit) + bitValue = pow(2,bitIdx) + self.data[byteIdx] = self.data[byteIdx] | bitValue + + def getBit(self, bit): + byteIdx, bitIdx = self._getIndices(bit) + bitValue = pow(2,bitIdx) + return 0 != self.data[byteIdx] & bitValue + + def __str__(self): + return str(self.data) + + def __init__(self, propConfig): + self.string_value = "" + self.bytes = "" + self.numIntSensors = propConfig.config[0].config_array[0] + \ + OBD2_SENSOR_INTEGER_LAST_SYSTEM_INDEX + 1 + self.numFloatSensors = propConfig.config[0].config_array[1] + \ + OBD2_SENSOR_FLOAT_LAST_SYSTEM_INDEX + 1 + self.bitmask = DiagnosticEventBuilder.ByteArray( + self.numIntSensors+self.numFloatSensors) + self.int32_values = [0] * self.numIntSensors + self.float_values = [0.0] * self.numFloatSensors + + def addIntSensor(self, idx, value): + self.int32_values[idx] = value + self.bitmask.setBit(idx) + return self + + def addFloatSensor(self, idx, value): + self.float_values[idx] = value + self.bitmask.setBit(len(self.int32_values)+idx) + return self + + def setStringValue(self, string): + self.string_value = string + return self + + def build(self): + self.bytes_value = str(self.bitmask) + return self + + def __str__(self): + s = "diagnostic event {\n" + for x in ['string_value', 'int32_values', 'float_values']: + s = s + "\t%s: %s\n" % (x, self.__dict__[x]) + return s + "}" diff --git a/tools/emulator/diagnostic_injector.py b/tools/emulator/diagnostic_injector.py new file mode 100755 index 0000000000..4bd3317746 --- /dev/null +++ b/tools/emulator/diagnostic_injector.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# Copyright (C) 2017 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. +# + +# A tool that can read diagnostic events from a Diagnostic JSON document +# and forward them to Vehicle HAL via vhal_emulator +# Use thusly: +# $ ./diagnostic_injector.py <path/to/diagnostic.json> + +import sys +import json +import time + +import vhal_consts_2_1 as c + +# vhal_emulator depends on a custom Python package that requires installation +# give user guidance should the import fail +try: + from vhal_emulator import Vhal +except ImportError as e: + isProtobuf = False + pipTool = "pip%s" % ("3" if sys.version_info > (3,0) else "") + if hasattr(e, 'name'): + if e.name == 'google': isProtobuf = True + elif hasattr(e, 'message'): + if e.message.endswith('symbol_database'): + isProtobuf = True + if isProtobuf: + print('could not find protobuf.') + print('protobuf can be installed via "sudo %s install --upgrade protobuf"' % pipTool) + sys.exit(1) + else: + raise e + +from diagnostic_builder import DiagnosticEventBuilder + +class DiagnosticHalWrapper(object): + def __init__(self): + self.vhal = Vhal(c.vhal_types_2_0) + self.liveFrameConfig = self.chat( + lambda hal: hal.getConfig(c.VEHICLEPROPERTY_OBD2_LIVE_FRAME)) + self.freezeFrameConfig = self.chat( + lambda hal: hal.getConfig(c.VEHICLEPROPERTY_OBD2_FREEZE_FRAME)) + self.eventTypeData = { + 'live' : { + 'builder' : lambda: DiagnosticEventBuilder(self.liveFrameConfig), + 'property' : c.VEHICLEPROPERTY_OBD2_LIVE_FRAME + }, + 'freeze' : { + 'builder' : lambda: DiagnosticEventBuilder(self.freezeFrameConfig), + 'property' : c.VEHICLEPROPERTY_OBD2_FREEZE_FRAME + }, + } + + def chat(self, request): + request(self.vhal) + return self.vhal.rxMsg() + + def inject(self, file): + data = json.load(open(file)) + lastTimestamp = 0 + for event in data: + currentTimestamp = event['timestamp'] + # time travel isn't supported (yet) + assert currentTimestamp >= lastTimestamp + # wait the delta between this event and the previous one + # before sending it out; but on the first event, send now + # or we'd wait for a long long long time + if lastTimestamp != 0: + # also, timestamps are in nanoseconds, but sleep() uses seconds + time.sleep((currentTimestamp-lastTimestamp)/1000000000) + lastTimestamp = currentTimestamp + # now build the event + eventType = event['type'].encode('utf-8') + eventTypeData = self.eventTypeData[eventType] + builder = eventTypeData['builder']() + builder.setStringValue(event.get('stringValue', '')) + for intValue in event['intValues']: + builder.addIntSensor(intValue['id'], intValue['value']) + for floatValue in event['floatValues']: + builder.addFloatSensor(floatValue['id'], floatValue['value']) + builtEvent = builder.build() + print ("Sending %s %s..." % (eventType, builtEvent)), + # and send it + status = self.chat( + lambda hal: + hal.setProperty(eventTypeData['property'], + 0, + builtEvent)).status + if status == 0: + print("ok!") + else: + print("fail: %s" % status) + +if len(sys.argv) < 2: + print("Syntax: diagnostic_injector.py <path/to/diagnostic.json>") + sys.exit(1) + +halWrapper = DiagnosticHalWrapper() + +for arg in sys.argv[1:]: + halWrapper.inject(arg) diff --git a/tools/emulator/diagnostic_sensors.py b/tools/emulator/diagnostic_sensors.py new file mode 100644 index 0000000000..abde4b803f --- /dev/null +++ b/tools/emulator/diagnostic_sensors.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017 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. +# +# This file is generated by types.hal by packages/services/Car/tools/update-obd2-sensors.py +# DO NOT EDIT MANUALLY + +OBD2_SENSOR_INTEGER_FUEL_SYSTEM_STATUS = 0 +OBD2_SENSOR_INTEGER_MALFUNCTION_INDICATOR_LIGHT_ON = 1 +OBD2_SENSOR_INTEGER_IGNITION_MONITORS_SUPPORTED = 2 +OBD2_SENSOR_INTEGER_IGNITION_SPECIFIC_MONITORS = 3 +OBD2_SENSOR_INTEGER_INTAKE_AIR_TEMPERATURE = 4 +OBD2_SENSOR_INTEGER_COMMANDED_SECONDARY_AIR_STATUS = 5 +OBD2_SENSOR_INTEGER_NUM_OXYGEN_SENSORS_PRESENT = 6 +OBD2_SENSOR_INTEGER_RUNTIME_SINCE_ENGINE_START = 7 +OBD2_SENSOR_INTEGER_DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 8 +OBD2_SENSOR_INTEGER_WARMUPS_SINCE_CODES_CLEARED = 9 +OBD2_SENSOR_INTEGER_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 10 +OBD2_SENSOR_INTEGER_ABSOLUTE_BAROMETRIC_PRESSURE = 11 +OBD2_SENSOR_INTEGER_CONTROL_MODULE_VOLTAGE = 12 +OBD2_SENSOR_INTEGER_AMBIENT_AIR_TEMPERATURE = 13 +OBD2_SENSOR_INTEGER_TIME_WITH_MALFUNCTION_LIGHT_ON = 14 +OBD2_SENSOR_INTEGER_TIME_SINCE_TROUBLE_CODES_CLEARED = 15 +OBD2_SENSOR_INTEGER_MAX_FUEL_AIR_EQUIVALENCE_RATIO = 16 +OBD2_SENSOR_INTEGER_MAX_OXYGEN_SENSOR_VOLTAGE = 17 +OBD2_SENSOR_INTEGER_MAX_OXYGEN_SENSOR_CURRENT = 18 +OBD2_SENSOR_INTEGER_MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 19 +OBD2_SENSOR_INTEGER_MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 20 +OBD2_SENSOR_INTEGER_FUEL_TYPE = 21 +OBD2_SENSOR_INTEGER_FUEL_RAIL_ABSOLUTE_PRESSURE = 22 +OBD2_SENSOR_INTEGER_ENGINE_OIL_TEMPERATURE = 23 +OBD2_SENSOR_INTEGER_DRIVER_DEMAND_PERCENT_TORQUE = 24 +OBD2_SENSOR_INTEGER_ENGINE_ACTUAL_PERCENT_TORQUE = 25 +OBD2_SENSOR_INTEGER_ENGINE_REFERENCE_PERCENT_TORQUE = 26 +OBD2_SENSOR_INTEGER_ENGINE_PERCENT_TORQUE_DATA_IDLE = 27 +OBD2_SENSOR_INTEGER_ENGINE_PERCENT_TORQUE_DATA_POINT1 = 28 +OBD2_SENSOR_INTEGER_ENGINE_PERCENT_TORQUE_DATA_POINT2 = 29 +OBD2_SENSOR_INTEGER_ENGINE_PERCENT_TORQUE_DATA_POINT3 = 30 +OBD2_SENSOR_INTEGER_ENGINE_PERCENT_TORQUE_DATA_POINT4 = 31 +OBD2_SENSOR_INTEGER_LAST_SYSTEM_INDEX = OBD2_SENSOR_INTEGER_ENGINE_PERCENT_TORQUE_DATA_POINT4 +OBD2_SENSOR_INTEGER_VENDOR_START_INDEX = OBD2_SENSOR_INTEGER_LAST_SYSTEM_INDEX + 1 + + + +OBD2_SENSOR_FLOAT_CALCULATED_ENGINE_LOAD = 0 +OBD2_SENSOR_FLOAT_ENGINE_COOLANT_TEMPERATURE = 1 +OBD2_SENSOR_FLOAT_SHORT_TERM_FUEL_TRIM_BANK1 = 2 +OBD2_SENSOR_FLOAT_LONG_TERM_FUEL_TRIM_BANK1 = 3 +OBD2_SENSOR_FLOAT_SHORT_TERM_FUEL_TRIM_BANK2 = 4 +OBD2_SENSOR_FLOAT_LONG_TERM_FUEL_TRIM_BANK2 = 5 +OBD2_SENSOR_FLOAT_FUEL_PRESSURE = 6 +OBD2_SENSOR_FLOAT_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 7 +OBD2_SENSOR_FLOAT_ENGINE_RPM = 8 +OBD2_SENSOR_FLOAT_VEHICLE_SPEED = 9 +OBD2_SENSOR_FLOAT_TIMING_ADVANCE = 10 +OBD2_SENSOR_FLOAT_MAF_AIR_FLOW_RATE = 11 +OBD2_SENSOR_FLOAT_THROTTLE_POSITION = 12 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR1_VOLTAGE = 13 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR1_SHORT_TERM_FUEL_TRIM = 14 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR1_FUEL_AIR_EQUIVALENCE_RATIO = 15 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR2_VOLTAGE = 16 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR2_SHORT_TERM_FUEL_TRIM = 17 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR2_FUEL_AIR_EQUIVALENCE_RATIO = 18 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR3_VOLTAGE = 19 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR3_SHORT_TERM_FUEL_TRIM = 20 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR3_FUEL_AIR_EQUIVALENCE_RATIO = 21 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR4_VOLTAGE = 22 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR4_SHORT_TERM_FUEL_TRIM = 23 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR4_FUEL_AIR_EQUIVALENCE_RATIO = 24 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR5_VOLTAGE = 25 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR5_SHORT_TERM_FUEL_TRIM = 26 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR5_FUEL_AIR_EQUIVALENCE_RATIO = 27 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR6_VOLTAGE = 28 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR6_SHORT_TERM_FUEL_TRIM = 29 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR6_FUEL_AIR_EQUIVALENCE_RATIO = 30 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR7_VOLTAGE = 31 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR7_SHORT_TERM_FUEL_TRIM = 32 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR7_FUEL_AIR_EQUIVALENCE_RATIO = 33 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR8_VOLTAGE = 34 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR8_SHORT_TERM_FUEL_TRIM = 35 +OBD2_SENSOR_FLOAT_OXYGEN_SENSOR8_FUEL_AIR_EQUIVALENCE_RATIO = 36 +OBD2_SENSOR_FLOAT_FUEL_RAIL_PRESSURE = 37 +OBD2_SENSOR_FLOAT_FUEL_RAIL_GAUGE_PRESSURE = 38 +OBD2_SENSOR_FLOAT_COMMANDED_EXHAUST_GAS_RECIRCULATION = 39 +OBD2_SENSOR_FLOAT_EXHAUST_GAS_RECIRCULATION_ERROR = 40 +OBD2_SENSOR_FLOAT_COMMANDED_EVAPORATIVE_PURGE = 41 +OBD2_SENSOR_FLOAT_FUEL_TANK_LEVEL_INPUT = 42 +OBD2_SENSOR_FLOAT_EVAPORATION_SYSTEM_VAPOR_PRESSURE = 43 +OBD2_SENSOR_FLOAT_CATALYST_TEMPERATURE_BANK1_SENSOR1 = 44 +OBD2_SENSOR_FLOAT_CATALYST_TEMPERATURE_BANK2_SENSOR1 = 45 +OBD2_SENSOR_FLOAT_CATALYST_TEMPERATURE_BANK1_SENSOR2 = 46 +OBD2_SENSOR_FLOAT_CATALYST_TEMPERATURE_BANK2_SENSOR2 = 47 +OBD2_SENSOR_FLOAT_ABSOLUTE_LOAD_VALUE = 48 +OBD2_SENSOR_FLOAT_FUEL_AIR_COMMANDED_EQUIVALENCE_RATIO = 49 +OBD2_SENSOR_FLOAT_RELATIVE_THROTTLE_POSITION = 50 +OBD2_SENSOR_FLOAT_ABSOLUTE_THROTTLE_POSITION_B = 51 +OBD2_SENSOR_FLOAT_ABSOLUTE_THROTTLE_POSITION_C = 52 +OBD2_SENSOR_FLOAT_ACCELERATOR_PEDAL_POSITION_D = 53 +OBD2_SENSOR_FLOAT_ACCELERATOR_PEDAL_POSITION_E = 54 +OBD2_SENSOR_FLOAT_ACCELERATOR_PEDAL_POSITION_F = 55 +OBD2_SENSOR_FLOAT_COMMANDED_THROTTLE_ACTUATOR = 56 +OBD2_SENSOR_FLOAT_ETHANOL_FUEL_PERCENTAGE = 57 +OBD2_SENSOR_FLOAT_ABSOLUTE_EVAPORATION_SYSTEM_VAPOR_PRESSURE = 58 +OBD2_SENSOR_FLOAT_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1 = 59 +OBD2_SENSOR_FLOAT_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2 = 60 +OBD2_SENSOR_FLOAT_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3 = 61 +OBD2_SENSOR_FLOAT_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4 = 62 +OBD2_SENSOR_FLOAT_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1 = 63 +OBD2_SENSOR_FLOAT_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2 = 64 +OBD2_SENSOR_FLOAT_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3 = 65 +OBD2_SENSOR_FLOAT_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4 = 66 +OBD2_SENSOR_FLOAT_RELATIVE_ACCELERATOR_PEDAL_POSITION = 67 +OBD2_SENSOR_FLOAT_HYBRID_BATTERY_PACK_REMAINING_LIFE = 68 +OBD2_SENSOR_FLOAT_FUEL_INJECTION_TIMING = 69 +OBD2_SENSOR_FLOAT_ENGINE_FUEL_RATE = 70 +OBD2_SENSOR_FLOAT_LAST_SYSTEM_INDEX = OBD2_SENSOR_FLOAT_ENGINE_FUEL_RATE +OBD2_SENSOR_FLOAT_VENDOR_START_INDEX = OBD2_SENSOR_FLOAT_LAST_SYSTEM_INDEX + 1 + + diff --git a/tools/emulator/gui.py b/tools/emulator/gui.py new file mode 100755 index 0000000000..594526ac80 --- /dev/null +++ b/tools/emulator/gui.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# +# Copyright (C) 2017 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. +# + +# A simple GUI to remotely actuate the Vehicle HAL via the eumalator + +import sys +from threading import Thread +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +import VehicleHalProto_pb2 +from vhal_emulator import Vhal +import vhal_consts_2_1 as c + + +# Define a simple thread that receives messages from a vhal object (v) and prints them +def rxThread(v): + while(1): + msg = v.rxMsg() + if (msg.msg_type == VehicleHalProto_pb2.SET_PROPERTY_RESP): + if msg.status == 0: + print "Success ("+str(msg.status)+")" + else: + print "Error ("+str(msg.status)+")" + else: + print msg; + + +# Main window setup +def window(): + app = QApplication(sys.argv) + widget = QWidget() + widget.setWindowTitle("VHal Driver") + widget.setGeometry(100,100,200,50) + topLevelLayout = QHBoxLayout() + widget.setLayout(topLevelLayout) + + shiftLayout = QVBoxLayout() + topLevelLayout.addLayout(shiftLayout) + + gearTitle = QLabel(widget) + gearTitle.setText("Gear Shift") + shiftLayout.addWidget(gearTitle); + + gearDisplay = QLabel(widget) + shiftLayout.addWidget(gearDisplay); + + slider = QSlider(Qt.Vertical) + slider.setMinimum(0) + slider.setMaximum(2) + slider.setInvertedAppearance(True) + slider.valueChanged.connect(lambda:sliderMove(slider, gearDisplay)) + shiftLayout.addWidget(slider) + sliderMove(slider, gearDisplay) + + + buttonLayout = QVBoxLayout() + topLevelLayout.addLayout(buttonLayout) + + signalButtonGroup = QButtonGroup() + + bNoSignal = QPushButton("None") + bNoSignal.setCheckable(True) + bNoSignal.setChecked(True) + buttonLayout.addWidget(bNoSignal) + signalButtonGroup.addButton(bNoSignal) + + bHazards = QPushButton("Hazards") + bHazards.setCheckable(True) + buttonLayout.addWidget(bHazards) + signalButtonGroup.addButton(bHazards) + + bLeft = QPushButton("Left") + bLeft.setCheckable(True) + buttonLayout.addWidget(bLeft) + signalButtonGroup.addButton(bLeft) + + bRight = QPushButton("Right") + bRight.setCheckable(True) + buttonLayout.addWidget(bRight) + signalButtonGroup.addButton(bRight) + + signalButtonGroup.buttonClicked.connect(lambda:onSignalClicked(signalButtonGroup)) + + widget.show() + sys.exit(app.exec_()) + + +def onSignalClicked(group): + print "signal "+group.checkedButton().text()+" is active" + try: + vhal.setProperty(c.VEHICLEPROPERTY_TURN_SIGNAL_STATE, 0, group.checkedId()) + except: + print "Ignoring error setting property 0x{:08X}".format(c.VEHICLEPROPERTY_TURN_SIGNAL_STATE) + + +def sliderMove(slider, gearDisplay): + if slider.value() == 0: + gearName = 'park' + vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_PARK) + elif slider.value() == 1: + gearName = 'reverse' + vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_REVERSE) + elif slider.value() == 2: + gearName = 'drive' + vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_DRIVE) + else: + gearName = "UNK" + print "slider "+slider.objectName()+" requested "+str(slider.value())+" = "+gearName + gearDisplay.setText(gearName) + + +if __name__ == '__main__': + print "Starting VHal driver GUI" + vhal = Vhal(c.vhal_types_2_0) + + # Start a receive thread to consume any replies from the vhal + print "Starting receiver thread" + rx = Thread(target=rxThread, args=(vhal,)) + rx.setDaemon(True) + rx.start() + + # Put the car in park so we start in a known state (consistent with the GUI default state) + vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_PARK) + + # Start the main UI -- never returns + window() diff --git a/tools/emulator/obd2_to_diagjson.py b/tools/emulator/obd2_to_diagjson.py new file mode 100755 index 0000000000..89066dfa62 --- /dev/null +++ b/tools/emulator/obd2_to_diagjson.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017 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. +# + +# OBD2 standard sensor indices are different from those used by the +# Android Auto Diagnostics API. This script maps from OBD2 sensors to +# those expected by the Diagnostics API. +# To use: +# ./obd2_to_diagjson.py --src file1.json --dst file2.json +# It is acceptable and supported to point --src and --dst to the same file + +import collections +import json +import os, os.path, sys + +class Json(object): + @classmethod + def load(cls, file): + return Json(json.load(file)) + + @classmethod + def wrapIfNeeded(cls, item): + if isinstance(item, list) or isinstance(item, dict): + return Json(item) + return item + + def __init__(self, doc): + self.doc = doc + + def __str__(self): + return str(self.doc) + + def __repr__(self): + return self.__str__() + + def __getattr__(self, attr): + return Json.wrapIfNeeded(self.doc.get(attr)) + + def __iter__(self): + class Iter(object): + def __init__(self, doc): + self.doc = doc.__iter__() + + def __next__(self): + return Json.wrapIfNeeded(self.doc.__next__()) + + return Iter(self.doc) + +class OrderedStore(object): + def __init__(self): + self.__dict__['store'] = collections.OrderedDict() + + def __setattr__(self, name, value): + self.__dict__['store'][name] = value + + def __getattr__(self, name): + return self.__dict__['store'][name] + + def get(self, name, default=None): + return self.__dict__['store'].get(name, default) + + def getStore(self): + return self.__dict__['store'] + + def __iter__(self): + return iter(self.__dict__['store']) + + def __delattr__(self, name): + del self.__dict__['store'][name] + + def __str__(self): + return str(self.__dict__['store']) + + def toJSON(self): + return json.dumps(self.store) + +class Event(object): + def __init__(self): + self.store = OrderedStore() + + def setTimestamp(self, timestamp): + self.store.timestamp = timestamp + return self + + def getTimestamp(self): + return self.store.timestamp + + def setType(self, type): + self.store.type = type + return self + + def getType(self): + return self.store.type + + def setStringValue(self, string): + if string: + self.store.stringValue = string + return self + + def getStringValue(self): + return self.store.get('stringValue') + + def setIntValue(self, id, value): + if 'intValues' not in self.store: + self.store.intValues = [] + d = collections.OrderedDict() + d['id'] = id + d['value'] = value + self.store.intValues.append(d) + return self + + def intValues(self): + if 'intValues' not in self.store: + return [] + for value in self.store.intValues: + yield (value['id'], value['value']) + + def setFloatValue(self, id, value): + if 'floatValues' not in self.store: + self.store.floatValues = [] + d = collections.OrderedDict() + d['id'] = id + d['value'] = value + self.store.floatValues.append(d) + return self + + def floatValues(self): + if 'floatValues' not in self.store: + return [] + for value in self.store.floatValues: + yield (value['id'], value['value']) + + @classmethod + def fromJson(cls, json): + event = Event() + event.setTimestamp(json.timestamp) + event.setType(json.type) + for intValue in json.intValues: + event.setIntValue(intValue.id, intValue.value) + for floatValue in json.floatValues: + event.setFloatValue(floatValue.id, floatValue.value) + event.setStringValue(json.stringValue) + return event + + def transform(self, intMapping, floatMapping): + event = Event() + event.setTimestamp(self.getTimestamp()) + event.setType(self.getType()) + for id, value in self.intValues(): + if id in intMapping: + intMapping[id](event, value) + else: + print('warning: integer id 0x%x not found in mapping. dropped.' % id) + for id, value in self.floatValues(): + if id in floatMapping: + floatMapping[id](event, value) + else: + print('warning: float id 0x%x not found in mapping. dropped.' % id) + event.setStringValue(self.getStringValue()) + return event + + def getStore(self): + return self.store.getStore() + +class EventEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, Event): + return o.getStore() + +# Mappings between standard OBD2 sensors and the indices +# used by Vehicle HAL +intSensorsMapping = { + 0x03 : lambda event,value: event.setIntValue(0, value), + 0x05 : lambda event,value: event.setFloatValue(1, value), + 0x0A : lambda event,value: event.setIntValue(22, value), + 0x0C : lambda event,value: event.setFloatValue(8, value), + 0x0D : lambda event,value: event.setFloatValue(9, value), + 0x1F : lambda event,value: event.setIntValue(7, value), + 0x5C : lambda event,value: event.setIntValue(23, value), +} + +floatSensorsMapping = { + 0x04 : lambda event, value: event.setFloatValue(0, value), + 0x06 : lambda event, value: event.setFloatValue(2, value), + 0x07 : lambda event, value: event.setFloatValue(3, value), + 0x08 : lambda event, value: event.setFloatValue(4, value), + 0x09 : lambda event, value: event.setFloatValue(5, value), + 0x11 : lambda event, value: event.setFloatValue(12, value), + 0x2F : lambda event, value: event.setFloatValue(42, value), + 0x46 : lambda event, value: event.setIntValue(13, int(value)), +} + +def parseOptions(): + from argparse import ArgumentParser + parser = ArgumentParser(description='OBD2 to Diagnostics JSON Converter') + parser.add_argument('--src', '-S', dest='source_file', + help='The source file to convert from', required=True) + parser.add_argument('--dst', '-D', dest='destination_file', + help='The destination file to convert to', required=True) + return parser.parse_args() + +args = parseOptions() +if not os.path.exists(args.source_file): + print('source file %s does not exist' % args.source_file) + sys.exit(1) + +source_json = Json.load(open(args.source_file)) +dest_events = [] + +for source_json_event in source_json: + source_event = Event.fromJson(source_json_event) + destination_event = source_event.transform(intSensorsMapping, floatSensorsMapping) + dest_events.append(destination_event) + +json.dump(dest_events, open(args.destination_file, 'w'), cls=EventEncoder) diff --git a/tools/emulator/vhal_const_generate.py b/tools/emulator/vhal_const_generate.py new file mode 100755 index 0000000000..6695bd39ee --- /dev/null +++ b/tools/emulator/vhal_const_generate.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3.4 +# +# Copyright (C) 2017 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. +# + +# This script generates vhal_consts_x_y.py files for use in vhal_emulator +# They are generated from corresponding data in Vehicle HAL types.hal files +# To run, invoke at a shell by saying: +# $ packages/services/Car/tools/emulator/vhal_const_generate.py +# The script will automatically locate itself and the required HAL files and will write next +# to itself vhal_consts_x.y.py for any version of Vehicle HAL that it knows about +# Those files can then be used with vhal_emulator.py as per that script's documentation + +from __future__ import print_function + +import datetime + +def printHeader(dest): + year = datetime.datetime.now().year + print("# Copyright (C) %s The Android Open Source Project" % year, file=dest) + print("#", file=dest) + print("# Licensed under the Apache License, Version 2.0 (the \"License\");", file=dest) + print("# you may not use this file except in compliance with the License.", file=dest) + print("# You may obtain a copy of the License at", file=dest) + print("#", file=dest) + print("# http://www.apache.org/licenses/LICENSE-2.0", file=dest) + print("#", file=dest) + print("# Unless required by applicable law or agreed to in writing, software", file=dest) + print("# distributed under the License is distributed on an \"AS IS\" BASIS,", file=dest) + print("# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", file=dest) + print("# See the License for the specific language governing permissions and", file=dest) + print("# limitations under the License.", file=dest) + print("#", file=dest) + print("# DO NOT EDIT MANUALLY", file=dest) + print("# This file was autogenerated by vhal_const_generate.py", file=dest) + +def printEnum(doc, name, dest, postprocess=lambda x: x): + # Construct a value name prefix from the group name + valueNamePrefix = name.upper() + '_' + + enum_object = doc['enums'][name] + print("\n# %s" % name, file=dest) + for case in enum_object.cases: + print('%s%s = %s' % (valueNamePrefix, case.name, + postprocess(case.value.resolve(enum_object, doc))), + file=dest) + +import os, os.path +import sys + +script_directory = os.path.join(os.path.dirname(os.path.abspath(__file__))) +parent_location = os.path.abspath(os.path.join(script_directory, '..')) +sys.path.append(parent_location) + +# hidl_parser depends on a custom Python package that requires installation +# give user guidance should the import fail +try: + from hidl_parser import parser +except ImportError as e: + isPly = False + pipTool = "pip%s" % ("3" if sys.version_info > (3,0) else "") + if hasattr(e, 'name'): + if e.name == 'ply': isPly = True + elif hasattr(e, 'message'): + if e.message.endswith('ply'): isPly = True + if isPly: + print('could not import ply.') + print('ply is available as part of an Android checkout in external/ply') + print('or it can be installed via "sudo %s install ply"' % pipTool) + sys.exit(1) + else: + raise e + +android_build_top = os.environ.get("ANDROID_BUILD_TOP", None) +if android_build_top is not None: + vhal_location = os.path.join(android_build_top, 'hardware','interfaces','automotive','vehicle') +else: + vhal_location = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), + '..','..','..','..','..','hardware','interfaces','automotive','vehicle' + )) +if not(os.path.exists(vhal_location) and os.path.isdir(vhal_location)): + print("Vehicle HAL was not found at %s. lunch may provide a correct environment, or files moved" % vhal_location) + sys.exit(1) + +vhal_20_file = os.path.join(vhal_location, '2.0', 'types.hal') +vhal_21_file = os.path.join(vhal_location, '2.1', 'types.hal') + +print("Generating content from Vehicle HAL 2.0 (%s) and 2.1 (%s)" % (vhal_20_file, vhal_21_file)) + +vhal_20_doc = parser.parse(vhal_20_file) +vhal_21_doc = parser.parse(vhal_21_file) + +# Work around the fact that the parser doesn't (yet?) deal with inheritance. +# WARNING: This pattern is rather unsafe since we're not merging the lists as we should! +vhal_21_doc['enums']['VehiclePropertyGroup'] = vhal_20_doc['enums']['VehiclePropertyGroup'] +vhal_21_doc['enums']['VehiclePropertyType'] = vhal_20_doc['enums']['VehiclePropertyType'] +vhal_21_doc['enums']['VehicleArea'] = vhal_20_doc['enums']['VehicleArea'] + +def generateHal20(): + print("********************************") + print("Generating VHal 2.0 constants...") + vhal_20_file = open(os.path.join(script_directory, 'vhal_consts_2_0.py'), 'w') + + printHeader(vhal_20_file) + + for group in vhal_20_doc['enums']: + print(group) + printEnum(vhal_20_doc, group, vhal_20_file, lambda x : hex(x)) + + print("\n# Create a container of value_type constants to be used by vhal_emulator", file=vhal_20_file) + print("class vhal_types_2_0:", file=vhal_20_file) + print(" TYPE_STRING = [VEHICLEPROPERTYTYPE_STRING]", file=vhal_20_file) + print(" TYPE_BYTES = [VEHICLEPROPERTYTYPE_BYTES]", file=vhal_20_file) + print(" TYPE_INT32 = [VEHICLEPROPERTYTYPE_BOOLEAN,", file=vhal_20_file) + print(" VEHICLEPROPERTYTYPE_INT32]", file=vhal_20_file) + print(" TYPE_INT64 = [VEHICLEPROPERTYTYPE_INT64]", file=vhal_20_file) + print(" TYPE_FLOAT = [VEHICLEPROPERTYTYPE_FLOAT]", file=vhal_20_file) + print(" TYPE_INT32S = [VEHICLEPROPERTYTYPE_INT32_VEC]", file=vhal_20_file) + print(" TYPE_FLOATS = [VEHICLEPROPERTYTYPE_FLOAT_VEC]", file=vhal_20_file) + print(" TYPE_COMPLEX = [VEHICLEPROPERTYTYPE_COMPLEX]", file=vhal_20_file) + +def generateHal21(): + print("********************************") + print("Generating VHal 2.1 constants...") + vhal_21_file = open(os.path.join(script_directory, 'vhal_consts_2_1.py'), 'w') + printHeader(vhal_21_file) + print('from vhal_consts_2_0 import *', file=vhal_21_file) + + for group in vhal_21_doc['enums']: + print(group) + printEnum(vhal_21_doc, group, vhal_21_file, lambda x : hex(x)) + + +generateHal20() +generateHal21() diff --git a/tools/emulator/vhal_consts_2_0.py b/tools/emulator/vhal_consts_2_0.py index f2433f6be5..50518fdc5a 100644 --- a/tools/emulator/vhal_consts_2_0.py +++ b/tools/emulator/vhal_consts_2_0.py @@ -1,10 +1,10 @@ -# Copyright 2017 Google Inc. +# Copyright (C) 2017 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 +# 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, @@ -12,164 +12,427 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# DO NOT EDIT MANUALLY +# This file was autogenerated by vhal_const_generate.py -""" - This file contains constants defined in hardware/interfaces/vehicle/2.0/types.hal - - Constants in this file are parsed from: - out/soong/.intermediates/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle@2.0_genc++_headers/gen/android/hardware/automotive/vehicle/2.0/types.h - - Currently, there is no script to auto-generate this constants file. The file is generated by - copying enum fields into an editor and running a macro to format it. The elements being used - are shown in the following table: - - type.h file: this file: - VehiclePropertyType enum --> VEHICLE_VALUE_TYPE_* - VehicleProperty enum --> VEHICLE_PROPERTY_* - VehicleAreaZone enum --> VEHICLE_ZONE_* - VehiclePropertyType enum --> class vhal_types_2_0 -""" - -# Vehicle Property ID -VEHICLE_PROPERTY_INFO_VIN = 286261504 -VEHICLE_PROPERTY_INFO_MAKE = 286261505 -VEHICLE_PROPERTY_INFO_MODEL = 286261506 -VEHICLE_PROPERTY_INFO_MODEL_YEAR = 289407235 -VEHICLE_PROPERTY_INFO_FUEL_CAPACITY = 291504388 -VEHICLE_PROPERTY_PERF_ODOMETER = 291504644 -VEHICLE_PROPERTY_PERF_VEHICLE_SPEED = 291504647 -VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP = 291504897 -VEHICLE_PROPERTY_ENGINE_OIL_TEMP = 291504900 -VEHICLE_PROPERTY_ENGINE_RPM = 291504901 -VEHICLE_PROPERTY_GEAR_SELECTION = 289408000 -VEHICLE_PROPERTY_CURRENT_GEAR = 289408001 -VEHICLE_PROPERTY_PARKING_BRAKE_ON = 287310850 -VEHICLE_PROPERTY_DRIVING_STATUS = 289408004 -VEHICLE_PROPERTY_FUEL_LEVEL_LOW = 287310853 -VEHICLE_PROPERTY_NIGHT_MODE = 287310855 -VEHICLE_PROPERTY_TURN_SIGNAL_STATE = 289408008 -VEHICLE_PROPERTY_IGNITION_STATE = 289408009 -VEHICLE_PROPERTY_HVAC_FAN_SPEED = 306185472 -VEHICLE_PROPERTY_HVAC_FAN_DIRECTION = 306185473 -VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT = 308282626 -VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET = 308282627 -VEHICLE_PROPERTY_HVAC_DEFROSTER = 320865540 -VEHICLE_PROPERTY_HVAC_AC_ON = 304088325 -VEHICLE_PROPERTY_HVAC_MAX_AC_ON = 304088326 -VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON = 304088327 -VEHICLE_PROPERTY_HVAC_RECIRC_ON = 304088328 -VEHICLE_PROPERTY_HVAC_DUAL_ON = 304088329 -VEHICLE_PROPERTY_HVAC_AUTO_ON = 304088330 -VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE = 356517131 -VEHICLE_PROPERTY_HVAC_SIDE_MIRROR_HEAT = 339739916 -VEHICLE_PROPERTY_HVAC_STEERING_WHEEL_TEMP = 289408269 -VEHICLE_PROPERTY_HVAC_TEMPERATURE_UNITS = 306185486 -VEHICLE_PROPERTY_HVAC_ACTUAL_FAN_SPEED_RPM = 306185487 -VEHICLE_PROPERTY_HVAC_FAN_DIRECTION_AVAILABLE = 306185489 -VEHICLE_PROPERTY_HVAC_POWER_ON = 304088336 -VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE = 291505923 -VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE = 291505924 -VEHICLE_PROPERTY_RADIO_PRESET = 289474561 -VEHICLE_PROPERTY_AUDIO_FOCUS = 289474816 -VEHICLE_PROPERTY_AUDIO_FOCUS_EXT_SYNC = 289474832 -VEHICLE_PROPERTY_AUDIO_VOLUME = 289474817 -VEHICLE_PROPERTY_AUDIO_VOLUME_EXT_SYNC = 289474833 -VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT = 289474818 -VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY = 289474819 -VEHICLE_PROPERTY_AUDIO_HW_VARIANT = 289409284 -VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT = 289474821 -VEHICLE_PROPERTY_AUDIO_STREAM_STATE = 289474822 -VEHICLE_PROPERTY_AUDIO_PARAMETERS = 286263559 -VEHICLE_PROPERTY_AP_POWER_STATE = 2560 -VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS = 289409537 -VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON = 289409538 -VEHICLE_PROPERTY_HW_KEY_INPUT = 289475088 -VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO = 289475104 -VEHICLE_PROPERTY_UNIX_TIME = 290458160 -VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS = 289409585 -VEHICLE_PROPERTY_DOOR_POS = 373295872 -VEHICLE_PROPERTY_DOOR_MOVE = 373295873 -VEHICLE_PROPERTY_DOOR_LOCK = 371198722 -VEHICLE_PROPERTY_MIRROR_Z_POS = 339741504 -VEHICLE_PROPERTY_MIRROR_Z_MOVE = 339741505 -VEHICLE_PROPERTY_MIRROR_Y_POS = 339741506 -VEHICLE_PROPERTY_MIRROR_Y_MOVE = 339741507 -VEHICLE_PROPERTY_MIRROR_LOCK = 287312708 -VEHICLE_PROPERTY_MIRROR_FOLD = 287312709 -VEHICLE_PROPERTY_SEAT_MEMORY_SELECT = 356518784 -VEHICLE_PROPERTY_SEAT_MEMORY_SET = 356518785 -VEHICLE_PROPERTY_SEAT_BELT_BUCKLED = 354421634 -VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS = 356518787 -VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE = 356518788 -VEHICLE_PROPERTY_SEAT_FORE_AFT_POS = 356518789 -VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE = 356518790 -VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS = 356518791 -VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE = 356518792 -VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS = 356518793 -VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE = 356518794 -VEHICLE_PROPERTY_SEAT_HEIGHT_POS = 356518795 -VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE = 356518796 -VEHICLE_PROPERTY_SEAT_DEPTH_POS = 356518797 -VEHICLE_PROPERTY_SEAT_DEPTH_MOVE = 356518798 -VEHICLE_PROPERTY_SEAT_TILT_POS = 356518799 -VEHICLE_PROPERTY_SEAT_TILT_MOVE = 356518800 -VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS = 356518801 -VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE = 356518802 -VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803 -VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804 -VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS = 289409941 -VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE = 356518806 -VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS = 356518807 -VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE = 356518808 -VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS = 356518809 -VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE = 356518810 -VEHICLE_PROPERTY_WINDOW_POS = 289409984 -VEHICLE_PROPERTY_WINDOW_MOVE = 289409985 -VEHICLE_PROPERTY_WINDOW_VENT_POS = 289409986 -VEHICLE_PROPERTY_WINDOW_VENT_MOVE = 289409987 -VEHICLE_PROPERTY_WINDOW_LOCK = 287312836 -VEHICLE_PROPERTY_VEHICLE_MAPS_DATA_SERVICE = 299895808 -VEHICLE_PROPERTY_OBD2_LIVE_FRAME = 299896064 -VEHICLE_PROPERTY_OBD2_FREEZE_FRAME = 299896065 - -# Vehicle Value Type -VEHICLE_VALUE_TYPE_STRING = 0x00100000 -VEHICLE_VALUE_TYPE_BOOLEAN = 0x00200000 -VEHICLE_VALUE_TYPE_INT32 = 0x00400000 -VEHICLE_VALUE_TYPE_INT32_VEC = 0x00410000 -VEHICLE_VALUE_TYPE_INT64 = 0x00500000 -VEHICLE_VALUE_TYPE_FLOAT = 0x00600000 -VEHICLE_VALUE_TYPE_FLOAT_VEC = 0x00610000 -VEHICLE_VALUE_TYPE_BYTES = 0x00700000 -VEHICLE_VALUE_TYPE_COMPLEX = 0x00E00000 - -# Vehicle zone / area definitions -VEHICLE_ZONE_ROW_1_LEFT = 0x00000001 -VEHICLE_ZONE_ROW_1_CENTER = 0x00000002 -VEHICLE_ZONE_ROW_1_RIGHT = 0x00000004 -VEHICLE_ZONE_ROW_1_ALL = 0x00000008 -VEHICLE_ZONE_ROW_2_LEFT = 0x00000010 -VEHICLE_ZONE_ROW_2_CENTER = 0x00000020 -VEHICLE_ZONE_ROW_2_RIGHT = 0x00000040 -VEHICLE_ZONE_ROW_2_ALL = 0x00000080 -VEHICLE_ZONE_ROW_3_LEFT = 0x00000100 -VEHICLE_ZONE_ROW_3_CENTER = 0x00000200 -VEHICLE_ZONE_ROW_3_RIGHT = 0x00000400 -VEHICLE_ZONE_ROW_3_ALL = 0x00000800 -VEHICLE_ZONE_ROW_4_LEFT = 0x00001000 -VEHICLE_ZONE_ROW_4_CENTER = 0x00002000 -VEHICLE_ZONE_ROW_4_RIGHT = 0x00004000 -VEHICLE_ZONE_ROW_4_ALL = 0x00008000 -VEHICLE_ZONE_ALL = 0x80000000 +# VehicleApPowerSetState +VEHICLEAPPOWERSETSTATE_BOOT_COMPLETE = 0x1 +VEHICLEAPPOWERSETSTATE_DEEP_SLEEP_ENTRY = 0x2 +VEHICLEAPPOWERSETSTATE_DEEP_SLEEP_EXIT = 0x3 +VEHICLEAPPOWERSETSTATE_SHUTDOWN_POSTPONE = 0x4 +VEHICLEAPPOWERSETSTATE_SHUTDOWN_START = 0x5 +VEHICLEAPPOWERSETSTATE_DISPLAY_OFF = 0x6 +VEHICLEAPPOWERSETSTATE_DISPLAY_ON = 0x7 + +# VehicleApPowerStateIndex +VEHICLEAPPOWERSTATEINDEX_STATE = 0x0 +VEHICLEAPPOWERSTATEINDEX_ADDITIONAL = 0x1 + +# VehicleAudioFocusRequest +VEHICLEAUDIOFOCUSREQUEST_REQUEST_GAIN = 0x1 +VEHICLEAUDIOFOCUSREQUEST_REQUEST_GAIN_TRANSIENT = 0x2 +VEHICLEAUDIOFOCUSREQUEST_REQUEST_GAIN_TRANSIENT_MAY_DUCK = 0x3 +VEHICLEAUDIOFOCUSREQUEST_REQUEST_GAIN_TRANSIENT_NO_DUCK = 0x4 +VEHICLEAUDIOFOCUSREQUEST_REQUEST_RELEASE = 0x5 + +# VehicleDisplay +VEHICLEDISPLAY_MAIN = 0x0 +VEHICLEDISPLAY_INSTRUMENT_CLUSTER = 0x1 + +# VehicleRadioConstants +VEHICLERADIOCONSTANTS_VEHICLE_RADIO_PRESET_MIN_VALUE = 0x1 + +# VehicleAudioFocusIndex +VEHICLEAUDIOFOCUSINDEX_FOCUS = 0x0 +VEHICLEAUDIOFOCUSINDEX_STREAMS = 0x1 +VEHICLEAUDIOFOCUSINDEX_EXTERNAL_FOCUS_STATE = 0x2 +VEHICLEAUDIOFOCUSINDEX_AUDIO_CONTEXTS = 0x3 + +# VehicleProperty +VEHICLEPROPERTY_INVALID = 0x0 +VEHICLEPROPERTY_INFO_VIN = 0x11100100 +VEHICLEPROPERTY_INFO_MAKE = 0x11100101 +VEHICLEPROPERTY_INFO_MODEL = 0x11100102 +VEHICLEPROPERTY_INFO_MODEL_YEAR = 0x11400103 +VEHICLEPROPERTY_INFO_FUEL_CAPACITY = 0x11600104 +VEHICLEPROPERTY_PERF_ODOMETER = 0x11600204 +VEHICLEPROPERTY_PERF_VEHICLE_SPEED = 0x11600207 +VEHICLEPROPERTY_ENGINE_COOLANT_TEMP = 0x11600301 +VEHICLEPROPERTY_ENGINE_OIL_TEMP = 0x11600304 +VEHICLEPROPERTY_ENGINE_RPM = 0x11600305 +VEHICLEPROPERTY_GEAR_SELECTION = 0x11400400 +VEHICLEPROPERTY_CURRENT_GEAR = 0x11400401 +VEHICLEPROPERTY_PARKING_BRAKE_ON = 0x11200402 +VEHICLEPROPERTY_DRIVING_STATUS = 0x11400404 +VEHICLEPROPERTY_FUEL_LEVEL_LOW = 0x11200405 +VEHICLEPROPERTY_NIGHT_MODE = 0x11200407 +VEHICLEPROPERTY_TURN_SIGNAL_STATE = 0x11400408 +VEHICLEPROPERTY_IGNITION_STATE = 0x11400409 +VEHICLEPROPERTY_HVAC_FAN_SPEED = 0x12400500 +VEHICLEPROPERTY_HVAC_FAN_DIRECTION = 0x12400501 +VEHICLEPROPERTY_HVAC_TEMPERATURE_CURRENT = 0x12600502 +VEHICLEPROPERTY_HVAC_TEMPERATURE_SET = 0x12600503 +VEHICLEPROPERTY_HVAC_DEFROSTER = 0x13200504 +VEHICLEPROPERTY_HVAC_AC_ON = 0x12200505 +VEHICLEPROPERTY_HVAC_MAX_AC_ON = 0x12200506 +VEHICLEPROPERTY_HVAC_MAX_DEFROST_ON = 0x12200507 +VEHICLEPROPERTY_HVAC_RECIRC_ON = 0x12200508 +VEHICLEPROPERTY_HVAC_DUAL_ON = 0x12200509 +VEHICLEPROPERTY_HVAC_AUTO_ON = 0x1220050a +VEHICLEPROPERTY_HVAC_SEAT_TEMPERATURE = 0x1540050b +VEHICLEPROPERTY_HVAC_SIDE_MIRROR_HEAT = 0x1440050c +VEHICLEPROPERTY_HVAC_STEERING_WHEEL_TEMP = 0x1140050d +VEHICLEPROPERTY_HVAC_TEMPERATURE_UNITS = 0x1240050e +VEHICLEPROPERTY_HVAC_ACTUAL_FAN_SPEED_RPM = 0x1240050f +VEHICLEPROPERTY_HVAC_FAN_DIRECTION_AVAILABLE = 0x12400511 +VEHICLEPROPERTY_HVAC_POWER_ON = 0x12200510 +VEHICLEPROPERTY_ENV_OUTSIDE_TEMPERATURE = 0x11600703 +VEHICLEPROPERTY_ENV_CABIN_TEMPERATURE = 0x11600704 +VEHICLEPROPERTY_RADIO_PRESET = 0x11410801 +VEHICLEPROPERTY_AUDIO_FOCUS = 0x11410900 +VEHICLEPROPERTY_AUDIO_FOCUS_EXT_SYNC = 0x11410910 +VEHICLEPROPERTY_AUDIO_VOLUME = 0x11410901 +VEHICLEPROPERTY_AUDIO_VOLUME_EXT_SYNC = 0x11410911 +VEHICLEPROPERTY_AUDIO_VOLUME_LIMIT = 0x11410902 +VEHICLEPROPERTY_AUDIO_ROUTING_POLICY = 0x11410903 +VEHICLEPROPERTY_AUDIO_HW_VARIANT = 0x11400904 +VEHICLEPROPERTY_AUDIO_EXT_ROUTING_HINT = 0x11410905 +VEHICLEPROPERTY_AUDIO_STREAM_STATE = 0x11410906 +VEHICLEPROPERTY_AUDIO_PARAMETERS = 0x11100907 +VEHICLEPROPERTY_AP_POWER_STATE = 0x11410a00 +VEHICLEPROPERTY_DISPLAY_BRIGHTNESS = 0x11400a01 +VEHICLEPROPERTY_AP_POWER_BOOTUP_REASON = 0x11400a02 +VEHICLEPROPERTY_HW_KEY_INPUT = 0x11410a10 +VEHICLEPROPERTY_INSTRUMENT_CLUSTER_INFO = 0x11410a20 +VEHICLEPROPERTY_UNIX_TIME = 0x11500a30 +VEHICLEPROPERTY_CURRENT_TIME_IN_SECONDS = 0x11400a31 +VEHICLEPROPERTY_DOOR_POS = 0x16400b00 +VEHICLEPROPERTY_DOOR_MOVE = 0x16400b01 +VEHICLEPROPERTY_DOOR_LOCK = 0x16200b02 +VEHICLEPROPERTY_MIRROR_Z_POS = 0x14400b40 +VEHICLEPROPERTY_MIRROR_Z_MOVE = 0x14400b41 +VEHICLEPROPERTY_MIRROR_Y_POS = 0x14400b42 +VEHICLEPROPERTY_MIRROR_Y_MOVE = 0x14400b43 +VEHICLEPROPERTY_MIRROR_LOCK = 0x11200b44 +VEHICLEPROPERTY_MIRROR_FOLD = 0x11200b45 +VEHICLEPROPERTY_SEAT_MEMORY_SELECT = 0x15400b80 +VEHICLEPROPERTY_SEAT_MEMORY_SET = 0x15400b81 +VEHICLEPROPERTY_SEAT_BELT_BUCKLED = 0x15200b82 +VEHICLEPROPERTY_SEAT_BELT_HEIGHT_POS = 0x15400b83 +VEHICLEPROPERTY_SEAT_BELT_HEIGHT_MOVE = 0x15400b84 +VEHICLEPROPERTY_SEAT_FORE_AFT_POS = 0x15400b85 +VEHICLEPROPERTY_SEAT_FORE_AFT_MOVE = 0x15400b86 +VEHICLEPROPERTY_SEAT_BACKREST_ANGLE_1_POS = 0x15400b87 +VEHICLEPROPERTY_SEAT_BACKREST_ANGLE_1_MOVE = 0x15400b88 +VEHICLEPROPERTY_SEAT_BACKREST_ANGLE_2_POS = 0x15400b89 +VEHICLEPROPERTY_SEAT_BACKREST_ANGLE_2_MOVE = 0x15400b8a +VEHICLEPROPERTY_SEAT_HEIGHT_POS = 0x15400b8b +VEHICLEPROPERTY_SEAT_HEIGHT_MOVE = 0x15400b8c +VEHICLEPROPERTY_SEAT_DEPTH_POS = 0x15400b8d +VEHICLEPROPERTY_SEAT_DEPTH_MOVE = 0x15400b8e +VEHICLEPROPERTY_SEAT_TILT_POS = 0x15400b8f +VEHICLEPROPERTY_SEAT_TILT_MOVE = 0x15400b90 +VEHICLEPROPERTY_SEAT_LUMBAR_FORE_AFT_POS = 0x15400b91 +VEHICLEPROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE = 0x15400b92 +VEHICLEPROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS = 0x15400b93 +VEHICLEPROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 0x15400b94 +VEHICLEPROPERTY_SEAT_HEADREST_HEIGHT_POS = 0x11400b95 +VEHICLEPROPERTY_SEAT_HEADREST_HEIGHT_MOVE = 0x15400b96 +VEHICLEPROPERTY_SEAT_HEADREST_ANGLE_POS = 0x15400b97 +VEHICLEPROPERTY_SEAT_HEADREST_ANGLE_MOVE = 0x15400b98 +VEHICLEPROPERTY_SEAT_HEADREST_FORE_AFT_POS = 0x15400b99 +VEHICLEPROPERTY_SEAT_HEADREST_FORE_AFT_MOVE = 0x15400b9a +VEHICLEPROPERTY_WINDOW_POS = 0x11400bc0 +VEHICLEPROPERTY_WINDOW_MOVE = 0x11400bc1 +VEHICLEPROPERTY_WINDOW_VENT_POS = 0x11400bc2 +VEHICLEPROPERTY_WINDOW_VENT_MOVE = 0x11400bc3 +VEHICLEPROPERTY_WINDOW_LOCK = 0x11200bc4 + +# VehicleAreaZone +VEHICLEAREAZONE_ROW_1_LEFT = 0x1 +VEHICLEAREAZONE_ROW_1_CENTER = 0x2 +VEHICLEAREAZONE_ROW_1_RIGHT = 0x4 +VEHICLEAREAZONE_ROW_1 = 0x8 +VEHICLEAREAZONE_ROW_2_LEFT = 0x10 +VEHICLEAREAZONE_ROW_2_CENTER = 0x20 +VEHICLEAREAZONE_ROW_2_RIGHT = 0x40 +VEHICLEAREAZONE_ROW_2 = 0x80 +VEHICLEAREAZONE_ROW_3_LEFT = 0x100 +VEHICLEAREAZONE_ROW_3_CENTER = 0x200 +VEHICLEAREAZONE_ROW_3_RIGHT = 0x400 +VEHICLEAREAZONE_ROW_3 = 0x800 +VEHICLEAREAZONE_ROW_4_LEFT = 0x1000 +VEHICLEAREAZONE_ROW_4_CENTER = 0x2000 +VEHICLEAREAZONE_ROW_4_RIGHT = 0x4000 +VEHICLEAREAZONE_ROW_4 = 0x8000 +VEHICLEAREAZONE_WHOLE_CABIN = 0x80000000 + +# SubscribeFlags +SUBSCRIBEFLAGS_UNDEFINED = 0x0 +SUBSCRIBEFLAGS_HAL_EVENT = 0x1 +SUBSCRIBEFLAGS_SET_CALL = 0x2 +SUBSCRIBEFLAGS_DEFAULT = 0x1 + +# Wheel +WHEEL_UNKNOWN = 0x0 +WHEEL_LEFT_FRONT = 0x1 +WHEEL_RIGHT_FRONT = 0x2 +WHEEL_LEFT_REAR = 0x4 +WHEEL_RIGHT_REAR = 0x8 + +# StatusCode +STATUSCODE_OK = 0x0 +STATUSCODE_TRY_AGAIN = 0x1 +STATUSCODE_INVALID_ARG = 0x2 +STATUSCODE_NOT_AVAILABLE = 0x3 +STATUSCODE_ACCESS_DENIED = 0x4 +STATUSCODE_INTERNAL_ERROR = 0x5 + +# VehicleAudioHwVariantConfigFlag +VEHICLEAUDIOHWVARIANTCONFIGFLAG_INTERNAL_RADIO_FLAG = 0x1 + +# VehiclePropertyGroup +VEHICLEPROPERTYGROUP_SYSTEM = 0x10000000 +VEHICLEPROPERTYGROUP_VENDOR = 0x20000000 +VEHICLEPROPERTYGROUP_MASK = 0xf0000000 + +# VehicleAudioStreamFlag +VEHICLEAUDIOSTREAMFLAG_STREAM0_FLAG = 0x1 +VEHICLEAUDIOSTREAMFLAG_STREAM1_FLAG = 0x2 +VEHICLEAUDIOSTREAMFLAG_STREAM2_FLAG = 0x4 + +# VehiclePropertyChangeMode +VEHICLEPROPERTYCHANGEMODE_STATIC = 0x0 +VEHICLEPROPERTYCHANGEMODE_ON_CHANGE = 0x1 +VEHICLEPROPERTYCHANGEMODE_CONTINUOUS = 0x2 +VEHICLEPROPERTYCHANGEMODE_POLL = 0x3 +VEHICLEPROPERTYCHANGEMODE_ON_SET = 0x4 + +# VehicleAreaSeat +VEHICLEAREASEAT_ROW_1_LEFT = 0x1 +VEHICLEAREASEAT_ROW_1_CENTER = 0x2 +VEHICLEAREASEAT_ROW_1_RIGHT = 0x4 +VEHICLEAREASEAT_ROW_2_LEFT = 0x10 +VEHICLEAREASEAT_ROW_2_CENTER = 0x20 +VEHICLEAREASEAT_ROW_2_RIGHT = 0x40 +VEHICLEAREASEAT_ROW_3_LEFT = 0x100 +VEHICLEAREASEAT_ROW_3_CENTER = 0x200 +VEHICLEAREASEAT_ROW_3_RIGHT = 0x400 + +# VehicleAudioVolumeIndex +VEHICLEAUDIOVOLUMEINDEX_INDEX_STREAM = 0x0 +VEHICLEAUDIOVOLUMEINDEX_INDEX_VOLUME = 0x1 +VEHICLEAUDIOVOLUMEINDEX_INDEX_STATE = 0x2 + +# VehicleUnit +VEHICLEUNIT_SHOULD_NOT_USE = 0x0 +VEHICLEUNIT_METER_PER_SEC = 0x1 +VEHICLEUNIT_RPM = 0x2 +VEHICLEUNIT_HERTZ = 0x3 +VEHICLEUNIT_PERCENTILE = 0x10 +VEHICLEUNIT_MILLIMETER = 0x20 +VEHICLEUNIT_METER = 0x21 +VEHICLEUNIT_KILOMETER = 0x23 +VEHICLEUNIT_CELSIUS = 0x30 +VEHICLEUNIT_FAHRENHEIT = 0x31 +VEHICLEUNIT_KELVIN = 0x32 +VEHICLEUNIT_MILLILITER = 0x40 +VEHICLEUNIT_NANO_SECS = 0x50 +VEHICLEUNIT_SECS = 0x53 +VEHICLEUNIT_YEAR = 0x59 + +# VehicleAreaMirror +VEHICLEAREAMIRROR_DRIVER_LEFT = 0x1 +VEHICLEAREAMIRROR_DRIVER_RIGHT = 0x2 +VEHICLEAREAMIRROR_DRIVER_CENTER = 0x4 + +# VehiclePropertyAccess +VEHICLEPROPERTYACCESS_NONE = 0x0 +VEHICLEPROPERTYACCESS_READ = 0x1 +VEHICLEPROPERTYACCESS_WRITE = 0x2 +VEHICLEPROPERTYACCESS_READ_WRITE = 0x3 + +# VehicleAudioContextFlag +VEHICLEAUDIOCONTEXTFLAG_MUSIC_FLAG = 0x1 +VEHICLEAUDIOCONTEXTFLAG_NAVIGATION_FLAG = 0x2 +VEHICLEAUDIOCONTEXTFLAG_VOICE_COMMAND_FLAG = 0x4 +VEHICLEAUDIOCONTEXTFLAG_CALL_FLAG = 0x8 +VEHICLEAUDIOCONTEXTFLAG_ALARM_FLAG = 0x10 +VEHICLEAUDIOCONTEXTFLAG_NOTIFICATION_FLAG = 0x20 +VEHICLEAUDIOCONTEXTFLAG_UNKNOWN_FLAG = 0x40 +VEHICLEAUDIOCONTEXTFLAG_SAFETY_ALERT_FLAG = 0x80 +VEHICLEAUDIOCONTEXTFLAG_CD_ROM_FLAG = 0x100 +VEHICLEAUDIOCONTEXTFLAG_AUX_AUDIO_FLAG = 0x200 +VEHICLEAUDIOCONTEXTFLAG_SYSTEM_SOUND_FLAG = 0x400 +VEHICLEAUDIOCONTEXTFLAG_RADIO_FLAG = 0x800 +VEHICLEAUDIOCONTEXTFLAG_EXT_SOURCE_FLAG = 0x1000 + +# VehicleDrivingStatus +VEHICLEDRIVINGSTATUS_UNRESTRICTED = 0x0 +VEHICLEDRIVINGSTATUS_NO_VIDEO = 0x1 +VEHICLEDRIVINGSTATUS_NO_KEYBOARD_INPUT = 0x2 +VEHICLEDRIVINGSTATUS_NO_VOICE_INPUT = 0x4 +VEHICLEDRIVINGSTATUS_NO_CONFIG = 0x8 +VEHICLEDRIVINGSTATUS_LIMIT_MESSAGE_LEN = 0x10 + +# VehicleGear +VEHICLEGEAR_GEAR_NEUTRAL = 0x1 +VEHICLEGEAR_GEAR_REVERSE = 0x2 +VEHICLEGEAR_GEAR_PARK = 0x4 +VEHICLEGEAR_GEAR_DRIVE = 0x8 +VEHICLEGEAR_GEAR_LOW = 0x10 +VEHICLEGEAR_GEAR_1 = 0x10 +VEHICLEGEAR_GEAR_2 = 0x20 +VEHICLEGEAR_GEAR_3 = 0x40 +VEHICLEGEAR_GEAR_4 = 0x80 +VEHICLEGEAR_GEAR_5 = 0x100 +VEHICLEGEAR_GEAR_6 = 0x200 +VEHICLEGEAR_GEAR_7 = 0x400 +VEHICLEGEAR_GEAR_8 = 0x800 +VEHICLEGEAR_GEAR_9 = 0x1000 + +# VehicleTurnSignal +VEHICLETURNSIGNAL_NONE = 0x0 +VEHICLETURNSIGNAL_RIGHT = 0x1 +VEHICLETURNSIGNAL_LEFT = 0x2 +VEHICLETURNSIGNAL_EMERGENCY = 0x4 + +# VehicleApPowerStateShutdownParam +VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_IMMEDIATELY = 0x1 +VEHICLEAPPOWERSTATESHUTDOWNPARAM_CAN_SLEEP = 0x2 +VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_ONLY = 0x3 + +# VehiclePropertyOperation +VEHICLEPROPERTYOPERATION_GENERIC = 0x0 +VEHICLEPROPERTYOPERATION_SET = 0x1 +VEHICLEPROPERTYOPERATION_GET = 0x2 +VEHICLEPROPERTYOPERATION_SUBSCRIBE = 0x3 + +# VehiclePropertyType +VEHICLEPROPERTYTYPE_STRING = 0x100000 +VEHICLEPROPERTYTYPE_BOOLEAN = 0x200000 +VEHICLEPROPERTYTYPE_INT32 = 0x400000 +VEHICLEPROPERTYTYPE_INT32_VEC = 0x410000 +VEHICLEPROPERTYTYPE_INT64 = 0x500000 +VEHICLEPROPERTYTYPE_FLOAT = 0x600000 +VEHICLEPROPERTYTYPE_FLOAT_VEC = 0x610000 +VEHICLEPROPERTYTYPE_BYTES = 0x700000 +VEHICLEPROPERTYTYPE_COMPLEX = 0xe00000 +VEHICLEPROPERTYTYPE_MASK = 0xff0000 + +# VehicleAreaDoor +VEHICLEAREADOOR_ROW_1_LEFT = 0x1 +VEHICLEAREADOOR_ROW_1_RIGHT = 0x4 +VEHICLEAREADOOR_ROW_2_LEFT = 0x10 +VEHICLEAREADOOR_ROW_2_RIGHT = 0x40 +VEHICLEAREADOOR_ROW_3_LEFT = 0x100 +VEHICLEAREADOOR_ROW_3_RIGHT = 0x400 +VEHICLEAREADOOR_HOOD = 0x10000000 +VEHICLEAREADOOR_REAR = 0x20000000 + +# VehicleHwKeyInputAction +VEHICLEHWKEYINPUTACTION_ACTION_DOWN = 0x0 +VEHICLEHWKEYINPUTACTION_ACTION_UP = 0x1 + +# VehicleApPowerStateConfigFlag +VEHICLEAPPOWERSTATECONFIGFLAG_ENABLE_DEEP_SLEEP_FLAG = 0x1 +VEHICLEAPPOWERSTATECONFIGFLAG_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2 + +# VehicleIgnitionState +VEHICLEIGNITIONSTATE_UNDEFINED = 0x0 +VEHICLEIGNITIONSTATE_LOCK = 0x1 +VEHICLEIGNITIONSTATE_OFF = 0x2 +VEHICLEIGNITIONSTATE_ACC = 0x3 +VEHICLEIGNITIONSTATE_ON = 0x4 +VEHICLEIGNITIONSTATE_START = 0x5 + +# VehicleAudioVolumeLimitIndex +VEHICLEAUDIOVOLUMELIMITINDEX_STREAM = 0x0 +VEHICLEAUDIOVOLUMELIMITINDEX_MAX_VOLUME = 0x1 + +# VehicleAreaWindow +VEHICLEAREAWINDOW_FRONT_WINDSHIELD = 0x1 +VEHICLEAREAWINDOW_REAR_WINDSHIELD = 0x2 +VEHICLEAREAWINDOW_ROOF_TOP = 0x4 +VEHICLEAREAWINDOW_ROW_1_LEFT = 0x10 +VEHICLEAREAWINDOW_ROW_1_RIGHT = 0x20 +VEHICLEAREAWINDOW_ROW_2_LEFT = 0x100 +VEHICLEAREAWINDOW_ROW_2_RIGHT = 0x200 +VEHICLEAREAWINDOW_ROW_3_LEFT = 0x1000 +VEHICLEAREAWINDOW_ROW_3_RIGHT = 0x2000 + +# VehicleAudioFocusState +VEHICLEAUDIOFOCUSSTATE_STATE_GAIN = 0x1 +VEHICLEAUDIOFOCUSSTATE_STATE_GAIN_TRANSIENT = 0x2 +VEHICLEAUDIOFOCUSSTATE_STATE_LOSS_TRANSIENT_CAN_DUCK = 0x3 +VEHICLEAUDIOFOCUSSTATE_STATE_LOSS_TRANSIENT = 0x4 +VEHICLEAUDIOFOCUSSTATE_STATE_LOSS = 0x5 +VEHICLEAUDIOFOCUSSTATE_STATE_LOSS_TRANSIENT_EXLCUSIVE = 0x6 + +# VehicleAudioVolumeCapabilityFlag +VEHICLEAUDIOVOLUMECAPABILITYFLAG_PERSISTENT_STORAGE = 0x1 +VEHICLEAUDIOVOLUMECAPABILITYFLAG_MASTER_VOLUME_ONLY = 0x2 + +# VehicleApPowerState +VEHICLEAPPOWERSTATE_OFF = 0x0 +VEHICLEAPPOWERSTATE_DEEP_SLEEP = 0x1 +VEHICLEAPPOWERSTATE_ON_DISP_OFF = 0x2 +VEHICLEAPPOWERSTATE_ON_FULL = 0x3 +VEHICLEAPPOWERSTATE_SHUTDOWN_PREPARE = 0x4 + +# VehicleAudioVolumeState +VEHICLEAUDIOVOLUMESTATE_STATE_OK = 0x0 +VEHICLEAUDIOVOLUMESTATE_LIMIT_REACHED = 0x1 + +# VehicleAudioRoutingPolicyIndex +VEHICLEAUDIOROUTINGPOLICYINDEX_STREAM = 0x0 +VEHICLEAUDIOROUTINGPOLICYINDEX_CONTEXTS = 0x1 + +# VehicleAudioStream +VEHICLEAUDIOSTREAM_STREAM0 = 0x0 +VEHICLEAUDIOSTREAM_STREAM1 = 0x1 + +# VehicleInstrumentClusterType +VEHICLEINSTRUMENTCLUSTERTYPE_NONE = 0x0 +VEHICLEINSTRUMENTCLUSTERTYPE_HAL_INTERFACE = 0x1 +VEHICLEINSTRUMENTCLUSTERTYPE_EXTERNAL_DISPLAY = 0x2 + +# VehicleAudioExtFocusFlag +VEHICLEAUDIOEXTFOCUSFLAG_NONE_FLAG = 0x0 +VEHICLEAUDIOEXTFOCUSFLAG_PERMANENT_FLAG = 0x1 +VEHICLEAUDIOEXTFOCUSFLAG_TRANSIENT_FLAG = 0x2 +VEHICLEAUDIOEXTFOCUSFLAG_PLAY_ONLY_FLAG = 0x4 +VEHICLEAUDIOEXTFOCUSFLAG_MUTE_MEDIA_FLAG = 0x8 + +# VehicleHvacFanDirection +VEHICLEHVACFANDIRECTION_FACE = 0x1 +VEHICLEHVACFANDIRECTION_FLOOR = 0x2 +VEHICLEHVACFANDIRECTION_FACE_AND_FLOOR = 0x3 +VEHICLEHVACFANDIRECTION_DEFROST = 0x4 +VEHICLEHVACFANDIRECTION_DEFROST_AND_FLOOR = 0x5 + +# VehicleApPowerBootupReason +VEHICLEAPPOWERBOOTUPREASON_USER_POWER_ON = 0x0 +VEHICLEAPPOWERBOOTUPREASON_USER_UNLOCK = 0x1 +VEHICLEAPPOWERBOOTUPREASON_TIMER = 0x2 + +# VehicleArea +VEHICLEAREA_GLOBAL = 0x1000000 +VEHICLEAREA_ZONE = 0x2000000 +VEHICLEAREA_WINDOW = 0x3000000 +VEHICLEAREA_MIRROR = 0x4000000 +VEHICLEAREA_SEAT = 0x5000000 +VEHICLEAREA_DOOR = 0x6000000 +VEHICLEAREA_MASK = 0xf000000 # Create a container of value_type constants to be used by vhal_emulator class vhal_types_2_0: - TYPE_STRING = [VEHICLE_VALUE_TYPE_STRING] - TYPE_BYTES = [VEHICLE_VALUE_TYPE_BYTES] - TYPE_INT32 = [VEHICLE_VALUE_TYPE_BOOLEAN, - VEHICLE_VALUE_TYPE_INT32] - TYPE_INT64 = [VEHICLE_VALUE_TYPE_INT64] - TYPE_FLOAT = [VEHICLE_VALUE_TYPE_FLOAT] - TYPE_INT32S = [VEHICLE_VALUE_TYPE_INT32_VEC] - TYPE_FLOATS = [VEHICLE_VALUE_TYPE_FLOAT_VEC] - + TYPE_STRING = [VEHICLEPROPERTYTYPE_STRING] + TYPE_BYTES = [VEHICLEPROPERTYTYPE_BYTES] + TYPE_INT32 = [VEHICLEPROPERTYTYPE_BOOLEAN, + VEHICLEPROPERTYTYPE_INT32] + TYPE_INT64 = [VEHICLEPROPERTYTYPE_INT64] + TYPE_FLOAT = [VEHICLEPROPERTYTYPE_FLOAT] + TYPE_INT32S = [VEHICLEPROPERTYTYPE_INT32_VEC] + TYPE_FLOATS = [VEHICLEPROPERTYTYPE_FLOAT_VEC] + TYPE_COMPLEX = [VEHICLEPROPERTYTYPE_COMPLEX] diff --git a/tools/emulator/vhal_consts_2_1.py b/tools/emulator/vhal_consts_2_1.py new file mode 100644 index 0000000000..d367b85896 --- /dev/null +++ b/tools/emulator/vhal_consts_2_1.py @@ -0,0 +1,269 @@ +# Copyright (C) 2017 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. +# +# DO NOT EDIT MANUALLY +# This file was autogenerated by vhal_const_generate.py +from vhal_consts_2_0 import * + +# FuelType +FUELTYPE_NOT_AVAILABLE = 0x0 +FUELTYPE_GASOLINE = 0x1 +FUELTYPE_METHANOL = 0x2 +FUELTYPE_ETHANOL = 0x3 +FUELTYPE_DIESEL = 0x4 +FUELTYPE_LPG = 0x5 +FUELTYPE_CNG = 0x6 +FUELTYPE_PROPANE = 0x7 +FUELTYPE_ELECTRIC = 0x8 +FUELTYPE_BIFUEL_RUNNING_GASOLINE = 0x9 +FUELTYPE_BIFUEL_RUNNING_METHANOL = 0xa +FUELTYPE_BIFUEL_RUNNING_ETHANOL = 0xb +FUELTYPE_BIFUEL_RUNNING_LPG = 0xc +FUELTYPE_BIFUEL_RUNNING_CNG = 0xd +FUELTYPE_BIFUEL_RUNNING_PROPANE = 0xe +FUELTYPE_BIFUEL_RUNNING_ELECTRIC = 0xf +FUELTYPE_BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION = 0x10 +FUELTYPE_HYBRID_GASOLINE = 0x11 +FUELTYPE_HYBRID_ETHANOL = 0x12 +FUELTYPE_HYBRID_DIESEL = 0x13 +FUELTYPE_HYBRID_ELECTRIC = 0x14 +FUELTYPE_HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION = 0x15 +FUELTYPE_HYBRID_REGENERATIVE = 0x16 +FUELTYPE_BIFUEL_RUNNING_DIESEL = 0x17 + +# VmsBaseMessageIntegerValuesIndex +VMSBASEMESSAGEINTEGERVALUESINDEX_VMS_MESSAGE_TYPE = 0x0 + +# SparkIgnitionMonitors +SPARKIGNITIONMONITORS_EGR_AVAILABLE = 0x40 +SPARKIGNITIONMONITORS_EGR_INCOMPLETE = 0x80 +SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_AVAILABLE = 0x100 +SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_INCOMPLETE = 0x200 +SPARKIGNITIONMONITORS_OXYGEN_SENSOR_AVAILABLE = 0x400 +SPARKIGNITIONMONITORS_OXYGEN_SENSOR_INCOMPLETE = 0x800 +SPARKIGNITIONMONITORS_AC_REFRIGERANT_AVAILABLE = 0x1000 +SPARKIGNITIONMONITORS_AC_REFRIGERANT_INCOMPLETE = 0x2000 +SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_AVAILABLE = 0x4000 +SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_INCOMPLETE = 0x8000 +SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_AVAILABLE = 0x10000 +SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_INCOMPLETE = 0x20000 +SPARKIGNITIONMONITORS_HEATED_CATALYST_AVAILABLE = 0x40000 +SPARKIGNITIONMONITORS_HEATED_CATALYST_INCOMPLETE = 0x80000 +SPARKIGNITIONMONITORS_CATALYST_AVAILABLE = 0x100000 +SPARKIGNITIONMONITORS_CATALYST_INCOMPLETE = 0x200000 + +# Obd2FloatSensorIndex +OBD2FLOATSENSORINDEX_CALCULATED_ENGINE_LOAD = 0x0 +OBD2FLOATSENSORINDEX_ENGINE_COOLANT_TEMPERATURE = 0x1 +OBD2FLOATSENSORINDEX_SHORT_TERM_FUEL_TRIM_BANK1 = 0x2 +OBD2FLOATSENSORINDEX_LONG_TERM_FUEL_TRIM_BANK1 = 0x3 +OBD2FLOATSENSORINDEX_SHORT_TERM_FUEL_TRIM_BANK2 = 0x4 +OBD2FLOATSENSORINDEX_LONG_TERM_FUEL_TRIM_BANK2 = 0x5 +OBD2FLOATSENSORINDEX_FUEL_PRESSURE = 0x6 +OBD2FLOATSENSORINDEX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x7 +OBD2FLOATSENSORINDEX_ENGINE_RPM = 0x8 +OBD2FLOATSENSORINDEX_VEHICLE_SPEED = 0x9 +OBD2FLOATSENSORINDEX_TIMING_ADVANCE = 0xa +OBD2FLOATSENSORINDEX_MAF_AIR_FLOW_RATE = 0xb +OBD2FLOATSENSORINDEX_THROTTLE_POSITION = 0xc +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR1_VOLTAGE = 0xd +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR1_SHORT_TERM_FUEL_TRIM = 0xe +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR1_FUEL_AIR_EQUIVALENCE_RATIO = 0xf +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR2_VOLTAGE = 0x10 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR2_SHORT_TERM_FUEL_TRIM = 0x11 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR2_FUEL_AIR_EQUIVALENCE_RATIO = 0x12 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR3_VOLTAGE = 0x13 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR3_SHORT_TERM_FUEL_TRIM = 0x14 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR3_FUEL_AIR_EQUIVALENCE_RATIO = 0x15 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR4_VOLTAGE = 0x16 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR4_SHORT_TERM_FUEL_TRIM = 0x17 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR4_FUEL_AIR_EQUIVALENCE_RATIO = 0x18 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR5_VOLTAGE = 0x19 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR5_SHORT_TERM_FUEL_TRIM = 0x1a +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR5_FUEL_AIR_EQUIVALENCE_RATIO = 0x1b +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR6_VOLTAGE = 0x1c +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR6_SHORT_TERM_FUEL_TRIM = 0x1d +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR6_FUEL_AIR_EQUIVALENCE_RATIO = 0x1e +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR7_VOLTAGE = 0x1f +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR7_SHORT_TERM_FUEL_TRIM = 0x20 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR7_FUEL_AIR_EQUIVALENCE_RATIO = 0x21 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR8_VOLTAGE = 0x22 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR8_SHORT_TERM_FUEL_TRIM = 0x23 +OBD2FLOATSENSORINDEX_OXYGEN_SENSOR8_FUEL_AIR_EQUIVALENCE_RATIO = 0x24 +OBD2FLOATSENSORINDEX_FUEL_RAIL_PRESSURE = 0x25 +OBD2FLOATSENSORINDEX_FUEL_RAIL_GAUGE_PRESSURE = 0x26 +OBD2FLOATSENSORINDEX_COMMANDED_EXHAUST_GAS_RECIRCULATION = 0x27 +OBD2FLOATSENSORINDEX_EXHAUST_GAS_RECIRCULATION_ERROR = 0x28 +OBD2FLOATSENSORINDEX_COMMANDED_EVAPORATIVE_PURGE = 0x29 +OBD2FLOATSENSORINDEX_FUEL_TANK_LEVEL_INPUT = 0x2a +OBD2FLOATSENSORINDEX_EVAPORATION_SYSTEM_VAPOR_PRESSURE = 0x2b +OBD2FLOATSENSORINDEX_CATALYST_TEMPERATURE_BANK1_SENSOR1 = 0x2c +OBD2FLOATSENSORINDEX_CATALYST_TEMPERATURE_BANK2_SENSOR1 = 0x2d +OBD2FLOATSENSORINDEX_CATALYST_TEMPERATURE_BANK1_SENSOR2 = 0x2e +OBD2FLOATSENSORINDEX_CATALYST_TEMPERATURE_BANK2_SENSOR2 = 0x2f +OBD2FLOATSENSORINDEX_ABSOLUTE_LOAD_VALUE = 0x30 +OBD2FLOATSENSORINDEX_FUEL_AIR_COMMANDED_EQUIVALENCE_RATIO = 0x31 +OBD2FLOATSENSORINDEX_RELATIVE_THROTTLE_POSITION = 0x32 +OBD2FLOATSENSORINDEX_ABSOLUTE_THROTTLE_POSITION_B = 0x33 +OBD2FLOATSENSORINDEX_ABSOLUTE_THROTTLE_POSITION_C = 0x34 +OBD2FLOATSENSORINDEX_ACCELERATOR_PEDAL_POSITION_D = 0x35 +OBD2FLOATSENSORINDEX_ACCELERATOR_PEDAL_POSITION_E = 0x36 +OBD2FLOATSENSORINDEX_ACCELERATOR_PEDAL_POSITION_F = 0x37 +OBD2FLOATSENSORINDEX_COMMANDED_THROTTLE_ACTUATOR = 0x38 +OBD2FLOATSENSORINDEX_ETHANOL_FUEL_PERCENTAGE = 0x39 +OBD2FLOATSENSORINDEX_ABSOLUTE_EVAPORATION_SYSTEM_VAPOR_PRESSURE = 0x3a +OBD2FLOATSENSORINDEX_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1 = 0x3b +OBD2FLOATSENSORINDEX_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2 = 0x3c +OBD2FLOATSENSORINDEX_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3 = 0x3d +OBD2FLOATSENSORINDEX_SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4 = 0x3e +OBD2FLOATSENSORINDEX_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1 = 0x3f +OBD2FLOATSENSORINDEX_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2 = 0x40 +OBD2FLOATSENSORINDEX_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3 = 0x41 +OBD2FLOATSENSORINDEX_LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4 = 0x42 +OBD2FLOATSENSORINDEX_RELATIVE_ACCELERATOR_PEDAL_POSITION = 0x43 +OBD2FLOATSENSORINDEX_HYBRID_BATTERY_PACK_REMAINING_LIFE = 0x44 +OBD2FLOATSENSORINDEX_FUEL_INJECTION_TIMING = 0x45 +OBD2FLOATSENSORINDEX_ENGINE_FUEL_RATE = 0x46 +OBD2FLOATSENSORINDEX_LAST_SYSTEM_INDEX = 0x46 + +# CommonIgnitionMonitors +COMMONIGNITIONMONITORS_COMPONENTS_AVAILABLE = 0x1 +COMMONIGNITIONMONITORS_COMPONENTS_INCOMPLETE = 0x2 +COMMONIGNITIONMONITORS_FUEL_SYSTEM_AVAILABLE = 0x4 +COMMONIGNITIONMONITORS_FUEL_SYSTEM_INCOMPLETE = 0x8 +COMMONIGNITIONMONITORS_MISFIRE_AVAILABLE = 0x10 +COMMONIGNITIONMONITORS_MISFIRE_INCOMPLETE = 0x20 + +# IgnitionMonitorKind +IGNITIONMONITORKIND_SPARK = 0x0 +IGNITIONMONITORKIND_COMPRESSION = 0x1 + +# SecondaryAirStatus +SECONDARYAIRSTATUS_UPSTREAM = 0x1 +SECONDARYAIRSTATUS_DOWNSTREAM_OF_CATALYCIC_CONVERTER = 0x2 +SECONDARYAIRSTATUS_FROM_OUTSIDE_OR_OFF = 0x4 +SECONDARYAIRSTATUS_PUMP_ON_FOR_DIAGNOSTICS = 0x8 + +# VmsMessageType +VMSMESSAGETYPE_SUBSCRIBE = 0x1 +VMSMESSAGETYPE_UNSUBSCRIBE = 0x2 +VMSMESSAGETYPE_DATA = 0x3 +VMSMESSAGETYPE_OFFERING = 0x4 +VMSMESSAGETYPE_AVAILABILITY_REQUEST = 0x5 +VMSMESSAGETYPE_AVAILABILITY_RESPONSE = 0x6 +VMSMESSAGETYPE_SUBSCRIPTION_REQUEST = 0x7 +VMSMESSAGETYPE_SUBSCRIPTION_RESPONSE = 0x8 + +# CompressionIgnitionMonitors +COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_AVAILABLE = 0x40 +COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_INCOMPLETE = 0x80 +COMPRESSIONIGNITIONMONITORS_PM_FILTER_AVAILABLE = 0x100 +COMPRESSIONIGNITIONMONITORS_PM_FILTER_INCOMPLETE = 0x200 +COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_AVAILABLE = 0x400 +COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_INCOMPLETE = 0x800 +COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_AVAILABLE = 0x1000 +COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_INCOMPLETE = 0x2000 +COMPRESSIONIGNITIONMONITORS_NOx_SCR__AVAILABLE = 0x4000 +COMPRESSIONIGNITIONMONITORS_NOx_SCR_INCOMPLETE = 0x8000 +COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_AVAILABLE = 0x10000 +COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_INCOMPLETE = 0x20000 + +# VehiclePropertyGroup +VEHICLEPROPERTYGROUP_SYSTEM = 0x10000000 +VEHICLEPROPERTYGROUP_VENDOR = 0x20000000 +VEHICLEPROPERTYGROUP_MASK = 0xf0000000 + +# Obd2IntegerSensorIndex +OBD2INTEGERSENSORINDEX_FUEL_SYSTEM_STATUS = 0x0 +OBD2INTEGERSENSORINDEX_MALFUNCTION_INDICATOR_LIGHT_ON = 0x1 +OBD2INTEGERSENSORINDEX_IGNITION_MONITORS_SUPPORTED = 0x2 +OBD2INTEGERSENSORINDEX_IGNITION_SPECIFIC_MONITORS = 0x3 +OBD2INTEGERSENSORINDEX_INTAKE_AIR_TEMPERATURE = 0x4 +OBD2INTEGERSENSORINDEX_COMMANDED_SECONDARY_AIR_STATUS = 0x5 +OBD2INTEGERSENSORINDEX_NUM_OXYGEN_SENSORS_PRESENT = 0x6 +OBD2INTEGERSENSORINDEX_RUNTIME_SINCE_ENGINE_START = 0x7 +OBD2INTEGERSENSORINDEX_DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 0x8 +OBD2INTEGERSENSORINDEX_WARMUPS_SINCE_CODES_CLEARED = 0x9 +OBD2INTEGERSENSORINDEX_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 0xa +OBD2INTEGERSENSORINDEX_ABSOLUTE_BAROMETRIC_PRESSURE = 0xb +OBD2INTEGERSENSORINDEX_CONTROL_MODULE_VOLTAGE = 0xc +OBD2INTEGERSENSORINDEX_AMBIENT_AIR_TEMPERATURE = 0xd +OBD2INTEGERSENSORINDEX_TIME_WITH_MALFUNCTION_LIGHT_ON = 0xe +OBD2INTEGERSENSORINDEX_TIME_SINCE_TROUBLE_CODES_CLEARED = 0xf +OBD2INTEGERSENSORINDEX_MAX_FUEL_AIR_EQUIVALENCE_RATIO = 0x10 +OBD2INTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_VOLTAGE = 0x11 +OBD2INTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_CURRENT = 0x12 +OBD2INTEGERSENSORINDEX_MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x13 +OBD2INTEGERSENSORINDEX_MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 0x14 +OBD2INTEGERSENSORINDEX_FUEL_TYPE = 0x15 +OBD2INTEGERSENSORINDEX_FUEL_RAIL_ABSOLUTE_PRESSURE = 0x16 +OBD2INTEGERSENSORINDEX_ENGINE_OIL_TEMPERATURE = 0x17 +OBD2INTEGERSENSORINDEX_DRIVER_DEMAND_PERCENT_TORQUE = 0x18 +OBD2INTEGERSENSORINDEX_ENGINE_ACTUAL_PERCENT_TORQUE = 0x19 +OBD2INTEGERSENSORINDEX_ENGINE_REFERENCE_PERCENT_TORQUE = 0x1a +OBD2INTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_IDLE = 0x1b +OBD2INTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT1 = 0x1c +OBD2INTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT2 = 0x1d +OBD2INTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT3 = 0x1e +OBD2INTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT4 = 0x1f +OBD2INTEGERSENSORINDEX_LAST_SYSTEM_INDEX = 0x1f + +# VehicleProperty +VEHICLEPROPERTY_WHEEL_TICK = 0x11610306 +VEHICLEPROPERTY_OBD2_LIVE_FRAME = 0x11e00d00 +VEHICLEPROPERTY_OBD2_FREEZE_FRAME = 0x11e00d01 +VEHICLEPROPERTY_OBD2_FREEZE_FRAME_INFO = 0x11e00d02 +VEHICLEPROPERTY_OBD2_FREEZE_FRAME_CLEAR = 0x11e00d03 +VEHICLEPROPERTY_VEHICLE_MAP_SERVICE = 0x11e00c00 + +# FuelSystemStatus +FUELSYSTEMSTATUS_OPEN_INSUFFICIENT_ENGINE_TEMPERATURE = 0x1 +FUELSYSTEMSTATUS_CLOSED_LOOP = 0x2 +FUELSYSTEMSTATUS_OPEN_ENGINE_LOAD_OR_DECELERATION = 0x4 +FUELSYSTEMSTATUS_OPEN_SYSTEM_FAILURE = 0x8 +FUELSYSTEMSTATUS_CLOSED_LOOP_BUT_FEEDBACK_FAULT = 0x10 + +# VehiclePropertyType +VEHICLEPROPERTYTYPE_STRING = 0x100000 +VEHICLEPROPERTYTYPE_BOOLEAN = 0x200000 +VEHICLEPROPERTYTYPE_INT32 = 0x400000 +VEHICLEPROPERTYTYPE_INT32_VEC = 0x410000 +VEHICLEPROPERTYTYPE_INT64 = 0x500000 +VEHICLEPROPERTYTYPE_FLOAT = 0x600000 +VEHICLEPROPERTYTYPE_FLOAT_VEC = 0x610000 +VEHICLEPROPERTYTYPE_BYTES = 0x700000 +VEHICLEPROPERTYTYPE_COMPLEX = 0xe00000 +VEHICLEPROPERTYTYPE_MASK = 0xff0000 + +# VmsSimpleMessageIntegerValuesIndex +VMSSIMPLEMESSAGEINTEGERVALUESINDEX_VMS_LAYER_ID = 0x1 +VMSSIMPLEMESSAGEINTEGERVALUESINDEX_VMS_LAYER_VERSION = 0x2 + +# VehicleArea +VEHICLEAREA_GLOBAL = 0x1000000 +VEHICLEAREA_ZONE = 0x2000000 +VEHICLEAREA_WINDOW = 0x3000000 +VEHICLEAREA_MIRROR = 0x4000000 +VEHICLEAREA_SEAT = 0x5000000 +VEHICLEAREA_DOOR = 0x6000000 +VEHICLEAREA_MASK = 0xf000000 + +# VmsOfferingMessageIntegerValuesIndex +VMSOFFERINGMESSAGEINTEGERVALUESINDEX_VMS_NUMBER_OF_LAYERS_DEPENDENCIES = 0x1 +VMSOFFERINGMESSAGEINTEGERVALUESINDEX_FIRST_DEPENDENCIES_INDEX = 0x2 + +# VmsSubscriptionResponseFormat +VMSSUBSCRIPTIONRESPONSEFORMAT_SEQUENCE_NUMBER = 0x1 +VMSSUBSCRIPTIONRESPONSEFORMAT_NUMBER_OF_LAYERS = 0x2 +VMSSUBSCRIPTIONRESPONSEFORMAT_FIRST_LAYER = 0x3 diff --git a/tools/emulator/vhal_emulator.py b/tools/emulator/vhal_emulator.py index 44e5566f19..4c94e4f33a 100644 --- a/tools/emulator/vhal_emulator.py +++ b/tools/emulator/vhal_emulator.py @@ -18,37 +18,37 @@ """ This module provides a vhal class which sends and receives messages to the vehicle HAL module - on an Android Auto device. It uses port forwarding via ADB to communicted with the Android + on an Android Auto device. It uses port forwarding via ADB to communicate with the Android device. Example Usage: - import vhal_consts_1_0 as c + import vhal_consts_2_0 as c from vhal_emulator import Vhal # Create an instance of vhal class. Need to pass the vhal_types constants. - v = Vhal(c.vhal_types_1_0) + v = Vhal(c.vhal_types_2_0) # Get the property config (if desired) - v.getConfig(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET) + v.getConfig(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET) # Get the response message to getConfig() reply = v.rxMsg() - print reply + print(reply) # Set left temperature to 70 degrees - v.setProperty(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_ROW_1_LEFT, 70) + v.setProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLEAREAZONE_ROW_1_LEFT, 70) # Get the response message to setProperty() reply = v.rxMsg() - print reply + print(reply) # Get the left temperature value - v.getProperty(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_ROW_1_LEFT) + v.getProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLEAREAZONE_ROW_1_LEFT) # Get the response message to getProperty() reply = v.rxMsg() - print reply + print(reply) NOTE: The rxMsg() is a blocking call, so it may be desirable to set up a separate RX thread to handle any asynchronous messages coming from the device. @@ -57,7 +57,7 @@ from threading import Thread - # Define a simple thread that receives messags from a vhal object (v) and prints them + # Define a simple thread that receives messages from a vhal object (v) and prints them def rxThread(v): while(1): print v.rxMsg() @@ -72,6 +72,8 @@ protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto """ +from __future__ import print_function + # Suppress .pyc files import sys sys.dont_write_bytecode = True @@ -80,10 +82,24 @@ import socket import struct import subprocess -# Generate the protobuf file from vendor/auto/embedded/lib/vehicle_hal: +# Generate the protobuf file from hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0 +# It is recommended to use the protoc provided in: prebuilts/tools/common/m2/repository/com/google/protobuf/protoc/3.0.0 +# or a later version, in order to provide Python 3 compatibility # protoc -I=proto --python_out=proto proto/VehicleHalProto.proto import VehicleHalProto_pb2 +# If container is a dictionary, retrieve the value for key item; +# Otherwise, get the attribute named item out of container +def getByAttributeOrKey(container, item, default=None): + if isinstance(container, dict): + try: + return container[item] + except KeyError as e: + return default + try: + return getattr(container, item) + except AttributeError as e: + return default class Vhal: """ @@ -102,16 +118,16 @@ class Vhal: # Convert the message length into int32 byte array msgHdr = struct.pack('!I', msgLen) # Send the message length first - self.sock.send(msgHdr) + self.sock.sendall(msgHdr) # Then send the protobuf - self.sock.send(msgStr) + self.sock.sendall(msgStr) ### Public Functions def printHex(self, data): """ For debugging, print the protobuf message string in hex. """ - print "len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data) + print("len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data)) def openSocket(self): """ @@ -143,6 +159,8 @@ class Vhal: msg = VehicleHalProto_pb2.EmulatorMessage() msg.ParseFromString(b) return msg + else: + print("Ignored message fragment") def getConfig(self, prop): """ @@ -156,7 +174,7 @@ class Vhal: def getConfigAll(self): """ - Sends a getConfigAll message to the host. This will return all configs avaialable. + Sends a getConfigAll message to the host. This will return all configs available. """ cmd = VehicleHalProto_pb2.EmulatorMessage() cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_ALL_CMD @@ -175,7 +193,7 @@ class Vhal: def getPropertyAll(self): """ - Sends a getPropertyAll message to the host. This will return all properties avaialable. + Sends a getPropertyAll message to the host. This will return all properties available. """ cmd = VehicleHalProto_pb2.EmulatorMessage() cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_ALL_CMD @@ -215,6 +233,17 @@ class Vhal: propValue.int32_values.extend(value) elif valType in self._types.TYPE_FLOATS: propValue.float_values.extend(value) + elif valType in self._types.TYPE_COMPLEX: + propValue.string_value = \ + getByAttributeOrKey(value, 'string_value', '') + propValue.bytes_value = \ + getByAttributeOrKey(value, 'bytes_value', '') + for newValue in getByAttributeOrKey(value, 'int32_values', []): + propValue.int32_values.append(newValue) + for newValue in getByAttributeOrKey(value, 'int64_values', []): + propValue.int64_values.append(newValue) + for newValue in getByAttributeOrKey(value, 'float_values', []): + propValue.float_values.append(newValue) else: raise ValueError('value type not recognized:', valType) return @@ -231,4 +260,3 @@ class Vhal: # Parse the list of configs to generate a dictionary of prop_id to type for cfg in msg.config: self._propToType[cfg.prop] = cfg.value_type - diff --git a/tools/emulator/vhal_emulator_test.py b/tools/emulator/vhal_emulator_test.py index 325e0a84bd..1d0ada042a 100644..100755 --- a/tools/emulator/vhal_emulator_test.py +++ b/tools/emulator/vhal_emulator_test.py @@ -21,12 +21,17 @@ Protocol Buffer: This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL. - If the VehicleHalProto.proto file has changed, re-generate the python version using: - + If the VehicleHalProto.proto file has changed, re-generate the python version using + a command of the form: protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto - protoc -I=proto --python_out=proto proto/VehicleHalProto.proto + For example: + protoDir=~/android/master/hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto + outDir=~/android/master/packages/services/Car/tools/emulator + protoc -I=$protoDir --python_out=$outDir $protoDir/VehicleHalProto.proto """ +from __future__ import print_function + # Suppress .pyc files import sys sys.dont_write_bytecode = True @@ -54,8 +59,8 @@ class VhalTest: testValue = "test string" elif valType in self._types.TYPE_BYTES: # Generate array of integers counting from 0 - testValue = range(len(origValue)) - elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN: + testValue = list(range(len(origValue))) + elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN: testValue = origValue ^ 1 elif valType in self._types.TYPE_INT32: try: @@ -98,7 +103,7 @@ class VhalTest: value = rxMsg.value[0].string_value elif valType in self._types.TYPE_BYTES: value = rxMsg.value[0].bytes_value - elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN: + elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN: value = rxMsg.value[0].int32_values[0] elif valType in self._types.TYPE_INT32: value = rxMsg.value[0].int32_values[0] @@ -216,7 +221,7 @@ class VhalTest: newValue = self._getValueFromMsg(rxMsg) if newValue != testValue: self._log.error("testGetSet: set failed for propId=%d, area=%d", cfg.prop, area) - print "testValue= ", testValue, "newValue= ", newValue + print("testValue= ", testValue, "newValue= ", newValue) continue # Reset the value to what it was before @@ -287,4 +292,3 @@ class VhalTest: if __name__ == '__main__': v = VhalTest(vhal_consts_2_0.vhal_types_2_0) v.runTests() - diff --git a/tools/hidl_parser/parser.py b/tools/hidl_parser/parser.py index b0349c0c30..6ef19d50b1 100644 --- a/tools/hidl_parser/parser.py +++ b/tools/hidl_parser/parser.py @@ -18,19 +18,21 @@ # A parser for enum types defined in HIDL. # This script can parse HIDL files and generate a parse tree. # To use, import and call parse("path/to/file.hal") -# It will return a Python dictionary with two keys: +# It will return a Python dictionary with three keys: # - header: an instance of Header # - enums: a dictionary of EnumDecl objects by name -# This script cannot parse structs for now, but that would be easy to add. +# - structs: a dictionary of StructDecl objects by name # It requires 'ply' (Python Lex/Yacc). +from __future__ import print_function + import ply -tokens = ('package', 'import', 'enum', +tokens = ('package', 'import', 'enum', 'struct', 'COLON', 'IDENTIFIER', 'COMMENT', 'NUMBER', 'HEX', 'OR', 'EQUALS', 'LPAREN', 'RPAREN', 'LBRACE', 'RBRACE', 'DOT', 'SEMICOLON', 'VERSION', - 'COMMA', 'SHIFT') + 'COMMA', 'SHIFT', 'LESSTHAN', 'GREATERTHAN') t_COLON = r':' t_NUMBER = r'[0-9]+' @@ -40,6 +42,8 @@ t_EQUALS = r'=' t_LPAREN = r'\(' t_RPAREN = r'\)' t_SHIFT = r'<<' +t_LESSTHAN = r'<' +t_GREATERTHAN = r'>' def t_COMMENT(t): r'(/\*(.|\n)*?\*/)|(//.*)' @@ -61,6 +65,8 @@ def t_IDENTIFIER(t): t.type = 'import' elif t.value == 'enum': t.type = 'enum' + elif t.value == 'struct': + t.type = 'struct' return t def t_error(t): @@ -72,6 +78,24 @@ def t_error(t): import ply.lex as lex lexer = lex.lex() +class Typename(object): + pass + +class SimpleTypename(Typename): + def __init__(self, name): + self.name = name + + def __str__(self): + return self.name + +class GenericTypename(Typename): + def __init__(self, name, arg): + self.name = name + self.arg = arg + + def __str__(self): + return '%s<%s>' % (self.name, self.arg) + class EnumHeader(object): def __init__(self, name, base): self.name = name @@ -80,10 +104,32 @@ class EnumHeader(object): def __str__(self): return '%s%s' % (self.name, ' %s' % self.base if self.base else '') +class StructHeader(object): + def __init__(self, name): + self.name = name + + def __str__(self): + return 'struct %s' % self.name + class EnumDecl(object): def __init__(self, header, cases): self.header = header self.cases = cases + self.fillInValues() + + def fillInValues(self): + # if no cases, we're done + if len(self.cases) < 1: return + # then, if case 0 has no value, set it to 0 + if self.cases[0].value is None: + self.cases[0].value = EnumValueConstant("0") + # then for all other cases... + for i in range(1,len(self.cases)): + # ...if there's no value + if self.cases[i].value is None: + # set to previous case + 1 + self.cases[i].value = EnumValueSuccessor( + EnumValueLocalRef(self.cases[i-1].name)) def __str__(self): return '%s {\n%s\n}' % (self.header, @@ -92,6 +138,37 @@ class EnumDecl(object): def __repr__(self): return self.__str__() +class StructDecl(object): + def __init__(self, header, items): + self.header = header + self.items = items + + def __str__(self): + return '%s {\n%s\n}' % (self.header, + '\n'.join(str(x) for x in self.items)) + + def __repr__(self): + return self.__str__() + +class StructElement(object): + pass + +class StructElementIVar(StructElement): + def __init__(self, typename, name): + self.typename = typename + self.name = name + + def __str__(self): + return '%s %s' % (self.typename, self.name) + +class StructElementStruct(StructElement): + def __init__(self, struct): + self.name = struct.header.name + self.struct = struct + + def __str__(self): + return self.struct.__str__() + class EnumCase(object): def __init__(self, name, value): self.name = name @@ -131,24 +208,117 @@ class Header(object): return str(self.package) + "\n" + \ '\n'.join(str(x) for x in self.imports) +class EnumValue(object): + def resolve(self, enum, document): + pass + +class EnumValueConstant(EnumValue): + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value + + def resolve(self, enum, document): + if self.value.startswith("0x"): + return int(self.value, 16) + else: + return int(self.value, 10) + +class EnumValueSuccessor(EnumValue): + def __init__(self, value): + self.value = value + + def __str__(self): + return '%s + 1' % self.value + + def resolve(self, enum, document): + return self.value.resolve(enum, document) + 1 + +class EnumValueLocalRef(EnumValue): + def __init__(self, ref): + self.ref = ref + + def __str__(self): + return self.ref + + def resolve(self, enum, document): + for case in enum.cases: + if case.name == self.ref: return case.value.resolve(enum, document) + +class EnumValueLShift(EnumValue): + def __init__(self, base, offset): + self.base = base + self.offset = offset + + def __str__(self): + return '%s << %s' % (self.base, self.offset) + + def resolve(self, enum, document): + base = self.base.resolve(enum, document) + offset = self.offset.resolve(enum, document) + return base << offset + +class EnumValueOr(EnumValue): + def __init__(self, param1, param2): + self.param1 = param1 + self.param2 = param2 + + def __str__(self): + return '%s | %s' % (self.param1, self.param2) + + def resolve(self, enum, document): + param1 = self.param1.resolve(enum, document) + param2 = self.param2.resolve(enum, document) + return param1 | param2 + +class EnumValueExternRef(EnumValue): + def __init__(self, where, ref): + self.where = where + self.ref = ref + + def __str__(self): + return '%s:%s' % (self.where, self.ref) + + def resolve(self, enum, document): + enum = document['enums'][self.where] + return EnumValueLocalRef(self.ref).resolve(enum, document) + # Error rule for syntax errors def p_error(p): print("Syntax error in input: %s" % p) + try: + while True: + print(p.lexer.next().value, end=' ') + except: + pass def p_document(t): - 'document : header enum_decls' + 'document : header type_decls' enums = {} + structs = {} for enum in t[2]: + if not isinstance(enum, EnumDecl): continue enums[enum.header.name] = enum - t[0] = {'header' : t[1], 'enums' : enums} + for struct in t[2]: + if not isinstance(struct, StructDecl): continue + structs[struct.header.name] = struct + t[0] = {'header' : t[1], 'enums' : enums, 'structs' : structs} -def p_enum_decls_1(t): - 'enum_decls : enum_decl' +def p_type_decls_1(t): + 'type_decls : type_decl' t[0] = [t[1]] -def p_enum_decls_2(t): - 'enum_decls : enum_decls enum_decl' +def p_type_decls_2(t): + 'type_decls : type_decls type_decl' t[0] = t[1] + [t[2]] +def p_type_decl_e(t): + 'type_decl : enum_decl' + t[0] = t[1] +def p_type_decl_s(t): + 'type_decl : struct_decl' + t[0] = t[1] + def p_enum_cases_1(t): 'enum_cases : enum_case' t[0] = [t[1]] @@ -156,6 +326,13 @@ def p_enum_cases_2(t): 'enum_cases : enum_cases COMMA enum_case' t[0] = t[1] + [t[3]] +def p_struct_elements_1(t): + 'struct_elements : struct_element' + t[0] = [t[1]] +def p_struct_elements_2(t): + 'struct_elements : struct_elements struct_element' + t[0] = t[1] + [t[2]] + def p_enum_base_1(t): 'enum_base : VERSION COLON COLON IDENTIFIER' t[0] = '%s::%s' % (t[1], t[4]) @@ -163,6 +340,10 @@ def p_enum_base_2(t): 'enum_base : IDENTIFIER' t[0] = t[1] +def p_struct_header(t): + 'struct_header : struct IDENTIFIER' + t[0] = StructHeader(t[2]) + def p_enum_header_1(t): 'enum_header : enum IDENTIFIER' t[0] = EnumHeader(t[2], None) @@ -170,6 +351,10 @@ def p_enum_header_2(t): 'enum_header : enum IDENTIFIER COLON enum_base' t[0] = EnumHeader(t[2], t[4]) +def p_struct_decl(t): + 'struct_decl : struct_header LBRACE struct_elements RBRACE SEMICOLON' + t[0] = StructDecl(t[1], t[3]) + def p_enum_decl_1(t): 'enum_decl : enum_header LBRACE enum_cases RBRACE SEMICOLON' t[0] = EnumDecl(t[1], t[3]) @@ -179,25 +364,45 @@ def p_enum_decl_2(t): def p_enum_value_1(t): '''enum_value : NUMBER - | HEX - | IDENTIFIER''' - t[0] = t[1] + | HEX''' + t[0] = EnumValueConstant(t[1]) def p_enum_value_2(t): 'enum_value : enum_value SHIFT NUMBER' - t[0] = '%s << %s' % (t[1], t[3]) + t[0] = EnumValueLShift(t[1], EnumValueConstant(t[3])) def p_enum_value_3(t): 'enum_value : enum_value OR enum_value' - t[0] = "%s | %s" % (t[1], t[3]) + t[0] = EnumValueOr(t[1], t[3]) def p_enum_value_4(t): 'enum_value : LPAREN enum_value RPAREN' t[0] = t[2] def p_enum_value_5(t): 'enum_value : IDENTIFIER COLON IDENTIFIER' - t[0] = '%s:%s' % (t[1],t[3]) - -def p_enum_case(t): + t[0] = EnumValueExternRef(t[1],t[3]) +def p_enum_value_6(t): + 'enum_value : IDENTIFIER' + t[0] = EnumValueLocalRef(t[1]) + +def p_typename_v(t): + 'typename : IDENTIFIER' + t[0] = SimpleTypename(t[1]) +def p_typename_g(t): + 'typename : IDENTIFIER LESSTHAN IDENTIFIER GREATERTHAN' + t[0] = GenericTypename(t[1], t[3]) + +def p_struct_element_ivar(t): + 'struct_element : typename IDENTIFIER SEMICOLON' + t[0] = StructElementIVar(t[1], t[2]) + +def p_struct_element_struct(t): + 'struct_element : struct_decl' + t[0] = StructElementStruct(t[1]) + +def p_enum_case_v(t): 'enum_case : IDENTIFIER EQUALS enum_value' t[0] = EnumCase(t[1], t[3]) +def p_enum_case_b(t): + 'enum_case : IDENTIFIER' + t[0] = EnumCase(t[1], None) def p_header_1(t): 'header : package_decl' diff --git a/tools/update-obd2-sensors.py b/tools/update-obd2-sensors.py index d0a89d48d0..7f22f52329 100755 --- a/tools/update-obd2-sensors.py +++ b/tools/update-obd2-sensors.py @@ -68,7 +68,7 @@ class SensorPolicy(object): """Prefix string before any sensor data is generated.""" return "" - def suffix(self): + def suffix(self, theSensors): """Suffix string after all sensor data is generated.""" return "" @@ -121,6 +121,19 @@ class JavaSensorPolicy(SensorPolicy): def indent(self): return 8 +class PythonSensorPolicy(SensorPolicy): + """The sensor policy that emits Python sensor descriptions.""" + def sensor(self, theSensor, theSensors): + return "OBD2_SENSOR_%s_%s = %s" % ( + theSensors.descriptor.upper(), + theSensor.name.upper(), + self.adjustSensorId(theSensors.descriptor.upper(), str(theSensor.id)) + ) + + def adjustSensorId(self, descriptor, sensorId): + if sensorId.isdigit(): return sensorId + return "OBD2_SENSOR_%s_%s" % (descriptor, sensorId.upper()) + class IntDefSensorPolicy(SensorPolicy): """The sensor policy that emits @IntDef sensor descriptions.""" def sensor(self, theSensor, theSensors): @@ -178,8 +191,11 @@ def java(destfile): def intdef(destfile): applyPolicy(IntDefSensorPolicy(), destfile) -def generate(filepath): - """Generate data for all sensors.""" +def python(destfile): + applyPolicy(PythonSensorPolicy(), destfile) + +def generateJava(filepath): + """Generate Java code for all sensors.""" destfile = open(filepath, "w") print("/*", file=destfile) print(" * Copyright (C) 2017 The Android Open Source Project", file=destfile) @@ -216,6 +232,29 @@ def generate(filepath): intdef(destfile) print("}", file=destfile) +def generatePython(filepath): + """Generate Python code for all sensors.""" + destfile = open(filepath, "w") + print("#!/usr/bin/env python3", file=destfile) + print("#", file=destfile) + print("# Copyright (C) 2017 The Android Open Source Project", file=destfile) + print("#", file=destfile) + print("# Licensed under the Apache License, Version 2.0 (the \"License\");", file=destfile) + print("# you may not use this file except in compliance with the License.", file=destfile) + print("# You may obtain a copy of the License at", file=destfile) + print("#", file=destfile) + print("# http://www.apache.org/licenses/LICENSE-2.0", file=destfile) + print("#", file=destfile) + print("# Unless required by applicable law or agreed to in writing, software", file=destfile) + print("# distributed under the License is distributed on an \"AS IS\" BASIS,", file=destfile) + print("# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", file=destfile) + print("# See the License for the specific language governing permissions and", file=destfile) + print("# limitations under the License.", file=destfile) + print("#", file=destfile) + print("# This file is generated by types.hal by packages/services/Car/tools/update-obd2-sensors.py", file=destfile) + print("# DO NOT EDIT MANUALLY", file=destfile) + python(destfile) + def load(filepath): """Load sensor data from Vehicle HAL.""" ast = hidl_parser.parser.parse(filepath) @@ -228,10 +267,11 @@ def load(filepath): import os -if len(sys.argv) != 3: - print('syntax: update-obd2-sensors.py <path/to/types.hal> <path/to/CarDiagnosticSensorIndices.java>') - print('This scrippt will parse types.hal, and use the resulting', end='') +if len(sys.argv) != 4: + print('syntax: update-obd2-sensors.py <path/to/types.hal> <path/to/CarDiagnosticSensorIndices.java> <path/to/diagnostic_sensors.py>') + print('This script will parse types.hal, and use the resulting', end='') print('parse tree to generate CarDiagnosticSensorIndices.java.') sys.exit(1) load(sys.argv[1]) -generate(sys.argv[2]) +generateJava(sys.argv[2]) +generatePython(sys.argv[3]) diff --git a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJson.java b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJson.java index 25fdfdfa3d..6936f22706 100644 --- a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJson.java +++ b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJson.java @@ -26,14 +26,14 @@ import java.util.Objects; import java.util.Optional; public class DiagnosticJson { - public final int type; + public final String type; public final long timestamp; public final SparseArray<Integer> intValues; public final SparseArray<Float> floatValues; public final String dtc; DiagnosticJson( - int type, + String type, long timestamp, SparseArray<Integer> intValues, SparseArray<Float> floatValues, @@ -81,7 +81,7 @@ public class DiagnosticJson { } } - final WriteOnce<Integer> mType = new WriteOnce<>(); + final WriteOnce<String> mType = new WriteOnce<>(); final WriteOnce<Long> mTimestamp = new WriteOnce<>(); final SparseArray<Integer> mIntValues = new SparseArray<>(); final SparseArray<Float> mFloatValues = new SparseArray<>(); @@ -119,12 +119,11 @@ public class DiagnosticJson { Builder(JsonReader jsonReader) throws IOException { jsonReader.beginObject(); - long timestamp = 0; while (jsonReader.hasNext()) { String name = jsonReader.nextName(); switch (name) { case "type": - mType.write(jsonReader.nextInt()); + mType.write(jsonReader.nextString()); break; case "timestamp": mTimestamp.write(jsonReader.nextLong()); @@ -153,9 +152,9 @@ public class DiagnosticJson { return new DiagnosticJson( mType.get(), mTimestamp.get(), mIntValues, mFloatValues, mDtc.get(null)); } + } - public static DiagnosticJson build(JsonReader jsonReader) throws IOException { - return new Builder(jsonReader).build(); - } + public static DiagnosticJson build(JsonReader jsonReader) throws IOException { + return new Builder(jsonReader).build(); } } diff --git a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJsonReader.java b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJsonReader.java index 526018c199..5dd6a6657e 100644 --- a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJsonReader.java +++ b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/DiagnosticJsonReader.java @@ -25,8 +25,8 @@ import android.util.JsonReader; import java.io.IOException; public class DiagnosticJsonReader { - public static final int FRAME_TYPE_LIVE = 1; - public static final int FRAME_TYPE_FREEZE = 2; + public static final String FRAME_TYPE_LIVE = "live"; + public static final String FRAME_TYPE_FREEZE = "freeze"; private final DiagnosticEventBuilder mLiveFrameBuilder; private final DiagnosticEventBuilder mFreezeFrameBuilder; @@ -50,7 +50,7 @@ public class DiagnosticJsonReader { } public VehiclePropValue build(JsonReader jsonReader) throws IOException { - DiagnosticJson diagnosticJson = DiagnosticJson.Builder.build(jsonReader); + DiagnosticJson diagnosticJson = DiagnosticJson.build(jsonReader); switch (diagnosticJson.type) { case FRAME_TYPE_LIVE: return diagnosticJson.build(mLiveFrameBuilder); |