Bluetooth: move AIDL files related to Bluetooth into system/bt (3/3) am: d7870ff246  -s ours am: 8b7068ed54
am: fcde72b007

Change-Id: I29feee78adb52b2db34925c1589a1baed2c97fb0
diff --git a/.gitignore b/.gitignore
index 5bf05e5..b292847 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
 .classpath
 *.iml
 gen/
+*.pyc
+__pycache__
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 1c6a5b1..4bf7e9a 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -51,6 +51,8 @@
 $(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 94c21be..b846481 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -28,15 +28,12 @@
     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 80fabd0..6ed5d73 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -124,7 +124,6 @@
      * @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 @@
      * @hide
      */
     @FutureFeature
-    @SystemApi
     public static final String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER";
 
     /**
@@ -240,7 +238,6 @@
      * @hide
      */
     @FutureFeature
-    @SystemApi
     public static final String PERMISSION_VMS_SUBSCRIBER = "android.car.permission.VMS_SUBSCRIBER";
 
     /**
@@ -582,9 +579,7 @@
                 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 1510438..9f8c367 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 b970b23..1d82533 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.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 @@
             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 @@
         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 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 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 @@
 
     /**
      * 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 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 @@
     }
 
     @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 @@
                 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 @@
         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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
-     * 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 @@
                 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 @@
             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 @@
             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.Decoder EGR_DECODER =
+                    new IgnitionMonitor.Decoder(EGR_AVAILABLE, 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.Decoder OXYGEN_SENSOR_HEATER_DECODER =
+                    new IgnitionMonitor.Decoder(OXYGEN_SENSOR_HEATER_AVAILABLE,
+                            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.Decoder OXYGEN_SENSOR_DECODER =
+                    new IgnitionMonitor.Decoder(OXYGEN_SENSOR_AVAILABLE, OXYGEN_SENSOR_INCOMPLETE);
 
-            static final IgnitionMonitor.Builder AC_REFRIGERANT_BUILDER =
-                new IgnitionMonitor.Builder()
-                    .withAvailableBitmask(AC_REFRIGERANT_AVAILABLE)
-                    .withIncompleteBitmask(AC_REFRIGERANT_INCOMPLETE);
+            static final IgnitionMonitor.Decoder AC_REFRIGERANT_DECODER =
+                    new IgnitionMonitor.Decoder(AC_REFRIGERANT_AVAILABLE,
+                            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.Decoder SECONDARY_AIR_SYSTEM_DECODER =
+                    new IgnitionMonitor.Decoder(SECONDARY_AIR_SYSTEM_AVAILABLE,
+                            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.Decoder EVAPORATIVE_SYSTEM_DECODER =
+                    new IgnitionMonitor.Decoder(EVAPORATIVE_SYSTEM_AVAILABLE,
+                            EVAPORATIVE_SYSTEM_INCOMPLETE);
 
-            static final IgnitionMonitor.Builder HEATED_CATALYST_BUILDER =
-                new IgnitionMonitor.Builder()
-                    .withAvailableBitmask(HEATED_CATALYST_AVAILABLE)
-                    .withIncompleteBitmask(HEATED_CATALYST_INCOMPLETE);
+            static final IgnitionMonitor.Decoder HEATED_CATALYST_DECODER =
+                    new IgnitionMonitor.Decoder(HEATED_CATALYST_AVAILABLE,
+                            HEATED_CATALYST_INCOMPLETE);
 
-            static final IgnitionMonitor.Builder CATALYST_BUILDER =
-                new IgnitionMonitor.Builder()
-                    .withAvailableBitmask(CATALYST_AVAILABLE)
-                    .withIncompleteBitmask(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 @@
             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 6d8b5ba..0444c14 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.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 @@
 /** 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 @@
 
     // 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 @@
      * @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 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 @@
             }
             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 @@
         }
     }
 
-    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 @@
         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 @@
         }
 
         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 098d2d4..3afffc5 100644
--- a/car-lib/src/android/car/hardware/ICarDiagnostic.aidl
+++ b/car-lib/src/android/car/hardware/ICarDiagnostic.aidl
@@ -52,4 +52,24 @@
      */
      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 26b6e52..3312794 100644
--- a/car-lib/src/android/car/vms/IVmsPublisherService.aidl
+++ b/car-lib/src/android/car/vms/IVmsPublisherService.aidl
@@ -40,4 +40,11 @@
      * 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 236ae5a..9234134 100644
--- a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
+++ b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
@@ -52,8 +52,12 @@
             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 702daec..afd0ae7 100644
--- a/car-lib/src/android/car/vms/VmsLayer.java
+++ b/car-lib/src/android/car/vms/VmsLayer.java
@@ -24,8 +24,10 @@
 
 /**
  * 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 bb588eb..e14c7ec 100644
--- a/car-lib/src/android/car/vms/VmsLayerDependency.java
+++ b/car-lib/src/android/car/vms/VmsLayerDependency.java
@@ -80,6 +80,10 @@
             }
         };
 
+    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 12b3dd3..51a0b99 100644
--- a/car-lib/src/android/car/vms/VmsLayersOffering.java
+++ b/car-lib/src/android/car/vms/VmsLayersOffering.java
@@ -55,6 +55,11 @@
         };
 
     @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 2743ff1..ea265df 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -106,6 +106,41 @@
         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 @@
         if (token == null) {
             throw new IllegalStateException("VmsPublisherService does not have a valid token.");
         }
-        try {
-            mVmsPublisherService.publish(token, layer, payload);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "unable to publish message: " + payload, e);
+        return token;
+    }
+
+    public final int getPublisherStaticId(byte[] publisherInfo) {
+        if (mVmsPublisherService == null) {
+            throw new IllegalStateException("VmsPublisherService not set.");
         }
-        return false;
+        Integer publisherStaticId = null;
+        try {
+            Log.i(TAG, "Getting publisher static ID");
+            publisherStaticId = mVmsPublisherService.getPublisherStaticId(publisherInfo);
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to invoke binder method.", e);
+        }
+        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 d0b8c62..84405f4 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -57,6 +57,9 @@
 
         /** Called when layers availability change */
         void onLayersAvailabilityChange(List<VmsLayer> availableLayers);
+
+        /** Notifies the client of the disconnect event */
+        void onCarDisconnected();
     }
 
     /**
@@ -136,6 +139,8 @@
      * 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 @@
     }
 
     /**
-     * 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.");
         }
-        // TODO(antoniocortes): logic to unsubscribe from all the layer/version pairs.
+        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);
+        }
     }
 
     /**
@@ -167,8 +180,7 @@
      * @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 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 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 @@
     /** @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 8371463..55da870 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 @@
     /** 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 62bfd06..66cff60 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 @@
     /** 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 a8bcd04..5f0b93a 100644
--- a/car-maps-placeholder/Android.mk
+++ b/car-maps-placeholder/Android.mk
@@ -32,6 +32,6 @@
 
 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 2f8400e..d0052e4 100644
--- a/car-support-lib/Android.mk
+++ b/car-support-lib/Android.mk
@@ -28,14 +28,13 @@
 
 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 @@
  $(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_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 081d6a6..7feba67 100644
--- a/car-support-lib/car-support.mk
+++ b/car-support-lib/car-support.mk
@@ -22,35 +22,9 @@
 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 a1a2240..0000000
--- 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 29f9764..0000000
--- 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 8675cd8..0000000
--- 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 b81d61b..0000000
--- 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 d8dae80..0000000
--- 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
deleted file mode 100644
index 8964dc4..0000000
--- a/car-support-lib/res/drawable-hdpi/drawer_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-hdpi/error_illustration.png b/car-support-lib/res/drawable-hdpi/error_illustration.png
deleted file mode 100644
index 61e5145..0000000
--- a/car-support-lib/res/drawable-hdpi/error_illustration.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-hdpi/ic_down.png b/car-support-lib/res/drawable-hdpi/ic_down.png
deleted file mode 100644
index 6f47204..0000000
--- a/car-support-lib/res/drawable-hdpi/ic_down.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-hdpi/ic_launcher.png b/car-support-lib/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 71c6d76..0000000
--- a/car-support-lib/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-hdpi/ic_remove_circle.png b/car-support-lib/res/drawable-hdpi/ic_remove_circle.png
deleted file mode 100644
index 1f40a6f..0000000
--- a/car-support-lib/res/drawable-hdpi/ic_remove_circle.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-hdpi/ic_up.png b/car-support-lib/res/drawable-hdpi/ic_up.png
deleted file mode 100644
index b2b9929..0000000
--- a/car-support-lib/res/drawable-hdpi/ic_up.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-hdpi/ic_up_dark.png b/car-support-lib/res/drawable-hdpi/ic_up_dark.png
deleted file mode 100644
index 3586ac2..0000000
--- a/car-support-lib/res/drawable-hdpi/ic_up_dark.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-mdpi/drawer_shadow.9.png b/car-support-lib/res/drawable-mdpi/drawer_shadow.9.png
deleted file mode 100644
index 1417d1f..0000000
--- a/car-support-lib/res/drawable-mdpi/drawer_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-mdpi/error_illustration.png b/car-support-lib/res/drawable-mdpi/error_illustration.png
deleted file mode 100644
index 1c6ab5c..0000000
--- a/car-support-lib/res/drawable-mdpi/error_illustration.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-mdpi/ic_down.png b/car-support-lib/res/drawable-mdpi/ic_down.png
deleted file mode 100644
index 0f691e2..0000000
--- a/car-support-lib/res/drawable-mdpi/ic_down.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-mdpi/ic_remove_circle.png b/car-support-lib/res/drawable-mdpi/ic_remove_circle.png
deleted file mode 100644
index 795c1ce..0000000
--- a/car-support-lib/res/drawable-mdpi/ic_remove_circle.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-mdpi/ic_up.png b/car-support-lib/res/drawable-mdpi/ic_up.png
deleted file mode 100644
index a3fc019..0000000
--- a/car-support-lib/res/drawable-mdpi/ic_up.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-mdpi/ic_up_dark.png b/car-support-lib/res/drawable-mdpi/ic_up_dark.png
deleted file mode 100644
index 13afaa1..0000000
--- a/car-support-lib/res/drawable-mdpi/ic_up_dark.png
+++ /dev/null
Binary files differ
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 7f72c8b..0000000
--- 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 8ed8c1a..0000000
--- 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 c109739..0000000
--- 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
deleted file mode 100644
index 46cc273..0000000
--- a/car-support-lib/res/drawable-xhdpi/drawer_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xhdpi/error_illustration.png b/car-support-lib/res/drawable-xhdpi/error_illustration.png
deleted file mode 100644
index 2533a43..0000000
--- a/car-support-lib/res/drawable-xhdpi/error_illustration.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xhdpi/ic_down.png b/car-support-lib/res/drawable-xhdpi/ic_down.png
deleted file mode 100644
index a221d0b..0000000
--- a/car-support-lib/res/drawable-xhdpi/ic_down.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xhdpi/ic_remove_circle.png b/car-support-lib/res/drawable-xhdpi/ic_remove_circle.png
deleted file mode 100644
index 6eda519..0000000
--- a/car-support-lib/res/drawable-xhdpi/ic_remove_circle.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xhdpi/ic_up.png b/car-support-lib/res/drawable-xhdpi/ic_up.png
deleted file mode 100644
index bbbe1fa..0000000
--- a/car-support-lib/res/drawable-xhdpi/ic_up.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xhdpi/ic_up_dark.png b/car-support-lib/res/drawable-xhdpi/ic_up_dark.png
deleted file mode 100644
index ed73bd4..0000000
--- a/car-support-lib/res/drawable-xhdpi/ic_up_dark.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xxhdpi/drawer_shadow.9.png b/car-support-lib/res/drawable-xxhdpi/drawer_shadow.9.png
deleted file mode 100644
index 7b8a58b..0000000
--- a/car-support-lib/res/drawable-xxhdpi/drawer_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xxhdpi/error_illustration.png b/car-support-lib/res/drawable-xxhdpi/error_illustration.png
deleted file mode 100644
index dd4cc28..0000000
--- a/car-support-lib/res/drawable-xxhdpi/error_illustration.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xxhdpi/ic_down.png b/car-support-lib/res/drawable-xxhdpi/ic_down.png
deleted file mode 100644
index 853ce45..0000000
--- a/car-support-lib/res/drawable-xxhdpi/ic_down.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xxhdpi/ic_remove_circle.png b/car-support-lib/res/drawable-xxhdpi/ic_remove_circle.png
deleted file mode 100644
index c4fc1dd..0000000
--- a/car-support-lib/res/drawable-xxhdpi/ic_remove_circle.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xxhdpi/ic_up.png b/car-support-lib/res/drawable-xxhdpi/ic_up.png
deleted file mode 100644
index fb91db9..0000000
--- a/car-support-lib/res/drawable-xxhdpi/ic_up.png
+++ /dev/null
Binary files differ
diff --git a/car-support-lib/res/drawable-xxhdpi/ic_up_dark.png b/car-support-lib/res/drawable-xxhdpi/ic_up_dark.png
deleted file mode 100644
index 6c44c5d..0000000
--- a/car-support-lib/res/drawable-xxhdpi/ic_up_dark.png
+++ /dev/null
Binary files differ
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 b3a4b24..0000000
--- 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 120738c..0000000
--- 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 9f6863f..0000000
--- 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 7aa4df4..0000000
--- 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 7aa4df4..0000000
--- 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 7aa4df4..0000000
--- 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 165fcee..0000000
--- 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 0565a63..0000000
--- 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 8ac579a..0000000
--- 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 8541cec..0000000
--- 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 ac51118..0000000
--- 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 0bd9ee9..0000000
--- 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 63ea028..0000000
--- 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 526070e..0000000
--- 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 2d90f3c..0000000
--- 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 9cf7179..0000000
--- 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 88a2c5f..0000000
--- 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 de0e727..0000000
--- 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_checkbox.xml b/car-support-lib/res/layout/car_menu_checkbox.xml
deleted file mode 100644
index fb9fa07..0000000
--- a/car-support-lib/res/layout/car_menu_checkbox.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.
--->
-<android.support.car.ui.CheckboxWrapperView
-    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
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 9cc3ebf..0000000
--- 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 d53bb46..0000000
--- 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 e976f1c..0000000
--- 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 5810af7..0000000
--- 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 d9da5a1..0000000
--- 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 5887ab1..0000000
--- 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 1527404..0000000
--- 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 831d3ca..0000000
--- 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 e851246..0000000
--- 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 0ecdb84..0000000
--- 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 26f9c76..0000000
--- 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-w1024dp/dimens.xml b/car-support-lib/res/values-w1024dp/dimens.xml
deleted file mode 100644
index f329135..0000000
--- a/car-support-lib/res/values-w1024dp/dimens.xml
+++ /dev/null
@@ -1,20 +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="stream_margin_size">112dp</dimen>
-    <dimen name="stream_content_keyline_1">112dp</dimen>
-    <dimen name="stream_content_keyline_2">228dp</dimen>
-</resources>
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 e32083d..0000000
--- 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 d72006e..0000000
--- 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-w840dp/dimens.xml b/car-support-lib/res/values-w840dp/dimens.xml
deleted file mode 100644
index e391d80..0000000
--- a/car-support-lib/res/values-w840dp/dimens.xml
+++ /dev/null
@@ -1,24 +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_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>
-</resources>
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 19fe2c4..0000000
--- 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 f29b070..0000000
--- 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 c887fa0..0000000
--- 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 dd685b3..0000000
--- 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 ccf22aa..0000000
--- 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 ae8b4b2..0000000
--- 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 ef63383..0000000
--- 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 137f68e..ab697e1 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&#58;mm</string>
-    <!-- Format string for the clock. TODO: Use the system preference -->
-    <string name="car_world_clock_12_hours_format" translatable="false">h&#58;mm&#8202;</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 67370c9..0000000
--- 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 50ddf84..c47b650 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 @@
  * {@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 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 e4b5567..0000000
--- 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/CarAppUtil.java b/car-support-lib/src/android/support/car/app/CarAppUtil.java
deleted file mode 100644
index 2371025..0000000
--- a/car-support-lib/src/android/support/car/app/CarAppUtil.java
+++ /dev/null
@@ -1,33 +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;
-
-/**
- * @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";
-
-    public static boolean isEmbeddedCar(Context context) {
-       return context.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE);
-    }
-}
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 2a6b631..0000000
--- 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 c1c3405..0000000
--- 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 2588a81..0000000
--- 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 59c4ea7..0000000
--- 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 29226b5..0000000
--- 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 afb91ff..0000000
--- 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 248245d..0000000
--- 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 64193ab..0000000
--- 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/SearchBoxEditListener.java b/car-support-lib/src/android/support/car/app/menu/SearchBoxEditListener.java
deleted file mode 100644
index c8e67bc..0000000
--- a/car-support-lib/src/android/support/car/app/menu/SearchBoxEditListener.java
+++ /dev/null
@@ -1,32 +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;
-
-/**
- * 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
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 f27bc2f..0000000
--- 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 d107260..0000000
--- 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 cad2848..0000000
--- 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 20b9616..0000000
--- 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 633768a..a0eb482 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 @@
                 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 33fee8b..0000000
--- 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 898a0bf..0000000
--- 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 9e819c4..0000000
--- 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 3195afe..0000000
--- 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 986b549..0000000
--- 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 26a2c52..0000000
--- 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 9838e18..0000000
--- 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 bc0a769..0000000
--- 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 157f7fb..0000000
--- 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 3f3696b..0000000
--- 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 4952720..0000000
--- 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 cbc627c..0000000
--- 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 8191d6f..0000000
--- 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 e73f6df..0000000
--- 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 7ebb865..0000000
--- 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 bb7ff87..0000000
--- 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 718a249..0000000
--- 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 100df67..0000000
--- 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 08f64a2..0000000
--- 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 844f07f..0000000
--- 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 d5f7adb..0000000
--- 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 cbb4d12..0000000
--- 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 f3dc94f..0000000
--- 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 5c9b504..0000000
--- 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 d6ddfb6..0000000
--- 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/PathClippingView.java b/car-support-lib/src/android/support/car/ui/PathClippingView.java
deleted file mode 100644
index 812977a..0000000
--- a/car-support-lib/src/android/support/car/ui/PathClippingView.java
+++ /dev/null
@@ -1,31 +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.graphics.Path;
-
-/**
- * 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);
-
-}
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 cc10d4a..0000000
--- 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 ff9843d..0000000
--- 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
deleted file mode 100644
index 6e79734..0000000
--- a/car-ui-provider/res/drawable-hdpi/ic_google.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-hdpi/ic_googleg.png b/car-ui-provider/res/drawable-hdpi/ic_googleg.png
deleted file mode 100644
index 0d4158f..0000000
--- a/car-ui-provider/res/drawable-hdpi/ic_googleg.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-mdpi/ic_google.png b/car-ui-provider/res/drawable-mdpi/ic_google.png
deleted file mode 100644
index b00365a..0000000
--- a/car-ui-provider/res/drawable-mdpi/ic_google.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-mdpi/ic_googleg.png b/car-ui-provider/res/drawable-mdpi/ic_googleg.png
deleted file mode 100644
index 4871a0f..0000000
--- a/car-ui-provider/res/drawable-mdpi/ic_googleg.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-xhdpi/ic_google.png b/car-ui-provider/res/drawable-xhdpi/ic_google.png
deleted file mode 100644
index db17d6f..0000000
--- a/car-ui-provider/res/drawable-xhdpi/ic_google.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-xhdpi/ic_googleg.png b/car-ui-provider/res/drawable-xhdpi/ic_googleg.png
deleted file mode 100644
index addab88..0000000
--- a/car-ui-provider/res/drawable-xhdpi/ic_googleg.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-xxhdpi/ic_google.png b/car-ui-provider/res/drawable-xxhdpi/ic_google.png
deleted file mode 100644
index a27a8bc..0000000
--- a/car-ui-provider/res/drawable-xxhdpi/ic_google.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-provider/res/drawable-xxhdpi/ic_googleg.png b/car-ui-provider/res/drawable-xxhdpi/ic_googleg.png
deleted file mode 100644
index 0e89bbb..0000000
--- a/car-ui-provider/res/drawable-xxhdpi/ic_googleg.png
+++ /dev/null
Binary files differ
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 041afd3..0000000
--- 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 a0f9537..0000000
--- 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 c972bad..0000000
--- 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 378e6f8..0000000
--- 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 c6a70d7..0000000
--- 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 5e70420..0000000
--- 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 04fcd63..0000000
--- 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 3668e03..0000000
--- 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 894938b..0000000
--- 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 241224a..0000000
--- 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 4311c5a..0000000
--- 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 9648dd6..0000000
--- 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 91a25ab..0000000
--- 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 2242648..be1e7a2 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 5084414..288f598 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 @@
 
 import android.content.ComponentName;
 import android.hardware.usb.UsbDevice;
-import com.android.internal.util.Preconditions;
 
 /**
  * Settings for USB device.
@@ -34,8 +33,6 @@
     private boolean mDefaultHandler;
 
     UsbDeviceSettings(String serialNumber, int vid, int pid) {
-        Preconditions.checkNotNull(serialNumber);
-
         mSerialNumber = serialNumber;
         mVid = vid;
         mPid = pid;
@@ -96,7 +93,15 @@
      * 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 b705928..5c61a98 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 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 @@
     @GuardedBy("this")
     private UsbDevice mActiveDevice;
 
-    @GuardedBy("this")
-    private String mProcessingDeviceSerial;
-
     public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
         mContext = context;
         mCallback = callbacks;
@@ -96,17 +93,17 @@
 
     }
 
-    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 @@
     private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
         if (mActiveDevice == null) {
             mActiveDevice = device;
-            mProcessingDeviceSerial = device.getSerialNumber();
             return true;
         }
         return false;
@@ -122,7 +118,6 @@
 
     private synchronized void stopDeviceProcessing() {
         mActiveDevice = null;
-        mProcessingDeviceSerial = null;
     }
 
     private synchronized UsbDevice getActiveDevice() {
@@ -131,8 +126,22 @@
 
     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 @@
         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 @@
             }
             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 157c92f..1b251f8 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.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 @@
         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 @@
 
 
     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 @@
 
         @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 16aa275..1c2e7ee 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -74,7 +74,6 @@
 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 3e8d776..a6f2f43 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -62,6 +62,7 @@
     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 @@
     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
 
-ifeq ($(TARGET_USES_CAR_FUTURE_FEATURES),true)
+# 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
+
 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
new file mode 100644
index 0000000..9a4033e
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/drawable-night-nodpi/default_wallpaper.png
Binary files differ
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
index 22441ae..8d20548 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
Binary files differ
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
new file mode 100644
index 0000000..9a4033e
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/drawable-sw600dp-night/default_wallpaper.png
Binary files differ
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
index cc651de..8d20548 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
Binary files differ
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
new file mode 100644
index 0000000..9a4033e
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/drawable-sw720dp-night/default_wallpaper.png
Binary files differ
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
index 03b93aa..8d20548 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
Binary files differ
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 0000000..0252706
--- /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 0000000..c93390c
--- /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-w480dp/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/drawable/notification_action_bg.xml
similarity index 73%
rename from car-support-lib/res/values-w480dp/dimens.xml
rename to car_product/overlay/frameworks/base/core/res/res/drawable/notification_action_bg.xml
index b4c611a..f13b28a 100644
--- a/car-support-lib/res/values-w480dp/dimens.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/drawable/notification_action_bg.xml
@@ -13,8 +13,9 @@
 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>
-</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 0000000..cefd830
--- /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_product/overlay/frameworks/base/core/res/res/layout/notification_material_media_action.xml b/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_media_action.xml
new file mode 100644
index 0000000..29aafd4
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/layout/notification_material_media_action.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.
+-->
+<ImageButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    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 0000000..068e8b6
--- /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 0000000..b659bef
--- /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 0000000..2774c9e
--- /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_product/overlay/frameworks/base/core/res/res/values-w840dp/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/values-w840dp/dimens.xml
new file mode 100644
index 0000000..ea826b4
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/values-w840dp/dimens.xml
@@ -0,0 +1,23 @@
+<?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>
+    <!-- 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 0000000..f7a7a12
--- /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 2a97f64..a9fb0e7 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 136ca80..125e1fb 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 de6f6ec..7bcedb9 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_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml
new file mode 100644
index 0000000..3140afc
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.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.
+-->
+<resources>
+    <!-- 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 0000000..be66b5b
--- /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
similarity index 83%
rename from car-support-lib/res/values-w840dp/integers.xml
rename to car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml
index 5910519..7b85870 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 @@
 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
similarity index 76%
rename from car-support-lib/res/values-wheel/dimens.xml
rename to car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml
index f9bcc38..932bae2 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 @@
 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
similarity index 84%
rename from car-ui-provider/res/values-h480dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml
index 44d9df6..2248352 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 @@
 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 7b6e5eb..b66ff92 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 b5aae40..a9f231c 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 1001784..5d675fe 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 0000000..0e8881e
--- /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 0000000..1307616
--- /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 0000000..f5c4ba8
--- /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 0000000..b89b1ba
--- /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 25cbef2..53759c7 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 1978616..f5d9b6e 100644
--- a/evs/app/Android.mk
+++ b/evs/app/Android.mk
@@ -6,37 +6,75 @@
 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
new file mode 100644
index 0000000..11f929e
--- /dev/null
+++ b/evs/app/CarFromTop.png
Binary files differ
diff --git a/evs/app/ConfigManager.cpp b/evs/app/ConfigManager.cpp
index 48966d9..07e570d 100644
--- a/evs/app/ConfigManager.cpp
+++ b/evs/app/ConfigManager.cpp
@@ -49,24 +49,6 @@
 }
 
 
-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 @@
             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 @@
             // 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 1b61146..0d24919 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 @@
     };
     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 @@
     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 ec29e39..79533f7 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 @@
     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 @@
     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 @@
                                 // 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 @@
                             }
                         }
     );
+#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 @@
     }
 
     // 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 @@
         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());
+    ALOGD("  Current state %d has %zu cameras", mCurrentState,
+          mCameraList[mCurrentState].size());
+    ALOGD("  Desired state %d has %zu cameras", desiredState,
+          mCameraList[desiredState].size());
 
-        // Yup, we need to change cameras, so close the previous one, if necessary.
-        if (mCurrentCamera != nullptr) {
-            mCurrentStreamHandler->blockingStopStream();
-            mCurrentStreamHandler = nullptr;
-            mCurrentCamera = nullptr;
+    // 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
+    }
+
+    // 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 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;
         }
 
-        // 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);
-
-            // 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;
-            }
-        }
-
-        // 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();
-
-            // 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 d8ce9e6..cfb6833 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::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 @@
                     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;
 
-    sp<StreamHandler>           mCurrentStreamHandler;
+    std::vector<ConfigManager::CameraInfo>  mCameraList[NUM_STATES];
+    std::unique_ptr<RenderBase> mCurrentRenderer;
+
+    std::thread                 mRenderThread;  // The thread that runs the main rendering loop
+
+    // 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 b15ac0d..2935ab0 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 @@
             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 0000000..bd83ba3
--- /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 0000000..3ff1eec
--- /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
new file mode 100644
index 0000000..02da85d
--- /dev/null
+++ b/evs/app/LabeledChecker.png
Binary files differ
diff --git a/evs/app/RenderBase.cpp b/evs/app/RenderBase.cpp
new file mode 100644
index 0000000..cb6aa93
--- /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 0000000..25474d5
--- /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 0000000..24eb485
--- /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 0000000..1543fce
--- /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 0000000..0a586a4
--- /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 0000000..ee6eede
--- /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 0000000..1579a0a
--- /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 0000000..570718f
--- /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 ee50e96..5477642 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
 
 
-// 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
-
-
-StreamHandler::StreamHandler(android::sp<IEvsCamera> pCamera, android::sp<IEvsDisplay> pDisplay) :
-    mCamera(pCamera),
-    mDisplay(pDisplay) {
+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);
 }
 
 
-void StreamHandler::startStream() {
-    // Mark ourselves as running
-    mLock.lock();
-    mRunning = true;
-    mLock.unlock();
+void StreamHandler::shutdown()
+{
+    // Make sure we're not still streaming
+    blockingStopStream();
 
-    // Tell the camera to start streaming
-    mCamera->startVideoStream(this);
+    // 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;
+}
+
+
+bool StreamHandler::startStream() {
+    std::unique_lock<std::mutex> lock(mLock);
+
+    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 @@
 
     // 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 @@
 }
 
 
-unsigned StreamHandler::getFramesReceived() {
+bool StreamHandler::newFrameAvailable() {
     std::unique_lock<std::mutex> lock(mLock);
-    return mFramesReceived;
-};
-
-
-unsigned StreamHandler::getFramesCompleted() {
-    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");
-    } 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");
-            }
-        }
-
-        // 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");
-    }
-
-
-    // Update our received frame count and notify anybody who cares that things have changed
-    mLock.lock();
-    if (timeToStop) {
-        mRunning = false;
-    } else {
-        mFramesReceived++;
-    }
-    mLock.unlock();
-    mSignal.notify_all();
-
-
-    return Void();
+    return (mReadyBuffer >= 0);
 }
 
 
-bool StreamHandler::copyBufferContents(const BufferDesc& tgtBuffer,
-                                       const BufferDesc& srcBuffer) {
-    bool success = true;
+const BufferDesc& StreamHandler::getNewFrame() {
+    std::unique_lock<std::mutex> lock(mLock);
 
-    // 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;
-        }
+    if (mHeldBuffer >= 0) {
+        ALOGE("Ignored call for new frame while still holding the old one.");
     } else {
-        ALOGE("Failed to copy buffer contents");
-        success = false;
+        if (mReadyBuffer < 0) {
+            ALOGE("Returning invalid buffer because we don't have any.  Call newFrameAvailable first?");
+            mReadyBuffer = 0;   // This is a lie!
+        }
+
+        // Move the ready buffer into the held position, and clear the ready position
+        mHeldBuffer = mReadyBuffer;
+        mReadyBuffer = -1;
     }
 
-    if (srcPixels) {
-        src->unlock();
-    }
-    if (tgtPixels) {
-        tgt->unlock();
+    return mBuffers[mHeldBuffer];
+}
+
+
+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!");
     }
 
-    return success;
+    // Send the buffer back to the underlying camera
+    mCamera->doneWithFrame(mBuffers[mHeldBuffer]);
+
+    // Clear the held position
+    mHeldBuffer = -1;
+}
+
+
+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;
+        }
+    }
+
+    // Notify anybody who cares that things have changed
+    mSignal.notify_all();
+
+    return Void();
 }
diff --git a/evs/app/StreamHandler.h b/evs/app/StreamHandler.h
index eb2f6ce..9e1d3b7 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::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(); };
 
-    void startStream();
+    StreamHandler(android::sp <IEvsCamera> pCamera);
+    void shutdown();
+
+    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 0000000..7ec2191
--- /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 0000000..7c92247
--- /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 0000000..10d54bd
--- /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 0000000..0b95c1d
--- /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 0000000..a3f56bc
--- /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 0000000..966ea11
--- /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 791dc4e..5de8bb5 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
+      "cameraId" : "/dev/video32",
+      "function" : "reverse,park",
+      "x" : 0.0,
+      "y" : -40.0,
+      "z" : 48,
+      "yaw" : 180,
+      "pitch" : -30,
+      "hfov" : 125,
+      "vfov" :103 
     },
     {
-      "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
-    },
-    {
-      "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 d17119e..5f8d64f 100644
--- a/evs/app/evs_app.cpp
+++ b/evs/app/evs_app.cpp
@@ -40,18 +40,66 @@
 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 @@
 
     // 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 @@
     }
 
     // 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 0000000..a61edfe
--- /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 0000000..53188d3
--- /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
similarity index 65%
copy from tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
copy to evs/app/glError.h
index 94abeb1..52c5d5a 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 0000000..6922fbe
--- /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,
+                           &paramSize,
+                           &paramType,
+                           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/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java b/evs/app/shader.h
similarity index 65%
copy from tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
copy to evs/app/shader.h
index 94abeb1..476a2f0 100644
--- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
+++ b/evs/app/shader.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,13 @@
  * limitations under the License.
  */
 
-package com.android.support.car.apitest;
+#ifndef SHADER_H
+#define SHADER_H
 
-import android.support.car.app.CarProxyActivity;
+#include <GLES2/gl2.h>
 
-public class TestCarProxyActivity extends CarProxyActivity {
 
-    public TestCarProxyActivity() {
-        super(TestCarActivity.class);
-    }
-}
+// Create a program object given vertex and pixels shader source
+GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc, const char* name);
+
+#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 0000000..65e9109
--- /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 0000000..0e962bd
--- /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 9522f81..fe384d9 100644
--- a/evs/manager/Android.mk
+++ b/evs/manager/Android.mk
@@ -9,26 +9,24 @@
     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 3d85001..fb87536 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 0000000..8a53ba7
--- /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 b3b6e9a..fe1136b 100644
--- a/evs/manager/service.cpp
+++ b/evs/manager/service.cpp
@@ -40,14 +40,43 @@
 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 0000000..231ab6e
--- /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 0000000..25b8133
--- /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 0000000..d8d7b36
--- /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 0000000..0f62e64
--- /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 0000000..7adbac9
--- /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 0000000..045d7ab
--- /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 0000000..3d351b9
--- /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 0000000..a49eb20
--- /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 0000000..07b5525
--- /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/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java b/evs/sampleDriver/ServiceNames.h
similarity index 65%
rename from tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
rename to evs/sampleDriver/ServiceNames.h
index 94abeb1..1178da5 100644
--- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
+++ b/evs/sampleDriver/ServiceNames.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,13 +14,4 @@
  * limitations under the License.
  */
 
-package com.android.support.car.apitest;
-
-import android.support.car.app.CarProxyActivity;
-
-public class TestCarProxyActivity extends CarProxyActivity {
-
-    public TestCarProxyActivity() {
-        super(TestCarActivity.class);
-    }
-}
+const static char kEnumeratorServiceName[] = "EvsEnumeratorHw";
diff --git a/evs/sampleDriver/VideoCapture.cpp b/evs/sampleDriver/VideoCapture.cpp
new file mode 100644
index 0000000..2122a2c
--- /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 0000000..f2d1175
--- /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 0000000..81fe33a
--- /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 0000000..a585040
--- /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 0000000..9f68f2b
--- /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 0000000..d73f758
--- /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 56f64f7..4768ceb 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 @@
         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 e3366cf..30fca0c 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 @@
      * @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 bfdb9c0..f7a2d36 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 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 @@
         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 @@
         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 @@
         }
 
         String responseValue = response.toString();
+
+        if (DBG) {
+            Log.i(TAG, "runImpl() returned " + responseValue);
+        }
+
         return responseValue;
     }
 
@@ -137,11 +154,30 @@
         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 @@
         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 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 @@
                 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 @@
 
         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 0000000..3de4863
--- /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 121b54a..26f408f 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.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 @@
                                 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 b277abf..a062e97 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 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 5d474cd..175a8bb 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 088b4ed..fd4a841 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 10c6f2f..0000000
--- 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 29012da..d161883 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -83,6 +83,8 @@
      */
     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 @@
     @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 @@
             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 @@
             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 @@
                 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 @@
                 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 @@
             mAudioParamKeys = null;
             mCurrentFocusState = FocusState.STATE_LOSS;
             mLastFocusRequestToCar = null;
-            mTopFocusInfo = null;
+            mPrimaryFocusInfo = null;
             mPendingFocusChanges.clear();
             mRadioOrExtSourceActive = false;
             if (mCarAudioContextChangeHandler != null) {
@@ -458,7 +451,7 @@
                     " 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 @@
                 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 @@
                     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 @@
         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;
+
+        AudioAttributes attributes = info.getAttributes();
+        if (attributes == null) {
+            return false;
+        }
+
+        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) {
-        if (info == 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;
-        }
-        return false;
+        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 @@
      * @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 @@
                 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 @@
                     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 @@
             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 @@
                 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 @@
         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 @@
             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 @@
                     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 @@
                 streamsToRequest = 0;
                 break;
         }
-        int audioContexts = 0;
+        int audioContexts = primaryContext | secondaryContext;
         if (muteMedia) {
             boolean addMute = true;
             if (primaryIsExternal) {
@@ -1106,7 +1093,6 @@
             } else {
                 mRadioOrExtSourceActive = false;
             }
-            audioContexts = primaryContext | secondaryContext;
             if (addMute) {
                 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
                         AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
@@ -1116,7 +1102,7 @@
                 streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
             }
         } else if (mRadioOrExtSourceActive) {
-            boolean addExtFocusFlag = true;
+            boolean shouldDropSecondaryContext = false;
             if (primaryIsExternal) {
                 int primaryExtPhysicalStreamFlag =
                         getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
@@ -1125,23 +1111,23 @@
                             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 @@
                 mFocusHandler.handleFocusReleaseRequest();
                 return false;
             }
-        } else {
-            audioContexts = primaryContext | secondaryContext;
         }
         if (mSystemSoundPhysicalStreamActive) {
             boolean addSystemStream = true;
@@ -1198,7 +1182,7 @@
             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 @@
     }
 
     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 @@
         }
     }
 
+    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 @@
                 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 @@
             } 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 @@
             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 @@
         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 @@
                 }
                 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.
+                if (mExternalRoutingHintSupported) {
+                    mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
                 }
-                try {
-                    mLock.wait(mFocusResponseWaitTimeoutMs);
-                } catch (InterruptedException e) {
-                    //ignore
-                }
+                doSendFocusRequestToCarLocked(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE,
+                        0, 0, 0);
                 mCurrentPrimaryAudioContext = 0;
                 mCurrentPrimaryPhysicalStream = 0;
                 if (mCarAudioContextChangeHandler != null) {
@@ -1578,9 +1561,6 @@
     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 c14a385..88e7e34 100644
--- a/service/src/com/android/car/CarDiagnosticService.java
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -29,6 +29,7 @@
 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 @@
             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 @@
         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 @@
                 }
                 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 @@
                 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 @@
             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 @@
         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 2d65c0a..298290b 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 @@
         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 a53771f..f23f10c 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -26,7 +26,9 @@
 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.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 @@
     @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 @@
             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 @@
                 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 @@
     }
 
     @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 @@
                 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 @@
         }
 
     }
-}
\ 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 5f5ac30..d6e89f2 100644
--- a/service/src/com/android/car/VmsLayersAvailability.java
+++ b/service/src/com/android/car/VmsLayersAvailability.java
@@ -83,7 +83,7 @@
     /**
      * 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 @@
      * 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 8f7fba3..37d265a 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -27,9 +27,12 @@
 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 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 @@
     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 @@
     @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 @@
 
     @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 @@
         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 @@
                     // 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 @@
             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 0000000..04ee82f
--- /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 fc2cbac..2829cc0 100644
--- a/service/src/com/android/car/VmsRouting.java
+++ b/service/src/com/android/car/VmsRouting.java
@@ -20,16 +20,14 @@
 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 @@
      * @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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
+     * 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 97ed27f..fc0a885 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -262,6 +262,13 @@
     }
 
     @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 @@
 
     // 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 @@
                 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 84e3678..98a3b3d 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -95,13 +95,13 @@
                 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 @@
             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 5bf4e64..c60d16d 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -111,9 +111,7 @@
         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 @@
                 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 @@
         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 @@
             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 c23f36a..8ab5427 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.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 @@
  */
 @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 @@
      * 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 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 @@
             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 Set<IVmsSubscriberClient> getAllListeners() {
+        synchronized (mLock) {
+            return mRouting.getAllListeners();
+        }
+    }
+
     public boolean isHalSubscribed(VmsLayer layer) {
         synchronized (mLock) {
             return mRouting.isHalSubscribed(layer);
@@ -180,21 +199,46 @@
         }
     }
 
+    /**
+     * 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 @@
             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 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 @@
      * @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);
+    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 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 @@
         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,42 +363,206 @@
         }
         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));
-            } else {
-                // messageType == VmsMessageType.UNSUBSCRIBE
-                removeHalSubscription(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 {
+                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
     public void dump(PrintWriter writer) {
         writer.println(TAG);
@@ -339,14 +578,22 @@
      */
     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 @@
     }
 
     /** 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 @@
 
     /** 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 @@
         }
         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 0000000..7fd234c
--- /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 6a3d9ca..7554508 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 884d982..e68c3fa 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.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.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 CarHvacManager getHvacManager() {
+        return mHvacManager;
+    }
+
     @Override
     protected CarDrawerAdapter getRootAdapter() {
         return new DrawerAdapter();
@@ -194,132 +274,30 @@
 
     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 0000000..71deee8
--- /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 05e33e8..3f5ef86 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 @@
 
 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 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 6bb5bf7..2aa6c40 100644
--- a/tests/VmsPublisherClientSample/Android.mk
+++ b/tests/VmsPublisherClientSample/Android.mk
@@ -28,7 +28,7 @@
 
 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 d3ac195..fdc1a31 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 c310464..08d37cd 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.os.Handler;
 import android.os.Message;
 
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -31,7 +30,7 @@
  */
 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 @@
     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 @@
      */
     @Override
     public void onVmsPublisherServiceReady() {
+        VmsSubscriptionState subscriptionState = getSubscriptions();
+        onVmsSubscriptionChange(subscriptionState);
     }
 
     @Override
@@ -64,6 +65,13 @@
         }
     }
 
+    @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 4ee95a1..fe32ab9 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 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 4385e41..21fb5e0 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.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 @@
         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 @@
      * @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 @@
      * @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 0fc8dc6..0000000
--- 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 da19b72..0000000
--- 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 6a1e2bf..6f7ba34 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 0000000..8c3de7c
--- /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 047ad41..71e0d04 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 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 @@
                 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 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 @@
                 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 @@
         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 @@
         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 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 @@
         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 @@
         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 @@
         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 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 @@
         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 @@
                 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 @@
         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 @@
                 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 99958cc..afafb28 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 @@
 
 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 @@
         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 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 @@
                 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 9ead75d..6f5da5f 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 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 @@
                 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 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 @@
                 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 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 @@
         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 @@
                 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 @@
             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 @@
         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 @@
                 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 6562542..d316ba6 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_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 @@
                 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 @@
 
         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 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 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 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 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 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 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 @@
         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 @@
 
         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 @@
         event2 = listener2.getLastEvent();
 
         assertTrue(event1.isEarlierThan(event2));
+        assertFalse(event1.equals(event2));
+        assertFalse(event2.equals(event1));
 
         assertEquals(
                 5000,
@@ -591,15 +607,10 @@
     }
 
     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 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 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 @@
         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 cdd8838..df2b532 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 @@
             // 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 @@
             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 @@
 
             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 @@
             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 0000000..c8badf2
--- /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 0000000..240598a
--- /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 0000000..d8e344b
--- /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 0000000..c22f63a
--- /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 0000000..739f5d0
--- /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 0000000..3b3a94f
--- /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 0000000..063d5cf
--- /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 0000000..2f3af52
--- /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 ac9654c..d6fd68d 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.suitebuilder.annotation.SmallTest;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -64,6 +65,15 @@
         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 0000000..2b75012
--- /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
similarity index 69%
rename from car-ui-provider/Android.mk
rename to tests/obd2_app/Android.mk
index f63674a..7e1e22f 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_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := nostripping
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
-include packages/services/Car/car-support-lib/car-support.mk
+LOCAL_PACKAGE_NAME := Obd2App
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_STATIC_JAVA_LIBRARIES += \
+        com.android.car.obd2 \
+
+LOCAL_JAVA_VERSION := 1.8
 
 include $(BUILD_PACKAGE)
-
-endif #TARGET_BUILD_PDK
diff --git a/tests/obd2_app/AndroidManifest.xml b/tests/obd2_app/AndroidManifest.xml
new file mode 100644
index 0000000..184d939
--- /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 0000000..b9139d1
--- /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 0000000..486956c
--- /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 0000000..8511efa
--- /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 0000000..29d7bd2
--- /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
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
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 0000000..1da9658
--- /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/car-ui-provider/AndroidManifest.xml b/tests/obd2_app/res/values/arrays.xml
similarity index 60%
copy from car-ui-provider/AndroidManifest.xml
copy to tests/obd2_app/res/values/arrays.xml
index ffbdfed..2bd7ce3 100644
--- a/car-ui-provider/AndroidManifest.xml
+++ b/tests/obd2_app/res/values/arrays.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,11 +13,15 @@
      See the License for the specific language governing permissions and
      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>
+  <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
similarity index 60%
rename from car-ui-provider/AndroidManifest.xml
rename to tests/obd2_app/res/values/colors.xml
index ffbdfed..49a370a 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/car-ui-provider/AndroidManifest.xml b/tests/obd2_app/res/values/dimens.xml
similarity index 60%
copy from car-ui-provider/AndroidManifest.xml
copy to tests/obd2_app/res/values/dimens.xml
index ffbdfed..261477e 100644
--- a/car-ui-provider/AndroidManifest.xml
+++ b/tests/obd2_app/res/values/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.
@@ -13,11 +13,8 @@
      See the License for the specific language governing permissions and
      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>
+  <!-- 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 0000000..c615c4f
--- /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 &amp; 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/car-ui-provider/AndroidManifest.xml b/tests/obd2_app/res/values/styles.xml
similarity index 60%
copy from car-ui-provider/AndroidManifest.xml
copy to tests/obd2_app/res/values/styles.xml
index ffbdfed..c21c0ac 100644
--- a/car-ui-provider/AndroidManifest.xml
+++ b/tests/obd2_app/res/values/styles.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,11 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<resources>
+  <style name="AppTheme" parent="android:Theme.Material">
+  </style>
 
-<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>
diff --git a/tests/obd2_app/res/xml/preferences.xml b/tests/obd2_app/res/xml/preferences.xml
new file mode 100644
index 0000000..0c6f534
--- /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 0000000..59da4c0
--- /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 0000000..6e9e9dc
--- /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 0000000..dc38b5c
--- /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 0000000..b38cf30
--- /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/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java b/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java
new file mode 100644
index 0000000..23f120e
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.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 0000000..185c384
--- /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 3c3b1e6..e9bf98b 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 @@
         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 b342cbc..f4ac13d 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 @@
         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 @@
         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 @@
 
     @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 0000000..20919a1
--- /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 e2022c1..ba3dbb8 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 @@
             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 @@
         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 da874ce..480c9d4 100755
--- a/tools/bootanalyze/bootanalyze.py
+++ b/tools/bootanalyze/bootanalyze.py
@@ -522,7 +522,7 @@
       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 0000000..e69de29
--- /dev/null
+++ b/tools/emulator/__init__.py
diff --git a/tools/emulator/diagjson.example b/tools/emulator/diagjson.example
new file mode 100644
index 0000000..1029dfc
--- /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 0000000..b205a5b
--- /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 0000000..4bd3317
--- /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 0000000..abde4b8
--- /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 0000000..594526a
--- /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 0000000..89066df
--- /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 0000000..6695bd3
--- /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 f2433f6..50518fd 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
+# 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
 
-    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
+# VehicleApPowerStateIndex
+VEHICLEAPPOWERSTATEINDEX_STATE = 0x0
+VEHICLEAPPOWERSTATEINDEX_ADDITIONAL = 0x1
 
-    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:
+# 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
 
-        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
-"""
+# VehicleDisplay
+VEHICLEDISPLAY_MAIN = 0x0
+VEHICLEDISPLAY_INSTRUMENT_CLUSTER = 0x1
 
-# 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
+# VehicleRadioConstants
+VEHICLERADIOCONSTANTS_VEHICLE_RADIO_PRESET_MIN_VALUE = 0x1
 
-# 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
+# VehicleAudioFocusIndex
+VEHICLEAUDIOFOCUSINDEX_FOCUS = 0x0
+VEHICLEAUDIOFOCUSINDEX_STREAMS = 0x1
+VEHICLEAUDIOFOCUSINDEX_EXTERNAL_FOCUS_STATE = 0x2
+VEHICLEAUDIOFOCUSINDEX_AUDIO_CONTEXTS = 0x3
 
-# 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
+# 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 0000000..d367b85
--- /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 44e5566..4c94e4f 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 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 @@
         # 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 @@
                     msg = VehicleHalProto_pb2.EmulatorMessage()
                     msg.ParseFromString(b)
                     return msg
+                else:
+                    print("Ignored message fragment")
 
     def getConfig(self, prop):
         """
@@ -156,7 +174,7 @@
 
     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 @@
 
     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 @@
             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 @@
         # 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
old mode 100644
new mode 100755
index 325e0a8..1d0ada0
--- 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 @@
             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 @@
                 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 @@
                 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 @@
 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 b0349c0..6ef19d5 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_LPAREN = r'\('
 t_RPAREN = r'\)'
 t_SHIFT = r'<<'
+t_LESSTHAN = r'<'
+t_GREATERTHAN = r'>'
 
 def t_COMMENT(t):
     r'(/\*(.|\n)*?\*/)|(//.*)'
@@ -61,6 +65,8 @@
         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 @@
 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 @@
     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 @@
     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 @@
         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 @@
     '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 @@
     '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 @@
     '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_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])
+    t[0] = EnumValueExternRef(t[1],t[3])
+def p_enum_value_6(t):
+    'enum_value : IDENTIFIER'
+    t[0] = EnumValueLocalRef(t[1])
 
-def p_enum_case(t):
+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 d0a89d4..7f22f52 100755
--- a/tools/update-obd2-sensors.py
+++ b/tools/update-obd2-sensors.py
@@ -68,7 +68,7 @@
         """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 @@
     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 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 @@
     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 @@
 
 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 25fdfdf..6936f22 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.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 @@
             }
         }
 
-        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 @@
 
         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 @@
             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 526018c..5dd6a66 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 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 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);