Use -Werror in packages/services/Car/tools am: 7f50f1e4bd am: 20fdefebcf
am: 3022022eff

Change-Id: I945c8e641d79a885523ea8d196bde06f54443586
diff --git a/Android.mk b/Android.mk
index b33eecc..04f8004 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,7 +17,6 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-CAR_CURRENT_SDK_VERSION := 23
 CAR_API_CHECK := $(LOCAL_PATH)/apicheck.mk
 api_check_current_msg_file := $(LOCAL_PATH)/apicheck_msg_current.txt
 api_check_last_msg_file := $(LOCAL_PATH)/apicheck_msg_last.txt
@@ -28,7 +27,6 @@
 include $(call all-makefiles-under,$(LOCAL_PATH))
 
 # Clear out variables
-CAR_CURRENT_SDK_VERSION :=
 CAR_API_CHECK :=
 api_check_current_msg_file :=
 api_check_last_msg_file :=
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 6f32dd7..9a3fe1d 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -32,11 +32,13 @@
     field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
     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_STORAGE_MONITORING = "android.car.permission.STORAGE_MONITORING";
     field public static final java.lang.String PERMISSION_VEHICLE_DYNAMICS_STATE = "android.car.permission.VEHICLE_DYNAMICS_STATE";
     field public static final java.lang.String PERMISSION_VENDOR_EXTENSION = "android.car.permission.CAR_VENDOR_EXTENSION";
     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 STORAGE_MONITORING_SERVICE = "storage_monitoring";
     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 = 3; // 0x3
@@ -485,6 +487,7 @@
     method public boolean isFreezeFrameNotificationSupported() throws android.car.CarNotConnectedException;
     method public boolean isGetFreezeFrameSupported() throws android.car.CarNotConnectedException;
     method public boolean isLiveFrameSupported() throws android.car.CarNotConnectedException;
+    method public boolean isSelectiveClearFreezeFramesSupported() throws android.car.CarNotConnectedException;
     method public void onCarDisconnected();
     method public boolean registerListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener, int, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
     method public void unregisterListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener);
@@ -998,6 +1001,93 @@
 
 }
 
+package android.car.storagemonitoring {
+
+  public final class CarStorageMonitoringManager {
+    method public java.util.List<android.car.storagemonitoring.UidIoStats> getAggregateIoStats() throws android.car.CarNotConnectedException;
+    method public java.util.List<android.car.storagemonitoring.UidIoStats> getBootIoStats() throws android.car.CarNotConnectedException;
+    method public int getPreEolIndicatorStatus() throws android.car.CarNotConnectedException;
+    method public android.car.storagemonitoring.WearEstimate getWearEstimate() throws android.car.CarNotConnectedException;
+    method public java.util.List<android.car.storagemonitoring.WearEstimateChange> getWearEstimateHistory() throws android.car.CarNotConnectedException;
+    field public static final int PRE_EOL_INFO_NORMAL = 1; // 0x1
+    field public static final int PRE_EOL_INFO_UNKNOWN = 0; // 0x0
+    field public static final int PRE_EOL_INFO_URGENT = 3; // 0x3
+    field public static final int PRE_EOL_INFO_WARNING = 2; // 0x2
+  }
+
+  public final class UidIoStats implements android.os.Parcelable {
+    ctor public UidIoStats(int, long, android.car.storagemonitoring.UidIoStats.PerStateMetrics, android.car.storagemonitoring.UidIoStats.PerStateMetrics);
+    ctor public UidIoStats(android.os.Parcel);
+    ctor public UidIoStats(android.car.storagemonitoring.UidIoStatsRecord, long);
+    ctor public UidIoStats(org.json.JSONObject) throws org.json.JSONException;
+    method public int describeContents();
+    method public void writeToJson(android.util.JsonWriter) throws java.io.IOException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.storagemonitoring.UidIoStats> CREATOR;
+    field public final android.car.storagemonitoring.UidIoStats.PerStateMetrics background;
+    field public final android.car.storagemonitoring.UidIoStats.PerStateMetrics foreground;
+    field public final long runtimeMillis;
+    field public final int uid;
+  }
+
+  public static final class UidIoStats.PerStateMetrics implements android.os.Parcelable {
+    ctor public UidIoStats.PerStateMetrics(long, long, long, long, long);
+    ctor public UidIoStats.PerStateMetrics(android.os.Parcel);
+    ctor public UidIoStats.PerStateMetrics(org.json.JSONObject) throws org.json.JSONException;
+    method public int describeContents();
+    method public void writeToJson(android.util.JsonWriter) throws java.io.IOException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.storagemonitoring.UidIoStats.PerStateMetrics> CREATOR;
+    field public final long bytesRead;
+    field public final long bytesReadFromStorage;
+    field public final long bytesWritten;
+    field public final long bytesWrittenToStorage;
+    field public final long fsyncCalls;
+  }
+
+  public final class UidIoStatsRecord {
+    ctor public UidIoStatsRecord(int, long, long, long, long, long, long, long, long, long, long);
+    field public final long background_fsync;
+    field public final long background_rchar;
+    field public final long background_read_bytes;
+    field public final long background_wchar;
+    field public final long background_write_bytes;
+    field public final long foreground_fsync;
+    field public final long foreground_rchar;
+    field public final long foreground_read_bytes;
+    field public final long foreground_wchar;
+    field public final long foreground_write_bytes;
+    field public final int uid;
+  }
+
+  public class WearEstimate implements android.os.Parcelable {
+    ctor public WearEstimate(int, int);
+    ctor public WearEstimate(android.os.Parcel);
+    ctor public WearEstimate(android.util.JsonReader) throws java.io.IOException;
+    method public int describeContents();
+    method public void writeToJson(android.util.JsonWriter) throws java.io.IOException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.storagemonitoring.WearEstimate> CREATOR;
+    field public static final int UNKNOWN = -1; // 0xffffffff
+    field public final int typeA;
+    field public final int typeB;
+  }
+
+  public class WearEstimateChange implements android.os.Parcelable {
+    ctor public WearEstimateChange(android.car.storagemonitoring.WearEstimate, android.car.storagemonitoring.WearEstimate, long, java.time.Instant, boolean);
+    ctor public WearEstimateChange(android.os.Parcel);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.storagemonitoring.WearEstimateChange> CREATOR;
+    field public final java.time.Instant dateAtChange;
+    field public final boolean isAcceptableDegradation;
+    field public final android.car.storagemonitoring.WearEstimate newEstimate;
+    field public final android.car.storagemonitoring.WearEstimate oldEstimate;
+    field public final long uptimeAtChange;
+  }
+
+}
+
 package android.car.test {
 
   public class CarTestManagerBinderWrapper {
@@ -1008,3 +1098,112 @@
 
 }
 
+package android.car.vms {
+
+  public final class VmsAssociatedLayer implements android.os.Parcelable {
+    ctor public VmsAssociatedLayer(android.car.vms.VmsLayer, java.util.Set<java.lang.Integer>);
+    method public int describeContents();
+    method public java.util.Set<java.lang.Integer> getPublisherIds();
+    method public android.car.vms.VmsLayer getVmsLayer();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsAssociatedLayer> CREATOR;
+  }
+
+  public final class VmsLayer implements android.os.Parcelable {
+    ctor public VmsLayer(int, int, int);
+    method public int describeContents();
+    method public int getSubtype();
+    method public int getType();
+    method public int getVersion();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayer> CREATOR;
+  }
+
+  public final class VmsLayerDependency implements android.os.Parcelable {
+    ctor public VmsLayerDependency(android.car.vms.VmsLayer, java.util.Set<android.car.vms.VmsLayer>);
+    ctor public VmsLayerDependency(android.car.vms.VmsLayer);
+    method public int describeContents();
+    method public java.util.Set<android.car.vms.VmsLayer> getDependencies();
+    method public android.car.vms.VmsLayer getLayer();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayerDependency> CREATOR;
+  }
+
+  public final class VmsLayersOffering implements android.os.Parcelable {
+    ctor public VmsLayersOffering(java.util.Set<android.car.vms.VmsLayerDependency>, int);
+    method public int describeContents();
+    method public java.util.Set<android.car.vms.VmsLayerDependency> getDependencies();
+    method public int getPublisherId();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsLayersOffering> CREATOR;
+  }
+
+  public final class VmsOperationRecorder {
+    ctor public VmsOperationRecorder(android.car.vms.VmsOperationRecorder.Writer);
+    method public void addHalSubscription(int, android.car.vms.VmsLayer);
+    method public void addPromiscuousSubscription(int);
+    method public void addSubscription(int, android.car.vms.VmsLayer);
+    method public static android.car.vms.VmsOperationRecorder get();
+    method public void getPublisherId(int);
+    method public void removeHalSubscription(int, android.car.vms.VmsLayer);
+    method public void removePromiscuousSubscription(int);
+    method public void removeSubscription(int, android.car.vms.VmsLayer);
+    method public void setHalPublisherLayersOffering(android.car.vms.VmsLayersOffering);
+    method public void setLayersOffering(android.car.vms.VmsLayersOffering);
+    method public void setPublisherLayersOffering(android.car.vms.VmsLayersOffering);
+    method public void startMonitoring();
+    method public void stopMonitoring();
+    method public void subscribe(android.car.vms.VmsLayer);
+    method public void subscribe(android.car.vms.VmsLayer, int);
+    method public void unsubscribe(android.car.vms.VmsLayer);
+    method public void unsubscribe(android.car.vms.VmsLayer, int);
+  }
+
+  public static class VmsOperationRecorder.Writer {
+    ctor public VmsOperationRecorder.Writer();
+    method public boolean isEnabled();
+    method public void write(java.lang.String);
+  }
+
+  public abstract class VmsPublisherClientService extends android.app.Service {
+    ctor public VmsPublisherClientService();
+    method public final int getPublisherId(byte[]);
+    method public final android.car.vms.VmsSubscriptionState getSubscriptions();
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public final boolean onUnbind(android.content.Intent);
+    method protected abstract void onVmsPublisherServiceReady();
+    method public abstract void onVmsSubscriptionChange(android.car.vms.VmsSubscriptionState);
+    method public final void publish(android.car.vms.VmsLayer, int, byte[]);
+    method public final void setLayersOffering(android.car.vms.VmsLayersOffering);
+  }
+
+  public final class VmsSubscriberManager {
+    ctor public VmsSubscriberManager(android.os.IBinder, android.os.Handler);
+    method public byte[] getPublisherInfo(int) throws android.car.CarNotConnectedException, java.lang.IllegalStateException;
+    method public void registerClientCallback(android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback) throws android.car.CarNotConnectedException;
+    method public void startMonitoring() throws android.car.CarNotConnectedException;
+    method public void stopMonitoring();
+    method public void subscribe(android.car.vms.VmsLayer) throws android.car.CarNotConnectedException;
+    method public void subscribe(android.car.vms.VmsLayer, int) throws android.car.CarNotConnectedException;
+    method public void unregisterClientCallback() throws android.car.CarNotConnectedException;
+    method public void unsubscribe(android.car.vms.VmsLayer);
+    method public void unsubscribe(android.car.vms.VmsLayer, int);
+  }
+
+  public static abstract interface VmsSubscriberManager.VmsSubscriberClientCallback {
+    method public abstract void onLayersAvailabilityChanged(java.util.List<android.car.vms.VmsLayer>);
+    method public abstract void onVmsMessageReceived(android.car.vms.VmsLayer, byte[]);
+  }
+
+  public final class VmsSubscriptionState implements android.os.Parcelable {
+    ctor public VmsSubscriptionState(int, java.util.Set<android.car.vms.VmsLayer>, java.util.Set<android.car.vms.VmsAssociatedLayer>);
+    method public int describeContents();
+    method public java.util.Set<android.car.vms.VmsAssociatedLayer> getAssociatedLayers();
+    method public java.util.Set<android.car.vms.VmsLayer> getLayers();
+    method public int getSequenceNumber();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.vms.VmsSubscriptionState> CREATOR;
+  }
+
+}
+
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 9a9e531..6101797 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -31,7 +31,9 @@
 import android.car.media.CarAudioManager;
 import android.car.navigation.CarNavigationStatusManager;
 import android.car.CarBluetoothManager;
+import android.car.storagemonitoring.CarStorageMonitoringManager;
 import android.car.test.CarTestManagerBinderWrapper;
+import android.car.vms.VmsSubscriberManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -134,6 +136,18 @@
     public static final String BLUETOOTH_SERVICE = "car_bluetooth";
 
     /**
+     * @FutureFeature Cannot drop due to usage in non-flag protected place.
+     * @hide
+     */
+    public static final String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service";
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    public static final String STORAGE_MONITORING_SERVICE = "storage_monitoring";
+
+    /**
      * Service for testing. This is system app only feature.
      * Service name for {@link CarTestManager}, to be used in {@link #getCarManager(String)}.
      * @hide
@@ -254,6 +268,22 @@
             "android.car.permission.CAR_TEST_SERVICE";
 
     /**
+     * Permissions necessary to access VMS publisher APIs.
+     *
+     * @hide
+     */
+    @FutureFeature
+    public static final String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER";
+
+    /**
+     * Permissions necessary to access VMS subscriber APIs.
+     *
+     * @hide
+     */
+    @FutureFeature
+    public static final String PERMISSION_VMS_SUBSCRIBER = "android.car.permission.VMS_SUBSCRIBER";
+
+    /**
      * Permissions necessary to read diagnostic information, including vendor-specific bits.
      *
      * @hide
@@ -270,6 +300,14 @@
     @SystemApi
     public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.DIAGNOSTIC_CLEAR";
 
+    /**
+     * Permissions necessary to clear diagnostic information.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_STORAGE_MONITORING = "android.car.permission.STORAGE_MONITORING";
+
     /** Type of car connection: platform runs directly in car. */
     public static final int CONNECTION_TYPE_EMBEDDED = 5;
 
@@ -631,8 +669,13 @@
                  * only pass binder wrapper so that CarTestManager can be constructed outside. */
                 manager = new CarTestManagerBinderWrapper(binder);
                 break;
+            case VMS_SUBSCRIBER_SERVICE:
+                manager = new VmsSubscriberManager(binder, mEventHandler);
+                break;
             case BLUETOOTH_SERVICE:
                 manager = new CarBluetoothManager(binder, mContext);
+            case STORAGE_MONITORING_SERVICE:
+                manager = new CarStorageMonitoringManager(binder);
         }
         return manager;
     }
diff --git a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
index 84cdd3e..88980bf 100644
--- a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
+++ b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
@@ -349,8 +349,13 @@
     }
 
     /**
-     * Returns true if this vehicle supports clearing freeze frame timestamps.
+     * Returns true if this vehicle supports clearing all freeze frames.
      * This is only meaningful if freeze frame data is also supported.
+     *
+     * A return value of true for this method indicates that it is supported to call
+     * carDiagnosticManager.clearFreezeFrames()
+     * to delete all freeze frames stored in vehicle memory.
+     *
      * @return
      * @throws CarNotConnectedException
      */
@@ -365,6 +370,28 @@
         return false;
     }
 
+    /**
+     * Returns true if this vehicle supports clearing specific freeze frames by timestamp.
+     * This is only meaningful if freeze frame data is also supported.
+     *
+     * A return value of true for this method indicates that it is supported to call
+     * carDiagnosticManager.clearFreezeFrames(timestamp1, timestamp2, ...)
+     * to delete the freeze frames stored for the provided input timestamps, provided any exist.
+     *
+     * @return
+     * @throws CarNotConnectedException
+     */
+    public boolean isSelectiveClearFreezeFramesSupported() throws CarNotConnectedException {
+        try {
+            return mService.isSelectiveClearFreezeFramesSupported();
+        } catch (IllegalStateException e) {
+            CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return false;
+    }
+
     private static class CarDiagnosticEventListenerToService
             extends Stub {
         private final WeakReference<CarDiagnosticManager> mManager;
diff --git a/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl b/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl
index 3d1808f..57443d8 100644
--- a/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl
+++ b/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl
@@ -74,4 +74,10 @@
      * Returns whether the underlying HAL supports clearing freeze frames.
      */
      boolean isClearFreezeFramesSupported() = 10;
+
+    /**
+     * Returns whether the underlying HAL supports clearing specific freeze frames specified
+     * by means of their timestamps.
+     */
+     boolean isSelectiveClearFreezeFramesSupported() = 11;
 }
diff --git a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
new file mode 100644
index 0000000..51d77d1
--- /dev/null
+++ b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
@@ -0,0 +1,165 @@
+/*
+ * 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 android.car.storagemonitoring;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.CarNotConnectedException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import java.util.Collections;
+import java.util.List;
+
+import static android.car.CarApiUtil.checkCarNotConnectedExceptionFromCarService;
+
+/**
+ * API for retrieving information and metrics about the flash storage.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CarStorageMonitoringManager implements CarManagerBase {
+    private final ICarStorageMonitoring mService;
+
+    public static final int PRE_EOL_INFO_UNKNOWN = 0;
+    public static final int PRE_EOL_INFO_NORMAL = 1;
+    public static final int PRE_EOL_INFO_WARNING = 2;
+    public static final int PRE_EOL_INFO_URGENT = 3;
+
+    /**
+     * @hide
+     */
+    public CarStorageMonitoringManager(IBinder service) {
+        mService = ICarStorageMonitoring.Stub.asInterface(service);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void onCarDisconnected() {
+    }
+
+    // ICarStorageMonitoring forwards
+
+    /**
+     * This method returns the value of the "pre EOL" indicator for the flash storage
+     * as retrieved during the current boot cycle.
+     *
+     * It will return either PRE_EOL_INFO_UNKNOWN if the value can't be determined,
+     * or one of PRE_EOL_INFO_{NORMAL|WARNING|URGENT} depending on the device state.
+     */
+    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    public int getPreEolIndicatorStatus() throws CarNotConnectedException {
+        try {
+            return mService.getPreEolIndicatorStatus();
+        } catch (IllegalStateException e) {
+            checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return PRE_EOL_INFO_UNKNOWN;
+    }
+
+    /**
+     * This method returns the value of the wear estimate indicators for the flash storage
+     * as retrieved during the current boot cycle.
+     *
+     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
+     * Current technology in common automotive usage offers estimates in 10% increments.
+     *
+     * If either or both indicators are not available, they will be reported as UNKNOWN.
+     */
+    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    public WearEstimate getWearEstimate() throws CarNotConnectedException {
+        try {
+            return mService.getWearEstimate();
+        } catch (IllegalStateException e) {
+            checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return WearEstimate.UNKNOWN_ESTIMATE;
+    }
+
+    /**
+     * This method returns a list of all changes in wear estimate indicators detected during the
+     * lifetime of the system.
+     *
+     * The indicators are not guaranteed to persist across a factory reset.
+     *
+     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
+     * Current technology in common automotive usage offers estimates in 10% increments.
+     *
+     * If no indicators are available, an empty list will be returned.
+     */
+    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    public List<WearEstimateChange> getWearEstimateHistory() throws CarNotConnectedException {
+        try {
+            return mService.getWearEstimateHistory();
+        } catch (IllegalStateException e) {
+            checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * This method returns a list of per user-id I/O activity metrics as collected at the end of
+     * system boot.
+     *
+     * The BOOT_COMPLETE broadcast is used as the trigger to collect this data. The implementation
+     * may impose an additional, and even variable across boot cycles, delay between the sending
+     * of the broadcast and the collection of the data.
+     *
+     * If the information is not available, an empty list will be returned.
+     */
+    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    public List<UidIoStats> getBootIoStats() throws CarNotConnectedException {
+        try {
+            return mService.getBootIoStats();
+        } catch (IllegalStateException e) {
+            checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * This method returns a list of per user-id I/O activity metrics as collected from kernel
+     * start until the last snapshot.
+     *
+     * The samples provided might be as old as the value of the ioStatsRefreshRateSeconds setting.
+     *
+     * If the information is not available, an empty list will be returned.
+     */
+    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    public List<UidIoStats> getAggregateIoStats() throws CarNotConnectedException {
+        try {
+            return mService.getAggregateIoStats();
+        } catch (IllegalStateException e) {
+            checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return Collections.emptyList();
+    }
+
+}
diff --git a/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl b/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl
new file mode 100644
index 0000000..cedb343
--- /dev/null
+++ b/car-lib/src/android/car/storagemonitoring/ICarStorageMonitoring.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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 android.car.storagemonitoring;
+
+import android.car.storagemonitoring.UidIoStats;
+import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
+
+/** @hide */
+interface ICarStorageMonitoring {
+  /**
+   * Returns the value of the PRE_EOL register.
+   */
+  int getPreEolIndicatorStatus() = 1;
+
+  /**
+   * Returns the current wear estimate indicators.
+   */
+  WearEstimate getWearEstimate() = 2;
+
+  /**
+   * Returns the list of all observed wear estimate changes.
+   */
+  List<WearEstimateChange> getWearEstimateHistory() = 3;
+
+  /**
+   * Returns I/O stats as collected at service boot time.
+   */
+  List<UidIoStats> getBootIoStats() = 4;
+
+  /**
+   * Returns total I/O stats as collected from kernel start until the last snapshot.
+   */
+  List<UidIoStats> getAggregateIoStats() = 5;
+
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/storagemonitoring/UidIoStats.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/storagemonitoring/UidIoStats.aidl
index ca0ac83..2a9aee1 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/storagemonitoring/UidIoStats.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.storagemonitoring;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable UidIoStats;
diff --git a/car-lib/src/android/car/storagemonitoring/UidIoStats.java b/car-lib/src/android/car/storagemonitoring/UidIoStats.java
new file mode 100644
index 0000000..3b8ae93
--- /dev/null
+++ b/car-lib/src/android/car/storagemonitoring/UidIoStats.java
@@ -0,0 +1,335 @@
+/*
+ * 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 android.car.storagemonitoring;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.JsonWriter;
+import java.io.IOException;
+import java.util.Objects;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * uid_io stats about one user ID.
+ *
+ * Contains information about I/O activity that can be attributed to processes running on
+ * behalf of one user of the system, as collected by the kernel.
+ *
+ * @hide
+ */
+@SystemApi
+public final class UidIoStats implements Parcelable {
+
+    public static final Parcelable.Creator<UidIoStats> CREATOR =
+        new Parcelable.Creator<UidIoStats>() {
+            public UidIoStats createFromParcel(Parcel in) {
+                return new UidIoStats(in);
+            }
+
+            public UidIoStats[] newArray(int size) {
+                return new UidIoStats[size];
+            }
+        };
+
+    /**
+     * The user id that this object contains metrics for.
+     *
+     * In many cases this can be converted to a list of Java app packages installed on the device.
+     * In other cases, the user id can refer to either the kernel itself (uid 0), or low-level
+     * system services that are running entirely natively.
+     */
+    public final int uid;
+
+    /**
+     * How long any process running on behalf of this user id running for, in milliseconds.
+     *
+     * This field is allowed to be an approximation and it does not provide any way to
+     * relate uptime to specific processes.
+     */
+    public final long runtimeMillis;
+
+    /**
+     * Statistics for apps running in foreground.
+     */
+    public final PerStateMetrics foreground;
+
+    /**
+     * Statistics for apps running in background.
+     */
+    public final PerStateMetrics background;
+
+    public UidIoStats(int uid,
+            long runtimeMillis, PerStateMetrics foreground, PerStateMetrics background) {
+        this.uid = uid;
+        this.runtimeMillis = runtimeMillis;
+        this.foreground = Objects.requireNonNull(foreground);
+        this.background = Objects.requireNonNull(background);
+    }
+
+    public UidIoStats(Parcel in) {
+        uid = in.readInt();
+        runtimeMillis = in.readLong();
+        foreground = in.readParcelable(PerStateMetrics.class.getClassLoader());
+        background = in.readParcelable(PerStateMetrics.class.getClassLoader());
+    }
+
+    public UidIoStats(UidIoStatsRecord record, long runtimeMillis) {
+        uid = record.uid;
+        this.runtimeMillis = runtimeMillis;
+        foreground = new PerStateMetrics(record.foreground_rchar,
+                record.foreground_wchar,
+                record.foreground_read_bytes,
+                record.foreground_write_bytes,
+                record.foreground_fsync);
+        background = new PerStateMetrics(record.background_rchar,
+            record.background_wchar,
+            record.background_read_bytes,
+            record.background_write_bytes,
+            record.background_fsync);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(uid);
+        dest.writeLong(runtimeMillis);
+        dest.writeParcelable(foreground, flags);
+        dest.writeParcelable(background, flags);
+    }
+
+    public void writeToJson(JsonWriter jsonWriter) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("uid").value(uid);
+        jsonWriter.name("runtimeMillis").value(runtimeMillis);
+        jsonWriter.name("foreground"); foreground.writeToJson(jsonWriter);
+        jsonWriter.name("background"); background.writeToJson(jsonWriter);
+        jsonWriter.endObject();
+    }
+
+    public UidIoStats(JSONObject in) throws JSONException {
+        uid = in.getInt("uid");
+        runtimeMillis = in.getLong("runtimeMillis");
+        foreground = new PerStateMetrics(in.getJSONObject("foreground"));
+        background = new PerStateMetrics(in.getJSONObject("background"));
+    }
+
+    /**
+     * Returns the difference between the values stored in this object vs. those
+     * stored in other.
+     *
+     * It is the same as doing a delta() on foreground and background, plus verifying that
+     * both objects refer to the same uid.
+     *
+     * @hide
+     */
+    public UidIoStats delta(UidIoStats other) {
+        if (uid != other.uid) {
+            throw new IllegalArgumentException("cannot calculate delta between different user IDs");
+        }
+        return new UidIoStats(uid,
+                runtimeMillis - other.runtimeMillis,
+                foreground.delta(other.foreground), background.delta(other.background));
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof UidIoStats) {
+            UidIoStats uidIoStatEntry = (UidIoStats)other;
+
+            return uid == uidIoStatEntry.uid &&
+                    runtimeMillis == uidIoStatEntry.runtimeMillis &&
+                    foreground.equals(uidIoStatEntry.foreground) &&
+                    background.equals(uidIoStatEntry.background);
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(uid, runtimeMillis, foreground, background);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("uid = %d, runtime = %d, foreground = %s, background = %s",
+            uid, runtimeMillis, foreground, background);
+    }
+
+    /**
+     * Validates that this object contains the same I/O metrics as a UidIoStatsRecord.
+     *
+     * It matches UID, and I/O activity values, but ignores runtime.
+     * @hide
+     */
+    public boolean representsSameMetrics(UidIoStatsRecord record) {
+        return record.uid == uid &&
+               record.foreground_rchar == foreground.bytesRead &&
+               record.foreground_wchar == foreground.bytesWritten &&
+               record.foreground_read_bytes == foreground.bytesReadFromStorage &&
+               record.foreground_write_bytes == foreground.bytesWrittenToStorage &&
+               record.foreground_fsync == foreground.fsyncCalls &&
+               record.background_rchar == background.bytesRead &&
+               record.background_wchar == background.bytesWritten &&
+               record.background_read_bytes == background.bytesReadFromStorage &&
+               record.background_write_bytes == background.bytesWrittenToStorage &&
+               record.background_fsync == background.fsyncCalls;
+    }
+
+    /**
+     * I/O activity metrics that pertain to either the foreground or the background state.
+     */
+    public static final class PerStateMetrics implements Parcelable {
+
+        public static final Parcelable.Creator<PerStateMetrics> CREATOR =
+            new Parcelable.Creator<PerStateMetrics>() {
+                public PerStateMetrics createFromParcel(Parcel in) {
+                    return new PerStateMetrics(in);
+                }
+
+                public PerStateMetrics[] newArray(int size) {
+                    return new PerStateMetrics[size];
+                }
+            };
+
+        /**
+         * Total bytes that processes running on behalf of this user obtained
+         * via read() system calls.
+         */
+        public final long bytesRead;
+
+        /**
+         * Total bytes that processes running on behalf of this user transferred
+         * via write() system calls.
+         */
+        public final long bytesWritten;
+
+        /**
+         * Total bytes that processes running on behalf of this user obtained
+         * via read() system calls that actually were served by physical storage.
+         */
+        public final long bytesReadFromStorage;
+
+        /**
+         * Total bytes that processes running on behalf of this user transferred
+         * via write() system calls that were actually sent to physical storage.
+         */
+        public final long bytesWrittenToStorage;
+
+        /**
+         * Total number of fsync() system calls that processes running on behalf of this user made.
+         */
+        public final long fsyncCalls;
+
+        public PerStateMetrics(long bytesRead, long bytesWritten, long bytesReadFromStorage,
+            long bytesWrittenToStorage, long fsyncCalls) {
+            this.bytesRead = bytesRead;
+            this.bytesWritten = bytesWritten;
+            this.bytesReadFromStorage = bytesReadFromStorage;
+            this.bytesWrittenToStorage = bytesWrittenToStorage;
+            this.fsyncCalls = fsyncCalls;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(bytesRead);
+            dest.writeLong(bytesWritten);
+            dest.writeLong(bytesReadFromStorage);
+            dest.writeLong(bytesWrittenToStorage);
+            dest.writeLong(fsyncCalls);
+        }
+
+        public void writeToJson(JsonWriter jsonWriter) throws IOException {
+            jsonWriter.beginObject();
+            jsonWriter.name("bytesRead").value(bytesRead);
+            jsonWriter.name("bytesWritten").value(bytesWritten);
+            jsonWriter.name("bytesReadFromStorage").value(bytesReadFromStorage);
+            jsonWriter.name("bytesWrittenToStorage").value(bytesWrittenToStorage);
+            jsonWriter.name("fsyncCalls").value(fsyncCalls);
+            jsonWriter.endObject();
+        }
+
+        public PerStateMetrics(Parcel in) {
+            bytesRead = in.readLong();
+            bytesWritten = in.readLong();
+            bytesReadFromStorage = in.readLong();
+            bytesWrittenToStorage = in.readLong();
+            fsyncCalls = in.readLong();
+        }
+
+        public PerStateMetrics(JSONObject in) throws JSONException {
+            bytesRead = in.getLong("bytesRead");
+            bytesWritten = in.getLong("bytesWritten");
+            bytesReadFromStorage = in.getLong("bytesReadFromStorage");
+            bytesWrittenToStorage = in.getLong("bytesWrittenToStorage");
+            fsyncCalls = in.getLong("fsyncCalls");
+        }
+
+        /**
+         * Computes the difference between the values stored in this object
+         * vs. those stored in other
+         *
+         * It is the same as doing
+         * new PerStateMetrics(bytesRead-other.bytesRead,bytesWritten-other.bytesWritten, ...)
+         *
+         * @hide
+         */
+        public PerStateMetrics delta(PerStateMetrics other) {
+            return new PerStateMetrics(bytesRead-other.bytesRead,
+                bytesWritten-other.bytesWritten,
+                bytesReadFromStorage-other.bytesReadFromStorage,
+                bytesWrittenToStorage-other.bytesWrittenToStorage,
+                fsyncCalls-other.fsyncCalls);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof PerStateMetrics) {
+                PerStateMetrics perStateMetrics = (PerStateMetrics)other;
+
+                return (bytesRead == perStateMetrics.bytesRead) &&
+                    (bytesWritten == perStateMetrics.bytesWritten) &&
+                    (bytesReadFromStorage == perStateMetrics.bytesReadFromStorage) &&
+                    (bytesWrittenToStorage == perStateMetrics.bytesWrittenToStorage) &&
+                    (fsyncCalls == perStateMetrics.fsyncCalls);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(bytesRead, bytesWritten, bytesReadFromStorage,
+                bytesWrittenToStorage, fsyncCalls);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("bytesRead=%d, bytesWritten=%d, bytesReadFromStorage=%d, bytesWrittenToStorage=%d, fsyncCalls=%d",
+                bytesRead, bytesWritten, bytesReadFromStorage, bytesWrittenToStorage, fsyncCalls);
+        }
+    }
+}
diff --git a/car-lib/src/android/car/storagemonitoring/UidIoStatsRecord.java b/car-lib/src/android/car/storagemonitoring/UidIoStatsRecord.java
new file mode 100644
index 0000000..abd9ee0
--- /dev/null
+++ b/car-lib/src/android/car/storagemonitoring/UidIoStatsRecord.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.car.storagemonitoring;
+
+import android.annotation.SystemApi;
+
+/**
+ * Record of data as extracted from /proc/uid_io/stats
+ *
+ * @hide
+ */
+@SystemApi
+public final class UidIoStatsRecord {
+
+    public final int uid;
+
+    public final long foreground_rchar;
+    public final long foreground_wchar;
+    public final long foreground_read_bytes;
+    public final long foreground_write_bytes;
+    public final long foreground_fsync;
+
+    public final long background_rchar;
+    public final long background_wchar;
+    public final long background_read_bytes;
+    public final long background_write_bytes;
+    public final long background_fsync;
+
+    public UidIoStatsRecord(int uid,
+            long foreground_rchar,
+            long foreground_wchar,
+            long foreground_read_bytes,
+            long foreground_write_bytes,
+            long foreground_fsync,
+            long background_rchar,
+            long background_wchar,
+            long background_read_bytes,
+            long background_write_bytes,
+            long background_fsync) {
+        this.uid = uid;
+
+        this.foreground_rchar = foreground_rchar;
+        this.foreground_wchar = foreground_wchar;
+        this.foreground_read_bytes = foreground_read_bytes;
+        this.foreground_write_bytes = foreground_write_bytes;
+        this.foreground_fsync = foreground_fsync;
+
+        this.background_rchar = background_rchar;
+        this.background_wchar = background_wchar;
+        this.background_read_bytes = background_read_bytes;
+        this.background_write_bytes = background_write_bytes;
+        this.background_fsync = background_fsync;
+    }
+
+    /** @hide */
+    public UidIoStatsRecord delta(UidIoStats other) {
+        if (uid != other.uid) {
+            throw new IllegalArgumentException("cannot calculate delta between different user IDs");
+        }
+
+        return new UidIoStatsRecord(uid,
+            foreground_rchar - other.foreground.bytesRead,
+            foreground_wchar - other.foreground.bytesWritten,
+            foreground_read_bytes - other.foreground.bytesReadFromStorage,
+            foreground_write_bytes - other.foreground.bytesWrittenToStorage,
+            foreground_fsync - other.foreground.fsyncCalls,
+            background_rchar - other.background.bytesRead,
+            background_wchar - other.background.bytesWritten,
+            background_read_bytes - other.background.bytesReadFromStorage,
+            background_write_bytes - other.background.bytesWrittenToStorage,
+            background_fsync - other.background.fsyncCalls);
+    }
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/storagemonitoring/WearEstimate.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/storagemonitoring/WearEstimate.aidl
index ca0ac83..c7ea505 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimate.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.storagemonitoring;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable WearEstimate;
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimate.java b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
new file mode 100644
index 0000000..507e6ed
--- /dev/null
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
@@ -0,0 +1,152 @@
+/*
+ * 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 android.car.storagemonitoring;
+
+import android.annotation.IntRange;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import java.io.IOException;
+import java.util.Objects;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Wear-out information for flash storage.
+ *
+ * Contains a lower-bound estimate of the wear of "type A" (SLC) and "type B" (MLC) storage.
+ *
+ * Current technology offers wear data in increments of 10% (i.e. from 0 == new device up to
+ * 100 == worn-out device). It is possible for a storage device to only support one type of memory
+ * cell, in which case it is expected that the storage type not supported will have UNKNOWN wear.
+ *
+ * @hide
+ */
+@SystemApi
+public class WearEstimate implements Parcelable {
+    public static final int UNKNOWN = -1;
+
+    /** @hide */
+    public static final WearEstimate UNKNOWN_ESTIMATE = new WearEstimate(UNKNOWN, UNKNOWN);
+
+    public static final Parcelable.Creator<WearEstimate> CREATOR =
+        new Parcelable.Creator<WearEstimate>() {
+            public WearEstimate createFromParcel(Parcel in) {
+                return new WearEstimate(in);
+            }
+
+            public WearEstimate[] newArray(int size) {
+                return new WearEstimate[size];
+            }
+        };
+
+    /**
+     * Wear estimate data for "type A" storage.
+     */
+    @IntRange(from=-1, to=100)
+    public final int typeA;
+
+    /**
+     * Wear estimate data for "type B" storage.
+     */
+    @IntRange(from=-1, to=100)
+    public final int typeB;
+
+    private static final int validateWearValue(int value) {
+        if (value == UNKNOWN) return value;
+        if ((value >= 0) && (value <= 100)) return value;
+        throw new IllegalArgumentException(value + " is not a valid wear estimate");
+    }
+
+    public WearEstimate(int typeA, int typeB) {
+        this.typeA = validateWearValue(typeA);
+        this.typeB = validateWearValue(typeB);
+    }
+
+    public WearEstimate(Parcel in) {
+        typeA = validateWearValue(in.readInt());
+        typeB = validateWearValue(in.readInt());
+    }
+
+    public WearEstimate(JsonReader in) throws IOException {
+        int typeA = UNKNOWN;
+        int typeB = UNKNOWN;
+        in.beginObject();
+        while (in.hasNext()) {
+            switch (in.nextName()) {
+                case "wearEstimateTypeA":
+                    typeA = validateWearValue(in.nextInt());
+                    break;
+                case "wearEstimateTypeB":
+                    typeB = validateWearValue(in.nextInt());
+                    break;
+            }
+        }
+        in.endObject();
+        this.typeA = typeA;
+        this.typeB = typeB;
+    }
+
+    /** @hide */
+    public WearEstimate(JSONObject in) throws JSONException {
+        typeA = in.getInt("wearEstimateTypeA");
+        typeB = in.getInt("wearEstimateTypeB");
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(typeA);
+        dest.writeInt(typeB);
+    }
+
+    public void writeToJson(JsonWriter jsonWriter) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("wearEstimateTypeA").value(typeA);
+        jsonWriter.name("wearEstimateTypeB").value(typeB);
+        jsonWriter.endObject();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof WearEstimate) {
+            WearEstimate wo = (WearEstimate)other;
+            return wo.typeA == typeA && wo.typeB == typeB;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(typeA, typeB);
+    }
+
+    private static final String wearValueToString(int value) {
+        if (value == UNKNOWN) return "unknown";
+        return value + "%";
+    }
+
+    @Override
+    public String toString() {
+        return "type A: " + wearValueToString(typeA) + ", type B: " + wearValueToString(typeB);
+    }
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/storagemonitoring/WearEstimateChange.aidl
index ca0ac83..49adfe5 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.storagemonitoring;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable WearEstimateChange;
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
new file mode 100644
index 0000000..5c80a31
--- /dev/null
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
@@ -0,0 +1,143 @@
+/*
+ * 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 android.car.storagemonitoring;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.time.Instant;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Change in wear-out information.
+ *
+ * Contains information about the first cycle during which the system detected a change
+ * in wear-out information of the flash storage.
+ *
+ * @hide
+ */
+@SystemApi
+public class WearEstimateChange implements Parcelable {
+    public static final Parcelable.Creator<WearEstimateChange> CREATOR =
+        new Parcelable.Creator<WearEstimateChange>() {
+            public WearEstimateChange createFromParcel(Parcel in) {
+                return new WearEstimateChange(in);
+            }
+
+            public WearEstimateChange[] newArray(int size) {
+                return new WearEstimateChange[size];
+            }
+        };
+
+    /**
+     * The previous wear estimate.
+     */
+    public final @NonNull WearEstimate oldEstimate;
+
+    /**
+     * The new wear estimate.
+     */
+    public final @NonNull WearEstimate newEstimate;
+
+    /**
+     * Total CarService uptime when this change was detected.
+     */
+    public final long uptimeAtChange;
+
+    /**
+     * Wall-clock time when this change was detected.
+     */
+    public final @NonNull Instant dateAtChange;
+
+    /**
+     * Whether this change was within the vendor range for acceptable flash degradation.
+     */
+    public final boolean isAcceptableDegradation;
+
+    public WearEstimateChange(WearEstimate oldEstimate,
+                              WearEstimate newEstimate,
+                              long uptimeAtChange,
+                              Instant dateAtChange,
+                              boolean isAcceptableDegradation) {
+        if (uptimeAtChange < 0) {
+            throw new IllegalArgumentException("uptimeAtChange must be >= 0");
+        }
+        this.oldEstimate = requireNonNull(oldEstimate);
+        this.newEstimate = requireNonNull(newEstimate);
+        this.uptimeAtChange = uptimeAtChange;
+        this.dateAtChange = requireNonNull(dateAtChange);
+        this.isAcceptableDegradation = isAcceptableDegradation;
+    }
+
+    public WearEstimateChange(Parcel in) {
+        oldEstimate = in.readParcelable(WearEstimate.class.getClassLoader());
+        newEstimate = in.readParcelable(WearEstimate.class.getClassLoader());
+        uptimeAtChange = in.readLong();
+        dateAtChange = Instant.ofEpochMilli(in.readLong());
+        isAcceptableDegradation = in.readInt() == 1;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(oldEstimate, flags);
+        dest.writeParcelable(newEstimate, flags);
+        dest.writeLong(uptimeAtChange);
+        dest.writeLong(dateAtChange.toEpochMilli());
+        dest.writeInt(isAcceptableDegradation ? 1 : 0);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof WearEstimateChange) {
+            WearEstimateChange wo = (WearEstimateChange)other;
+            return wo.isAcceptableDegradation == isAcceptableDegradation &&
+                wo.uptimeAtChange == uptimeAtChange &&
+                wo.dateAtChange.equals(dateAtChange) &&
+                wo.oldEstimate.equals(oldEstimate) &&
+                wo.newEstimate.equals(newEstimate);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(oldEstimate,
+                            newEstimate,
+                            uptimeAtChange,
+                            dateAtChange,
+                            isAcceptableDegradation);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "wear change{old level=%s, new level=%s, uptime=%d, date=%s, acceptable=%s}",
+                oldEstimate,
+                newEstimate,
+                uptimeAtChange,
+                dateAtChange,
+                isAcceptableDegradation ? "yes" : "no");
+    }
+
+}
diff --git a/car-lib/src/android/car/vms/IVmsPublisherClient.aidl b/car-lib/src/android/car/vms/IVmsPublisherClient.aidl
new file mode 100644
index 0000000..96b993b
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsPublisherClient.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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 android.car.vms;
+
+import android.car.vms.IVmsPublisherService;
+import android.car.vms.VmsSubscriptionState;
+
+/**
+ * @hide
+ */
+interface IVmsPublisherClient {
+    /**
+    * Once the VmsPublisherService is bound to the client, this callback is used to set the
+    * binder that the client can use to invoke publisher services. This also gives the client
+    * the token it should use when calling the service.
+    */
+    oneway void setVmsPublisherService(in IBinder token, IVmsPublisherService service) = 0;
+
+    /**
+     * The VmsPublisherService uses this callback to notify about subscription changes.
+     * @param subscriptionState all the layers that have subscribers and a sequence number,
+     *                          clients should ignore any packet with a sequence number that is less
+     *                          than the highest sequence number they have seen thus far.
+     */
+    oneway void onVmsSubscriptionChange(in VmsSubscriptionState subscriptionState) = 1;
+}
diff --git a/car-lib/src/android/car/vms/IVmsPublisherService.aidl b/car-lib/src/android/car/vms/IVmsPublisherService.aidl
new file mode 100644
index 0000000..3c26a1b
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsPublisherService.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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 android.car.vms;
+
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsSubscriptionState;
+
+/**
+ * Exposes publisher services to VMS clients.
+ *
+ * @hide
+ */
+interface IVmsPublisherService {
+    /**
+     * Client call to publish a message.
+     */
+    oneway void publish(in IBinder token, in VmsLayer layer, int publisherId, in byte[] message) = 0;
+
+    /**
+     * Returns the list of VmsLayers that has any clients subscribed to it.
+     */
+    VmsSubscriptionState getSubscriptions() = 1;
+
+    /**
+     * 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 an 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 getPublisherId(in byte[] publisherInfo) = 3;
+}
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberClient.aidl b/car-lib/src/android/car/vms/IVmsSubscriberClient.aidl
new file mode 100644
index 0000000..d5b5606
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsSubscriberClient.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.car.vms;
+
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+
+/**
+ * @hide
+ */
+oneway interface IVmsSubscriberClient {
+    /**
+     * A VmsService uses this callback to pass messages to subscribers.
+     */
+    void onVmsMessageReceived(in VmsLayer layer, in byte[] payload) = 0;
+
+    void onLayersAvailabilityChanged(in List<VmsAssociatedLayer> availableLayers) = 1;
+}
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
new file mode 100644
index 0000000..d10a6e4
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
@@ -0,0 +1,96 @@
+/*
+ * 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 android.car.vms;
+
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.VmsLayer;
+
+/**
+ * @hide
+ */
+interface IVmsSubscriberService {
+    /**
+     * Adds a subscriber to notifications only.
+     * Should be called when a subscriber registers its callback, and before any subscription to a
+     * layer is made.
+     */
+    void addVmsSubscriberToNotifications(
+            in IVmsSubscriberClient subscriber) = 0;
+
+    /**
+     * Adds a subscriber to a VMS layer.
+     */
+    void addVmsSubscriber(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer) = 1;
+
+    /**
+     * Adds a subscriber to all actively broadcasted layers.
+     * Publishers will not be notified regarding this request so the state of the service will not
+     * change.
+     */
+    void addVmsSubscriberPassive(in IVmsSubscriberClient subscriber) = 2;
+
+    /**
+     * Adds a subscriber to a VMS layer from a specific publisher.
+     */
+    void addVmsSubscriberToPublisher(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer,
+            int publisherId) = 3;
+
+    /**
+     * Removes a subscriber to notifications only.
+     * Should be called when a subscriber unregisters its callback, and after all subscriptions to
+     * layers are removed.
+     */
+    void removeVmsSubscriberToNotifications(
+            in IVmsSubscriberClient subscriber) = 4;
+
+    /**
+     * Removes a subscriber to a VMS layer.
+     */
+    void removeVmsSubscriber(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer) = 5;
+
+    /**
+     * Removes a subscriber to all actively broadcasted layers.
+     * Publishers will not be notified regarding this request so the state of the service will not
+     * change.
+     */
+    void removeVmsSubscriberPassive(
+            in IVmsSubscriberClient subscriber) = 6;
+
+    /**
+     * Removes a subscriber to a VMS layer from a specific publisher.
+     */
+    void removeVmsSubscriberToPublisher(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer,
+            int publisherId) = 7;
+
+    /**
+     * Returns a list of available layers from the closure of the publishers offerings.
+     */
+    List<VmsLayer> getAvailableLayers() = 8;
+
+    /**
+     *  Returns a the publisher information for a publisher ID.
+     */
+    byte[] getPublisherInfo(in int publisherId) = 9;
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/vms/VmsAssociatedLayer.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/vms/VmsAssociatedLayer.aidl
index ca0ac83..c2cf271 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/vms/VmsAssociatedLayer.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.vms;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable VmsAssociatedLayer;
\ No newline at end of file
diff --git a/car-lib/src/android/car/vms/VmsAssociatedLayer.java b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
new file mode 100644
index 0000000..55ea541
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsAssociatedLayer.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.car.vms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.*;
+
+/**
+ * A VMS Layer with a list of publisher IDs it is associated with.
+ *
+ * @hide
+ */
+public final class VmsAssociatedLayer implements Parcelable {
+
+    // The VmsLayer.
+    private final VmsLayer mLayer;
+
+    // The IDs of the publishers that can publish this VmsLayer.
+    private final Set<Integer> mPublisherIds;
+
+    public VmsAssociatedLayer(VmsLayer layer, Set<Integer> publisherIds) {
+        mLayer = layer;
+        mPublisherIds = Collections.unmodifiableSet(publisherIds);
+    }
+
+    public VmsLayer getVmsLayer() {
+        return mLayer;
+    }
+
+    public Set<Integer> getPublisherIds() {
+        return mPublisherIds;
+    }
+
+    @Override
+    public String toString() {
+        return "VmsAssociatedLayer{ VmsLayer: " + mLayer + ", Publishers: " + mPublisherIds + "}";
+    }
+
+    // Parcelable related methods.
+    public static final Parcelable.Creator<VmsAssociatedLayer> CREATOR =
+            new Parcelable.Creator<VmsAssociatedLayer>() {
+                public VmsAssociatedLayer createFromParcel(Parcel in) {
+                    return new VmsAssociatedLayer(in);
+                }
+
+                public VmsAssociatedLayer[] newArray(int size) {
+                    return new VmsAssociatedLayer[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mLayer, flags);
+        out.writeArray(mPublisherIds.toArray());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VmsAssociatedLayer)) {
+            return false;
+        }
+        VmsAssociatedLayer p = (VmsAssociatedLayer) o;
+        return Objects.equals(p.mLayer, mLayer) && p.mPublisherIds.equals(mPublisherIds);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mLayer, mPublisherIds);
+    }
+
+    private VmsAssociatedLayer(Parcel in) {
+        mLayer = in.readParcelable(VmsLayer.class.getClassLoader());
+
+        mPublisherIds = Collections.unmodifiableSet(
+                new HashSet<>(Arrays.asList(
+                        (Integer[]) in.readArray(Integer.class.getClassLoader()))));
+    }
+}
\ No newline at end of file
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/vms/VmsLayer.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/vms/VmsLayer.aidl
index ca0ac83..ff0768a 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/vms/VmsLayer.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.vms;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable VmsLayer;
\ No newline at end of file
diff --git a/car-lib/src/android/car/vms/VmsLayer.java b/car-lib/src/android/car/vms/VmsLayer.java
new file mode 100644
index 0000000..b921230
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsLayer.java
@@ -0,0 +1,126 @@
+/*
+ * 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 android.car.vms;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A VMS Layer which can be subscribed to by VMS clients.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VmsLayer implements Parcelable {
+
+    // The layer Type.
+    private int mType;
+
+    // The layer Subtype.
+    private int mSubtype;
+
+    // The layer version.
+    private int mVersion;
+
+    public VmsLayer(int type, int subtype, int version) {
+        mType = type;
+        mSubtype = subtype;
+        mVersion = version;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public int getSubtype() {
+        return mSubtype;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Checks the two objects for equality by comparing their IDs and Versions.
+     *
+     * @param o the {@link VmsLayer} to which this one is to be checked for equality
+     * @return true if the underlying objects of the VmsLayer are both considered equal
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VmsLayer)) {
+            return false;
+        }
+        VmsLayer p = (VmsLayer) o;
+        return Objects.equals(p.mType, mType) &&
+            Objects.equals(p.mSubtype, mSubtype) &&
+            Objects.equals(p.mVersion, mVersion);
+    }
+
+    /**
+     * Compute a hash code similarly tp {@link android.util.Pair}
+     *
+     * @return a hashcode of the Pair
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mSubtype, mVersion);
+    }
+
+    @Override
+    public String toString() {
+        return "VmsLayer{ Type: " + mType + ", Sub type: " + mSubtype + ", Version: " + mVersion + "}";
+    }
+
+
+    // Parcelable related methods.
+    public static final Parcelable.Creator<VmsLayer> CREATOR = new
+            Parcelable.Creator<VmsLayer>() {
+                public VmsLayer createFromParcel(Parcel in) {
+                    return new VmsLayer(in);
+                }
+
+                public VmsLayer[] newArray(int size) {
+                    return new VmsLayer[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mType);
+        out.writeInt(mSubtype);
+        out.writeInt(mVersion);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private VmsLayer(Parcel in) {
+        readFromParcel(in);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mType = in.readInt();
+        mSubtype = in.readInt();
+        mVersion = in.readInt();
+    }
+}
\ No newline at end of file
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/vms/VmsLayerDependency.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/vms/VmsLayerDependency.aidl
index ca0ac83..3e64001 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/vms/VmsLayerDependency.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.vms;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable VmsLayerDependency;
\ No newline at end of file
diff --git a/car-lib/src/android/car/vms/VmsLayerDependency.java b/car-lib/src/android/car/vms/VmsLayerDependency.java
new file mode 100644
index 0000000..869fc84
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsLayerDependency.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.car.vms;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A dependency for a VMS layer on other VMS layers.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VmsLayerDependency implements Parcelable {
+    private final VmsLayer mLayer;
+    private final Set<VmsLayer> mDependency;
+
+    /**
+     * Construct a dependency for layer on other layers.
+     */
+    public VmsLayerDependency(VmsLayer layer, Set<VmsLayer> dependencies) {
+        mLayer = layer;
+        mDependency = Collections.unmodifiableSet(dependencies);
+    }
+
+    /**
+     * Constructs a layer without a dependency.
+     */
+    public VmsLayerDependency(VmsLayer layer) {
+        mLayer = layer;
+        mDependency = Collections.emptySet();
+    }
+
+    public VmsLayer getLayer() {
+        return mLayer;
+    }
+
+    /**
+     * Returns the dependencies.
+     */
+    public Set<VmsLayer> getDependencies() {
+        return mDependency;
+    }
+
+    public static final Parcelable.Creator<VmsLayerDependency> CREATOR = new
+        Parcelable.Creator<VmsLayerDependency>() {
+            public VmsLayerDependency createFromParcel(Parcel in) {
+                return new VmsLayerDependency(in);
+            }
+            public VmsLayerDependency[] newArray(int size) {
+                return new VmsLayerDependency[size];
+            }
+        };
+
+    public String toString() {
+        return "VmsLayerDependency{ Layer: " + mLayer + " Dependency: " + mDependency + "}";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mLayer, flags);
+        out.writeParcelableList(new ArrayList<VmsLayer>(mDependency), flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private VmsLayerDependency(Parcel in) {
+        mLayer = in.readParcelable(VmsLayer.class.getClassLoader());
+        List<VmsLayer> dependency = new ArrayList<>();
+        in.readParcelableList(dependency, VmsLayer.class.getClassLoader());
+        mDependency = Collections.unmodifiableSet(new HashSet<VmsLayer>(dependency));
+    }
+}
\ No newline at end of file
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/vms/VmsLayersOffering.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to car-lib/src/android/car/vms/VmsLayersOffering.aidl
index ca0ac83..4231f2d 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/vms/VmsLayersOffering.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.vms;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable VmsLayersOffering;
\ No newline at end of file
diff --git a/car-lib/src/android/car/vms/VmsLayersOffering.java b/car-lib/src/android/car/vms/VmsLayersOffering.java
new file mode 100644
index 0000000..fc83696
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsLayersOffering.java
@@ -0,0 +1,89 @@
+/*
+ * 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 android.car.vms;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The state of dependencies for a single publisher.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VmsLayersOffering implements Parcelable {
+
+    private final Set<VmsLayerDependency> mDependencies;
+
+    private final int mPublisherId;
+
+    public VmsLayersOffering(Set<VmsLayerDependency> dependencies, int publisherId) {
+        mDependencies = Collections.unmodifiableSet(dependencies);
+        mPublisherId = publisherId;
+    }
+
+    /**
+     * Returns the dependencies.
+     */
+    public Set<VmsLayerDependency> getDependencies() {
+        return mDependencies;
+    }
+
+    public int getPublisherId() {
+        return mPublisherId;
+    }
+
+    public static final Parcelable.Creator<VmsLayersOffering> CREATOR = new
+        Parcelable.Creator<VmsLayersOffering>() {
+            public VmsLayersOffering createFromParcel(Parcel in) {
+                return new VmsLayersOffering(in);
+            }
+            public VmsLayersOffering[] newArray(int size) {
+                return new VmsLayersOffering[size];
+            }
+        };
+
+    @Override
+    public String toString() {
+        return "VmsLayersOffering{" + mDependencies+ "}";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+
+        out.writeParcelableList(new ArrayList(mDependencies), flags);
+        out.writeInt(mPublisherId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private VmsLayersOffering(Parcel in) {
+        List<VmsLayerDependency> dependencies = new ArrayList<>();
+        in.readParcelableList(dependencies, VmsLayerDependency.class.getClassLoader());
+        mDependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
+        mPublisherId = in.readInt();
+    }
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/vms/VmsOperationRecorder.java b/car-lib/src/android/car/vms/VmsOperationRecorder.java
new file mode 100644
index 0000000..bfb6055
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsOperationRecorder.java
@@ -0,0 +1,227 @@
+package android.car.vms;
+
+import android.annotation.SystemApi;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Records VMS operations using the Android Log.
+ *
+ * This class records VMS operations. The recorded messages include the VMS operations and its
+ * arguments encoded as JSON text so that the string can be both read as a log message and easily
+ * parsed. VmsOperationRecorder is intended to be called after successful state change.
+ *
+ * Access the VmsOperationRecorder using the {@link #get()} method, which returns a singleton
+ * instance. Each VMS operation has a corresponding VmsOperationRecorder method. For instance:
+ * <pre>{@code
+ *   VmsOperationRecorder.get().subscribe(layer);
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class VmsOperationRecorder {
+    private static final String TAG = "VmsOperationRecorder";
+    private static final VmsOperationRecorder INSTANCE = new VmsOperationRecorder(new Writer());
+    private final Writer mWriter;
+
+    @VisibleForTesting
+    public VmsOperationRecorder(Writer writer) {
+        mWriter = writer;
+    }
+
+    /** Return the singleton instance. */
+    public static VmsOperationRecorder get() {
+        return INSTANCE;
+    }
+
+    // VMS Client operations.
+
+    public void subscribe(VmsLayer layer) {
+        recordOp("subscribe", layer);
+    }
+
+    public void unsubscribe(VmsLayer layer) {
+        recordOp("unsubscribe", layer);
+    }
+
+    public void subscribe(VmsLayer layer, int publisherId) {
+        recordOp("subscribe", "publisherId", publisherId, layer);
+    }
+
+    public void unsubscribe(VmsLayer layer, int publisherId) {
+        recordOp("unsubscribe", "publisherId", publisherId, layer);
+    }
+
+    public void startMonitoring() {
+        recordOp("startMonitoring");
+    }
+
+    public void stopMonitoring() {
+        recordOp("stopMonitoring");
+    }
+
+    public void setLayersOffering(VmsLayersOffering layersOffering) {
+        recordOp("setLayersOffering", layersOffering);
+    }
+
+    public void getPublisherId(int publisherId) {
+        recordOp("getPublisherId", "publisherId", publisherId);
+    }
+
+    // VMS Service operations.
+
+    public void addSubscription(int sequenceNumber, VmsLayer layer) {
+        recordOp("addSubscription", "sequenceNumber", sequenceNumber, layer);
+    }
+
+    public void removeSubscription(int sequenceNumber, VmsLayer layer) {
+        recordOp("removeSubscription", "sequenceNumber", sequenceNumber, layer);
+    }
+
+    public void addPromiscuousSubscription(int sequenceNumber) {
+        recordOp("addPromiscuousSubscription", "sequenceNumber", sequenceNumber);
+    }
+
+    public void removePromiscuousSubscription(int sequenceNumber) {
+        recordOp("removePromiscuousSubscription", "sequenceNumber", sequenceNumber);
+    }
+
+    public void addHalSubscription(int sequenceNumber, VmsLayer layer) {
+        recordOp("addHalSubscription", "sequenceNumber", sequenceNumber, layer);
+    }
+
+    public void removeHalSubscription(int sequenceNumber, VmsLayer layer) {
+        recordOp("removeHalSubscription", "sequenceNumber", sequenceNumber, layer);
+    }
+
+    public void setPublisherLayersOffering(VmsLayersOffering layersOffering) {
+        recordOp("setPublisherLayersOffering", layersOffering);
+    }
+
+    public void setHalPublisherLayersOffering(VmsLayersOffering layersOffering) {
+        recordOp("setHalPublisherLayersOffering", layersOffering);
+    }
+
+    private void recordOp(String operation) {
+        if (isEnabled()) {
+            try {
+                write(new JSONObject().put(operation, new JSONObject()));
+            } catch (JSONException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    private void recordOp(String operation, VmsLayer layer) {
+        if (isEnabled()) {
+            try {
+                recordOp(operation, new JSONObject().put("layer", toJson(layer)));
+            } catch (JSONException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    private void recordOp(String operation, VmsLayersOffering layersOffering) {
+        if (isEnabled()) {
+            try {
+                JSONObject args = new JSONObject();
+                JSONArray offering = toJson(layersOffering);
+                if (offering.length() > 0) {
+                    args.put("layerDependency", offering);
+                }
+                recordOp(operation, args);
+            } catch (JSONException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    private void recordOp(String operation, String intArgName, int arg) {
+        if (isEnabled()) {
+            try {
+                recordOp(operation, new JSONObject().put(intArgName, arg));
+            } catch (JSONException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    private void recordOp(String operation, String intArgName, int arg, VmsLayer layer) {
+        if (isEnabled()) {
+            try {
+                recordOp(operation,
+                        new JSONObject().put(intArgName, arg).put("layer", toJson(layer)));
+            } catch (JSONException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    private void recordOp(String operation, JSONObject args) {
+        if (isEnabled()) {
+            try {
+                write(new JSONObject().put(operation, args));
+            } catch (JSONException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    private static JSONObject toJson(VmsLayer layer) throws JSONException {
+        return new JSONObject()
+                .put("type", layer.getType())
+                .put("subtype", layer.getSubtype())
+                .put("version", layer.getVersion());
+    }
+
+    private static JSONObject toJson(VmsLayerDependency layerDependency) throws JSONException {
+        JSONObject dep = new JSONObject();
+        dep.put("layer", toJson(layerDependency.getLayer()));
+        if (!layerDependency.getDependencies().isEmpty()) {
+            JSONArray dependencies = new JSONArray();
+            for (VmsLayer dependency : layerDependency.getDependencies()) {
+                dependencies.put(toJson(dependency));
+            }
+            dep.put("dependency", dependencies);
+        }
+        return dep;
+    }
+
+    private static JSONArray toJson(VmsLayersOffering layersOffering) throws JSONException {
+        JSONArray offerings = new JSONArray();
+        for (VmsLayerDependency layerDependency : layersOffering.getDependencies()) {
+            offerings.put(toJson(layerDependency));
+        }
+        return offerings;
+    }
+
+    private boolean isEnabled() {
+        return mWriter.isEnabled();
+    }
+
+    private void write(JSONObject object) {
+        mWriter.write(object.toString());
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public static class Writer {
+        private static final String TAG = "VMS.RECORD.EVENT";
+        private static final int LEVEL = Log.DEBUG;
+
+        public boolean isEnabled() {
+            return Log.isLoggable(TAG, LEVEL);
+        }
+
+        public void write(String msg) {
+            Log.println(LEVEL, TAG, msg);
+        }
+    }
+}
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
new file mode 100644
index 0000000..024fc14
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -0,0 +1,282 @@
+/*
+ * 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 android.car.vms;
+
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Services that need VMS publisher services need to inherit from this class and also need to be
+ * declared in the array vmsPublisherClients located in
+ * packages/services/Car/service/res/values/config.xml (most likely, this file will be in an overlay
+ * of the target product.
+ *
+ * The {@link com.android.car.VmsPublisherService} will start this service. The callback
+ * {@link #onVmsPublisherServiceReady()} notifies when VMS publisher services can be used, and the
+ * publisher can request a publisher ID in order to start publishing.
+ *
+ * SystemApi candidate.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class VmsPublisherClientService extends Service {
+    private static final boolean DBG = true;
+    private static final String TAG = "VmsPublisherClient";
+
+    private final Object mLock = new Object();
+
+    private Handler mHandler = new VmsEventHandler(this);
+    private final VmsPublisherClientBinder mVmsPublisherClient = new VmsPublisherClientBinder(this);
+    private volatile IVmsPublisherService mVmsPublisherService = null;
+    @GuardedBy("mLock")
+    private IBinder mToken = null;
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        if (DBG) {
+            Log.d(TAG, "onBind, intent: " + intent);
+        }
+        return mVmsPublisherClient.asBinder();
+    }
+
+    @Override
+    public final boolean onUnbind(Intent intent) {
+        if (DBG) {
+            Log.d(TAG, "onUnbind, intent: " + intent);
+        }
+        stopSelf();
+        return super.onUnbind(intent);
+    }
+
+    private void setToken(IBinder token) {
+        synchronized (mLock) {
+            mToken = token;
+        }
+    }
+
+    /**
+     * Notifies that the publisher services are ready.
+     */
+    protected abstract void onVmsPublisherServiceReady();
+
+    /**
+     * Publishers need to implement this method to receive notifications of subscription changes.
+     *
+     * @param subscriptionState the state of the subscriptions.
+     */
+    public abstract void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState);
+
+    /**
+     * Uses the VmsPublisherService binder to publish messages.
+     *
+     * @param layer   the layer to publish to.
+     * @param payload the message to be sent.
+     * @param publisherId the ID that got assigned to the publisher that published the message by
+     *                    VMS core.
+     * @return if the call to the method VmsPublisherService.publish was successful.
+     */
+    public final void publish(VmsLayer layer, int publisherId, byte[] payload) {
+        if (DBG) {
+            Log.d(TAG, "Publishing for layer : " + layer);
+        }
+
+        IBinder token = getTokenForPublisherServiceThreadSafe();
+
+        try {
+            mVmsPublisherService.publish(token, layer, publisherId, payload);
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to publish message: " + payload, e);
+        }
+    }
+
+    /**
+     * 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 void setLayersOffering(VmsLayersOffering offering) {
+        if (DBG) {
+            Log.d(TAG, "Setting layers offering : " + offering);
+        }
+
+        IBinder token = getTokenForPublisherServiceThreadSafe();
+
+        try {
+            mVmsPublisherService.setLayersOffering(token, offering);
+            VmsOperationRecorder.get().setLayersOffering(offering);
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to set layers offering: " + offering, e);
+        }
+    }
+
+    private IBinder getTokenForPublisherServiceThreadSafe() {
+        if (mVmsPublisherService == null) {
+            throw new IllegalStateException("VmsPublisherService not set.");
+        }
+
+        IBinder token;
+        synchronized (mLock) {
+            token = mToken;
+        }
+        if (token == null) {
+            throw new IllegalStateException("VmsPublisherService does not have a valid token.");
+        }
+        return token;
+    }
+
+    public final int getPublisherId(byte[] publisherInfo) {
+        if (mVmsPublisherService == null) {
+            throw new IllegalStateException("VmsPublisherService not set.");
+        }
+        Integer publisherId = null;
+        try {
+            Log.i(TAG, "Getting publisher static ID");
+            publisherId = mVmsPublisherService.getPublisherId(publisherInfo);
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to invoke binder method.", e);
+        }
+        if (publisherId == null) {
+            throw new IllegalStateException("VmsPublisherService cannot get a publisher static ID.");
+        } else {
+            VmsOperationRecorder.get().getPublisherId(publisherId);
+        }
+        return publisherId;
+    }
+
+    /**
+     * Uses the VmsPublisherService binder to get the state of the subscriptions.
+     *
+     * @return list of layer/version or null in case of error.
+     */
+    public final @Nullable VmsSubscriptionState getSubscriptions() {
+        if (mVmsPublisherService == null) {
+            throw new IllegalStateException("VmsPublisherService not set.");
+        }
+        try {
+            return mVmsPublisherService.getSubscriptions();
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to invoke binder method.", e);
+        }
+        return null;
+    }
+
+    private void setVmsPublisherService(IVmsPublisherService service) {
+        mVmsPublisherService = service;
+        onVmsPublisherServiceReady();
+    }
+
+    /**
+     * Implements the interface that the VMS service uses to communicate with this client.
+     */
+    private static class VmsPublisherClientBinder extends IVmsPublisherClient.Stub {
+        private final WeakReference<VmsPublisherClientService> mVmsPublisherClientService;
+        @GuardedBy("mSequenceLock")
+        private long mSequence = -1;
+        private final Object mSequenceLock = new Object();
+
+        public VmsPublisherClientBinder(VmsPublisherClientService vmsPublisherClientService) {
+            mVmsPublisherClientService = new WeakReference<>(vmsPublisherClientService);
+        }
+
+        @Override
+        public void setVmsPublisherService(IBinder token, IVmsPublisherService service)
+                throws RemoteException {
+            VmsPublisherClientService vmsPublisherClientService = mVmsPublisherClientService.get();
+            if (vmsPublisherClientService == null) return;
+            if (DBG) {
+                Log.d(TAG, "setting VmsPublisherService.");
+            }
+            Handler handler = vmsPublisherClientService.mHandler;
+            handler.sendMessage(
+                    handler.obtainMessage(VmsEventHandler.SET_SERVICE_CALLBACK, service));
+            vmsPublisherClientService.setToken(token);
+        }
+
+        @Override
+        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState)
+                throws RemoteException {
+            VmsPublisherClientService vmsPublisherClientService = mVmsPublisherClientService.get();
+            if (vmsPublisherClientService == null) return;
+            if (DBG) {
+                Log.d(TAG, "subscription event: " + subscriptionState);
+            }
+            synchronized (mSequenceLock) {
+                if (subscriptionState.getSequenceNumber() <= mSequence) {
+                    Log.w(TAG, "Sequence out of order. Current sequence = " + mSequence
+                            + "; expected new sequence = " + subscriptionState.getSequenceNumber());
+                    // Do not propagate old notifications.
+                    return;
+                } else {
+                    mSequence = subscriptionState.getSequenceNumber();
+                }
+            }
+            Handler handler = vmsPublisherClientService.mHandler;
+            handler.sendMessage(
+                    handler.obtainMessage(VmsEventHandler.ON_SUBSCRIPTION_CHANGE_EVENT,
+                            subscriptionState));
+        }
+    }
+
+    /**
+     * Receives events from the binder thread and dispatches them.
+     */
+    private final static class VmsEventHandler extends Handler {
+        /** Constants handled in the handler */
+        private static final int ON_SUBSCRIPTION_CHANGE_EVENT = 0;
+        private static final int SET_SERVICE_CALLBACK = 1;
+
+        private final WeakReference<VmsPublisherClientService> mVmsPublisherClientService;
+
+        VmsEventHandler(VmsPublisherClientService service) {
+            super(Looper.getMainLooper());
+            mVmsPublisherClientService = new WeakReference<>(service);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            VmsPublisherClientService service = mVmsPublisherClientService.get();
+            if (service == null) return;
+            switch (msg.what) {
+                case ON_SUBSCRIPTION_CHANGE_EVENT:
+                    VmsSubscriptionState subscriptionState = (VmsSubscriptionState) msg.obj;
+                    service.onVmsSubscriptionChange(subscriptionState);
+                    break;
+                case SET_SERVICE_CALLBACK:
+                    service.setVmsPublisherService((IVmsPublisherService) msg.obj);
+                    break;
+                default:
+                    Log.e(TAG, "Event type not handled:  " + msg.what);
+                    break;
+            }
+        }
+    }
+}
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
new file mode 100644
index 0000000..97d4d86
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -0,0 +1,377 @@
+/*
+ * 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 android.car.vms;
+
+import android.annotation.SystemApi;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.CarNotConnectedException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * API for interfacing with the VmsSubscriberService. It supports a single client callback that can
+ * (un)subscribe to different layers. Getting notifactions and managing subscriptions is enabled
+ * after setting the client callback with #registerClientCallback.
+ * SystemApi candidate
+ *
+ * @hide
+ */
+@SystemApi
+public final class VmsSubscriberManager implements CarManagerBase {
+    private static final boolean DBG = true;
+    private static final String TAG = "VmsSubscriberManager";
+
+    private final Handler mHandler;
+    private final IVmsSubscriberService mVmsSubscriberService;
+    private final IVmsSubscriberClient mSubscriberManagerClient;
+    private final Object mClientCallbackLock = new Object();
+    @GuardedBy("mClientCallbackLock")
+    private VmsSubscriberClientCallback mClientCallback;
+
+    /**
+     * Interface exposed to VMS subscribers: it is a wrapper of IVmsSubscriberClient.
+     */
+    public interface VmsSubscriberClientCallback {
+        /**
+         * Called when the property is updated
+         */
+        void onVmsMessageReceived(VmsLayer layer, byte[] payload);
+
+        /**
+         * Called when layers availability change
+         */
+        void onLayersAvailabilityChanged(List<VmsLayer> availableLayers);
+    }
+
+    /**
+     * Allows to asynchronously dispatch onVmsMessageReceived events.
+     */
+    private final static class VmsEventHandler extends Handler {
+        /**
+         * Constants handled in the handler
+         */
+        private static final int ON_RECEIVE_MESSAGE_EVENT = 0;
+        private static final int ON_AVAILABILITY_CHANGE_EVENT = 1;
+
+        private final WeakReference<VmsSubscriberManager> mMgr;
+
+        VmsEventHandler(VmsSubscriberManager mgr, Looper looper) {
+            super(looper);
+            mMgr = new WeakReference<>(mgr);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            VmsSubscriberManager mgr = mMgr.get();
+            switch (msg.what) {
+                case ON_RECEIVE_MESSAGE_EVENT:
+                    if (mgr != null) {
+                        // Parse the message
+                        VmsDataMessage vmsDataMessage = (VmsDataMessage) msg.obj;
+
+                        // Dispatch the parsed message
+                        mgr.dispatchOnReceiveMessage(vmsDataMessage.getLayer(),
+                                vmsDataMessage.getPayload());
+                    }
+                    break;
+                case ON_AVAILABILITY_CHANGE_EVENT:
+                    if (mgr != null) {
+                        // Parse the message
+                        List<VmsLayer> vmsAvailabilityChangeMessage = (List<VmsLayer>) msg.obj;
+
+                        // Dispatch the parsed message
+                        mgr.dispatchOnAvailabilityChangeMessage(vmsAvailabilityChangeMessage);
+                    }
+                    break;
+
+                default:
+                    Log.e(VmsSubscriberManager.TAG, "Event type not handled:  " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    public VmsSubscriberManager(IBinder service, Handler handler) {
+        mVmsSubscriberService = IVmsSubscriberService.Stub.asInterface(service);
+        mHandler = new VmsEventHandler(this, handler.getLooper());
+        mSubscriberManagerClient = new IVmsSubscriberClient.Stub() {
+            @Override
+            public void onVmsMessageReceived(VmsLayer layer, byte[] payload)
+                    throws RemoteException {
+                // Create the data message
+                VmsDataMessage vmsDataMessage = new VmsDataMessage(layer, payload);
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(
+                                VmsEventHandler.ON_RECEIVE_MESSAGE_EVENT,
+                                vmsDataMessage));
+            }
+
+            @Override
+            public void onLayersAvailabilityChanged(List<VmsAssociatedLayer> availableLayers) {
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(
+                                VmsEventHandler.ON_AVAILABILITY_CHANGE_EVENT,
+                                availableLayers));
+            }
+        };
+    }
+
+    /**
+     * Registers the client callback in order to enable communication with the client.
+     * By registering, the client will start getting notifications, and will be able to subscribe
+     * to layers.
+     * <p>
+     *
+     * @param clientCallback subscriber callback that will handle onVmsMessageReceived events.
+     * @throws IllegalStateException if the client callback was already set.
+     */
+    public void registerClientCallback(VmsSubscriberClientCallback clientCallback)
+            throws CarNotConnectedException {
+        synchronized (mClientCallbackLock) {
+            if (mClientCallback != null) {
+                throw new IllegalStateException("Client callback is already configured.");
+            }
+            mClientCallback = clientCallback;
+        }
+        try {
+            mVmsSubscriberService.addVmsSubscriberToNotifications(mSubscriberManagerClient);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        }
+    }
+
+    /**
+     * Unregisters the client callback which disables communication with the client.
+     * @throws CarNotConnectedException, IllegalStateException
+     */
+    public void unregisterClientCallback()
+            throws CarNotConnectedException {
+
+        try {
+            mVmsSubscriberService.removeVmsSubscriberToNotifications(mSubscriberManagerClient);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Could not unsubscribe from notifications");
+            throw e;
+        }
+
+        synchronized (mClientCallbackLock) {
+            mClientCallback = null;
+        }
+    }
+
+    /**
+     * Returns a serialized publisher information for a publisher ID.
+     */
+    public byte[] getPublisherInfo(int publisherId)
+            throws CarNotConnectedException, IllegalStateException {
+        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);
+        }
+    }
+
+    /**
+     * Subscribes to listen to the layer specified.
+     *
+     * @param layer the layer to subscribe to.
+     * @throws IllegalStateException if the client callback was not set via
+     *                               {@link #registerClientCallback}.
+     */
+    public void subscribe(VmsLayer layer) throws CarNotConnectedException {
+        verifySubscriptionIsAllowed();
+        try {
+            mVmsSubscriberService.addVmsSubscriber(mSubscriberManagerClient, layer);
+            VmsOperationRecorder.get().subscribe(layer);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        } catch (IllegalStateException ex) {
+            Car.checkCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    /**
+     * Subscribes to listen to the layer specified from the publisher specified.
+     *
+     * @param layer       the layer to subscribe to.
+     * @param publisherId the publisher of the layer.
+     * @throws IllegalStateException if the client callback was not set via
+     *                               {@link #registerClientCallback}.
+     */
+    public void subscribe(VmsLayer layer, int publisherId) throws CarNotConnectedException {
+        verifySubscriptionIsAllowed();
+        try {
+            mVmsSubscriberService.addVmsSubscriberToPublisher(
+                    mSubscriberManagerClient, layer, publisherId);
+            VmsOperationRecorder.get().subscribe(layer, publisherId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        } catch (IllegalStateException ex) {
+            Car.checkCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    public void startMonitoring() throws CarNotConnectedException {
+        verifySubscriptionIsAllowed();
+        try {
+            mVmsSubscriberService.addVmsSubscriberPassive(mSubscriberManagerClient);
+            VmsOperationRecorder.get().startMonitoring();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        } catch (IllegalStateException ex) {
+            Car.checkCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    /**
+     * Unsubscribes from the layer/version specified.
+     *
+     * @param layer the layer to unsubscribe from.
+     * @throws IllegalStateException if the client callback was not set via
+     *                               {@link #registerClientCallback}.
+     */
+    public void unsubscribe(VmsLayer layer) {
+        verifySubscriptionIsAllowed();
+        try {
+            mVmsSubscriberService.removeVmsSubscriber(mSubscriberManagerClient, layer);
+            VmsOperationRecorder.get().unsubscribe(layer);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unregister subscriber", e);
+            // ignore
+        } catch (IllegalStateException ex) {
+            Car.hideCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    /**
+     * Unsubscribes from the layer/version specified.
+     *
+     * @param layer       the layer to unsubscribe from.
+     * @param publisherId the pubisher of the layer.
+     * @throws IllegalStateException if the client callback was not set via
+     *                               {@link #registerClientCallback}.
+     */
+    public void unsubscribe(VmsLayer layer, int publisherId) {
+        try {
+            mVmsSubscriberService.removeVmsSubscriberToPublisher(
+                    mSubscriberManagerClient, layer, publisherId);
+            VmsOperationRecorder.get().unsubscribe(layer, publisherId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unregister subscriber", e);
+            // ignore
+        } catch (IllegalStateException ex) {
+            Car.hideCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    public void stopMonitoring() {
+        try {
+            mVmsSubscriberService.removeVmsSubscriberPassive(mSubscriberManagerClient);
+            VmsOperationRecorder.get().stopMonitoring();
+        } 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) {
+        VmsSubscriberClientCallback clientCallback = getClientCallbackThreadSafe();
+        if (clientCallback == null) {
+            Log.e(TAG, "Cannot dispatch received message.");
+            return;
+        }
+        clientCallback.onVmsMessageReceived(layer, payload);
+    }
+
+    private void dispatchOnAvailabilityChangeMessage(List<VmsLayer> availableLayers) {
+        VmsSubscriberClientCallback clientCallback = getClientCallbackThreadSafe();
+        if (clientCallback == null) {
+            Log.e(TAG, "Cannot dispatch availability change message.");
+            return;
+        }
+        clientCallback.onLayersAvailabilityChanged(availableLayers);
+    }
+
+    private VmsSubscriberClientCallback getClientCallbackThreadSafe() {
+        VmsSubscriberClientCallback clientCallback;
+        synchronized (mClientCallbackLock) {
+            clientCallback = mClientCallback;
+        }
+        if (clientCallback == null) {
+            Log.e(TAG, "client callback not set.");
+        }
+        return clientCallback;
+    }
+
+    /*
+     * Verifies that the subscriber is in a state where it is allowed to subscribe.
+     */
+    private void verifySubscriptionIsAllowed() {
+        VmsSubscriberClientCallback clientCallback = getClientCallbackThreadSafe();
+        if (clientCallback == null) {
+            throw new IllegalStateException("Cannot subscribe.");
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void onCarDisconnected() {
+    }
+
+    private static final class VmsDataMessage {
+        private final VmsLayer mLayer;
+        private final byte[] mPayload;
+
+        public VmsDataMessage(VmsLayer layer, byte[] payload) {
+            mLayer = layer;
+            mPayload = payload;
+        }
+
+        public VmsLayer getLayer() {
+            return mLayer;
+        }
+
+        public byte[] getPayload() {
+            return mPayload;
+        }
+    }
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/car-lib/src/android/car/vms/VmsSubscriptionState.aidl
similarity index 67%
rename from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
rename to car-lib/src/android/car/vms/VmsSubscriptionState.aidl
index ca0ac83..b5ce8ff 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/car-lib/src/android/car/vms/VmsSubscriptionState.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package android.car.vms;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable VmsSubscriptionState;
\ No newline at end of file
diff --git a/car-lib/src/android/car/vms/VmsSubscriptionState.java b/car-lib/src/android/car/vms/VmsSubscriptionState.java
new file mode 100644
index 0000000..1aba30c
--- /dev/null
+++ b/car-lib/src/android/car/vms/VmsSubscriptionState.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.car.vms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * The list of layers with subscribers.
+ *
+ * @hide
+ */
+public final class VmsSubscriptionState implements Parcelable {
+    private final int mSequenceNumber;
+    private final Set<VmsLayer> mLayers;
+    private final Set<VmsAssociatedLayer> mSubscribedLayersFromPublishers;
+
+    /**
+     * Construcs a summary of the state of the current subscriptions for publishers to consume
+     * and adjust which layers that the are publishing.
+     */
+    public VmsSubscriptionState(int sequenceNumber,
+                                Set<VmsLayer> subscribedLayers,
+                                Set<VmsAssociatedLayer> layersFromPublishers) {
+        mSequenceNumber = sequenceNumber;
+        mLayers = Collections.unmodifiableSet(subscribedLayers);
+        mSubscribedLayersFromPublishers = Collections.unmodifiableSet(layersFromPublishers);
+    }
+
+    /**
+     * Returns the sequence number assigned by the VMS service. Sequence numbers are
+     * monotonically increasing and help clients ignore potential out-of-order states.
+     */
+    public int getSequenceNumber() {
+        return mSequenceNumber;
+    }
+
+    public Set<VmsLayer> getLayers() {
+        return mLayers;
+    }
+
+    public Set<VmsAssociatedLayer> getAssociatedLayers() {
+        return mSubscribedLayersFromPublishers;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("sequence number=").append(mSequenceNumber);
+        sb.append("; layers={");
+        for (VmsLayer layer : mLayers) {
+            sb.append(layer).append(",");
+        }
+        sb.append("}");
+        sb.append("; associatedLayers={");
+        for (VmsAssociatedLayer layer : mSubscribedLayersFromPublishers) {
+            sb.append(layer).append(",");
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    public static final Parcelable.Creator<VmsSubscriptionState> CREATOR = new
+            Parcelable.Creator<VmsSubscriptionState>() {
+                public VmsSubscriptionState createFromParcel(Parcel in) {
+                    return new VmsSubscriptionState(in);
+                }
+
+                public VmsSubscriptionState[] newArray(int size) {
+                    return new VmsSubscriptionState[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSequenceNumber);
+        out.writeParcelableList(new ArrayList(mLayers), flags);
+        out.writeParcelableList(new ArrayList(mSubscribedLayersFromPublishers), flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private VmsSubscriptionState(Parcel in) {
+        mSequenceNumber = in.readInt();
+
+        List<VmsLayer> layers = new ArrayList<>();
+        in.readParcelableList(layers, VmsLayer.class.getClassLoader());
+        mLayers = Collections.unmodifiableSet(new HashSet(layers));
+
+        List<VmsAssociatedLayer> associatedLayers = new ArrayList<>();
+        in.readParcelableList(associatedLayers, VmsAssociatedLayer.class.getClassLoader());
+        mSubscribedLayersFromPublishers = Collections.unmodifiableSet(new HashSet(associatedLayers));
+    }
+}
\ No newline at end of file
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 55da870..462f320 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
@@ -25,5 +25,4 @@
     public static final boolean DEFAULT = false;
     /** product configuration in CarInfoManager */
     public static final boolean ENABLE_PRODUCT_CONFIGURATION_INFO = DEFAULT;
-    public static final boolean ENABLE_VEHICLE_MAP_SERVICE = DEFAULT;
 }
diff --git a/car-maps-placeholder/AndroidManifest.xml b/car-maps-placeholder/AndroidManifest.xml
index e166420..38caf38 100644
--- a/car-maps-placeholder/AndroidManifest.xml
+++ b/car-maps-placeholder/AndroidManifest.xml
@@ -31,8 +31,6 @@
             android:resizeableActivity="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.APP_MAPS" />
             </intent-filter>
         </activity>
     </application>
diff --git a/car-support-lib/AndroidManifest.xml b/car-support-lib/AndroidManifest.xml
index 36b1649..f92f21b 100644
--- a/car-support-lib/AndroidManifest.xml
+++ b/car-support-lib/AndroidManifest.xml
@@ -17,6 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         package="android.support.car.ui" >
-    <uses-sdk android:minSdkVersion="23"
+    <uses-sdk android:minSdkVersion="21"
         android:targetSdkVersion="23" />
 </manifest>
diff --git a/car-support-lib/proguard-extra-keeps.flags b/car-support-lib/proguard-extra-keeps.flags
index 5e67c6e..72ea927 100644
--- a/car-support-lib/proguard-extra-keeps.flags
+++ b/car-support-lib/proguard-extra-keeps.flags
@@ -17,3 +17,7 @@
     public static ... createCluster(...);
     public static ... createCustomImageCluster(...);
 }
+
+-keep class android.support.car.CarManagerBase {
+  *;
+}
diff --git a/car-support-lib/src/android/support/car/Car.java b/car-support-lib/src/android/support/car/Car.java
index a374d87..e0e3a0d 100644
--- a/car-support-lib/src/android/support/car/Car.java
+++ b/car-support-lib/src/android/support/car/Car.java
@@ -237,6 +237,7 @@
                         if (mConnectionState == STATE_DISCONNECTED) {
                             return;
                         }
+                        tearDownCarManagers();
                         mConnectionState = STATE_DISCONNECTED;
                     }
                     mCarConnectionCallback.onDisconnected(Car.this);
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 9984726..5470324 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -31,6 +31,7 @@
 
 # This is for testing
 PRODUCT_PACKAGES += \
+    DefaultStorageMonitoringCompanionApp \
     EmbeddedKitchenSinkApp \
     VmsPublisherClientSample \
     VmsSubscriberClientSample \
@@ -74,7 +75,6 @@
 
 # Automotive specific packages
 PRODUCT_PACKAGES += \
-    vehicle_monitor_service \
     CarService \
     CarTrustAgentService \
     CarDialerApp \
@@ -89,7 +89,7 @@
     CarLatinIME \
     CarUsbHandler \
     android.car \
-    libvehiclemonitor-native \
+    com.android.car.procfsinspector \
 
 # Boot animation
 PRODUCT_COPY_FILES += \
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index 815b9ba..9138090 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -84,11 +84,10 @@
 
 # EVS resources
 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
 #PRODUCT_PACKAGES += evs_app_default_resources
 #PRODUCT_PACKAGES += android.hardware.automotive.evs@1.0-service
-#PRODUCT_PACKAGES += android.hardware.automotive.evs@1.0-sample
 
 # Device running Android is a car
 PRODUCT_COPY_FILES += \
diff --git a/car_product/init/init.car.rc b/car_product/init/init.car.rc
index 8cec36e..1ca29ce 100644
--- a/car_product/init/init.car.rc
+++ b/car_product/init/init.car.rc
@@ -1,8 +1 @@
-service vms /system/bin/vehicle_monitor_service
-   class core
-   user root
-   group root
-   critical
-
-on boot
-    start vms
+# Insert car-specific startup services here
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-sw900dp/dimens.xml b/car_product/overlay/frameworks/base/core/res/res/values-sw900dp/dimens.xml
new file mode 100644
index 0000000..d1af884
--- /dev/null
+++ b/car_product/overlay/frameworks/base/core/res/res/values-sw900dp/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+<resources>
+
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_height">112dp</dimen>
+
+    <!-- Height of the bottom navigation bar in landscape; often
+         the same as @dimen/navigation_bar_height -->
+    <dimen name="navigation_bar_height_landscape">112dp</dimen>
+
+</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 ca8a8d8..5778970 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
@@ -69,4 +69,12 @@
 
     <!-- Home screen(Launcher) app presence -->
     <bool name="config_noHomeScreen">true</bool>
+
+    <!-- Flag indicating that this device does not rotate and will always remain in its default
+         orientation. Activities that desire to run in a non-compatible orientation will find that
+         they are not able to. -->
+    <bool name="config_forceDefaultOrientation">true</bool>
+    
+    <!-- Corner radius of system dialogs -->
+    <dimen name="config_dialogCornerRadius">16dp</dimen>
 </resources>
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/arrays_car.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/arrays_car.xml
index 94a6d45..030b422 100644
--- a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/arrays_car.xml
+++ b/car_product/overlay/frameworks/base/packages/SystemUI/res/values/arrays_car.xml
@@ -57,6 +57,6 @@
         <item>com.android.car.dialer</item>
         <item>com.android.car.overview</item>
         <item></item>
-        <item>com.android.car.hvac;com.android.settings;com.android.car.settings;com.android.vending;com.google.android.car.bugreport;com.google.android.car.kitchensink;com.android.car.systemupdater;org.chromium.webview_shell;com.android.contacts;org.codeaurora.bluetooth.bttestapp;com.google.android.projection.sink</item>
+        <item>com.android.car.hvac;com.android.settings;com.android.car.settings;com.android.vending;com.google.android.car.bugreport;com.google.android.car.kitchensink;com.android.car.systemupdater;org.chromium.webview_shell;com.android.contacts;org.codeaurora.bluetooth.bttestapp;com.google.android.projection.sink;com.google.android.googlequicksearchbox</item>
     </array>
 </resources>
diff --git a/car_product/sepolicy/vehicle_monitor_service.te b/car_product/sepolicy/vehicle_monitor_service.te
deleted file mode 100644
index 16c6f81..0000000
--- a/car_product/sepolicy/vehicle_monitor_service.te
+++ /dev/null
@@ -1,4 +0,0 @@
-type vehicle_monitor_service_exec, exec_type, file_type;
-type vehicle_monitor_service, domain;
-
-init_daemon_domain(vehicle_monitor_service)
diff --git a/evs/app/RenderTopView.cpp b/evs/app/RenderTopView.cpp
index 1579a0a..bff2b3c 100644
--- a/evs/app/RenderTopView.cpp
+++ b/evs/app/RenderTopView.cpp
@@ -133,7 +133,7 @@
     // Load the checkerboard text image
     mTexAssets.checkerBoard.reset(createTextureFromPng(
                                   "/system/etc/automotive/evs/LabeledChecker.png"));
-    if (!mTexAssets.checkerBoard->glId()) {
+    if (!mTexAssets.checkerBoard) {
         ALOGE("Failed to load checkerboard texture");
         return false;
     }
@@ -141,7 +141,7 @@
     // Load the car image
     mTexAssets.carTopView.reset(createTextureFromPng(
                                 "/system/etc/automotive/evs/CarFromTop.png"));
-    if (!mTexAssets.carTopView->glId()) {
+    if (!mTexAssets.carTopView) {
         ALOGE("Failed to load carTopView texture");
         return false;
     }
diff --git a/evs/app/WindowSurface.cpp b/evs/app/WindowSurface.cpp
index a3f56bc..04e7ac4 100644
--- a/evs/app/WindowSurface.cpp
+++ b/evs/app/WindowSurface.cpp
@@ -62,19 +62,10 @@
         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();
+    SurfaceComposerClient::Transaction{}
+            .setLayer(sc, 0x7FFFFFFF)     // always on top
+            .show(sc)
+            .apply();
 
     mSurfaceControl = sc;
 }
diff --git a/evs/app/evs_app.cpp b/evs/app/evs_app.cpp
index 618e8cc..521bc47 100644
--- a/evs/app/evs_app.cpp
+++ b/evs/app/evs_app.cpp
@@ -97,7 +97,10 @@
 
     // Load our configuration information
     ConfigManager config;
-    config.initialize("/system/etc/automotive/evs/config.json");
+    if (!config.initialize("/system/etc/automotive/evs/config.json")) {
+        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
+        return 1;
+    }
 
     // Set thread pool size to one to avoid concurrent events from the HAL.
     // This pool will handle the EvsCameraStream callbacks.
diff --git a/evs/sampleDriver/GlWrapper.cpp b/evs/sampleDriver/GlWrapper.cpp
index eb45bc9..3055ba0 100644
--- a/evs/sampleDriver/GlWrapper.cpp
+++ b/evs/sampleDriver/GlWrapper.cpp
@@ -339,19 +339,19 @@
 
 void GlWrapper::showWindow() {
     if (mFlingerSurfaceControl != nullptr) {
-        SurfaceComposerClient::openGlobalTransaction();
-        mFlingerSurfaceControl->setLayer(0x7FFFFFFF);     // always on top
-        mFlingerSurfaceControl->show();
-        SurfaceComposerClient::closeGlobalTransaction();
+        SurfaceComposerClient::Transaction{}
+                .setLayer(mFlingerSurfaceControl, 0x7FFFFFFF)     // always on top
+                .show(mFlingerSurfaceControl)
+                .apply();
     }
 }
 
 
 void GlWrapper::hideWindow() {
     if (mFlingerSurfaceControl != nullptr) {
-        SurfaceComposerClient::openGlobalTransaction();
-        mFlingerSurfaceControl->hide();
-        SurfaceComposerClient::closeGlobalTransaction();
+        SurfaceComposerClient::Transaction{}
+                .hide(mFlingerSurfaceControl)
+                .apply();
     }
 }
 
diff --git a/libvehiclemonitor/include/HandlerThread.h b/libvehiclemonitor/include/HandlerThread.h
deleted file mode 100644
index 03f523e..0000000
--- a/libvehiclemonitor/include/HandlerThread.h
+++ /dev/null
@@ -1,49 +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.
- */
-
-#ifndef LIBVEHICLEMONITOR_HANDLER_THREAD_H_
-#define LIBVEHICLEMONITOR_HANDLER_THREAD_H_
-
-#include <utils/Looper.h>
-#include <utils/threads.h>
-
-namespace android {
-
-/**
- * Native HandlerThread implementation looking similar to Java version.
- */
-class HandlerThread : public Thread {
-public:
-    HandlerThread();
-    virtual ~HandlerThread();
-
-    sp<Looper> getLooper();
-    status_t start(const char* name = 0, int32_t priority = PRIORITY_DEFAULT, size_t stack = 0);
-    void quit();
-
-private:
-    bool threadLoop();
-
-private:
-    sp<Looper> mLooper;
-    mutable Mutex mLock;
-    bool mShouldQuit;
-    Condition mLooperWait;
-};
-
-};
-
-#endif /* LIBVEHICLEMONITOR_HANDLER_THREAD_H_ */
diff --git a/libvehiclemonitor/include/IVehicleMonitor.h b/libvehiclemonitor/include/IVehicleMonitor.h
deleted file mode 100644
index 4aa2e14..0000000
--- a/libvehiclemonitor/include/IVehicleMonitor.h
+++ /dev/null
@@ -1,60 +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.
- */
-
-#ifndef ANDROID_IVEHICLE_MONITOR_H
-#define ANDROID_IVEHICLE_MONITOR_H
-
-#include <binder/Parcel.h>
-
-#include <IVehicleMonitorListener.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-
-/**
- * Application priorities used in vehicle monitoring.
- */
-enum vehicle_app_priority {
-    VEHICLE_APP_PRIORITY_NONE = 0,
-    VEHICLE_APP_PRIORITY_FOREGROUND = 1,
-};
-
-// ----------------------------------------------------------------------------
-
-class IVehicleMonitor : public IInterface {
-public:
-    static const char SERVICE_NAME[];
-    DECLARE_META_INTERFACE(VehicleMonitor);
-
-    virtual status_t setAppPriority(
-            uint32_t pid, uint32_t uid, vehicle_app_priority priority) = 0;
-    virtual status_t setMonitorListener(
-            const sp<IVehicleMonitorListener> &listener) = 0;
-};
-// ----------------------------------------------------------------------------
-
-class BnVehicleMonitor : public BnInterface<IVehicleMonitor> {
-    virtual status_t  onTransact(uint32_t code,
-                                 const Parcel& data,
-                                 Parcel* reply,
-                                 uint32_t flags = 0);
-};
-
-// ----------------------------------------------------------------------------
-}; // namespace android
-
-#endif /* ANDROID_IVEHICLE_MONITOR_H */
diff --git a/libvehiclemonitor/include/IVehicleMonitorListener.h b/libvehiclemonitor/include/IVehicleMonitorListener.h
deleted file mode 100644
index 9c9e1f5..0000000
--- a/libvehiclemonitor/include/IVehicleMonitorListener.h
+++ /dev/null
@@ -1,53 +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.
- */
-
-#ifndef ANDROID_IVEHICLE_MONITOR_LISTENER_H
-#define ANDROID_IVEHICLE_MONITOR_LISTENER_H
-
-#include <binder/Parcel.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-
-class IVehicleMonitorListener : public IInterface
-{
-public:
-    DECLARE_META_INTERFACE(VehicleMonitorListener);
-
-    /**
-     * Notifies when app misbehaved. Client (Bn implementor) should
-     * hold sp to keep the data received outside this call.
-     */
-    virtual void onAppViolation(
-            int32_t pid, int32_t uid, int32_t action, int32_t violation) = 0;
-};
-
-// ----------------------------------------------------------------------------
-
-class BnVehicleMonitorListener : public BnInterface<IVehicleMonitorListener>
-{
-    virtual status_t  onTransact(uint32_t code,
-                                 const Parcel& data,
-                                 Parcel* reply,
-                                 uint32_t flags = 0);
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif /* ANDROID_IVEHICLE_MONITOR_LISTENER_H */
diff --git a/libvehiclemonitor/include/VehicleMonitor.h b/libvehiclemonitor/include/VehicleMonitor.h
deleted file mode 100644
index 12510ec..0000000
--- a/libvehiclemonitor/include/VehicleMonitor.h
+++ /dev/null
@@ -1,66 +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.
- */
-
-#ifndef ANDROID_VEHICLE_MONITOR_H
-#define ANDROID_VEHICLE_MONITOR_H
-
-#include <binder/IInterface.h>
-
-#include <utils/Mutex.h>
-
-#include "IVehicleMonitor.h"
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-
-/**
- * Vehicle monitor API for low level components like HALs to access
- * monitoring service.
- * This is reference counted. So use with sp<>.
- */
-class VehicleMonitor : public IBinder::DeathRecipient {
-public:
-    /**
-     * Factory method for VehicleMonitor. Client should use this method
-     * to create a new instance.
-     */
-    static sp<VehicleMonitor> createVehicleMonitor();
-
-    virtual ~VehicleMonitor();
-
-    status_t setAppPriority(
-            uint32_t pid, uint32_t uid, vehicle_app_priority priority);
-
-    //IBinder::DeathRecipient, not for client
-    void binderDied(const wp<IBinder>& who);
-
-private:
-    VehicleMonitor(sp<IVehicleMonitor>& vehicleMonitor);
-    // RefBase
-    virtual void onFirstRef();
-    sp<IVehicleMonitor> getService();
-
-private:
-    sp<IVehicleMonitor> mService;
-    Mutex mLock;
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif /* ANDROID_VEHICLE_MONITOR_H */
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitor.aidl b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitor.aidl
deleted file mode 100644
index 0c495ec..0000000
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitor.aidl
+++ /dev/null
@@ -1,28 +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 com.android.car.vehiclemonitor;
-
-import com.android.car.vehiclemonitor.IVehicleMonitorListener;
-
-/**
-  * Binder API to access vehicle monitor service.
-  * @hide
-  */
-oneway interface IVehicleMonitor {
-    void setAppPriority(int pid, int uid, int priority) = 0;
-    void setMonitorListener(IVehicleMonitorListener listener) = 1;
-}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/VehicleMonitor.java b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/VehicleMonitor.java
deleted file mode 100644
index f5a9363..0000000
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/VehicleMonitor.java
+++ /dev/null
@@ -1,182 +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 com.android.car.vehiclemonitor;
-
-import android.annotation.IntDef;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-
-/**
- * System API to access Vehicle monitor. This is only for system services and applications should
- * not use this. All APIs will fail with security error if normal app tries this.
- */
-public class VehicleMonitor {
-    private static final String TAG = VehicleMonitor.class.getSimpleName();
-
-    private final IVehicleMonitor mService;
-    private final VehicleMonitorListener mListener;
-    private final IVehicleMonitorListenerImpl mVehicleMonitorListener;
-    private final EventHandler mEventHandler;
-
-    private static final int VMS_CONNECT_MAX_RETRY = 10;
-    private static final long VMS_RETRY_WAIT_TIME_MS = 1000;
-
-    /**
-     * Application priorities used in vehicle monitoring.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            ApplicationPriority.NONE,
-            ApplicationPriority.FOREGROUND
-    })
-    public @interface ApplicationPriority {
-        int NONE = 0;
-        int FOREGROUND = 1;
-    }
-
-    /**
-     * Listener for VMS events.
-     */
-    public interface VehicleMonitorListener {
-        void onAppViolation(int pid, int uid, int action, int violation);
-    }
-
-    /**
-     * Factory method to create VehicleMonitor
-     */
-    public static VehicleMonitor createVehicleMonitor(
-            VehicleMonitorListener listener, Looper looper) {
-        int retryCount = 0;
-        IVehicleMonitor service = null;
-        while (true) {
-            service = IVehicleMonitor.Stub.asInterface(
-                    ServiceManager.getService(IVehicleMonitor.class.getCanonicalName()));
-            if (service != null) {
-                break;
-            }
-            retryCount++;
-            if (retryCount > VMS_CONNECT_MAX_RETRY) {
-                break;
-            }
-            try {
-                Thread.sleep(VMS_RETRY_WAIT_TIME_MS);
-            } catch (InterruptedException e) {
-                //ignore
-            }
-        }
-        if (service == null) {
-            throw new RuntimeException("Vehicle monitor service not available:"
-                    + IVehicleMonitor.class.getCanonicalName());
-        }
-        return new VehicleMonitor(service, listener, looper);
-    }
-
-    private VehicleMonitor(
-            IVehicleMonitor service, VehicleMonitorListener listener, Looper looper) {
-        mService = service;
-        mListener = listener;
-        mEventHandler = new EventHandler(looper);
-        mVehicleMonitorListener = new IVehicleMonitorListenerImpl(this);
-        try {
-            mService.setMonitorListener(mVehicleMonitorListener);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Vehicle monitor service not working ", e);
-        }
-    }
-
-    /**
-     * Set application priority.
-     * <p>
-     * This will lead into writing application priority into vehicle monitor.
-     */
-    public void setAppPriority(int pid, int uid, @ApplicationPriority int priority)
-            throws ServiceSpecificException {
-        try {
-            mService.setAppPriority(pid, uid, priority);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Vehicle monitor service not working ", e);
-        }
-    }
-
-    private void handleVehicleMonitorAppViolation(AppViolation appViolation) {
-        mListener.onAppViolation(appViolation.mPid, appViolation.mUid, appViolation.mAction,
-                appViolation.mViolation);
-    }
-
-    private class EventHandler extends Handler {
-
-        private static final int MSG_APP_VIOLATION = 0;
-
-        private EventHandler(Looper looper) {
-            super(looper);
-        }
-
-        private void notifyAppViolation(int pid, int uid, int action, int violation) {
-            AppViolation appViolation = new AppViolation(pid, uid, action, violation);
-            Message msg = obtainMessage(MSG_APP_VIOLATION, appViolation);
-            sendMessage(msg);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_APP_VIOLATION:
-                    AppViolation appViolation = (AppViolation) msg.obj;
-                    handleVehicleMonitorAppViolation(appViolation);
-                    break;
-            }
-        }
-    }
-
-    private static class IVehicleMonitorListenerImpl extends IVehicleMonitorListener.Stub {
-
-        private final WeakReference<VehicleMonitor> mVehicleMonitor;
-
-        private IVehicleMonitorListenerImpl(VehicleMonitor vehicleNewotk) {
-            mVehicleMonitor = new WeakReference<>(vehicleNewotk);
-        }
-
-        @Override
-        public void onAppViolation(int pid, int uid, int action, int violation) {
-            VehicleMonitor vehicleMonitor = mVehicleMonitor.get();
-            if (vehicleMonitor != null) {
-                vehicleMonitor.mEventHandler.notifyAppViolation(pid, uid, action, violation);
-            }
-        }
-    }
-
-    private static class AppViolation {
-        public final int mPid;
-        public final int mUid;
-        public final int mAction;
-        public final int mViolation;
-
-        AppViolation(int pid, int uid, int action, int violation) {
-            mPid = pid;
-            mUid = uid;
-            mAction = action;
-            mViolation = violation;
-        }
-    }
-}
diff --git a/libvehiclemonitor/native/HandlerThread.cpp b/libvehiclemonitor/native/HandlerThread.cpp
deleted file mode 100644
index 5ee2715..0000000
--- a/libvehiclemonitor/native/HandlerThread.cpp
+++ /dev/null
@@ -1,72 +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.
- */
-
-#include "HandlerThread.h"
-
-namespace android {
-
-HandlerThread::HandlerThread()
-    : mShouldQuit(false) {
-
-}
-
-HandlerThread::~HandlerThread() {
-    quit();
-}
-
-sp<Looper> HandlerThread::getLooper() {
-    Mutex::Autolock autoLock(mLock);
-    if (mLooper.get() == 0) {
-        mLooperWait.wait(mLock);
-    }
-    return mLooper;
-}
-
-status_t HandlerThread::start(const char* name, int32_t priority, size_t stack) {
-    return run(name, priority, stack);
-}
-
-void HandlerThread::quit() {
-    if (!isRunning()) {
-        return;
-    }
-    sp<Looper> looper = getLooper();
-    mLock.lock();
-    mShouldQuit = true;
-    mLock.unlock();
-    looper->wake();
-    requestExitAndWait();
-}
-
-bool HandlerThread::threadLoop() {
-    mLock.lock();
-    mLooper = Looper::prepare(0);
-    mLooperWait.broadcast();
-    mLock.unlock();
-    while (true) {
-        do {
-            Mutex::Autolock autoLock(mLock);
-            if (mShouldQuit) {
-                return false;
-            }
-        } while (false);
-        mLooper->pollOnce(-1);
-    }
-    return false;
-}
-
-
-};
diff --git a/libvehiclemonitor/native/IVehicleMonitor.cpp b/libvehiclemonitor/native/IVehicleMonitor.cpp
deleted file mode 100644
index b29f665..0000000
--- a/libvehiclemonitor/native/IVehicleMonitor.cpp
+++ /dev/null
@@ -1,128 +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.
- */
-
-#define LOG_TAG "VehicleMonitor"
-
-#include <binder/IPCThreadState.h>
-#include <binder/Status.h>
-#include <private/android_filesystem_config.h>
-
-#include <utils/Log.h>
-
-#include <IVehicleMonitor.h>
-
-namespace android {
-enum {
-    SET_APP_PRIORITY = IBinder::FIRST_CALL_TRANSACTION,
-    SET_MONITOR_LISTENER,
-};
-
-const char IVehicleMonitor::SERVICE_NAME[] = "com.android.car.vehiclemonitor.IVehicleMonitor";
-
-// ----------------------------------------------------------------------------
-
-class BpVehicleMonitor : public BpInterface<IVehicleMonitor> {
-public:
-    explicit BpVehicleMonitor(const sp<IBinder> & impl)
-            : BpInterface<IVehicleMonitor>(impl) {
-    }
-
-    virtual status_t setAppPriority(
-            uint32_t pid, uint32_t uid, vehicle_app_priority priority) {
-        Parcel data, reply;
-        data.writeInterfaceToken(IVehicleMonitor::getInterfaceDescriptor());
-        data.writeInt32(1);
-        data.writeInt32(pid);
-        data.writeInt32(uid);
-        data.writeInt32(priority);
-        status_t status = remote()->transact(SET_APP_PRIORITY, data, &reply);
-        if (status == NO_ERROR) {
-            int32_t exceptionCode = reply.readExceptionCode();
-            if (exceptionCode != NO_ERROR) {
-                if (exceptionCode == binder::Status::EX_SERVICE_SPECIFIC) {
-                    return -EAGAIN;
-                } else if (exceptionCode == binder::Status::EX_ILLEGAL_STATE) {
-                    return -ESHUTDOWN;
-                }
-                return exceptionCode;
-            }
-        }
-        return status;
-    }
-
-    virtual status_t setMonitorListener(
-            const sp<IVehicleMonitorListener> &listener) {
-        Parcel data, reply;
-        data.writeInterfaceToken(IVehicleMonitor::getInterfaceDescriptor());
-        data.writeStrongBinder(IInterface::asBinder(listener));
-        status_t status = remote()->transact(SET_MONITOR_LISTENER, data, &reply);
-        return status;
-    }
-};
-
-IMPLEMENT_META_INTERFACE(VehicleMonitor, IVehicleMonitor::SERVICE_NAME);
-
-// ----------------------------------------------------------------------
-
-static bool isSystemUser() {
-    uid_t uid =  IPCThreadState::self()->getCallingUid();
-    switch (uid) {
-        // This list will be expanded. Only these UIDs are allowed to access vehicle monitor.
-        case AID_ROOT:
-        case AID_SYSTEM: {
-            return true;
-        } break;
-        default: {
-            ALOGE("non-system user tried access, uid %d", uid);
-        } break;
-    }
-    return false;
-}
-
-status_t BnVehicleMonitor::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-        uint32_t flags) {
-    if (!isSystemUser()) {
-        return PERMISSION_DENIED;
-    }
-    status_t r;
-    switch (code) {
-        case SET_APP_PRIORITY: {
-            CHECK_INTERFACE(IVehicleMonitor, data, reply);
-            if (data.readInt32() == 0) { // no data
-                ALOGE("null data");
-                return BAD_VALUE;
-            }
-            int32_t pid = data.readInt32();
-            int32_t uid = data.readInt32();
-            int32_t priority = data.readInt32();
-            r = setAppPriority(pid, uid, (vehicle_app_priority) priority);
-            reply->writeNoException();
-            return r;
-        } break;
-        case SET_MONITOR_LISTENER: {
-            CHECK_INTERFACE(IVehicleMonitor, data, reply);
-            sp<IVehicleMonitorListener> listener =
-                    interface_cast<IVehicleMonitorListener>(data.readStrongBinder());
-            r = setMonitorListener(listener);
-            reply->writeNoException();
-            return r;
-        } break;
-        default:
-            return BBinder::onTransact(code, data, reply, flags);
-    }
-}
-
-}; // namespace android
diff --git a/libvehiclemonitor/native/IVehicleMonitorListener.cpp b/libvehiclemonitor/native/IVehicleMonitorListener.cpp
deleted file mode 100644
index 985a228..0000000
--- a/libvehiclemonitor/native/IVehicleMonitorListener.cpp
+++ /dev/null
@@ -1,79 +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.
- */
-
-
-
-#define LOG_TAG "VehicleMonitorListener"
-
-#include <memory>
-
-#include <utils/Errors.h>
-#include <utils/Log.h>
-
-#include <IVehicleMonitorListener.h>
-
-namespace android {
-
-enum {
-    ON_APP_VIOLATION = IBinder::FIRST_CALL_TRANSACTION,
-};
-
-class BpVehicleMonitorListener : public BpInterface<IVehicleMonitorListener>
-{
-  public:
-    explicit BpVehicleMonitorListener(const sp<IBinder> & impl)
-            : BpInterface<IVehicleMonitorListener>(impl) {
-    }
-
-    virtual void onAppViolation(
-            int32_t pid, int32_t uid, int32_t action, int32_t violation) {
-        Parcel data, reply;
-        data.writeInterfaceToken(IVehicleMonitorListener::getInterfaceDescriptor());
-        data.writeInt32(1);
-        data.writeInt32(pid);
-        data.writeInt32(uid);
-        data.writeInt32(action);
-        data.writeInt32(violation);
-        remote()->transact(ON_APP_VIOLATION, data, &reply, IBinder::FLAG_ONEWAY);
-    }
-};
-
-IMPLEMENT_META_INTERFACE(VehicleMonitorListener, "com.android.car.vehiclemonitor.IVehicleMonitorListener");
-
-// ----------------------------------------------------------------------
-
-status_t BnVehicleMonitorListener::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                                              uint32_t flags) {
-    status_t r;
-    switch (code) {
-        case ON_APP_VIOLATION: {
-            CHECK_INTERFACE(IVehicleMonitorListener, data, reply);
-            if (data.readInt32() == 0) { // java side allows passing null with this.
-                return BAD_VALUE;
-            }
-            int32_t pid = data.readInt32();
-            int32_t uid = data.readInt32();
-            int32_t action = data.readInt32();
-            int32_t violation = data.readInt32();
-            onAppViolation(pid, uid, action, violation);
-            return NO_ERROR;
-        } break;
-        default:
-            return BBinder::onTransact(code, data, reply, flags);
-    }
-}
-
-}; // namespace android
diff --git a/libvehiclemonitor/native/VehicleMonitor.cpp b/libvehiclemonitor/native/VehicleMonitor.cpp
deleted file mode 100644
index 33235cc..0000000
--- a/libvehiclemonitor/native/VehicleMonitor.cpp
+++ /dev/null
@@ -1,90 +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.
- */
-#define LOG_TAG "VehicleMonitor.Lib"
-
-#include <assert.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-
-#include <VehicleMonitor.h>
-
-namespace android {
-// ----------------------------------------------------------------------------
-
-static const int MAX_SERVICE_RETRY = 4;
-
-sp<VehicleMonitor> VehicleMonitor::createVehicleMonitor() {
-    sp<IBinder> binder;
-    int retry = 0;
-    while (true) {
-        binder = defaultServiceManager()->getService(String16(IVehicleMonitor::SERVICE_NAME));
-        if (binder.get() != NULL) {
-            break;
-        }
-        retry++;
-        if (retry > MAX_SERVICE_RETRY) {
-            ALOGE("cannot get VMS, will crash");
-            break;
-        }
-    }
-    assert(binder.get() != NULL);
-    sp<IVehicleMonitor> ivm(interface_cast<IVehicleMonitor>(binder));
-    sp<VehicleMonitor> vm;
-    vm = new VehicleMonitor(ivm);
-    assert(vm.get() != NULL);
-    // in case thread pool is not started, start it.
-    ProcessState::self()->startThreadPool();
-    return vm;
-}
-
-VehicleMonitor::VehicleMonitor(sp<IVehicleMonitor>& vehicleMonitor) :
-        mService(vehicleMonitor) {
-}
-
-VehicleMonitor::~VehicleMonitor() {
-    sp<IVehicleMonitor> service = getService();
-    IInterface::asBinder(service)->unlinkToDeath(this);
-}
-
-void VehicleMonitor::onFirstRef() {
-    sp<IVehicleMonitor> service = getService();
-    IInterface::asBinder(service)->linkToDeath(this);
-}
-
-status_t VehicleMonitor::setAppPriority(
-        uint32_t pid, uint32_t uid, vehicle_app_priority priority) {
-    return getService()->setAppPriority(pid, uid, priority);
-}
-
-void VehicleMonitor::binderDied(const wp<IBinder>& who) {
-    ALOGE("service died");
-    {
-        Mutex::Autolock autoLock(mLock);
-        sp<IBinder> ibinder = who.promote();
-        ibinder->unlinkToDeath(this);
-        sp<IBinder> binder = defaultServiceManager()->getService(
-                String16(IVehicleMonitor::SERVICE_NAME));
-        mService = interface_cast<IVehicleMonitor>(binder);
-        IInterface::asBinder(mService)->linkToDeath(this);
-    };
-}
-
-sp<IVehicleMonitor> VehicleMonitor::getService() {
-    Mutex::Autolock autoLock(mLock);
-    return mService;
-}
-
-}; // namespace android
diff --git a/libvehiclemonitor/Android.mk b/procfs-inspector/Android.mk
similarity index 92%
rename from libvehiclemonitor/Android.mk
rename to procfs-inspector/Android.mk
index 23bc789..89bdf30 100644
--- a/libvehiclemonitor/Android.mk
+++ b/procfs-inspector/Android.mk
@@ -1,4 +1,4 @@
-# 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.
diff --git a/libvehiclemonitor/java/Android.mk b/procfs-inspector/client/Android.mk
similarity index 87%
rename from libvehiclemonitor/java/Android.mk
rename to procfs-inspector/client/Android.mk
index 1ced365..d9713de 100644
--- a/libvehiclemonitor/java/Android.mk
+++ b/procfs-inspector/client/Android.mk
@@ -1,4 +1,4 @@
-# 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.
@@ -18,7 +18,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE := libvehiclemonitor-java
+LOCAL_MODULE := com.android.car.procfsinspector-client
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl
index ca0ac83..7022ff4 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl
@@ -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,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package com.android.car.procfsinspector;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
+import com.android.car.procfsinspector.ProcessInfo;
+
+interface IProcfsInspector {
+    List<ProcessInfo> readProcessTable();
 }
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl
similarity index 67%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl
index ca0ac83..ca8d4be 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl
@@ -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,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.car.vehiclemonitor;
+package com.android.car.procfsinspector;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
-}
+parcelable ProcessInfo;
diff --git a/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java b/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java
new file mode 100644
index 0000000..310e1ba
--- /dev/null
+++ b/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.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.procfsinspector;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.Objects;
+
+public class ProcessInfo implements Parcelable {
+    public static final Parcelable.Creator<ProcessInfo> CREATOR =
+        new Parcelable.Creator<ProcessInfo>() {
+            public ProcessInfo createFromParcel(Parcel in) {
+                return new ProcessInfo(in);
+            }
+
+            public ProcessInfo[] newArray(int size) {
+                return new ProcessInfo[size];
+            }
+        };
+
+    public final int pid;
+    public final int uid;
+
+    public ProcessInfo(int pid, int uid) {
+        this.pid = pid;
+        this.uid = uid;
+    }
+
+    public ProcessInfo(Parcel in) {
+        this.pid = in.readInt();
+        this.uid = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(pid);
+        dest.writeInt(uid);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof ProcessInfo) {
+            ProcessInfo processInfo = (ProcessInfo)other;
+            return processInfo.pid == pid && processInfo.uid == uid;
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(pid, uid);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("pid = %d, uid = %d", pid, uid);
+    }
+}
diff --git a/procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java b/procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java
new file mode 100644
index 0000000..ff00bbf
--- /dev/null
+++ b/procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java
@@ -0,0 +1,54 @@
+/*
+ * 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.procfsinspector;
+
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public final class ProcfsInspector {
+    private static final String TAG = "car.procfsinspector";
+    private static final String SERVICE_NAME = "com.android.car.procfsinspector";
+    private final IProcfsInspector mService;
+
+    private ProcfsInspector(IProcfsInspector service) {
+        mService = service;
+    }
+
+    @Nullable
+    private static IProcfsInspector tryGet() {
+        return IProcfsInspector.Stub.asInterface(
+            ServiceManager.getService(SERVICE_NAME));
+    }
+
+    public static List<ProcessInfo> readProcessTable() {
+        IProcfsInspector procfsInspector = tryGet();
+        if (procfsInspector != null) {
+            try {
+                return procfsInspector.readProcessTable();
+            } catch (RemoteException e) {
+                Log.w(TAG, "caught RemoteException", e);
+            }
+        }
+
+        return Collections.emptyList();
+    }
+}
diff --git a/libvehiclemonitor/native/Android.mk b/procfs-inspector/server/Android.mk
similarity index 61%
rename from libvehiclemonitor/native/Android.mk
rename to procfs-inspector/server/Android.mk
index 880fcae..d6a7d43 100644
--- a/libvehiclemonitor/native/Android.mk
+++ b/procfs-inspector/server/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,30 +13,36 @@
 # limitations under the License.
 #
 #
+
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
-    find . -name "*.cpp" -and -not -name ".*"))
+BOARD_SEPOLICY_DIRS += \
+    packages/services/Car/procfs-inspector/server/sepolicy
+
+LOCAL_SRC_FILES := \
+    main.cpp \
+    server.cpp \
+    impl.cpp \
+    process.cpp \
+    directory.cpp
 
 LOCAL_C_INCLUDES += \
-    frameworks/base/include \
-    packages/services/Car/libvehiclemonitor/include
+    frameworks/base/include
 
 LOCAL_SHARED_LIBRARIES := \
+    libbinder \
     liblog \
-    libutils \
-    libhardware \
-    libbinder
-
-LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libbinder
+    libutils
 
 LOCAL_STRIP_MODULE := keep_symbols
 
-LOCAL_MODULE := libvehiclemonitor-native
+LOCAL_INIT_RC := com.android.car.procfsinspector.rc
+
+LOCAL_MODULE := com.android.car.procfsinspector
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_CFLAGS += -Werror
+LOCAL_CFLAGS  += -Wall -Werror
 
-include $(BUILD_SHARED_LIBRARY)
+include $(BUILD_EXECUTABLE)
diff --git a/procfs-inspector/server/com.android.car.procfsinspector.rc b/procfs-inspector/server/com.android.car.procfsinspector.rc
new file mode 100644
index 0000000..7faaf69
--- /dev/null
+++ b/procfs-inspector/server/com.android.car.procfsinspector.rc
@@ -0,0 +1,7 @@
+service com.android.car.procfsinspector /system/bin/com.android.car.procfsinspector
+    class core
+    user nobody
+    group readproc
+
+on boot && property:boot.car_service_created=1
+    start com.android.car.procfsinspector
diff --git a/procfs-inspector/server/directory.cpp b/procfs-inspector/server/directory.cpp
new file mode 100644
index 0000000..45dbc0a
--- /dev/null
+++ b/procfs-inspector/server/directory.cpp
@@ -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.
+ */
+
+#include "directory.h"
+
+#include <android-base/stringprintf.h>
+
+procfsinspector::Directory::Directory(const char* path) {
+    if (path && path[0]) {
+        mPath.assign(path);
+        mDirectory.reset(opendir(path));
+    }
+}
+
+bool procfsinspector::Directory::Entry::isEmpty() const {
+    return mParent.empty() && mChild.empty();
+}
+
+procfsinspector::Directory::Entry::Entry(std::string parent, std::string child) :
+    mParent(parent), mChild(child) {
+    if (!isEmpty()) {
+        if (mParent.back() != '/') {
+            mParent += '/';
+        }
+    }
+}
+
+std::string procfsinspector::Directory::Entry::str() {
+    return mParent + mChild;
+}
+
+uid_t procfsinspector::Directory::Entry::getOwnerUserId() {
+    if (isEmpty()) {
+        return -1;
+    }
+    struct stat buf;
+    // fill in stat info for this entry, or return invalid UID on failure
+    if (stat(str().c_str(), &buf)) {
+        return -1;
+    }
+    return buf.st_uid;
+}
+
+procfsinspector::Directory::Entry procfsinspector::Directory::next(unsigned char type) {
+    if (auto dir = mDirectory.get()) {
+        dirent *entry = readdir(dir);
+        if (entry) {
+            // only return entries of the right type (regular file, directory, ...)
+            // but always return UNKNOWN entries as it is an allowed wildcard entry
+            if (entry->d_type == DT_UNKNOWN ||
+                type == DT_UNKNOWN ||
+                entry->d_type == type) {
+                return Entry(mPath, entry->d_name);
+            }
+        }
+    }
+
+    return Entry();
+}
diff --git a/procfs-inspector/server/directory.h b/procfs-inspector/server/directory.h
new file mode 100644
index 0000000..19528fe
--- /dev/null
+++ b/procfs-inspector/server/directory.h
@@ -0,0 +1,69 @@
+/*
+ * 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_PROCFS_DIRECTORY
+#define CAR_PROCFS_DIRECTORY
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+
+namespace procfsinspector {
+
+class Directory {
+public:
+    class Entry {
+    public:
+        Entry(std::string parent = "", std::string child = "");
+
+        const std::string& getChild() { return mChild; }
+        std::string str();
+
+        bool isEmpty() const;
+
+        operator bool() const {
+            return !isEmpty();
+        }
+
+        uid_t getOwnerUserId();
+
+    private:
+        std::string mParent;
+        std::string mChild;
+    };
+
+    Directory(const char* path);
+
+    Entry next(unsigned char type = DT_UNKNOWN);
+
+private:
+    class Deleter {
+    public:
+        void operator()(DIR* dir) {
+            if (dir) closedir(dir);
+        }
+    };
+    std::string mPath;
+    std::unique_ptr<DIR, Deleter> mDirectory;
+};
+
+}
+
+#endif // CAR_PROCFS_DIRECTORY
\ No newline at end of file
diff --git a/procfs-inspector/server/impl.cpp b/procfs-inspector/server/impl.cpp
new file mode 100644
index 0000000..888f77d
--- /dev/null
+++ b/procfs-inspector/server/impl.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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 "directory.h"
+#include "server.h"
+
+template<typename IntTy>
+static bool asNumber(const std::string& s, IntTy *value) {
+    IntTy v = 0;
+    for (auto&& c : s) {
+        if (c < '0' || c > '9') {
+            return false;
+        } else {
+            v = v * 10 + (c - '0');
+        }
+    }
+
+    if (value) *value = v;
+
+    return true;
+}
+
+std::vector<procfsinspector::ProcessInfo> procfsinspector::Impl::readProcessTable() {
+    std::vector<procfsinspector::ProcessInfo> processes;
+
+    Directory dir("/proc");
+    while (auto entry = dir.next()) {
+        pid_t pid;
+        if (asNumber(entry.getChild(), &pid)) {
+            processes.push_back(ProcessInfo{pid, entry.getOwnerUserId()});
+        }
+    }
+
+    return processes;
+}
diff --git a/procfs-inspector/server/main.cpp b/procfs-inspector/server/main.cpp
new file mode 100644
index 0000000..1555aa3
--- /dev/null
+++ b/procfs-inspector/server/main.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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 "server.h"
+
+#include <signal.h>
+
+#include <binder/IServiceManager.h>
+
+#include <utils/Log.h>
+
+using namespace android;
+
+int main(int, char**)
+{
+    ALOGI("starting " LOG_TAG);
+    signal(SIGPIPE, SIG_IGN);
+
+    sp<ProcessState> processSelf(ProcessState::self());
+    sp<IServiceManager> serviceManager = defaultServiceManager();
+    std::unique_ptr<procfsinspector::Impl> server(new procfsinspector::Impl());
+
+    serviceManager->addService(String16(SERVICE_NAME), server.get());
+
+    processSelf->startThreadPool();
+
+    ALOGI(LOG_TAG " started");
+
+    IPCThreadState::self()->joinThreadPool();
+
+    ALOGW(LOG_TAG " joined and going down");
+    return 0;
+}
diff --git a/procfs-inspector/server/process.cpp b/procfs-inspector/server/process.cpp
new file mode 100644
index 0000000..3c25ce2
--- /dev/null
+++ b/procfs-inspector/server/process.cpp
@@ -0,0 +1,15 @@
+#include "process.h"
+
+#include <binder/Parcel.h>
+
+status_t procfsinspector::ProcessInfo::writeToParcel(Parcel* parcel) const {
+    parcel->writeUint32(mPid);
+    parcel->writeUint32(mUid);
+    return android::OK;
+}
+
+status_t procfsinspector::ProcessInfo::readFromParcel(const Parcel* parcel) {
+    mPid = parcel->readUint32();
+    mUid = parcel->readUint32();
+    return android::OK;
+}
diff --git a/procfs-inspector/server/process.h b/procfs-inspector/server/process.h
new file mode 100644
index 0000000..75b76c7
--- /dev/null
+++ b/procfs-inspector/server/process.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 CAR_PROCFS_PROCESS
+#define CAR_PROCFS_PROCESS
+
+#include <sys/types.h>
+
+#include <binder/Parcelable.h>
+
+using namespace android;
+
+namespace procfsinspector {
+    class ProcessInfo : public Parcelable {
+    public:
+        pid_t getPid() { return mPid; }
+        uid_t getUid() { return mUid; }
+
+        // default initialize to invalid values
+        ProcessInfo(pid_t pid = -1, uid_t uid = -1) : mPid(pid), mUid(uid) {}
+
+        virtual status_t writeToParcel(Parcel* parcel) const override;
+        virtual status_t readFromParcel(const Parcel* parcel) override;
+
+    private:
+        pid_t mPid;
+        uid_t mUid;
+    };
+}
+
+#endif // CAR_PROCFS_PROCESS
diff --git a/procfs-inspector/server/sepolicy/com.android.car.procfsinspector.te b/procfs-inspector/server/sepolicy/com.android.car.procfsinspector.te
new file mode 100644
index 0000000..acd7ddf
--- /dev/null
+++ b/procfs-inspector/server/sepolicy/com.android.car.procfsinspector.te
@@ -0,0 +1,6 @@
+type procfsinspector, domain, coredomain;
+type procfsinspector_exec, exec_type, file_type;
+
+init_daemon_domain(procfsinspector)
+
+allow system_app procfsinspector:binder call;
diff --git a/procfs-inspector/server/sepolicy/file_contexts b/procfs-inspector/server/sepolicy/file_contexts
new file mode 100644
index 0000000..5283993
--- /dev/null
+++ b/procfs-inspector/server/sepolicy/file_contexts
@@ -0,0 +1 @@
+/system/bin/com\.android\.car\.procfsinspector     u:object_r:procfsinspector_exec:s0
diff --git a/procfs-inspector/server/server.cpp b/procfs-inspector/server/server.cpp
new file mode 100644
index 0000000..82e196f
--- /dev/null
+++ b/procfs-inspector/server/server.cpp
@@ -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.
+ */
+
+#include "server.h"
+
+#include <binder/IPCThreadState.h>
+
+#include <private/android_filesystem_config.h>
+
+static bool isSystemUser() {
+    uid_t uid =  IPCThreadState::self()->getCallingUid();
+    switch (uid) {
+        case AID_ROOT:
+        case AID_SYSTEM: {
+            return true;
+        } break;
+        default: {
+            ALOGE("uid %u is not root nor system - access denied", uid);
+        } break;
+    }
+    return false;
+}
+
+namespace procfsinspector {
+class BpProcfsInspector: public BpInterface<IProcfsInspector> {
+    public:
+        BpProcfsInspector(sp<IBinder> binder) : BpInterface<IProcfsInspector>(binder) {}
+
+        virtual std::vector<ProcessInfo> readProcessTable() override {
+            Parcel data, reply;
+            remote()->transact((uint32_t)IProcfsInspector::Call::READ_PROCESS_TABLE, data, &reply);
+
+            std::vector<procfsinspector::ProcessInfo> result;
+            reply.readParcelableVector(&result);
+            return result;
+        }
+
+};
+
+IMPLEMENT_META_INTERFACE(ProcfsInspector, "com.android.car.procfsinspector.IProcfsInspector");
+
+status_t Impl::onTransact(uint32_t code,
+    const Parcel& data, Parcel* reply, uint32_t flags) {
+
+    if (code == (uint32_t)IProcfsInspector::Call::READ_PROCESS_TABLE) {
+        CHECK_INTERFACE(IProcfsInspector, data, reply);
+        if (isSystemUser()) {
+            reply->writeNoException();
+            reply->writeParcelableVector(readProcessTable());
+            return NO_ERROR;
+        } else {
+            return PERMISSION_DENIED;
+        }
+    }
+
+    return BBinder::onTransact(code, data, reply, flags);
+}
+
+}
diff --git a/procfs-inspector/server/server.h b/procfs-inspector/server/server.h
new file mode 100644
index 0000000..ae529ff
--- /dev/null
+++ b/procfs-inspector/server/server.h
@@ -0,0 +1,62 @@
+/*
+ * 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_PROCFS_SERVER
+#define CAR_PROCFS_SERVER
+
+#define LOG_TAG "com.android.car.procfsinspector"
+#define SERVICE_NAME "com.android.car.procfsinspector"
+
+#include <vector>
+
+#include <binder/Parcel.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <binder/IBinder.h>
+#include <binder/IInterface.h>
+
+#include <utils/Log.h>
+#include <utils/String16.h>
+
+#include "process.h"
+
+using namespace android;
+
+namespace procfsinspector {
+    class IProcfsInspector : public IInterface {
+    public:
+        DECLARE_META_INTERFACE(ProcfsInspector);
+
+        enum class Call : uint32_t {
+            READ_PROCESS_TABLE = IBinder::FIRST_CALL_TRANSACTION,
+        };
+
+        // API declarations start here
+        virtual std::vector<ProcessInfo> readProcessTable() = 0;
+    };
+
+    class Impl : public BnInterface<IProcfsInspector> {
+    public:
+        virtual status_t onTransact(uint32_t code,
+            const Parcel& data,
+            Parcel *reply,
+            uint32_t flags) override;
+        virtual std::vector<ProcessInfo> readProcessTable() override;
+    };
+}
+
+#endif // CAR_PROCFS_SERVER
diff --git a/service/Android.mk b/service/Android.mk
index 2b926df..8ee0147 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -42,6 +42,7 @@
         android.hardware.automotive.vehicle-V2.0-java \
         vehicle-hal-support-lib \
         car-systemtest \
+        com.android.car.procfsinspector-client \
 
 include $(BUILD_PACKAGE)
 
@@ -61,6 +62,7 @@
         android.hardware.automotive.vehicle-V2.0-java \
         vehicle-hal-support-lib \
         car-systemtest \
+        com.android.car.procfsinspector-client \
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 5c4dfaa..05073e3 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -101,6 +101,17 @@
       android:protectionLevel="system|signature"
       android:label="@string/car_permission_label_diag_clear"
       android:description="@string/car_permission_desc_diag_clear" />
+    <permission
+        android:name="android.car.permission.VMS_PUBLISHER"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_vms_publisher"
+        android:description="@string/car_permission_desc_vms_publisher" />
+    <permission
+        android:name="android.car.permission.VMS_SUBSCRIBER"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_vms_subscriber"
+        android:description="@string/car_permission_desc_vms_subscriber" />
+
     <!--  may replace this with system permission if proper one is defined. -->
     <permission
         android:name="android.car.permission.CONTROL_APP_BLOCKING"
@@ -143,6 +154,11 @@
                 android:label="@string/car_permission_car_cluster_control"
                 android:description="@string/car_permission_desc_car_cluster_control" />
 
+    <permission android:name="android.car.permission.STORAGE_MONITORING"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_storage_monitoring"
+        android:description="@string/car_permission_desc_storage_monitoring" />
+
     <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" />
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 73028cb..b228000 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -60,7 +60,7 @@
 
     <string name="instrumentClusterRendererService">android.car.cluster.sample/.SampleClusterServiceImpl</string>
 
-    <!--  Whether to enable Avtivity blocking for safety. When Activity blocking is enabled,
+    <!--  Whether to enable Activity blocking for safety. When Activity blocking is enabled,
           only whitelisted safe Activities will be allowed while car is not parked. -->
     <bool name="enableActivityBlockingForSafety">true</bool>
     <!--  Activity to be presented when un-safe activity is launched. Take a look at the javadoc of the
@@ -74,4 +74,35 @@
     <string name="defauiltActivityWhitelist">android,com.android.systemui</string>
     <!-- Default home activity -->
     <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">
+    </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>
+
+    <!-- Hours of uptime (excluding sleep) after which a 1% increase in the wear of the flash
+         storage in the head-unit is considered as acceptable level of wear. -->
+    <integer name="acceptableHoursPerOnePercentFlashWear">70</integer>
+
+    <!-- How often (in hours of uptime excluding sleep) CarService will flush to disk information
+         about the total running time of the head-unit. A shutdown or reboot of the head-unit
+          will always cause a flush of the uptime information, regardless of this setting. -->
+    <integer name="uptimeHoursIntervalBetweenUptimeDataWrite">5</integer>
+
+    <!-- The name of an activity to be launched by CarService whenever it detects a change in the
+         level of wear of the flash storage. Value must either be an empty string, which means that
+         no activity shall be launched, or must be in the format of a flattened ComponentName and
+         reference a valid activity. It is strongly recommended that the chosen activity be
+         protected with the android.car.permission.STORAGE_MONITORING permission. -->
+    <string name="activityHandlerForFlashWearChanges">com.google.android.car.defaultstoragemonitoringcompanionapp/.MainActivity</string>
+
+    <!-- How often (in seconds) CarService will update I/O metrics from the kernel. -->
+    <integer name="ioStatsRefreshRateSeconds">60</integer>
+
+    <!-- The number of I/O metrics samples to keep in memory at one time.
+         The system will keep a sliding window of samples of this size, and allow
+         retrieval of activity this many sample windows back in time. -->
+    <integer name="ioStatsNumSamplesToStore">15</integer>
 </resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 7f826d4..37503ab 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -116,4 +116,18 @@
     <!-- Permission text: apps can clear diagnostic data from the car [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_diag_clear">Clear diagnostic data from the car</string>
 
+    <!-- Permission text: apps can publish VMS data [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_vms_publisher">VMS publisher</string>
+    <!-- Permission text: apps can send VMS messages to the car [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_vms_publisher">Publish vms messages</string>
+
+    <!-- Permission text: apps can subscribe to VMS data [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_vms_subscriber">VMS subscriber</string>
+    <!-- Permission text: apps can receive VMS messages from the car [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_vms_subscriber">Subscribe to vms messages</string>
+
+    <!-- Permission text: apps can monitor flash storage usage [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_storage_monitoring">Flash storage monitoring</string>
+    <!-- Permission text: apps can monitor flash storage usage [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_storage_monitoring">Monitor flash storage usage</string>
 </resources>
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index f56b105..ab9d271 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -198,28 +198,32 @@
 
     @Override
     public void init() {
-        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
-        builder.setLooper(Looper.getMainLooper());
-        boolean isFocusSupported = mAudioHal.isFocusSupported();
-        if (isFocusSupported) {
-            builder.setAudioPolicyFocusListener(mSystemFocusListener);
-            FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
-            int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
-                    AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
-            synchronized (mLock) {
-                mCurrentFocusState = currentState;
-                mCurrentAudioContexts = 0;
-            }
-        }
         int audioHwVariant = mAudioHal.getHwVariant();
         AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
-        if (mUseDynamicRouting) {
-            setupDynamicRouting(audioRoutingPolicy, builder);
-        }
+
         AudioPolicy audioPolicy = null;
+        boolean isFocusSupported = mAudioHal.isFocusSupported();
+
+        // Do we need to install a custom audio policy?
         if (isFocusSupported || mUseDynamicRouting) {
+            AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+            builder.setLooper(Looper.getMainLooper());
+            if (isFocusSupported) {
+                builder.setAudioPolicyFocusListener(mSystemFocusListener);
+                FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
+                int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
+                        AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
+                synchronized (mLock) {
+                    mCurrentFocusState = currentState;
+                    mCurrentAudioContexts = 0;
+                }
+            }
+            if (mUseDynamicRouting) {
+                setupDynamicRouting(audioRoutingPolicy, builder);
+            }
             audioPolicy = builder.build();
         }
+
         mAudioHal.setFocusListener(this);
         mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
         mAudioHal.setOnParameterChangeListener(this);
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
index 809439c..3bd9979 100644
--- a/service/src/com/android/car/CarDiagnosticService.java
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -411,6 +411,13 @@
             diagnosticCapabilities.isFreezeFrameSupported();
     }
 
+    public boolean isSelectiveClearFreezeFramesSupported() {
+        DiagnosticCapabilities diagnosticCapabilities =
+            getDiagnosticHal().getDiagnosticCapabilities();
+        return isClearFreezeFramesSupported() &&
+                diagnosticCapabilities.isSelectiveClearFreezeFramesSupported();
+    }
+
     // ICarDiagnostic implementations
 
     @Override
@@ -441,14 +448,18 @@
     @Override
     public boolean clearFreezeFrames(long... timestamps) {
         mDiagnosticClearPermission.assertGranted();
-        if (mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameClearSupported()) {
-            mFreezeFrameDiagnosticRecords.lock();
-            mDiagnosticHal.clearFreezeFrames(timestamps);
-            mFreezeFrameDiagnosticRecords.clearEvents();
-            mFreezeFrameDiagnosticRecords.unlock();
-            return true;
+        if (!isClearFreezeFramesSupported())
+            return false;
+        if (timestamps != null && timestamps.length != 0) {
+            if (!isSelectiveClearFreezeFramesSupported()) {
+                return false;
+            }
         }
-        return false;
+        mFreezeFrameDiagnosticRecords.lock();
+        mDiagnosticHal.clearFreezeFrames(timestamps);
+        mFreezeFrameDiagnosticRecords.clearEvents();
+        mFreezeFrameDiagnosticRecords.unlock();
+        return true;
     }
 
     /**
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index d9600d6..6ca570f 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -43,6 +43,7 @@
     public static final String TAG_SYS = "CAR.SYS";
     public static final String TAG_TEST = "CAR.TEST";
     public static final String TAG_DIAGNOSTIC = "CAR.DIAGNOSTIC";
+    public static final String TAG_STORAGE = "CAR.STORAGE";
 
     public static String concatTag(String tagPrefix, Class clazz) {
         String tag = tagPrefix + "." + clazz.getSimpleName();
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 4eb04b4..745c46d 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -24,6 +24,7 @@
 
 import com.android.car.hal.PowerHalService;
 import com.android.car.hal.PowerHalService.PowerState;
+import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
index 87e891e..497135e 100644
--- a/service/src/com/android/car/CarService.java
+++ b/service/src/com/android/car/CarService.java
@@ -31,6 +31,8 @@
 import android.os.SystemProperties;
 import android.util.Log;
 
+import com.android.car.systeminterface.SystemInterface;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.RingBufferIndices;
 
@@ -85,7 +87,9 @@
 
         Log.i(CarLog.TAG_SERVICE, "Connected to " + mVehicleInterfaceName);
 
-        mICarImpl = new ICarImpl(this, mVehicle, SystemInterface.getDefault(this),
+        mICarImpl = new ICarImpl(this,
+                mVehicle,
+                SystemInterface.Builder.defaultSystemInterface(this).build(),
                 mCanBusErrorNotifier);
         mICarImpl.init();
         SystemProperties.set("boot.car_service_created", "1");
@@ -95,6 +99,9 @@
         super.onCreate();
     }
 
+    // onDestroy is best-effort and might not get called on shutdown/reboot. As such it is not
+    // suitable for permanently saving state or other need-to-happen operation. If you have a
+    // cleanup task that you want to make sure happens on shutdown/reboot, see OnShutdownReboot.
     @Override
     public void onDestroy() {
         Log.i(CarLog.TAG_SERVICE, "Service onDestroy");
diff --git a/service/src/com/android/car/CarStorageMonitoringService.java b/service/src/com/android/car/CarStorageMonitoringService.java
new file mode 100644
index 0000000..7404c08
--- /dev/null
+++ b/service/src/com/android/car/CarStorageMonitoringService.java
@@ -0,0 +1,338 @@
+/*
+ * 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.Car;
+import android.car.storagemonitoring.ICarStorageMonitoring;
+import android.car.storagemonitoring.UidIoStats;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.util.JsonWriter;
+import android.util.Log;
+import android.util.SparseArray;
+import com.android.car.internal.CarPermission;
+import com.android.car.storagemonitoring.IoStatsTracker;
+import com.android.car.storagemonitoring.UidIoStatsProvider;
+import com.android.car.storagemonitoring.WearEstimateRecord;
+import com.android.car.storagemonitoring.WearHistory;
+import com.android.car.storagemonitoring.WearInformation;
+import com.android.car.storagemonitoring.WearInformationProvider;
+import com.android.car.systeminterface.SystemInterface;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.json.JSONException;
+
+public class CarStorageMonitoringService extends ICarStorageMonitoring.Stub
+        implements CarServiceBase {
+    private static final boolean DBG = false;
+    private static final String TAG = CarLog.TAG_STORAGE;
+    private static final int MIN_WEAR_ESTIMATE_OF_CONCERN = 80;
+
+    static final String UPTIME_TRACKER_FILENAME = "service_uptime";
+    static final String WEAR_INFO_FILENAME = "wear_info";
+
+    private final WearInformationProvider[] mWearInformationProviders;
+    private final Context mContext;
+    private final File mUptimeTrackerFile;
+    private final File mWearInfoFile;
+    private final OnShutdownReboot mOnShutdownReboot;
+    private final SystemInterface mSystemInterface;
+    private final UidIoStatsProvider mUidIoStatsProvider;
+    private final SlidingWindow<SparseArray<UidIoStats>> mIoStatsSamples;
+    private final Object mIoStatsSamplesLock = new Object();
+
+    private final CarPermission mStorageMonitoringPermission;
+
+    private UptimeTracker mUptimeTracker = null;
+    private Optional<WearInformation> mWearInformation = Optional.empty();
+    private List<WearEstimateChange> mWearEstimateChanges = Collections.emptyList();
+    private List<UidIoStats> mBootIoStats = Collections.emptyList();
+    private IoStatsTracker mIoStatsTracker = null;
+    private boolean mInitialized = false;
+
+    public CarStorageMonitoringService(Context context, SystemInterface systemInterface) {
+        mContext = context;
+        Resources resources = mContext.getResources();
+        mUidIoStatsProvider = systemInterface.getUidIoStatsProvider();
+        mUptimeTrackerFile = new File(systemInterface.getFilesDir(), UPTIME_TRACKER_FILENAME);
+        mWearInfoFile = new File(systemInterface.getFilesDir(), WEAR_INFO_FILENAME);
+        mOnShutdownReboot = new OnShutdownReboot(mContext);
+        mSystemInterface = systemInterface;
+        mWearInformationProviders = systemInterface.getFlashWearInformationProviders();
+        mStorageMonitoringPermission =
+                new CarPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
+        mWearEstimateChanges = Collections.emptyList();
+        mIoStatsSamples = new SlidingWindow<>(
+            resources.getInteger(R.integer.ioStatsNumSamplesToStore));
+        systemInterface.scheduleActionForBootCompleted(this::doInitServiceIfNeeded,
+            Duration.ofSeconds(10));
+    }
+
+    private static long getUptimeSnapshotIntervalMs() {
+        return Duration.ofHours(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite).toMillis();
+    }
+
+    private Optional<WearInformation> loadWearInformation() {
+        for (WearInformationProvider provider : mWearInformationProviders) {
+            WearInformation wearInfo = provider.load();
+            if (wearInfo != null) {
+                Log.d(TAG, "retrieved wear info " + wearInfo + " via provider " + provider);
+                return Optional.of(wearInfo);
+            }
+        }
+
+        Log.d(TAG, "no wear info available");
+        return Optional.empty();
+    }
+
+    private WearHistory loadWearHistory() {
+        if (mWearInfoFile.exists()) {
+            try {
+                WearHistory wearHistory = WearHistory.fromJson(mWearInfoFile);
+                Log.d(TAG, "retrieved wear history " + wearHistory);
+                return wearHistory;
+            } catch (IOException | JSONException e) {
+                Log.e(TAG, "unable to read wear info file " + mWearInfoFile, e);
+            }
+        }
+
+        Log.d(TAG, "no wear history available");
+        return new WearHistory();
+    }
+
+    // returns true iff a new event was added (and hence the history needs to be saved)
+    private boolean addEventIfNeeded(WearHistory wearHistory) {
+        if (!mWearInformation.isPresent()) return false;
+
+        WearInformation wearInformation = mWearInformation.get();
+        WearEstimate lastWearEstimate;
+        WearEstimate currentWearEstimate = wearInformation.toWearEstimate();
+
+        if (wearHistory.size() == 0) {
+            lastWearEstimate = WearEstimate.UNKNOWN_ESTIMATE;
+        } else {
+            lastWearEstimate = wearHistory.getLast().getNewWearEstimate();
+        }
+
+        if (currentWearEstimate.equals(lastWearEstimate)) return false;
+
+        WearEstimateRecord newRecord = new WearEstimateRecord(lastWearEstimate,
+            currentWearEstimate,
+            mUptimeTracker.getTotalUptime(),
+            Instant.now());
+        Log.d(TAG, "new wear record generated " + newRecord);
+        wearHistory.add(newRecord);
+        return true;
+    }
+
+    private void storeWearHistory(WearHistory wearHistory) {
+        try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(mWearInfoFile))) {
+            wearHistory.writeToJson(jsonWriter);
+        } catch (IOException e) {
+            Log.e(TAG, "unable to write wear info file" + mWearInfoFile, e);
+        }
+    }
+
+    @Override
+    public void init() {
+        Log.d(TAG, "CarStorageMonitoringService init()");
+
+        mUptimeTracker = new UptimeTracker(mUptimeTrackerFile,
+            getUptimeSnapshotIntervalMs(),
+            mSystemInterface);
+    }
+
+    private void launchWearChangeActivity() {
+        final String activityPath = mContext.getResources().getString(
+            R.string.activityHandlerForFlashWearChanges);
+        if (activityPath.isEmpty()) return;
+        try {
+            final ComponentName activityComponent =
+                Objects.requireNonNull(ComponentName.unflattenFromString(activityPath));
+            Intent intent = new Intent();
+            intent.setComponent(activityComponent);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent);
+        } catch (ActivityNotFoundException | NullPointerException e) {
+            Log.e(TAG,
+                "value of activityHandlerForFlashWearChanges invalid non-empty string " +
+                    activityPath, e);
+        }
+    }
+
+    private static void logOnAdverseWearLevel(WearInformation wearInformation) {
+        if (wearInformation.preEolInfo > WearInformation.PRE_EOL_INFO_NORMAL ||
+            Math.max(wearInformation.lifetimeEstimateA,
+                wearInformation.lifetimeEstimateB) >= MIN_WEAR_ESTIMATE_OF_CONCERN) {
+            Log.w(TAG, "flash storage reached wear a level that requires attention: "
+                    + wearInformation);
+        }
+    }
+
+    private void collectNewIoMetrics() {
+        mIoStatsTracker.update(mUidIoStatsProvider.load());
+        synchronized (mIoStatsSamplesLock) {
+            mIoStatsSamples.add(mIoStatsTracker.getCurrentSample());
+        }
+
+        if (DBG) {
+            SparseArray<UidIoStats> currentSample = mIoStatsTracker.getCurrentSample();
+            if (currentSample.size() == 0) {
+                Log.d(TAG, "no new I/O stat data");
+            } else {
+                SparseArrayStream.valueStream(currentSample).forEach(
+                    uidIoStats -> Log.d(TAG, "updated I/O stat data: " + uidIoStats));
+            }
+        }
+    }
+
+    private synchronized void doInitServiceIfNeeded() {
+        if (mInitialized) return;
+
+        Log.d(TAG, "initializing CarStorageMonitoringService");
+
+        final Resources resources = mContext.getResources();
+
+        mWearInformation = loadWearInformation();
+
+        // TODO(egranata): can this be done lazily?
+        final WearHistory wearHistory = loadWearHistory();
+        final boolean didWearChangeHappen = addEventIfNeeded(wearHistory);
+        if (didWearChangeHappen) {
+            storeWearHistory(wearHistory);
+        }
+        Log.d(TAG, "wear history being tracked is " + wearHistory);
+        mWearEstimateChanges = wearHistory.toWearEstimateChanges(resources.getInteger(
+                        R.integer.acceptableHoursPerOnePercentFlashWear));
+
+        mOnShutdownReboot.addAction((Context ctx, Intent intent) -> release());
+
+        mWearInformation.ifPresent(CarStorageMonitoringService::logOnAdverseWearLevel);
+
+        if (didWearChangeHappen) {
+            launchWearChangeActivity();
+        }
+
+        long bootUptime = mSystemInterface.getUptime();
+        mBootIoStats = SparseArrayStream.valueStream(mUidIoStatsProvider.load())
+            .map(record -> {
+                // at boot, assume all UIDs have been running for as long as the system has
+                // been up, since we don't really know any better
+                UidIoStats stats = new UidIoStats(record, bootUptime);
+                if (DBG) {
+                    Log.d(TAG, "loaded boot I/O stat data: " + stats);
+                }
+                return stats;
+            }).collect(Collectors.toList());
+
+        final long newStatsDelayMs =
+                1000L * resources.getInteger(R.integer.ioStatsRefreshRateSeconds);
+
+        mIoStatsTracker = new IoStatsTracker(mBootIoStats,
+                newStatsDelayMs,
+                mSystemInterface.getSystemStateInterface());
+
+        mSystemInterface.scheduleAction(this::collectNewIoMetrics, newStatsDelayMs);
+
+        Log.i(TAG, "CarStorageMonitoringService is up");
+
+        mInitialized = true;
+    }
+
+    @Override
+    public void release() {
+        Log.i(TAG, "tearing down CarStorageMonitoringService");
+        if (mUptimeTracker != null) {
+            mUptimeTracker.onDestroy();
+        }
+        mOnShutdownReboot.clearActions();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        mStorageMonitoringPermission.assertGranted();
+        doInitServiceIfNeeded();
+
+        writer.println("*CarStorageMonitoringService*");
+        writer.println("last wear information retrieved: " +
+            mWearInformation.map(WearInformation::toString).orElse("missing"));
+        writer.println("wear change history: " +
+            mWearEstimateChanges.stream().map(WearEstimateChange::toString).reduce("",
+                (String s, String t) -> s + "\n" + t));
+    }
+
+    // ICarStorageMonitoring implementation
+
+    @Override
+    public int getPreEolIndicatorStatus() {
+        mStorageMonitoringPermission.assertGranted();
+        doInitServiceIfNeeded();
+
+        return mWearInformation.map(wi -> wi.preEolInfo)
+                .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO);
+    }
+
+    @Override
+    public WearEstimate getWearEstimate() {
+        mStorageMonitoringPermission.assertGranted();
+        doInitServiceIfNeeded();
+
+        return mWearInformation.map(wi ->
+                new WearEstimate(wi.lifetimeEstimateA,wi.lifetimeEstimateB)).orElse(
+                    WearEstimate.UNKNOWN_ESTIMATE);
+    }
+
+    @Override
+    public List<WearEstimateChange> getWearEstimateHistory() {
+        mStorageMonitoringPermission.assertGranted();
+        doInitServiceIfNeeded();
+
+        return mWearEstimateChanges;
+    }
+
+    @Override
+    public List<UidIoStats> getBootIoStats() {
+        mStorageMonitoringPermission.assertGranted();
+        doInitServiceIfNeeded();
+
+        return mBootIoStats;
+    }
+
+    @Override
+    public List<UidIoStats> getAggregateIoStats() {
+        mStorageMonitoringPermission.assertGranted();
+        doInitServiceIfNeeded();
+
+        return SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
+                .collect(Collectors.toList());
+    }
+}
diff --git a/service/src/com/android/car/CarTestService.java b/service/src/com/android/car/CarTestService.java
index ec1606c..8c3f64d 100644
--- a/service/src/com/android/car/CarTestService.java
+++ b/service/src/com/android/car/CarTestService.java
@@ -100,7 +100,7 @@
         }
 
         if (mTokens.size() == 0) {
-            mICarImpl.init();
+            CarServiceUtils.runOnMain(mICarImpl::init);
         }
     }
 
diff --git a/service/src/com/android/car/CarVolumeControllerFactory.java b/service/src/com/android/car/CarVolumeControllerFactory.java
index c83ef93..7dc3929 100644
--- a/service/src/com/android/car/CarVolumeControllerFactory.java
+++ b/service/src/com/android/car/CarVolumeControllerFactory.java
@@ -23,6 +23,7 @@
 import android.media.IVolumeController;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
@@ -90,8 +91,21 @@
      * support volume controls.
      */
     public static final class SimpleCarVolumeController extends CarVolumeController {
+        private static final String TAG = CarLog.TAG_AUDIO + ".SVolCtrl";
+        private static final int MSG_DISPLAY_SAFE_VOL_WARNING = 0;
+        private static final int MSG_VOL_CHANGED = 1;
+        private static final int MSG_MASTER_MUTE_CHANGED = 2;
+        private static final int MSG_SET_LAYOUT_DIRECTION = 3;
+        private static final int MSG_DISMISS = 4;
+        private static final int MSG_SET_ALLY_MODE = 5;
+
         private final AudioManager mAudioManager;
         private final Context mContext;
+        private RemoteCallbackList<IVolumeController> mVolumeCallbacks = new RemoteCallbackList<>();
+        @GuardedBy("this")
+        private IVolumeController mVolumeCallbackProxy;
+        private HandlerThread mVolumeThread;
+        private Handler mHandler;
 
         public SimpleCarVolumeController(Context context) {
             mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -100,16 +114,27 @@
 
         @Override
         void init() {
+            synchronized (this) {
+                mVolumeThread = new HandlerThread(TAG);
+                mVolumeThread.start();
+                mHandler = new VolumeHandler(mVolumeThread.getLooper());
+            }
         }
 
         @Override
         void release() {
+            synchronized (this) {
+                if (mVolumeThread != null) {
+                    mVolumeThread.quit();
+                }
+            }
+            mVolumeCallbacks.kill();
         }
 
         @Override
         public void setStreamVolume(int stream, int index, int flags) {
             if (DBG) {
-                Log.d(CarLog.TAG_AUDIO, "setStreamVolume " + stream + " " + index + " " + flags);
+                Log.d(TAG, "setStreamVolume " + stream + " " + index + " " + flags);
             }
             mAudioManager.setStreamVolume(stream, index, flags);
         }
@@ -121,7 +146,13 @@
 
         @Override
         public void setVolumeController(IVolumeController controller) {
-            mAudioManager.setVolumeController(controller);
+            synchronized (this) {
+                if (mVolumeCallbackProxy == null) {
+                    mVolumeCallbackProxy = new VolumeControllerProxy();
+                    mAudioManager.setVolumeController(mVolumeCallbackProxy);
+                }
+            }
+            mVolumeCallbacks.register(controller);
         }
 
         @Override
@@ -149,6 +180,146 @@
             // nothing else to dump
         }
 
+        private class VolumeHandler extends Handler {
+
+            public VolumeHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_DISPLAY_SAFE_VOL_WARNING:
+                        int count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .displaySafeVolumeWarning(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_VOL_CHANGED:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .volumeChanged(msg.arg1, msg.arg2);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_MASTER_MUTE_CHANGED:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .masterMuteChanged(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_SET_LAYOUT_DIRECTION:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .setLayoutDirection(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_DISMISS:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .dismiss();
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_SET_ALLY_MODE:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .setA11yMode(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                }
+            }
+        }
+
+        private class VolumeControllerProxy extends IVolumeController.Stub {
+            @Override
+            public void displaySafeVolumeWarning(int flags) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPLAY_SAFE_VOL_WARNING,
+                            flags, 0 /*unused*/));
+                }
+            }
+
+            @Override
+            public void volumeChanged(int streamType, int flags) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_VOL_CHANGED, streamType, flags));
+                }
+            }
+
+            @Override
+            public void masterMuteChanged(int flags) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_VOL_CHANGED, flags, 0 /*unused*/));
+                }
+            }
+
+            @Override
+            public void setLayoutDirection(int layoutDirection) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_VOL_CHANGED, layoutDirection, 0 /*unused*/));
+                }
+            }
+
+            @Override
+            public void dismiss() throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_DISMISS));
+                }
+            }
+
+            @Override
+            public void setA11yMode(int mode) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_SET_ALLY_MODE, mode, 0 /*unused*/));
+                }
+            }
+        }
+
         private void handleVolumeKeyDefault(KeyEvent event) {
             if (event.getAction() != KeyEvent.ACTION_DOWN
                     || interceptVolKeyBeforeDispatching(mContext)) {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index bbb86f6..7d28d12 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.car;
 
+import android.annotation.MainThread;
 import android.app.UiModeManager;
 import android.car.Car;
 import android.car.ICar;
@@ -36,6 +37,7 @@
 import com.android.car.internal.FeatureConfiguration;
 import com.android.car.internal.FeatureUtil;
 import com.android.car.pm.CarPackageManagerService;
+import com.android.car.systeminterface.SystemInterface;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.car.ICarServiceHelper;
 import java.io.PrintWriter;
@@ -71,7 +73,12 @@
     private final CarVendorExtensionService mCarVendorExtensionService;
     private final CarBluetoothService mCarBluetoothService;
     private final PerUserCarServiceHelper mPerUserCarServiceHelper;
-    private CarDiagnosticService mCarDiagnosticService;
+    private final CarDiagnosticService mCarDiagnosticService;
+    private final CarStorageMonitoringService mCarStorageMonitoringService;
+    @FutureFeature
+    private VmsSubscriberService mVmsSubscriberService;
+    @FutureFeature
+    private VmsPublisherService mVmsPublisherService;
 
     private final CarServiceBase[] mAllServices;
 
@@ -117,7 +124,11 @@
         mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext);
         mCarBluetoothService = new CarBluetoothService(serviceContext, mCarCabinService,
                 mCarSensorService, mPerUserCarServiceHelper);
+        mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
+        mVmsPublisherService = new VmsPublisherService(serviceContext, mHal.getVmsHal());
         mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal());
+        mCarStorageMonitoringService = new CarStorageMonitoringService(serviceContext,
+                systemInterface);
 
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>(Arrays.asList(
@@ -140,12 +151,16 @@
                 mCarVendorExtensionService,
                 mCarBluetoothService,
                 mCarDiagnosticService,
-                mPerUserCarServiceHelper
+                mPerUserCarServiceHelper,
+                mCarStorageMonitoringService,
+                mVmsSubscriberService,
+                mVmsPublisherService
         ));
         mAllServices = allServices.toArray(new CarServiceBase[0]);
     }
 
-    public void init() {
+    @MainThread
+    void init() {
         traceBegin("VehicleHal.init");
         mHal.init();
         traceEnd();
@@ -156,7 +171,7 @@
         traceEnd();
     }
 
-    public void release() {
+    void release() {
         // release done in opposite order from init
         for (int i = mAllServices.length - 1; i >= 0; i--) {
             mAllServices[i].release();
@@ -164,7 +179,7 @@
         mHal.release();
     }
 
-    public void vehicleHalReconnected(IVehicle vehicle) {
+    void vehicleHalReconnected(IVehicle vehicle) {
         mHal.vehicleHalReconnected(vehicle);
         for (CarServiceBase service : mAllServices) {
             service.vehicleHalReconnected();
@@ -221,6 +236,9 @@
             case Car.VENDOR_EXTENSION_SERVICE:
                 assertVendorExtensionPermission(mContext);
                 return mCarVendorExtensionService;
+            case Car.VMS_SUBSCRIBER_SERVICE:
+                assertVmsSubscriberPermission(mContext);
+                return mVmsSubscriberService;
             case Car.TEST_SERVICE: {
                 assertPermission(mContext, Car.PERMISSION_CAR_TEST_SERVICE);
                 synchronized (this) {
@@ -232,6 +250,9 @@
             }
             case Car.BLUETOOTH_SERVICE:
                 return mCarBluetoothService;
+            case Car.STORAGE_MONITORING_SERVICE:
+                assertPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
+                return mCarStorageMonitoringService;
             default:
                 Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
                 return null;
@@ -294,6 +315,16 @@
                 Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR);
     }
 
+    @FutureFeature
+    public static void assertVmsPublisherPermission(Context context) {
+        assertPermission(context, Car.PERMISSION_VMS_PUBLISHER);
+    }
+
+    @FutureFeature
+    public static void assertVmsSubscriberPermission(Context context) {
+        assertPermission(context, Car.PERMISSION_VMS_SUBSCRIBER);
+    }
+
     public static void assertPermission(Context context, String permission) {
         if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("requires " + permission);
@@ -328,11 +359,13 @@
         new CarShellCommand().exec(args, writer);
     }
 
+    @MainThread
     private static void traceBegin(String name) {
         Slog.i(TAG, name);
         mBootTiming.traceBegin(name);
     }
 
+    @MainThread
     private static void traceEnd() {
         mBootTiming.traceEnd();
     }
@@ -354,8 +387,8 @@
             pw.println("\t  Print this help text.");
             pw.println("\tday-night-mode [day|night|sensor]");
             pw.println("\t  Force into day/night mode or restore to auto.");
-            pw.println("\tinject-event zoned-boolean propertyType zone [true|false]");
-            pw.println("\t  Inject a Boolean HAL Event. ");
+            pw.println("\tday-night-mode [day|night|sensor]");
+            pw.println("\t  Force into day/night mode or restore to auto.");
         }
 
         public void exec(String[] args, PrintWriter writer) {
diff --git a/service/src/com/android/car/OnShutdownReboot.java b/service/src/com/android/car/OnShutdownReboot.java
new file mode 100644
index 0000000..701fabf
--- /dev/null
+++ b/service/src/com/android/car/OnShutdownReboot.java
@@ -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.
+ */
+
+package com.android.car;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BiConsumer;
+
+/**
+ * This class allows one to register actions they want executed when the vehicle is being shutdown
+ * or rebooted.
+ *
+ * To use this class instantiate it as part of your long-lived service, and then add actions to it.
+ * Actions receive the Context and Intent that go with the shutdown/reboot action, which allows the
+ * action to differentiate the two cases, should it need to do so.
+ *
+ * The actions will run on the UI thread.
+ */
+class OnShutdownReboot {
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            for (BiConsumer<Context, Intent> action : mActions) {
+                action.accept(context, intent);
+            }
+        }
+    };
+
+    private final CopyOnWriteArrayList<BiConsumer<Context, Intent>> mActions =
+            new CopyOnWriteArrayList<>();
+
+    OnShutdownReboot(Context context) {
+        mContext = context;
+        IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+        IntentFilter rebootFilter = new IntentFilter(Intent.ACTION_REBOOT);
+        mContext.registerReceiver(mReceiver, shutdownFilter);
+        mContext.registerReceiver(mReceiver, rebootFilter);
+    }
+
+    void addAction(BiConsumer<Context, Intent> action) {
+        mActions.add(action);
+    }
+
+    void clearActions() {
+        mActions.clear();
+    }
+}
diff --git a/service/src/com/android/car/SlidingWindow.java b/service/src/com/android/car/SlidingWindow.java
new file mode 100644
index 0000000..634f2eb
--- /dev/null
+++ b/service/src/com/android/car/SlidingWindow.java
@@ -0,0 +1,64 @@
+/*
+ * 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 java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * This class keeps track of a limited fixed number of sample data points, correctly removing
+ * older samples as new ones are added, and it allows inspecting the samples, as well as
+ * easily answering N out of M questions.
+ */
+class SlidingWindow<T> implements Iterable<T> {
+    private final ArrayDeque<T> mElements;
+    private final int mMaxSize;
+
+    public SlidingWindow(int size) {
+        mMaxSize = size;
+        mElements = new ArrayDeque<>(mMaxSize);
+    }
+
+    public void add(T sample) {
+        if (mElements.size() == mMaxSize) {
+            mElements.removeFirst();
+        }
+        mElements.addLast(sample);
+    }
+
+    public void addAll(Iterable<T> elements) {
+        elements.forEach(this::add);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return mElements.iterator();
+    }
+
+    public Stream<T> stream() {
+        return mElements.stream();
+    }
+
+    public int size() {
+        return mElements.size();
+    }
+
+    public int count(Predicate<T> predicate) {
+        return (int)stream().filter(predicate).count();
+    }
+}
diff --git a/service/src/com/android/car/SparseArrayStream.java b/service/src/com/android/car/SparseArrayStream.java
new file mode 100644
index 0000000..c3e8fcd
--- /dev/null
+++ b/service/src/com/android/car/SparseArrayStream.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;
+
+import android.util.Pair;
+import android.util.SparseArray;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+/**
+ * Helper class that provides Stream abstractions for android.util.SparseArray
+ */
+public class SparseArrayStream {
+    public static <E> IntStream keyStream(SparseArray<E> array) {
+        return IntStream.range(0, array.size()).map(array::keyAt);
+    }
+
+    public static <E> Stream<E> valueStream(SparseArray<E> array) {
+        return IntStream.range(0, array.size()).mapToObj(array::valueAt);
+    }
+
+    public static <E> Stream<Pair<Integer, E>> pairStream(SparseArray<E> array) {
+        return IntStream.range(0, array.size()).mapToObj(
+            i -> new Pair<>(array.keyAt(i), array.valueAt(i)));
+    }
+}
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index 2ff4e0e..4744ae5 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -208,7 +208,12 @@
         }
         int focusedStackId = -1;
         try {
-            focusedStackId = mAm.getFocusedStackId();
+            // TODO(b/66955160): Someone on the Auto-team should probably re-work the code in the
+            // synchronized block below based on this new API.
+            final StackInfo focusedStackInfo = mAm.getFocusedStackInfo();
+            if (focusedStackInfo != null) {
+                focusedStackId = focusedStackInfo.stackId;
+            }
         } catch (RemoteException e) {
             Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
             return;
@@ -251,16 +256,9 @@
     }
 
     public StackInfo getFocusedStackForTopActivity(ComponentName activity) {
-        int focusedStackId = -1;
-        try {
-            focusedStackId = mAm.getFocusedStackId();
-        } catch (RemoteException e) {
-            Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
-            return null;
-        }
         StackInfo focusedStack;
         try {
-            focusedStack = mAm.getStackInfo(focusedStackId);
+            focusedStack = mAm.getFocusedStackInfo();
         } catch (RemoteException e) {
             Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
             return null;
diff --git a/service/src/com/android/car/SystemInterface.java b/service/src/com/android/car/SystemInterface.java
deleted file mode 100644
index 6311d80..0000000
--- a/service/src/com/android/car/SystemInterface.java
+++ /dev/null
@@ -1,191 +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 com.android.car;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.Display;
-
-/**
- * Interface to abstract all system interaction.
- */
-public abstract class SystemInterface {
-    public abstract void setDisplayState(boolean on);
-    public abstract void releaseAllWakeLocks();
-    public abstract void shutdown();
-    public abstract void enterDeepSleep(int wakeupTimeSec);
-    public abstract void switchToPartialWakeLock();
-    public abstract void switchToFullWakeLock();
-    public abstract void startDisplayStateMonitoring(CarPowerManagementService service);
-    public abstract void stopDisplayStateMonitoring();
-    public abstract boolean isSystemSupportingDeepSleep();
-    public abstract boolean isWakeupCausedByTimer();
-
-
-    public static SystemInterface getDefault(Context context) {
-        return new SystemInterfaceImpl(context);
-    }
-
-    private static class SystemInterfaceImpl extends SystemInterface {
-        private final PowerManager mPowerManager;
-        private final DisplayManager mDisplayManager;
-        private final WakeLock mFullWakeLock;
-        private final WakeLock mPartialWakeLock;
-        private final DisplayStateListener mDisplayListener;
-        private CarPowerManagementService mService;
-        private boolean mDisplayStateSet;
-
-        private SystemInterfaceImpl(Context context) {
-            mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-            mFullWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK,
-                    CarLog.TAG_POWER);
-            mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    CarLog.TAG_POWER);
-            mDisplayListener = new DisplayStateListener();
-        }
-
-        @Override
-        public void startDisplayStateMonitoring(CarPowerManagementService service) {
-            synchronized (this) {
-                mService = service;
-                mDisplayStateSet = isMainDisplayOn();
-            }
-            mDisplayManager.registerDisplayListener(mDisplayListener, service.getHandler());
-        }
-
-        @Override
-        public void stopDisplayStateMonitoring() {
-            mDisplayManager.unregisterDisplayListener(mDisplayListener);
-        }
-
-        @Override
-        public void setDisplayState(boolean on) {
-            synchronized (this) {
-                mDisplayStateSet = on;
-            }
-            if (on) {
-                switchToFullWakeLock();
-                Log.i(CarLog.TAG_POWER, "on display");
-                mPowerManager.wakeUp(SystemClock.uptimeMillis());
-            } else {
-                switchToPartialWakeLock();
-                Log.i(CarLog.TAG_POWER, "off display");
-                mPowerManager.goToSleep(SystemClock.uptimeMillis());
-            }
-        }
-
-        private boolean isMainDisplayOn() {
-            Display disp = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
-            return disp.getState() == Display.STATE_ON;
-        }
-
-        @Override
-        public void shutdown() {
-            mPowerManager.shutdown(false /* no confirm*/, null, true /* true */);
-        }
-
-        @Override
-        public void enterDeepSleep(int wakeupTimeSec) {
-            //TODO set wake up time, bug: 32061842
-            mPowerManager.goToSleep(SystemClock.uptimeMillis(),
-                    PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN,
-                    PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
-        }
-
-        @Override
-        public boolean isSystemSupportingDeepSleep() {
-            //TODO should return by checking some kernel suspend control sysfs, bug: 32061842
-            return false;
-        }
-
-        @Override
-        public void switchToPartialWakeLock() {
-            if (!mPartialWakeLock.isHeld()) {
-                mPartialWakeLock.acquire();
-            }
-            if (mFullWakeLock.isHeld()) {
-                mFullWakeLock.release();
-            }
-        }
-
-        @Override
-        public void switchToFullWakeLock() {
-            if (!mFullWakeLock.isHeld()) {
-                mFullWakeLock.acquire();
-            }
-            if (mPartialWakeLock.isHeld()) {
-                mPartialWakeLock.release();
-            }
-        }
-
-        @Override
-        public void releaseAllWakeLocks() {
-            if (mPartialWakeLock.isHeld()) {
-                mPartialWakeLock.release();
-            }
-            if (mFullWakeLock.isHeld()) {
-                mFullWakeLock.release();
-            }
-        }
-
-        @Override
-        public boolean isWakeupCausedByTimer() {
-            //TODO bug: 32061842, check wake up reason and do necessary operation information should
-            // come from kernel. it can be either power on or wake up for maintenance
-            // power on will involve GPIO trigger from power controller
-            // its own wakeup will involve timer expiration.
-            return false;
-        }
-
-        private void handleMainDisplayChanged() {
-            boolean isOn = isMainDisplayOn();
-            CarPowerManagementService service;
-            synchronized (this) {
-                if (mDisplayStateSet == isOn) { // same as what is set
-                    return;
-                }
-                service = mService;
-            }
-            service.handleMainDisplayChanged(isOn);
-        }
-
-        private class DisplayStateListener implements DisplayManager.DisplayListener {
-
-            @Override
-            public void onDisplayAdded(int displayId) {
-                //ignore
-            }
-
-            @Override
-            public void onDisplayChanged(int displayId) {
-                if (displayId == Display.DEFAULT_DISPLAY) {
-                    handleMainDisplayChanged();
-                }
-            }
-
-            @Override
-            public void onDisplayRemoved(int displayId) {
-                //ignore
-            }
-        }
-    }
-}
diff --git a/service/src/com/android/car/UptimeTracker.java b/service/src/com/android/car/UptimeTracker.java
new file mode 100644
index 0000000..2a1d26e
--- /dev/null
+++ b/service/src/com/android/car/UptimeTracker.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;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.car.systeminterface.SystemInterface;
+
+import com.android.car.systeminterface.TimeInterface;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * A class that can keep track of how long its instances are alive for.
+ *
+ * It can be used as a helper object to track the lifetime of system components, e.g.
+ *
+ * class InterestingService {
+ *     private UptimeTracker mTracker;
+ *
+ *     public void onCreate() {
+ *         mTracker = new UptimeTracker(
+ *             "/storage/emulated/0/Android/data/interestingservice.uptime", 1 hour);
+ *         mTracker.onCreate();
+ *     }
+ *
+ *     public void onDestroy() {
+ *         mTracker.onDestroy();
+ *     }
+ * }
+ *
+ * Now it's possible to know how long InterestingService has been alive in the system by querying
+ * mTracker.getTotalUptime(). Because this data is stored to disk, the uptime is maintained across
+ * process and system reboot boundaries. It is possible to configure periodic snapshot points to
+ * ensure that crashes do not cause more than a certain amount of uptime to go untracked.
+ */
+public class UptimeTracker {
+    /**
+     * In order to prevent excessive wear-out of the storage, do not allow snapshots to happen
+     * more frequently than this value
+     */
+    public static final long MINIMUM_SNAPSHOT_INTERVAL_MS = 60 * 60 * 1000;
+
+    /**
+     * The default snapshot interval if none is given
+     */
+    private static long DEFAULT_SNAPSHOT_INTERVAL_MS = 5 * 60 * 60 * 1000; // 5 hours
+
+    private final Object mLock = new Object();
+
+    /**
+     * The file that uptime metrics are stored to
+     */
+    private File mUptimeFile;
+
+    /**
+     * The uptime value retrieved from mUptimeFile
+     */
+    private Optional<Long> mHistoricalUptime;
+
+    /**
+     * Last value of elapsedRealTime read from the system
+     */
+    private long mLastRealTimeSnapshot;
+
+    /**
+     * The source of real-time and scheduling
+     */
+    private TimeInterface mTimeInterface;
+
+    public UptimeTracker(File file) {
+        this(file, DEFAULT_SNAPSHOT_INTERVAL_MS);
+    }
+
+    public UptimeTracker(File file, long snapshotInterval) {
+        this(file, snapshotInterval, new TimeInterface.DefaultImpl());
+    }
+
+    UptimeTracker(File file, long snapshotInterval, SystemInterface systemInterface) {
+        this(file, snapshotInterval, systemInterface.getTimeInterface());
+    }
+
+    // This constructor allows one to replace the source of time-based truths with
+    // a mock version. This is mostly useful for testing purposes.
+    @VisibleForTesting
+    UptimeTracker(File file,
+            long snapshotInterval,
+            TimeInterface timeInterface) {
+        snapshotInterval = Math.max(snapshotInterval, MINIMUM_SNAPSHOT_INTERVAL_MS);
+        mUptimeFile = Objects.requireNonNull(file);
+        mTimeInterface = timeInterface;
+        mLastRealTimeSnapshot = mTimeInterface.getUptime(TimeInterface.EXCLUDE_DEEP_SLEEP_TIME);
+        mHistoricalUptime = Optional.empty();
+
+        mTimeInterface.scheduleAction(this::flushSnapshot, snapshotInterval);
+    }
+
+    void onDestroy() {
+        synchronized (mLock) {
+            if (mTimeInterface != null) {
+                mTimeInterface.cancelAllActions();
+            }
+            flushSnapshot();
+            mTimeInterface = null;
+            mUptimeFile = null;
+        }
+    }
+
+    /**
+     * Return the total amount of uptime that has been observed, in milliseconds.
+     *
+     * This is the sum of the uptime stored on disk + the uptime seen since the last snapshot.
+     */
+    long getTotalUptime() {
+        synchronized (mLock) {
+            if (mTimeInterface == null) {
+                return 0;
+            }
+            return getHistoricalUptimeLocked() + (
+                    mTimeInterface.getUptime(TimeInterface.EXCLUDE_DEEP_SLEEP_TIME)
+                            - mLastRealTimeSnapshot);
+        }
+    }
+
+    private long getHistoricalUptimeLocked() {
+        if (!mHistoricalUptime.isPresent() && mUptimeFile != null) {
+            try {
+                JsonReader reader = new JsonReader(new FileReader(mUptimeFile));
+                reader.beginObject();
+                if (!reader.nextName().equals("uptime")) {
+                    throw new IllegalArgumentException(
+                        mUptimeFile + " is not in a valid format");
+                } else {
+                    mHistoricalUptime = Optional.of(reader.nextLong());
+                }
+                reader.endObject();
+                reader.close();
+            } catch (IllegalArgumentException | IOException e) {
+                Log.w(CarLog.TAG_SERVICE, "unable to read historical uptime data", e);
+                mHistoricalUptime = Optional.empty();
+            }
+        }
+        return mHistoricalUptime.orElse(0L);
+    }
+
+    private void flushSnapshot() {
+        synchronized (mLock) {
+            if (mUptimeFile == null) {
+                return;
+            }
+            try {
+                long newUptime = getTotalUptime();
+                mHistoricalUptime = Optional.of(newUptime);
+                mLastRealTimeSnapshot = mTimeInterface.getUptime(
+                        TimeInterface.EXCLUDE_DEEP_SLEEP_TIME);
+
+                JsonWriter writer = new JsonWriter(new FileWriter(mUptimeFile));
+                writer.beginObject();
+                writer.name("uptime");
+                writer.value(newUptime);
+                writer.endObject();
+                writer.close();
+            } catch (IOException e) {
+                Log.w(CarLog.TAG_SERVICE, "unable to write historical uptime data", e);
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/VmsLayersAvailability.java b/service/src/com/android/car/VmsLayersAvailability.java
new file mode 100644
index 0000000..4c730c1
--- /dev/null
+++ b/service/src/com/android/car/VmsLayersAvailability.java
@@ -0,0 +1,200 @@
+/*
+ * 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.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
+import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Manages VMS availability for layers.
+ * <p>
+ * Each VMS publisher sets its layers offering which are a list of layers the publisher claims
+ * it might publish. VmsLayersAvailability calculates from all the offering what are the
+ * available layers.
+ *
+ * @hide
+ */
+
+public class VmsLayersAvailability {
+
+    private static final boolean DBG = true;
+    private static final String TAG = "VmsLayersAvailability";
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<VmsLayer, Set<Set<VmsLayer>>> mPotentialLayersAndDependencies =
+            new HashMap<>();
+    @GuardedBy("mLock")
+    private Set<VmsAssociatedLayer> mAvailableAssociatedLayers = Collections.EMPTY_SET;
+    @GuardedBy("mLock")
+    private Set<VmsAssociatedLayer> mUnavailableAssociatedLayers = Collections.EMPTY_SET;
+    @GuardedBy("mLock")
+    private Map<VmsLayer, Set<Integer>> mPotentialLayersAndPublishers = new HashMap<>();
+
+    /**
+     * Setting the current layers offerings as reported by publishers.
+     */
+    public void setPublishersOffering(Collection<VmsLayersOffering> publishersLayersOfferings) {
+        synchronized (mLock) {
+            reset();
+
+            for (VmsLayersOffering offering : publishersLayersOfferings) {
+                for (VmsLayerDependency dependency : offering.getDependencies()) {
+                    VmsLayer layer = dependency.getLayer();
+
+                    // Associate publishers with layers.
+                    Set<Integer> curPotentialLayerAndPublishers =
+                            mPotentialLayersAndPublishers.get(layer);
+                    if (curPotentialLayerAndPublishers == null) {
+                        curPotentialLayerAndPublishers = new HashSet<>();
+                        mPotentialLayersAndPublishers.put(layer, curPotentialLayerAndPublishers);
+                    }
+                    curPotentialLayerAndPublishers.add(offering.getPublisherId());
+
+                    // Add dependencies for availability calculation.
+                    Set<Set<VmsLayer>> curDependencies =
+                            mPotentialLayersAndDependencies.get(layer);
+                    if (curDependencies == null) {
+                        curDependencies = new HashSet<>();
+                        mPotentialLayersAndDependencies.put(layer, curDependencies);
+                    }
+                    curDependencies.add(dependency.getDependencies());
+                }
+            }
+            calculateLayers();
+        }
+    }
+
+    /**
+     * Returns a collection of all the layers which may be published.
+     */
+    public Set<VmsAssociatedLayer> getAvailableLayers() {
+        synchronized (mLock) {
+            return mAvailableAssociatedLayers;
+        }
+    }
+
+    /**
+     * Returns a collection of all the layers which publishers could have published if the
+     * dependencies were satisfied.
+     */
+    public Set<VmsAssociatedLayer> getUnavailableLayers() {
+        synchronized (mLock) {
+            return mUnavailableAssociatedLayers;
+        }
+    }
+
+    private void reset() {
+        synchronized (mLock) {
+            mPotentialLayersAndDependencies.clear();
+            mPotentialLayersAndPublishers.clear();
+            mAvailableAssociatedLayers = Collections.EMPTY_SET;
+            mUnavailableAssociatedLayers = Collections.EMPTY_SET;
+        }
+    }
+
+    private void calculateLayers() {
+        synchronized (mLock) {
+            Set<VmsLayer> availableLayersSet = new HashSet<>();
+            Set<VmsLayer> cyclicAvoidanceAuxiliarySet = new HashSet<>();
+
+            for (VmsLayer layer : mPotentialLayersAndDependencies.keySet()) {
+                addLayerToAvailabilityCalculationLocked(layer,
+                        availableLayersSet,
+                        cyclicAvoidanceAuxiliarySet);
+            }
+
+            mAvailableAssociatedLayers = Collections.unmodifiableSet(
+                    availableLayersSet
+                            .stream()
+                            .map(l -> new VmsAssociatedLayer(l, mPotentialLayersAndPublishers.get(l)))
+                            .collect(Collectors.toSet()));
+
+            mUnavailableAssociatedLayers = Collections.unmodifiableSet(
+                    mPotentialLayersAndDependencies.keySet()
+                            .stream()
+                            .filter(l -> !availableLayersSet.contains(l))
+                            .map(l -> new VmsAssociatedLayer(l, mPotentialLayersAndPublishers.get(l)))
+                            .collect(Collectors.toSet()));
+        }
+    }
+
+    private void addLayerToAvailabilityCalculationLocked(VmsLayer layer,
+                                                         Set<VmsLayer> currentAvailableLayers,
+                                                         Set<VmsLayer> cyclicAvoidanceSet) {
+        if (DBG) {
+            Log.d(TAG, "addLayerToAvailabilityCalculationLocked: checking layer: " + layer);
+        }
+        // If we already know that this layer is supported then we are done.
+        if (currentAvailableLayers.contains(layer)) {
+            return;
+        }
+        // If there is no offering for this layer we're done.
+        if (!mPotentialLayersAndDependencies.containsKey(layer)) {
+            return;
+        }
+        // Avoid cyclic dependency.
+        if (cyclicAvoidanceSet.contains(layer)) {
+            Log.e(TAG, "Detected a cyclic dependency: " + cyclicAvoidanceSet + " -> " + layer);
+            return;
+        }
+        // A layer may have multiple dependency sets. The layer is available if any dependency
+        // set is satisfied
+        for (Set<VmsLayer> dependencies : mPotentialLayersAndDependencies.get(layer)) {
+            // If layer does not have any dependencies then add to supported.
+            if (dependencies == null || dependencies.isEmpty()) {
+                currentAvailableLayers.add(layer);
+                return;
+            }
+            // Add the layer to cyclic avoidance set
+            cyclicAvoidanceSet.add(layer);
+
+            boolean isSupported = true;
+            for (VmsLayer dependency : dependencies) {
+                addLayerToAvailabilityCalculationLocked(dependency,
+                        currentAvailableLayers,
+                        cyclicAvoidanceSet);
+
+                if (!currentAvailableLayers.contains(dependency)) {
+                    isSupported = false;
+                    break;
+                }
+            }
+            cyclicAvoidanceSet.remove(layer);
+
+            if (isSupported) {
+                currentAvailableLayers.add(layer);
+                return;
+            }
+        }
+        return;
+    }
+}
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
new file mode 100644
index 0000000..2d2acf6
--- /dev/null
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -0,0 +1,338 @@
+/*
+ * 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.annotation.SystemApi;
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.IVmsPublisherClient;
+import android.car.vms.IVmsPublisherService;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsSubscriptionState;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.car.hal.VmsHalService;
+import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Receives HAL updates by implementing VmsHalService.VmsHalListener.
+ * Binds to publishers and configures them to use this service.
+ * Notifies publishers of subscription changes.
+ *
+ * @hide
+ */
+@SystemApi
+public class VmsPublisherService extends IVmsPublisherService.Stub
+        implements CarServiceBase, VmsHalService.VmsHalPublisherListener {
+    private static final boolean DBG = true;
+    private static final String TAG = "VmsPublisherService";
+
+    private final Context mContext;
+    private final VmsHalService mHal;
+    private final VmsPublisherManager mPublisherManager;
+    private Set<String> mSafePermissions;
+
+    public VmsPublisherService(Context context, VmsHalService hal) {
+        mContext = context;
+        mHal = hal;
+        mPublisherManager = new VmsPublisherManager(this);
+    }
+
+    // Implements CarServiceBase interface.
+    @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);
+        for (String publisherName : publisherNames) {
+            if (TextUtils.isEmpty(publisherName)) {
+                Log.e(TAG, "empty publisher name");
+                continue;
+            }
+            ComponentName name = ComponentName.unflattenFromString(publisherName);
+            if (name == null) {
+                Log.e(TAG, "invalid publisher name: " + publisherName);
+            }
+            mPublisherManager.bind(name);
+        }
+    }
+
+    @Override
+    public void release() {
+        mPublisherManager.release();
+        mHal.removePublisherListener(this);
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+    }
+
+    @Override
+    public void setLayersOffering(IBinder token, VmsLayersOffering offering) {
+        mHal.setPublisherLayersOffering(token, offering);
+    }
+
+    // Implements IVmsPublisherService interface.
+    @Override
+    public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
+        if (DBG) {
+            Log.d(TAG, "Publishing for layer: " + layer);
+        }
+        ICarImpl.assertVmsPublisherPermission(mContext);
+
+        // Send the message to application listeners.
+        Set<IVmsSubscriberClient> listeners =
+                mHal.getSubscribersForLayerFromPublisher(layer, publisherId);
+
+        if (DBG) {
+            Log.d(TAG, "Number of subscribed apps: " + listeners.size());
+        }
+        for (IVmsSubscriberClient listener : listeners) {
+            try {
+                listener.onVmsMessageReceived(layer, payload);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to publish to listener: " + listener);
+            }
+        }
+
+        // Send the message to HAL
+        if (mHal.isHalSubscribed(layer)) {
+            Log.d(TAG, "HAL is subscribed");
+            mHal.setDataMessage(layer, payload);
+        } else {
+            Log.d(TAG, "HAL is NOT subscribed");
+        }
+    }
+
+    @Override
+    public VmsSubscriptionState getSubscriptions() {
+        ICarImpl.assertVmsPublisherPermission(mContext);
+        return mHal.getSubscriptionState();
+    }
+
+    @Override
+    public int getPublisherId(byte[] publisherInfo) {
+        ICarImpl.assertVmsPublisherPermission(mContext);
+        return mHal.getPublisherId(publisherInfo);
+    }
+
+    // Implements VmsHalListener interface
+    /**
+     * This method is only invoked by VmsHalService.notifyPublishers which is synchronized.
+     * Therefore this method only sees a non-decreasing sequence.
+     */
+    @Override
+    public void onChange(VmsSubscriptionState subscriptionState) {
+        // Send the message to application listeners.
+        for (IVmsPublisherClient client : mPublisherManager.getClients()) {
+            try {
+                client.onVmsSubscriptionChange(subscriptionState);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to send notification to: " + client, ex);
+            }
+        }
+    }
+
+    /**
+     * Keeps track of publishers that are using this service.
+     */
+    private static class VmsPublisherManager {
+        /**
+         * Allows to modify mPublisherMap and mPublisherConnectionMap as a single unit.
+         */
+        private final Object mLock = new Object();
+        @GuardedBy("mLock")
+        private final Map<String, PublisherConnection> mPublisherConnectionMap = new HashMap<>();
+        @GuardedBy("mLock")
+        private final Map<String, IVmsPublisherClient> mPublisherMap = new HashMap<>();
+        private final WeakReference<VmsPublisherService> mPublisherService;
+
+        public VmsPublisherManager(VmsPublisherService publisherService) {
+            mPublisherService = new WeakReference<>(publisherService);
+        }
+
+        /**
+         * Tries to bind to a publisher.
+         *
+         * @param name publisher component name (e.g. android.car.vms.logger/.LoggingService).
+         */
+        public void bind(ComponentName name) {
+            VmsPublisherService publisherService = mPublisherService.get();
+            if (publisherService == null) return;
+            String publisherName = name.flattenToString();
+            if (DBG) {
+                Log.d(TAG, "binding to: " + publisherName);
+            }
+            synchronized (mLock) {
+                if (mPublisherConnectionMap.containsKey(publisherName)) {
+                    // Already registered, nothing to do.
+                    return;
+                }
+                grantPermissions(name);
+                Intent intent = new Intent();
+                intent.setComponent(name);
+                PublisherConnection connection = new PublisherConnection();
+                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);
+                }
+            }
+        }
+
+        /**
+         * Removes the publisher and associated connection.
+         *
+         * @param name publisher component name (e.g. android.car.vms.Logger).
+         */
+        public void unbind(ComponentName name) {
+            VmsPublisherService publisherService = mPublisherService.get();
+            if (publisherService == null) return;
+            String publisherName = name.flattenToString();
+            if (DBG) {
+                Log.d(TAG, "unbinding from: " + publisherName);
+            }
+            synchronized (mLock) {
+                boolean found = mPublisherMap.remove(publisherName) != null;
+                if (found) {
+                    PublisherConnection connection = mPublisherConnectionMap.get(publisherName);
+                    publisherService.mContext.unbindService(connection);
+                    mPublisherConnectionMap.remove(publisherName);
+                } else {
+                    Log.e(TAG, "unbind: unknown publisher." + publisherName);
+                }
+            }
+        }
+
+        /**
+         * Returns the list of publishers currently registered.
+         *
+         * @return list of publishers.
+         */
+        public List<IVmsPublisherClient> getClients() {
+            synchronized (mLock) {
+                return new ArrayList<>(mPublisherMap.values());
+            }
+        }
+
+        public void release() {
+            VmsPublisherService publisherService = mPublisherService.get();
+            if (publisherService == null) return;
+            for (PublisherConnection connection : mPublisherConnectionMap.values()) {
+                publisherService.mContext.unbindService(connection);
+            }
+            mPublisherConnectionMap.clear();
+            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();
+
+            /**
+             * Once the service binds to a publisher service, the publisher binder is added to
+             * mPublisherMap
+             * and the publisher is configured to use this service.
+             */
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder binder) {
+                VmsPublisherService publisherService = mPublisherService.get();
+                if (publisherService == null) return;
+                if (DBG) {
+                    Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
+                }
+                IVmsPublisherClient service = IVmsPublisherClient.Stub.asInterface(binder);
+                synchronized (mLock) {
+                    mPublisherMap.put(name.flattenToString(), service);
+                }
+                try {
+                    service.setVmsPublisherService(mToken, publisherService);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "unable to configure publisher: " + name);
+                }
+            }
+
+            /**
+             * Tries to rebind to the publisher service.
+             */
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                String publisherName = name.flattenToString();
+                Log.d(TAG, "onServiceDisconnected, name: " + publisherName);
+                VmsPublisherManager.this.unbind(name);
+                VmsPublisherManager.this.bind(name);
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/VmsPublishersInfo.java b/service/src/com/android/car/VmsPublishersInfo.java
new file mode 100644
index 0000000..0f73e5c
--- /dev/null
+++ b/service/src/com/android/car/VmsPublishersInfo.java
@@ -0,0 +1,95 @@
+/*
+ * 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 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;
+
+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
new file mode 100644
index 0000000..fa29a27
--- /dev/null
+++ b/service/src/com/android/car/VmsRouting.java
@@ -0,0 +1,413 @@
+/*
+ * 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.vms.IVmsSubscriberClient;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsOperationRecorder;
+import android.car.vms.VmsSubscriptionState;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Manages all the VMS subscriptions:
+ * + Subscriptions to data messages of individual layer + version.
+ * + Subscriptions to all data messages.
+ * + HAL subscriptions to layer + version.
+ */
+
+public class VmsRouting {
+    private final Object mLock = new Object();
+    // A map of Layer + Version to subscribers.
+    @GuardedBy("mLock")
+    private Map<VmsLayer, Set<IVmsSubscriberClient>> mLayerSubscriptions = new HashMap<>();
+
+    @GuardedBy("mLock")
+    private Map<VmsLayer, Map<Integer, Set<IVmsSubscriberClient>>> mLayerSubscriptionsToPublishers =
+            new HashMap<>();
+    // A set of subscribers that are interested in any layer + version.
+    @GuardedBy("mLock")
+    private Set<IVmsSubscriberClient> mPromiscuousSubscribers = new HashSet<>();
+
+    // A set of all the layers + versions the HAL is subscribed to.
+    @GuardedBy("mLock")
+    private Set<VmsLayer> mHalSubscriptions = new HashSet<>();
+
+    @GuardedBy("mLock")
+    private Map<VmsLayer, Set<Integer>> mHalSubscriptionsToPublishers = new HashMap<>();
+    // A sequence number that is increased every time the subscription state is modified. Note that
+    // modifying the list of promiscuous subscribers does not affect the subscription state.
+    @GuardedBy("mLock")
+    private int mSequenceNumber = 0;
+
+    /**
+     * Add a subscriber subscription to data messages from a VMS layer.
+     *
+     * @param subscriber a VMS subscriber.
+     * @param layer      the layer subscribing to.
+     */
+    public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
+        //TODO(b/36902947): revise if need to sync, and return value.
+        synchronized (mLock) {
+            ++mSequenceNumber;
+            // Get or create the list of subscribers for layer and version.
+            Set<IVmsSubscriberClient> subscribers = mLayerSubscriptions.get(layer);
+
+            if (subscribers == null) {
+                subscribers = new HashSet<>();
+                mLayerSubscriptions.put(layer, subscribers);
+            }
+            // Add the subscriber to the list.
+            subscribers.add(subscriber);
+            VmsOperationRecorder.get().addSubscription(mSequenceNumber, layer);
+        }
+    }
+
+    /**
+     * Add a subscriber subscription to all data messages.
+     *
+     * @param subscriber a VMS subscriber.
+     */
+    public void addSubscription(IVmsSubscriberClient subscriber) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+            mPromiscuousSubscribers.add(subscriber);
+            VmsOperationRecorder.get().addPromiscuousSubscription(mSequenceNumber);
+        }
+    }
+
+    /**
+     * Add a subscriber subscription to data messages from a VMS layer from a specific publisher.
+     *
+     * @param subscriber  a VMS subscriber.
+     * @param layer       the layer to subscribing to.
+     * @param publisherId the publisher ID.
+     */
+    public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer, int publisherId) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+
+            Map<Integer, Set<IVmsSubscriberClient>> publisherIdsToSubscribersForLayer =
+                    mLayerSubscriptionsToPublishers.get(layer);
+
+            if (publisherIdsToSubscribersForLayer == null) {
+                publisherIdsToSubscribersForLayer = new HashMap<>();
+                mLayerSubscriptionsToPublishers.put(layer, publisherIdsToSubscribersForLayer);
+            }
+
+            Set<IVmsSubscriberClient> subscribersForPublisher =
+                    publisherIdsToSubscribersForLayer.get(publisherId);
+
+            if (subscribersForPublisher == null) {
+                subscribersForPublisher = new HashSet<>();
+                publisherIdsToSubscribersForLayer.put(publisherId, subscribersForPublisher);
+            }
+
+            // Add the subscriber to the list.
+            subscribersForPublisher.add(subscriber);
+        }
+    }
+
+    /**
+     * Remove a subscription for a layer + version and make sure to remove the key if there are no
+     * more subscribers.
+     *
+     * @param subscriber to remove.
+     * @param layer      of the subscription.
+     */
+    public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+            Set<IVmsSubscriberClient> subscribers = mLayerSubscriptions.get(layer);
+
+            // If there are no subscribers we are done.
+            if (subscribers == null) {
+                return;
+            }
+            subscribers.remove(subscriber);
+            VmsOperationRecorder.get().removeSubscription(mSequenceNumber, layer);
+
+            // If there are no more subscribers then remove the list.
+            if (subscribers.isEmpty()) {
+                mLayerSubscriptions.remove(layer);
+            }
+        }
+    }
+
+    /**
+     * Remove a subscriber subscription to all data messages.
+     *
+     * @param subscriber a VMS subscriber.
+     */
+    public void removeSubscription(IVmsSubscriberClient subscriber) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+            mPromiscuousSubscribers.remove(subscriber);
+            VmsOperationRecorder.get().removePromiscuousSubscription(mSequenceNumber);
+        }
+    }
+
+    /**
+     * Remove a subscription to data messages from a VMS layer from a specific publisher.
+     *
+     * @param subscriber  a VMS subscriber.
+     * @param layer       the layer to unsubscribing from.
+     * @param publisherId the publisher ID.
+     */
+    public void removeSubscription(IVmsSubscriberClient subscriber,
+                                   VmsLayer layer,
+                                   int publisherId) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+
+            Map<Integer, Set<IVmsSubscriberClient>> subscribersToPublishers =
+                    mLayerSubscriptionsToPublishers.get(layer);
+
+            if (subscribersToPublishers == null) {
+                return;
+            }
+
+            Set<IVmsSubscriberClient> subscribers = subscribersToPublishers.get(publisherId);
+
+            if (subscribers == null) {
+                return;
+            }
+            subscribers.remove(subscriber);
+
+            if (subscribers.isEmpty()) {
+                subscribersToPublishers.remove(publisherId);
+            }
+
+            if (subscribersToPublishers.isEmpty()) {
+                mLayerSubscriptionsToPublishers.remove(layer);
+            }
+        }
+    }
+
+    /**
+     * Remove a subscriber from all routes (optional operation).
+     *
+     * @param subscriber a VMS subscriber.
+     */
+    public void removeDeadSubscriber(IVmsSubscriberClient subscriber) {
+        synchronized (mLock) {
+            // Remove the subscriber from all the routes.
+            for (VmsLayer layer : mLayerSubscriptions.keySet()) {
+                removeSubscription(subscriber, layer);
+            }
+            // Remove the subscriber from the loggers.
+            removeSubscription(subscriber);
+        }
+    }
+
+    /**
+     * Returns a list of all the subscribers for a layer from a publisher. This includes
+     * subscribers that subscribed to this layer from all publishers, subscribed to this layer
+     * from a specific publisher, and the promiscuous subscribers.
+     *
+     * @param layer       The layer of the message.
+     * @param publisherId the ID of the client that published the message to be routed.
+     * @return a list of the subscribers.
+     */
+    public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
+                                                                         int publisherId) {
+        Set<IVmsSubscriberClient> subscribers = new HashSet<>();
+        synchronized (mLock) {
+            // Add the subscribers which explicitly subscribed to this layer
+            if (mLayerSubscriptions.containsKey(layer)) {
+                subscribers.addAll(mLayerSubscriptions.get(layer));
+            }
+
+            // Add the subscribers which explicitly subscribed to this layer and publisher
+            if (mLayerSubscriptionsToPublishers.containsKey(layer)) {
+                if (mLayerSubscriptionsToPublishers.get(layer).containsKey(publisherId)) {
+                    subscribers.addAll(mLayerSubscriptionsToPublishers.get(layer).get(publisherId));
+                }
+            }
+
+            // Add the promiscuous subscribers.
+            subscribers.addAll(mPromiscuousSubscribers);
+        }
+        return subscribers;
+    }
+
+    /**
+     * Returns a list with all the subscribers.
+     */
+    public Set<IVmsSubscriberClient> getAllSubscribers() {
+        Set<IVmsSubscriberClient> subscribers = new HashSet<>();
+        synchronized (mLock) {
+            for (VmsLayer layer : mLayerSubscriptions.keySet()) {
+                subscribers.addAll(mLayerSubscriptions.get(layer));
+            }
+            // Add the promiscuous subscribers.
+            subscribers.addAll(mPromiscuousSubscribers);
+        }
+        return subscribers;
+    }
+
+    /**
+     * Checks if a subscriber is subscribed to any messages.
+     *
+     * @param subscriber that may have subscription.
+     * @return true if the subscriber uis subscribed to messages.
+     */
+    public boolean containsSubscriber(IVmsSubscriberClient subscriber) {
+        synchronized (mLock) {
+            // Check if subscriber is subscribed to a layer.
+            for (Set<IVmsSubscriberClient> layerSubscribers : mLayerSubscriptions.values()) {
+                if (layerSubscribers.contains(subscriber)) {
+                    return true;
+                }
+            }
+            // Check is subscriber is subscribed to all data messages.
+            return mPromiscuousSubscribers.contains(subscriber);
+        }
+    }
+
+    /**
+     * Add a layer and version to the HAL subscriptions.
+     *
+     * @param layer the HAL subscribes to.
+     */
+    public void addHalSubscription(VmsLayer layer) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+            mHalSubscriptions.add(layer);
+            VmsOperationRecorder.get().addHalSubscription(mSequenceNumber, layer);
+        }
+    }
+
+    public void addHalSubscriptionToPublisher(VmsLayer layer, int publisherId) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+
+            Set<Integer> publisherIdsForLayer = mHalSubscriptionsToPublishers.get(layer);
+            if (publisherIdsForLayer == null) {
+                publisherIdsForLayer = new HashSet<>();
+                mHalSubscriptionsToPublishers.put(layer, publisherIdsForLayer);
+            }
+            publisherIdsForLayer.add(publisherId);
+        }
+    }
+
+    /**
+     * remove a layer and version to the HAL subscriptions.
+     *
+     * @param layer the HAL unsubscribes from.
+     */
+    public void removeHalSubscription(VmsLayer layer) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+            mHalSubscriptions.remove(layer);
+            VmsOperationRecorder.get().removeHalSubscription(mSequenceNumber, layer);
+        }
+    }
+
+    public void removeHalSubscriptionToPublisher(VmsLayer layer, int publisherId) {
+        synchronized (mLock) {
+            ++mSequenceNumber;
+
+            Set<Integer> publisherIdsForLayer = mHalSubscriptionsToPublishers.get(layer);
+            if (publisherIdsForLayer == null) {
+                return;
+            }
+            publisherIdsForLayer.remove(publisherId);
+
+            if (publisherIdsForLayer.isEmpty()) {
+                mHalSubscriptionsToPublishers.remove(layer);
+            }
+        }
+    }
+
+    /**
+     * checks if the HAL is subscribed to a layer.
+     *
+     * @param layer
+     * @return true if the HAL is subscribed to layer.
+     */
+    public boolean isHalSubscribed(VmsLayer layer) {
+        synchronized (mLock) {
+            return mHalSubscriptions.contains(layer);
+        }
+    }
+
+    /**
+     * checks if there are subscribers to a layer.
+     *
+     * @param layer
+     * @return true if there are subscribers to layer.
+     */
+    public boolean hasLayerSubscriptions(VmsLayer layer) {
+        synchronized (mLock) {
+            return mLayerSubscriptions.containsKey(layer) || mHalSubscriptions.contains(layer);
+        }
+    }
+
+    /**
+     * returns true if there is already a subscription for the layer from publisherId.
+     *
+     * @param layer
+     * @param publisherId
+     * @return
+     */
+    public boolean hasLayerFromPublisherSubscriptions(VmsLayer layer, int publisherId) {
+        synchronized (mLock) {
+            boolean hasClientSubscription =
+                    mLayerSubscriptionsToPublishers.containsKey(layer) &&
+                            mLayerSubscriptionsToPublishers.get(layer).containsKey(publisherId);
+
+            boolean hasHalSubscription = mHalSubscriptionsToPublishers.containsKey(layer) &&
+                    mHalSubscriptionsToPublishers.get(layer).contains(publisherId);
+
+            return hasClientSubscription || hasHalSubscription;
+        }
+    }
+
+    /**
+     * @return a Set of layers and versions which VMS clients are subscribed to.
+     */
+    public VmsSubscriptionState getSubscriptionState() {
+        synchronized (mLock) {
+            Set<VmsLayer> layers = new HashSet<>();
+            layers.addAll(mLayerSubscriptions.keySet());
+            layers.addAll(mHalSubscriptions);
+
+
+            Set<VmsAssociatedLayer> layersFromPublishers = new HashSet<>();
+            layersFromPublishers.addAll(mLayerSubscriptionsToPublishers.entrySet()
+                    .stream()
+                    .map(e -> new VmsAssociatedLayer(e.getKey(), e.getValue().keySet()))
+                    .collect(Collectors.toSet()));
+            layersFromPublishers.addAll(mHalSubscriptionsToPublishers.entrySet()
+                    .stream()
+                    .map(e -> new VmsAssociatedLayer(e.getKey(), e.getValue()))
+                    .collect(Collectors.toSet()));
+
+            return new VmsSubscriptionState(mSequenceNumber, layers, layersFromPublishers);
+        }
+    }
+}
\ No newline at end of file
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
new file mode 100644
index 0000000..5031fb4
--- /dev/null
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -0,0 +1,353 @@
+/*
+ * 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.annotation.SystemApi;
+import android.car.Car;
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.IVmsSubscriberService;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.car.hal.VmsHalService;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * + Receives HAL updates by implementing VmsHalService.VmsHalListener.
+ * + Offers subscriber/publisher services by implementing IVmsService.Stub.
+ */
+@SystemApi
+public class VmsSubscriberService extends IVmsSubscriberService.Stub
+        implements CarServiceBase, VmsHalService.VmsHalSubscriberListener {
+    private static final boolean DBG = true;
+    private static final String PERMISSION = Car.PERMISSION_VMS_SUBSCRIBER;
+    private static final String TAG = "VmsSubscriberService";
+
+    private final Context mContext;
+    private final VmsHalService mHal;
+
+    @GuardedBy("mSubscriberServiceLock")
+    private final VmsSubscribersManager mSubscribersManager = new VmsSubscribersManager();
+    private final Object mSubscriberServiceLock = new Object();
+
+    /**
+     * Keeps track of subscribers of this service.
+     */
+    class VmsSubscribersManager {
+        /**
+         * Allows to modify mSubscriberMap and mListenerDeathRecipientMap as a single unit.
+         */
+        private final Object mListenerManagerLock = new Object();
+        @GuardedBy("mListenerManagerLock")
+        private final Map<IBinder, ListenerDeathRecipient> mListenerDeathRecipientMap =
+                new HashMap<>();
+        @GuardedBy("mListenerManagerLock")
+        private final Map<IBinder, IVmsSubscriberClient> mSubscriberMap = new HashMap<>();
+
+        class ListenerDeathRecipient implements IBinder.DeathRecipient {
+            private IBinder mSubscriberBinder;
+
+            ListenerDeathRecipient(IBinder subscriberBinder) {
+                mSubscriberBinder = subscriberBinder;
+            }
+
+            /**
+             * Listener died. Remove it from this service.
+             */
+            @Override
+            public void binderDied() {
+                if (DBG) {
+                    Log.d(TAG, "binderDied " + mSubscriberBinder);
+                }
+
+                // Get the Listener from the Binder
+                IVmsSubscriberClient subscriber = mSubscriberMap.get(mSubscriberBinder);
+
+                // Remove the subscriber subscriptions.
+                if (subscriber != null) {
+                    Log.d(TAG, "Removing subscriptions for dead subscriber: " + subscriber);
+                    mHal.removeDeadSubscriber(subscriber);
+                } else {
+                    Log.d(TAG, "Handling dead binder with no matching subscriber");
+
+                }
+
+                // Remove binder
+                VmsSubscribersManager.this.removeListener(mSubscriberBinder);
+            }
+
+            void release() {
+                mSubscriberBinder.unlinkToDeath(this, 0);
+            }
+        }
+
+        public void release() {
+            for (ListenerDeathRecipient recipient : mListenerDeathRecipientMap.values()) {
+                recipient.release();
+            }
+            mListenerDeathRecipientMap.clear();
+            mSubscriberMap.clear();
+        }
+
+        /**
+         * Adds the subscriber and a death recipient associated to it.
+         *
+         * @param subscriber to be added.
+         * @throws IllegalArgumentException if the subscriber is null.
+         * @throws IllegalStateException    if it was not possible to link a death recipient to the
+         *                                  subscriber.
+         */
+        public void add(IVmsSubscriberClient subscriber) {
+            ICarImpl.assertPermission(mContext, PERMISSION);
+            if (subscriber == null) {
+                Log.e(TAG, "register: subscriber is null.");
+                throw new IllegalArgumentException("subscriber cannot be null.");
+            }
+            if (DBG) {
+                Log.d(TAG, "register: " + subscriber);
+            }
+            IBinder subscriberBinder = subscriber.asBinder();
+            synchronized (mListenerManagerLock) {
+                if (mSubscriberMap.containsKey(subscriberBinder)) {
+                    // Already registered, nothing to do.
+                    return;
+                }
+                ListenerDeathRecipient deathRecipient =
+                        new ListenerDeathRecipient(subscriberBinder);
+                try {
+                    subscriberBinder.linkToDeath(deathRecipient, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to link death for recipient. ", e);
+                    throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG);
+                }
+                mListenerDeathRecipientMap.put(subscriberBinder, deathRecipient);
+                mSubscriberMap.put(subscriberBinder, subscriber);
+            }
+        }
+
+        /**
+         * Removes the subscriber and associated death recipient.
+         *
+         * @param subscriber to be removed.
+         * @throws IllegalArgumentException if subscriber is null.
+         */
+        public void remove(IVmsSubscriberClient subscriber) {
+            if (DBG) {
+                Log.d(TAG, "unregisterListener");
+            }
+            ICarImpl.assertPermission(mContext, PERMISSION);
+            if (subscriber == null) {
+                Log.e(TAG, "unregister: subscriber is null.");
+                throw new IllegalArgumentException("Listener is null");
+            }
+            IBinder subscriberBinder = subscriber.asBinder();
+            removeListener(subscriberBinder);
+        }
+
+        // Removes the subscriberBinder from the current state.
+        // The function assumes that binder will exist both in subscriber and death recipients list.
+        private void removeListener(IBinder subscriberBinder) {
+            synchronized (mListenerManagerLock) {
+                boolean found = mSubscriberMap.remove(subscriberBinder) != null;
+                if (found) {
+                    mListenerDeathRecipientMap.get(subscriberBinder).release();
+                    mListenerDeathRecipientMap.remove(subscriberBinder);
+                } else {
+                    Log.e(TAG, "removeListener: subscriber was not previously registered.");
+                }
+            }
+        }
+
+        /**
+         * Returns list of subscribers currently registered.
+         *
+         * @return list of subscribers.
+         */
+        public List<IVmsSubscriberClient> getListeners() {
+            synchronized (mListenerManagerLock) {
+                return new ArrayList<>(mSubscriberMap.values());
+            }
+        }
+    }
+
+    public VmsSubscriberService(Context context, VmsHalService hal) {
+        mContext = context;
+        mHal = hal;
+    }
+
+    // Implements CarServiceBase interface.
+    @Override
+    public void init() {
+        mHal.addSubscriberListener(this);
+    }
+
+    @Override
+    public void release() {
+        mSubscribersManager.release();
+        mHal.removeSubscriberListener(this);
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+    }
+
+    // Implements IVmsService interface.
+    @Override
+    public void addVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
+        synchronized (mSubscriberServiceLock) {
+            // Add the subscriber so it can subscribe.
+            mSubscribersManager.add(subscriber);
+        }
+    }
+
+    @Override
+    public void removeVmsSubscriberToNotifications(IVmsSubscriberClient subscriber) {
+        synchronized (mSubscriberServiceLock) {
+            if (mHal.containsSubscriber(subscriber)) {
+                throw new IllegalArgumentException("Subscriber has active subscriptions.");
+            }
+            mSubscribersManager.remove(subscriber);
+        }
+    }
+
+    @Override
+    public void addVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
+        synchronized (mSubscriberServiceLock) {
+            // Add the subscriber so it can subscribe.
+            mSubscribersManager.add(subscriber);
+
+            // Add the subscription for the layer.
+            mHal.addSubscription(subscriber, layer);
+        }
+    }
+
+    @Override
+    public void removeVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
+        synchronized (mSubscriberServiceLock) {
+            // Remove the subscription.
+            mHal.removeSubscription(subscriber, layer);
+        }
+    }
+
+    @Override
+    public void addVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
+                                                  VmsLayer layer,
+                                                  int publisherId) {
+        synchronized (mSubscriberServiceLock) {
+            // Add the subscriber so it can subscribe.
+            mSubscribersManager.add(subscriber);
+
+            // Add the subscription for the layer.
+            mHal.addSubscription(subscriber, layer, publisherId);
+        }
+    }
+
+    @Override
+    public void removeVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
+                                                     VmsLayer layer,
+                                                     int publisherId) {
+        synchronized (mSubscriberServiceLock) {
+            // Remove the subscription.
+            mHal.removeSubscription(subscriber, layer, publisherId);
+        }
+    }
+
+    @Override
+    public void addVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
+        synchronized (mSubscriberServiceLock) {
+            mSubscribersManager.add(subscriber);
+            mHal.addSubscription(subscriber);
+        }
+    }
+
+    @Override
+    public void removeVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
+        synchronized (mSubscriberServiceLock) {
+            // Remove the subscription.
+            mHal.removeSubscription(subscriber);
+        }
+    }
+
+    @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();
+    }
+
+    // Implements VmsHalSubscriberListener interface
+    @Override
+    public void onDataMessage(VmsLayer layer, int publisherId, byte[] payload) {
+        if (DBG) {
+            Log.d(TAG, "Publishing a message for layer: " + layer);
+        }
+
+        Set<IVmsSubscriberClient> subscribers =
+                mHal.getSubscribersForLayerFromPublisher(layer, publisherId);
+
+        for (IVmsSubscriberClient subscriber : subscribers) {
+            try {
+                subscriber.onVmsMessageReceived(layer, payload);
+            } 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, "onVmsMessageReceived calling failed: ", e);
+            }
+        }
+    }
+
+    @Override
+    public void onLayersAvaiabilityChange(List<VmsAssociatedLayer> availableLayers) {
+        if (DBG) {
+            Log.d(TAG, "Publishing layers availability change: " + availableLayers);
+        }
+
+        Set<IVmsSubscriberClient> subscribers;
+        synchronized (mSubscriberServiceLock) {
+            subscribers = new HashSet<>(mSubscribersManager.getListeners());
+        }
+
+        for (IVmsSubscriberClient subscriber : subscribers) {
+            try {
+                subscriber.onLayersAvailabilityChanged(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, "onLayersAvailabilityChanged calling failed: ", e);
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index 4ff0ada..bcbae7a 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -42,6 +42,8 @@
  * higher-level semantic information
  */
 public class DiagnosticHalService extends SensorHalServiceBase {
+    static final int OBD2_SELECTIVE_FRAME_CLEAR = 1;
+
     public static class DiagnosticCapabilities {
         private final CopyOnWriteArraySet<Integer> mProperties = new CopyOnWriteArraySet<>();
 
@@ -69,6 +71,10 @@
             return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
         }
 
+        public boolean isSelectiveClearFreezeFramesSupported() {
+            return isSupported(OBD2_SELECTIVE_FRAME_CLEAR);
+        }
+
         void clear() {
             mProperties.clear();
         }
@@ -102,6 +108,17 @@
                 return propConfig.prop;
             case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR:
                 mDiagnosticCapabilities.setSupported(propConfig.prop);
+                Log.i(CarLog.TAG_DIAGNOSTIC, String.format(
+                        "configArray for OBD2_FREEZE_FRAME_CLEAR is %s", propConfig.configArray));
+                if (propConfig.configArray.size() < 1) {
+                    Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
+                            "property 0x%x does not specify whether it supports selective " +
+                            "clearing of freeze frames. assuming it does not.", propConfig.prop));
+                } else {
+                    if (propConfig.configArray.get(0) == 1) {
+                        mDiagnosticCapabilities.setSupported(OBD2_SELECTIVE_FRAME_CLEAR);
+                    }
+                }
                 return propConfig.prop;
             default:
                 return SENSOR_TYPE_INVALID;
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 44c81d7..3c54bfc 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -80,6 +80,8 @@
     private final VendorExtensionHalService mVendorExtensionHal;
     private DiagnosticHalService mDiagnosticHal = null;
 
+    @FutureFeature
+    private VmsHalService mVmsHal;
 
 
     /** Might be re-assigned if Vehicle HAL is reconnected. */
@@ -106,6 +108,7 @@
         mHvacHal = new HvacHalService(this);
         mInputHal = new InputHalService(this);
         mVendorExtensionHal = new VendorExtensionHalService(this);
+        mVmsHal = new VmsHalService(this);
         mDiagnosticHal = new DiagnosticHalService(this);
         mAllServices.addAll(Arrays.asList(mPowerHal,
                 mSensorHal,
@@ -116,7 +119,8 @@
                 mHvacHal,
                 mInputHal,
                 mVendorExtensionHal,
-                mDiagnosticHal));
+                mDiagnosticHal,
+                mVmsHal));
 
         mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/);
     }
@@ -124,7 +128,7 @@
     /** Dummy version only for testing */
     @VisibleForTesting
     public VehicleHal(PowerHalService powerHal, SensorHalService sensorHal, InfoHalService infoHal,
-            AudioHalService audioHal, CabinHalService cabinHal,
+            AudioHalService audioHal, CabinHalService cabinHal, DiagnosticHalService diagnosticHal,
             RadioHalService radioHal, HvacHalService hvacHal, HalClient halClient) {
         mHandlerThread = null;
         mPowerHal = powerHal;
@@ -132,33 +136,14 @@
         mInfoHal = infoHal;
         mAudioHal = audioHal;
         mCabinHal = cabinHal;
+        mDiagnosticHal = diagnosticHal;
         mRadioHal = radioHal;
         mHvacHal = hvacHal;
         mInputHal = null;
         mVendorExtensionHal = null;
-        mDiagnosticHal = null;
-
+        mVmsHal = null;
         mHalClient = halClient;
-    }
-
-    /** Dummy version only for testing */
-    @VisibleForTesting
-    public VehicleHal(PowerHalService powerHal, SensorHalService sensorHal, InfoHalService infoHal,
-            AudioHalService audioHal, CabinHalService cabinHal, DiagnosticHalService diagnosticHal,
-            RadioHalService radioHal, HvacHalService hvacHal, HalClient halClient) {
-            mHandlerThread = null;
-            mPowerHal = powerHal;
-            mSensorHal = sensorHal;
-            mInfoHal = infoHal;
-            mAudioHal = audioHal;
-            mCabinHal = cabinHal;
-            mDiagnosticHal = diagnosticHal;
-            mRadioHal = radioHal;
-            mHvacHal = hvacHal;
-            mInputHal = null;
-            mVendorExtensionHal = null;
-            mHalClient = halClient;
-            mDiagnosticHal = diagnosticHal;
+        mDiagnosticHal = diagnosticHal;
     }
 
     public void vehicleHalReconnected(IVehicle vehicle) {
@@ -268,6 +253,9 @@
         return mVendorExtensionHal;
     }
 
+    @FutureFeature
+    public VmsHalService getVmsHal() { return mVmsHal; }
+
     private void assertServiceOwnerLocked(HalServiceBase service, int property) {
         if (service != mPropertyHandlers.get(property)) {
             throw new IllegalArgumentException("Property 0x" + toHexString(property)
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
new file mode 100644
index 0000000..36da390
--- /dev/null
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -0,0 +1,850 @@
+/*
+ * 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.hal;
+
+import static com.android.car.CarServiceUtils.toByteArray;
+import static java.lang.Integer.toHexString;
+
+import android.annotation.SystemApi;
+import android.car.VehicleAreaType;
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsOperationRecorder;
+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_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerAndPublisherIdIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_0.VmsOfferingMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerIntegerValuesIndex;
+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.Arrays;
+import java.util.ArrayList;
+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;
+
+/**
+ * This is a glue layer between the VehicleHal and the VmsService. It sends VMS properties back and
+ * forth.
+ */
+@SystemApi
+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 final static List<Integer> AVAILABILITY_MESSAGE_TYPES = Collections.unmodifiableList(
+            Arrays.asList(
+                    VmsMessageType.AVAILABILITY_RESPONSE,
+                    VmsMessageType.AVAILABILITY_CHANGE));
+
+    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;
+
+    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.
+     */
+    public interface VmsHalPublisherListener {
+        void onChange(VmsSubscriptionState subscriptionState);
+    }
+
+    /**
+     * The VmsSubscriberService implements this interface to receive data from the HAL.
+     */
+    public interface VmsHalSubscriberListener {
+        // Notifies the listener on a data Message from a publisher.
+        void onDataMessage(VmsLayer layer, int publisherId, byte[] payload);
+
+        // Notifies the listener on a change in available layers.
+        void onLayersAvaiabilityChange(List<VmsAssociatedLayer> availableLayers);
+    }
+
+    /**
+     * The VmsService implements this interface to receive data from the HAL.
+     */
+    protected VmsHalService(VehicleHal vehicleHal) {
+        mVehicleHal = vehicleHal;
+        if (DBG) {
+            Log.d(TAG, "started VmsHalService!");
+        }
+    }
+
+    public void addPublisherListener(VmsHalPublisherListener listener) {
+        mPublisherListeners.add(listener);
+    }
+
+    public void addSubscriberListener(VmsHalSubscriberListener listener) {
+        mSubscriberListeners.add(listener);
+    }
+
+    public void removePublisherListener(VmsHalPublisherListener listener) {
+        mPublisherListeners.remove(listener);
+    }
+
+    public void removeSubscriberListener(VmsHalSubscriberListener listener) {
+        mSubscriberListeners.remove(listener);
+    }
+
+    public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer) {
+        boolean firstSubscriptionForLayer = false;
+        synchronized (mLock) {
+            // Check if publishers need to be notified about this change in subscriptions.
+            firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
+
+            // Add the listeners subscription to the layer
+            mRouting.addSubscription(listener, layer);
+        }
+        if (firstSubscriptionForLayer) {
+            notifyHalPublishers(layer, true);
+            notifyClientPublishers();
+        }
+    }
+
+    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);
+                return;
+            }
+
+            // Remove the listeners subscription to the layer
+            mRouting.removeSubscription(listener, layer);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
+        }
+        if (!layerHasSubscribers) {
+            notifyHalPublishers(layer, false);
+            notifyClientPublishers();
+        }
+    }
+
+    public void addSubscription(IVmsSubscriberClient listener) {
+        synchronized (mLock) {
+            mRouting.addSubscription(listener);
+        }
+    }
+
+    public void removeSubscription(IVmsSubscriberClient listener) {
+        synchronized (mLock) {
+            mRouting.removeSubscription(listener);
+        }
+    }
+
+    public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer, int publisherId) {
+        boolean firstSubscriptionForLayer = false;
+        synchronized (mLock) {
+            // Check if publishers need to be notified about this change in subscriptions.
+            firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer) ||
+                    mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
+
+            // Add the listeners subscription to the layer
+            mRouting.addSubscription(listener, layer, publisherId);
+        }
+        if (firstSubscriptionForLayer) {
+            notifyHalPublishers(layer, true);
+            notifyClientPublishers();
+        }
+    }
+
+    public void removeSubscription(IVmsSubscriberClient listener, VmsLayer layer, int publisherId) {
+        boolean layerHasSubscribers = true;
+        synchronized (mLock) {
+            if (!mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId)) {
+                Log.i(TAG, "Trying to remove a layer with no subscription: " +
+                        layer + ", publisher ID:" + publisherId);
+                return;
+            }
+
+            // Remove the listeners subscription to the layer
+            mRouting.removeSubscription(listener, layer, publisherId);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer) ||
+                    mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
+        }
+        if (!layerHasSubscribers) {
+            notifyHalPublishers(layer, false);
+            notifyClientPublishers();
+        }
+    }
+
+    public void removeDeadSubscriber(IVmsSubscriberClient listener) {
+        synchronized (mLock) {
+            mRouting.removeDeadSubscriber(listener);
+        }
+    }
+
+    public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
+                                                                         int publisherId) {
+        synchronized (mLock) {
+            return mRouting.getSubscribersForLayerFromPublisher(layer, publisherId);
+        }
+    }
+
+    public Set<IVmsSubscriberClient> getAllSubscribers() {
+        synchronized (mLock) {
+            return mRouting.getAllSubscribers();
+        }
+    }
+
+    public boolean isHalSubscribed(VmsLayer layer) {
+        synchronized (mLock) {
+            return mRouting.isHalSubscribed(layer);
+        }
+    }
+
+    public VmsSubscriptionState getSubscriptionState() {
+        synchronized (mLock) {
+            return mRouting.getSubscriptionState();
+        }
+    }
+
+    /**
+     * 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 getPublisherId(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);
+        }
+    }
+
+    private void addHalSubscription(VmsLayer layer) {
+        boolean firstSubscriptionForLayer = true;
+        synchronized (mLock) {
+            // Check if publishers need to be notified about this change in subscriptions.
+            firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
+
+            // Add the listeners subscription to the layer
+            mRouting.addHalSubscription(layer);
+        }
+        if (firstSubscriptionForLayer) {
+            notifyHalPublishers(layer, true);
+            notifyClientPublishers();
+        }
+    }
+
+    private void addHalSubscriptionToPublisher(VmsLayer layer, int publisherId) {
+        boolean firstSubscriptionForLayer = true;
+        synchronized (mLock) {
+            // Check if publishers need to be notified about this change in subscriptions.
+            firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer) ||
+                    mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
+
+            // Add the listeners subscription to the layer
+            mRouting.addHalSubscriptionToPublisher(layer, publisherId);
+        }
+        if (firstSubscriptionForLayer) {
+            notifyHalPublishers(layer, publisherId, true);
+            notifyClientPublishers();
+        }
+    }
+
+    private 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);
+                return;
+            }
+
+            // Remove the listeners subscription to the layer
+            mRouting.removeHalSubscription(layer);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
+        }
+        if (!layerHasSubscribers) {
+            notifyHalPublishers(layer, false);
+            notifyClientPublishers();
+        }
+    }
+
+    public void removeHalSubscriptionFromPublisher(VmsLayer layer, int publisherId) {
+        boolean layerHasSubscribers = true;
+        synchronized (mLock) {
+            if (!mRouting.hasLayerSubscriptions(layer)) {
+                Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
+                return;
+            }
+
+            // Remove the listeners subscription to the layer
+            mRouting.removeHalSubscriptionToPublisher(layer, publisherId);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer) ||
+                    mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
+        }
+        if (!layerHasSubscribers) {
+            notifyHalPublishers(layer, publisherId, false);
+            notifyClientPublishers();
+        }
+    }
+
+    public boolean containsSubscriber(IVmsSubscriberClient subscriber) {
+        synchronized (mLock) {
+            return mRouting.containsSubscriber(subscriber);
+        }
+    }
+
+    public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering) {
+        Set<VmsAssociatedLayer> availableLayers = Collections.EMPTY_SET;
+        synchronized (mLock) {
+            updateOffering(publisherToken, offering);
+            VmsOperationRecorder.get().setPublisherLayersOffering(offering);
+            availableLayers = mAvailableLayers.getAvailableLayers();
+        }
+        notifyOfAvailabilityChange(availableLayers);
+    }
+
+    public Set<VmsAssociatedLayer> 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.
+     *
+     * @param layer          layer which is being subscribed to or unsubscribed from.
+     * @param hasSubscribers indicates if the notification is for subscription or unsubscription.
+     */
+    private void notifyHalPublishers(VmsLayer layer, boolean hasSubscribers) {
+        // notify the HAL
+        setSubscriptionRequest(layer, hasSubscribers);
+    }
+
+    private void notifyHalPublishers(VmsLayer layer, int publisherId, boolean hasSubscribers) {
+        // notify the HAL
+        setSubscriptionToPublisherRequest(layer, publisherId, hasSubscribers);
+    }
+
+    private void notifyClientPublishers() {
+        // 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 notifyOfAvailabilityChange(Set<VmsAssociatedLayer> availableLayers) {
+        // notify the HAL
+        notifyAvailabilityChangeToHal(availableLayers);
+
+        // Notify the App subscribers
+        for (VmsHalSubscriberListener listener : mSubscriberListeners) {
+            listener.onLayersAvaiabilityChange(new ArrayList<>(availableLayers));
+        }
+    }
+
+    @Override
+    public void init() {
+        if (DBG) {
+            Log.d(TAG, "init()");
+        }
+        if (mIsSupported) {
+            mVehicleHal.subscribeProperty(this, HAL_PROPERTY_ID);
+        }
+    }
+
+    @Override
+    public void release() {
+        if (DBG) {
+            Log.d(TAG, "release()");
+        }
+        if (mIsSupported) {
+            mVehicleHal.unsubscribeProperty(this, HAL_PROPERTY_ID);
+        }
+        mPublisherListeners.clear();
+        mSubscriberListeners.clear();
+    }
+
+    @Override
+    public Collection<VehiclePropConfig> takeSupportedProperties(
+            Collection<VehiclePropConfig> allProperties) {
+        List<VehiclePropConfig> taken = new LinkedList<>();
+        for (VehiclePropConfig p : allProperties) {
+            if (p.prop == HAL_PROPERTY_ID) {
+                taken.add(p);
+                mIsSupported = true;
+                if (DBG) {
+                    Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
+                }
+                break;
+            }
+        }
+        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) {
+            Log.d(TAG, "Handling a VMS property change");
+        }
+        for (VehiclePropValue v : values) {
+            ArrayList<Integer> vec = v.value.int32Values;
+            int messageType = vec.get(VmsBaseMessageIntegerValuesIndex.MESSAGE_TYPE);
+
+            if (DBG) {
+                Log.d(TAG, "Handling VMS message type: " + messageType);
+            }
+            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.SUBSCRIBE_TO_PUBLISHER:
+                    handleSubscribeToPublisherEvent(vec);
+                    break;
+                case VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER:
+                    handleUnsubscribeFromPublisherEvent(vec);
+                    break;
+                case VmsMessageType.OFFERING:
+                    handleOfferingEvent(vec);
+                    break;
+                case VmsMessageType.AVAILABILITY_REQUEST:
+                    handleHalAvailabilityRequestEvent();
+                    break;
+                case VmsMessageType.SUBSCRIPTIONS_REQUEST:
+                    handleSubscriptionRequestEvent();
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected message type: " + messageType);
+            }
+        }
+    }
+
+    private VmsLayer parseVmsLayerFromSimpleMessageIntegerValues(List<Integer> integerValues) {
+        return new VmsLayer(integerValues.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE),
+                integerValues.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_SUBTYPE),
+                integerValues.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_VERSION));
+    }
+
+    private VmsLayer parseVmsLayerFromDataMessageIntegerValues(List<Integer> integerValues) {
+        return parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+    }
+
+    private int parsePublisherIdFromDataMessageIntegerValues(List<Integer> integerValues) {
+        return integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
+    }
+
+
+    /**
+     * Data message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * <li>Layer subtype.
+     * <li>Publisher ID.
+     * <li>Payload.
+     * </ul>
+     */
+    private void handleDataEvent(List<Integer> integerValues, byte[] payload) {
+        VmsLayer vmsLayer = parseVmsLayerFromDataMessageIntegerValues(integerValues);
+        int publisherId = parsePublisherIdFromDataMessageIntegerValues(integerValues);
+        if (DBG) {
+            Log.d(TAG, "Handling a data event for Layer: " + vmsLayer);
+        }
+
+        // Send the message.
+        for (VmsHalSubscriberListener listener : mSubscriberListeners) {
+            listener.onDataMessage(vmsLayer, publisherId, payload);
+        }
+    }
+
+    /**
+     * Subscribe message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * <li>Layer subtype.
+     * </ul>
+     */
+    private void handleSubscribeEvent(List<Integer> integerValues) {
+        VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+        if (DBG) {
+            Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
+        }
+        addHalSubscription(vmsLayer);
+    }
+
+    /**
+     * Subscribe message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * <li>Layer subtype.
+     * <li>Publisher ID
+     * </ul>
+     */
+    private void handleSubscribeToPublisherEvent(List<Integer> integerValues) {
+        VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+        if (DBG) {
+            Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
+        }
+        int publisherId =
+                integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
+        addHalSubscriptionToPublisher(vmsLayer, publisherId);
+    }
+
+    /**
+     * Unsubscribe message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * </ul>
+     */
+    private void handleUnsubscribeEvent(List<Integer> integerValues) {
+        VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+        if (DBG) {
+            Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
+        }
+        removeHalSubscription(vmsLayer);
+    }
+
+    /**
+     * Unsubscribe message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * </ul>
+     */
+    private void handleUnsubscribeFromPublisherEvent(List<Integer> integerValues) {
+        VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+        int publisherId =
+                integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
+        if (DBG) {
+            Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
+        }
+        removeHalSubscriptionFromPublisher(vmsLayer, publisherId);
+    }
+
+    private static int NUM_INTEGERS_IN_VMS_LAYER = 3;
+
+    private VmsLayer parseVmsLayerFromIndex(List<Integer> integerValues, int index) {
+        int layerType = integerValues.get(index++);
+        int layerSutype = integerValues.get(index++);
+        int layerVersion = integerValues.get(index++);
+        return new VmsLayer(layerType, layerSutype, layerVersion);
+    }
+
+    /**
+     * Offering message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Publisher ID.
+     * <li>Number of offerings.
+     * <li>Each offering consists of:
+     * <ul>
+     * <li>Layer id.
+     * <li>Layer version.
+     * <li>Number of layer dependencies.
+     * <li>Layer type/subtype/version.
+     * </ul>
+     * </ul>
+     */
+    private void handleOfferingEvent(List<Integer> integerValues) {
+        int publisherId = integerValues.get(VmsOfferingMessageIntegerValuesIndex.PUBLISHER_ID);
+        int numLayersDependencies =
+                integerValues.get(
+                        VmsOfferingMessageIntegerValuesIndex.NUMBER_OF_OFFERS);
+        int idx = VmsOfferingMessageIntegerValuesIndex.OFFERING_START;
+
+        Set<VmsLayerDependency> offeredLayers = new HashSet<>();
+
+        // An offering is layerId, LayerVersion, LayerType, NumDeps, <LayerId, LayerVersion> X NumDeps.
+        for (int i = 0; i < numLayersDependencies; i++) {
+            VmsLayer offeredLayer = parseVmsLayerFromIndex(integerValues, idx);
+            idx += NUM_INTEGERS_IN_VMS_LAYER;
+
+            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++) {
+                    VmsLayer dependantLayer = parseVmsLayerFromIndex(integerValues, idx);
+                    idx += NUM_INTEGERS_IN_VMS_LAYER;
+                    dependencies.add(dependantLayer);
+                }
+                offeredLayers.add(new VmsLayerDependency(offeredLayer, dependencies));
+            }
+        }
+        // Store the HAL offering.
+        VmsLayersOffering offering = new VmsLayersOffering(offeredLayers, publisherId);
+        synchronized (mLock) {
+            updateOffering(mHalPublisherToken, offering);
+            VmsOperationRecorder.get().setHalPublisherLayersOffering(offering);
+        }
+    }
+
+    /**
+     * Availability message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Number of layers.
+     * <li>Layer type/subtype/version.
+     * </ul>
+     */
+    private void handleHalAvailabilityRequestEvent() {
+        synchronized (mLock) {
+            Collection<VmsAssociatedLayer> availableLayers = mAvailableLayers.getAvailableLayers();
+            VehiclePropValue vehiclePropertyValue =
+                    toAvailabilityUpdateVehiclePropValue(
+                            availableLayers,
+                            VmsMessageType.AVAILABILITY_RESPONSE);
+
+            setPropertyValue(vehiclePropertyValue);
+        }
+    }
+
+    /**
+     * VmsSubscriptionRequestFormat:
+     * <ul>
+     * <li>Message type.
+     * </ul>
+     * <p>
+     * VmsSubscriptionResponseFormat:
+     * <ul>
+     * <li>Message type.
+     * <li>Sequence number.
+     * <li>Number of layers.
+     * <li>Layer type/subtype/version.
+     * </ul>
+     */
+    private void handleSubscriptionRequestEvent() {
+        VmsSubscriptionState subscription = getSubscriptionState();
+        VehiclePropValue vehicleProp =
+                toTypedVmsVehiclePropValue(VmsMessageType.SUBSCRIPTIONS_RESPONSE);
+        VehiclePropValue.RawValue v = vehicleProp.value;
+        v.int32Values.add(subscription.getSequenceNumber());
+        Set<VmsLayer> layers = subscription.getLayers();
+        v.int32Values.add(layers.size());
+
+        //TODO(asafro): get the real number of associated layers in the subscriptions
+        //              state and send the associated layers themselves.
+        v.int32Values.add(0);
+
+        for (VmsLayer layer : layers) {
+            v.int32Values.add(layer.getType());
+            v.int32Values.add(layer.getSubtype());
+            v.int32Values.add(layer.getVersion());
+        }
+        setPropertyValue(vehicleProp);
+    }
+
+    private void updateOffering(IBinder publisherToken, VmsLayersOffering offering) {
+        Set<VmsAssociatedLayer> availableLayers = Collections.EMPTY_SET;
+        synchronized (mLock) {
+            mOfferings.put(publisherToken, offering);
+
+            // Update layers availability.
+            mAvailableLayers.setPublishersOffering(mOfferings.values());
+
+            availableLayers = mAvailableLayers.getAvailableLayers();
+        }
+        notifyOfAvailabilityChange(availableLayers);
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println(TAG);
+        writer.println("VmsProperty " + (mIsSupported ? "" : "not") + " supported.");
+    }
+
+    /**
+     * Updates the VMS HAL property with the given value.
+     *
+     * @param layer          layer data to update the hal property.
+     * @param hasSubscribers if it is a subscribe or unsubscribe message.
+     * @return true if the call to the HAL to update the property was successful.
+     */
+    public boolean setSubscriptionRequest(VmsLayer layer, boolean hasSubscribers) {
+        VehiclePropValue vehiclePropertyValue = toTypedVmsVehiclePropValueWithLayer(
+                hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, layer);
+        return setPropertyValue(vehiclePropertyValue);
+    }
+
+    public boolean setSubscriptionToPublisherRequest(VmsLayer layer,
+                                                     int publisherId,
+                                                     boolean hasSubscribers) {
+        VehiclePropValue vehiclePropertyValue = toTypedVmsVehiclePropValueWithLayer(
+                hasSubscribers ?
+                        VmsMessageType.SUBSCRIBE_TO_PUBLISHER :
+                        VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER, layer);
+        vehiclePropertyValue.value.int32Values.add(publisherId);
+        return setPropertyValue(vehiclePropertyValue);
+    }
+
+    public boolean setDataMessage(VmsLayer layer, byte[] payload) {
+        VehiclePropValue vehiclePropertyValue =
+                toTypedVmsVehiclePropValueWithLayer(VmsMessageType.DATA, layer);
+        VehiclePropValue.RawValue v = vehiclePropertyValue.value;
+        v.bytes.ensureCapacity(payload.length);
+        for (byte b : payload) {
+            v.bytes.add(b);
+        }
+        return setPropertyValue(vehiclePropertyValue);
+    }
+
+    public boolean notifyAvailabilityChangeToHal(Collection<VmsAssociatedLayer> availableLayers) {
+        VehiclePropValue vehiclePropertyValue =
+                toAvailabilityUpdateVehiclePropValue(
+                        availableLayers,
+                        VmsMessageType.AVAILABILITY_CHANGE);
+
+        return setPropertyValue(vehiclePropertyValue);
+    }
+
+    public boolean setPropertyValue(VehiclePropValue vehiclePropertyValue) {
+        try {
+            mVehicleHal.set(vehiclePropertyValue);
+            return true;
+        } catch (PropertyTimeoutException e) {
+            Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(HAL_PROPERTY_ID));
+        }
+        return false;
+    }
+
+    private static VehiclePropValue toTypedVmsVehiclePropValue(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 toTypedVmsVehiclePropValueWithLayer(
+            int messageType, VmsLayer layer) {
+        VehiclePropValue vehicleProp = toTypedVmsVehiclePropValue(messageType);
+        VehiclePropValue.RawValue v = vehicleProp.value;
+        v.int32Values.add(layer.getType());
+        v.int32Values.add(layer.getSubtype());
+        v.int32Values.add(layer.getVersion());
+        return vehicleProp;
+    }
+
+    private static VehiclePropValue toAvailabilityUpdateVehiclePropValue(
+            Collection<VmsAssociatedLayer> availableAssociatedLayers, int messageType) {
+
+        if (!AVAILABILITY_MESSAGE_TYPES.contains(messageType)) {
+            throw new IllegalArgumentException("Unsupported availability type: " + messageType);
+        }
+        VehiclePropValue vehicleProp =
+                toTypedVmsVehiclePropValue(messageType);
+        populateAvailabilityPropValueFields(availableAssociatedLayers, vehicleProp);
+        return vehicleProp;
+
+    }
+
+    private static void populateAvailabilityPropValueFields(
+            Collection<VmsAssociatedLayer> availableAssociatedLayers,
+            VehiclePropValue vehicleProp) {
+        VehiclePropValue.RawValue v = vehicleProp.value;
+        int numLayers = availableAssociatedLayers.size();
+        v.int32Values.add(numLayers);
+        for (VmsAssociatedLayer layer : availableAssociatedLayers) {
+            v.int32Values.add(layer.getVmsLayer().getType());
+            v.int32Values.add(layer.getVmsLayer().getSubtype());
+            v.int32Values.add(layer.getVmsLayer().getVersion());
+            v.int32Values.add(layer.getPublisherIds().size());
+            for (int publisherId : layer.getPublisherIds()) {
+                v.int32Values.add(publisherId);
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/storagemonitoring/EMmcWearInformationProvider.java b/service/src/com/android/car/storagemonitoring/EMmcWearInformationProvider.java
new file mode 100644
index 0000000..3940578
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/EMmcWearInformationProvider.java
@@ -0,0 +1,106 @@
+/*
+ * 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.storagemonitoring;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.File;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * Loads wear information from the eMMC sysfs entry points.
+ * sysfs exposes eMMC lifetime data in /sys/bus/mmc/devices/mmc0:0001/{pre_eol_info|life_time}
+ * The first file contains the value of the PRE_EOL_INFO register as an hexadecimal string
+ * The second file contains the values of the LIFE_TIME_EST A and B registers as two hexadecimal
+ * strings separated by a space.
+ */
+public class EMmcWearInformationProvider implements WearInformationProvider {
+    private static File DEFAULT_LIFE_TIME_FILE =
+        new File("/sys/bus/mmc/devices/mmc0:0001/life_time");
+
+    private static File DEFAULT_PRE_EOL_FILE =
+        new File("/sys/bus/mmc/devices/mmc0:0001/pre_eol_info");
+
+    private File mLifetimeFile;
+    private File mPreEolFile;
+
+    public EMmcWearInformationProvider() {
+        this(DEFAULT_LIFE_TIME_FILE, DEFAULT_PRE_EOL_FILE);
+    }
+
+    @VisibleForTesting
+    EMmcWearInformationProvider(@NonNull File lifetimeFile, @NonNull File preEolFile) {
+        mLifetimeFile = lifetimeFile;
+        mPreEolFile = preEolFile;
+    }
+
+    private String readLineFromFile(File f) {
+        if (!f.exists() || !f.isFile()) {
+            Log.i(CarLog.TAG_STORAGE, f + " does not exist or is not a file");
+            return null;
+        }
+
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(f));
+            String data = reader.readLine();
+            reader.close();
+            return data;
+        } catch (IOException e) {
+            Log.w(CarLog.TAG_STORAGE, f + " cannot be read from", e);
+            return null;
+        }
+    }
+
+    @Nullable
+    @Override
+    public WearInformation load() {
+        String lifetimeData = readLineFromFile(mLifetimeFile);
+        String eolData = readLineFromFile(mPreEolFile);
+
+        if ((lifetimeData == null) || (eolData == null)) {
+            return null;
+        }
+
+        String[] lifetimes = lifetimeData.split(" ");
+        if (lifetimes.length != 2) {
+            Log.w(CarLog.TAG_STORAGE, "lifetime data not in expected format: " + lifetimeData);
+            return null;
+        }
+
+        int lifetimeA;
+        int lifetimeB;
+        int eol;
+
+        try {
+            lifetimeA = Integer.decode(lifetimes[0]);
+            lifetimeB = Integer.decode(lifetimes[1]);
+            eol = Integer.decode("0x" + eolData);
+        } catch (NumberFormatException e) {
+            Log.w(CarLog.TAG_STORAGE,
+                    "lifetime data not in expected format: " + lifetimeData, e);
+            return null;
+        }
+
+        return new WearInformation(convertLifetime(lifetimeA),
+                convertLifetime(lifetimeB),
+                adjustEol(eol));
+    }
+}
diff --git a/service/src/com/android/car/storagemonitoring/IoStatsTracker.java b/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
new file mode 100644
index 0000000..25a3d69
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
@@ -0,0 +1,118 @@
+/*
+ * 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.storagemonitoring;
+
+import android.car.storagemonitoring.UidIoStats;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.util.SparseArray;
+import com.android.car.SparseArrayStream;
+import com.android.car.procfsinspector.ProcessInfo;
+import com.android.car.systeminterface.SystemStateInterface;
+import java.util.List;
+import java.util.Optional;
+
+public class IoStatsTracker {
+    private abstract class Lazy<T> {
+        protected Optional<T> mLazy = Optional.empty();
+
+        protected abstract T supply();
+
+        public synchronized T get() {
+            if (!mLazy.isPresent()) {
+                mLazy = Optional.of(supply());
+            }
+            return mLazy.get();
+        }
+    }
+
+    private final long mSampleWindowMs;
+    private final SystemStateInterface mSystemStateInterface;
+    private SparseArray<UidIoStats> mTotal;
+    private SparseArray<UidIoStats> mCurrentSample;
+
+    public IoStatsTracker(List<UidIoStats> initialValue,
+            long sampleWindowMs, SystemStateInterface systemStateInterface) {
+        mTotal = new SparseArray<>(initialValue.size());
+        initialValue.forEach(uidIoStats -> mTotal.append(uidIoStats.uid, uidIoStats));
+        mCurrentSample = mTotal.clone();
+        mSampleWindowMs = sampleWindowMs;
+        mSystemStateInterface = systemStateInterface;
+    }
+
+    public synchronized void update(SparseArray<UidIoStatsRecord> newMetrics) {
+        final Lazy<List<ProcessInfo>> processTable = new Lazy<List<ProcessInfo>>() {
+            @Override
+            protected List<ProcessInfo> supply() {
+                return mSystemStateInterface.getRunningProcesses();
+            }
+        };
+
+        SparseArray<UidIoStats> newSample = new SparseArray<>();
+        SparseArray<UidIoStats> newTotal = new SparseArray<>();
+
+        // prepare the new values
+        SparseArrayStream.valueStream(newMetrics).forEach( newRecord -> {
+            final int uid = newRecord.uid;
+            final UidIoStats oldRecord = mTotal.get(uid);
+
+            UidIoStats newStats = null;
+
+            if (oldRecord == null) {
+                // this user id has just showed up, so just add it to the current sample
+                // and its runtime is the size of our sample window
+                newStats = new UidIoStats(newRecord, mSampleWindowMs);
+            } else {
+                // this user id has already been detected
+
+                if (oldRecord.representsSameMetrics(newRecord)) {
+                    // if no new I/O happened, try to figure out if any process on behalf
+                    // of this user has happened, and use that to update the runtime metrics
+                    if (processTable.get().stream().anyMatch(pi -> pi.uid == uid)) {
+                        newStats = new UidIoStats(newRecord.delta(oldRecord),
+                                oldRecord.runtimeMillis + mSampleWindowMs);
+                    }
+                    // if no new I/O happened and no process is running for this user
+                    // then do not prepare a new sample, as nothing has changed
+                } else {
+                    // but if new I/O happened, assume something was running for the entire
+                    // sample window and compute the delta
+                    newStats = new UidIoStats(newRecord.delta(oldRecord),
+                            oldRecord.runtimeMillis + mSampleWindowMs);
+                }
+            }
+
+            if (newStats != null) {
+                newSample.put(uid, newStats);
+                newTotal.append(uid, new UidIoStats(newRecord, newStats.runtimeMillis));
+            } else {
+                // if oldRecord were null, newStats would be != null and we wouldn't be here
+                newTotal.append(uid, oldRecord);
+            }
+        });
+
+        // now update the stored values
+        mCurrentSample = newSample;
+        mTotal = newTotal;
+    }
+
+    public synchronized SparseArray<UidIoStats> getTotal() {
+        return mTotal;
+    }
+
+    public synchronized SparseArray<UidIoStats> getCurrentSample() {
+        return mCurrentSample;
+    }
+}
diff --git a/service/src/com/android/car/storagemonitoring/ProcfsUidIoStatsProvider.java b/service/src/com/android/car/storagemonitoring/ProcfsUidIoStatsProvider.java
new file mode 100644
index 0000000..73b0da6
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/ProcfsUidIoStatsProvider.java
@@ -0,0 +1,105 @@
+/*
+ * 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.storagemonitoring;
+
+import android.annotation.Nullable;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.util.Log;
+import android.util.SparseArray;
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/**
+ * Loads I/O stats from procfs
+ *
+ * The Android kernel can be configured to provide uid I/O stats in /proc/uid_io/stats.
+ */
+public class ProcfsUidIoStatsProvider implements UidIoStatsProvider {
+    private static Path DEFAULT_STATS_FILE = new File("/proc/uid_io/stats").toPath();
+
+    private final Path mStatsFile;
+
+    public ProcfsUidIoStatsProvider() {
+        this(DEFAULT_STATS_FILE);
+    }
+
+    @VisibleForTesting
+    ProcfsUidIoStatsProvider(Path statsFile) {
+        mStatsFile = Objects.requireNonNull(statsFile);
+    }
+
+    @Nullable
+    @Override
+    public SparseArray<UidIoStatsRecord> load() {
+        List<String> lines;
+        SparseArray<UidIoStatsRecord> result = new SparseArray<>();
+        try {
+            lines = Files.readAllLines(mStatsFile);
+        } catch (IOException e) {
+            Log.w(CarLog.TAG_STORAGE, "can't read I/O stats from " + mStatsFile, e);
+            return null;
+        }
+
+        for (String line : lines) {
+            StringTokenizer tokenizer = new StringTokenizer(line);
+            if (tokenizer.countTokens() != 11) {
+                Log.w(CarLog.TAG_STORAGE, "malformed I/O stats entry: " + line);
+                return null;
+            }
+
+            try {
+                int uid = Integer.valueOf(tokenizer.nextToken());
+                long foreground_rchar = Long.valueOf(tokenizer.nextToken());
+                long foreground_wchar = Long.valueOf(tokenizer.nextToken());
+                long foreground_read_bytes = Long.valueOf(tokenizer.nextToken());
+                long foreground_write_bytes = Long.valueOf(tokenizer.nextToken());
+                long background_rchar = Long.valueOf(tokenizer.nextToken());
+                long background_wchar = Long.valueOf(tokenizer.nextToken());
+                long background_read_bytes = Long.valueOf(tokenizer.nextToken());
+                long background_write_bytes = Long.valueOf(tokenizer.nextToken());
+                long foreground_fsync = Long.valueOf(tokenizer.nextToken());
+                long background_fsync = Long.valueOf(tokenizer.nextToken());
+
+                result.append(uid, new UidIoStatsRecord(uid,
+                            foreground_rchar,
+                            foreground_wchar,
+                            foreground_read_bytes,
+                            foreground_write_bytes,
+                            foreground_fsync,
+                            background_rchar,
+                            background_wchar,
+                            background_read_bytes,
+                            background_write_bytes,
+                            background_fsync));
+
+            } catch (NumberFormatException e) {
+                Log.w(CarLog.TAG_STORAGE, "malformed I/O stats entry: " + line, e);
+                return null;
+            }
+        }
+
+        return result;
+    }
+}
+
+
diff --git a/service/src/com/android/car/storagemonitoring/UfsWearInformationProvider.java b/service/src/com/android/car/storagemonitoring/UfsWearInformationProvider.java
new file mode 100644
index 0000000..fa7ba8b
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/UfsWearInformationProvider.java
@@ -0,0 +1,112 @@
+/*
+ * 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.storagemonitoring;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.regex.MatchResult;
+import java.util.regex.Pattern;
+
+/**
+ * Loads wear information from the UFS sysfs entry points.
+ * sysfs exposes UFS lifetime data in /sys/devices/soc/624000.ufshc/health
+ * The first line of the file contains the UFS version
+ * Subsequent lines contains individual information points in the format:
+ * Health Descriptor[Byte offset 0x%d]: %31s = 0x%hx
+ * Of these we care about the key values bPreEOLInfo and bDeviceLifeTimeEstA bDeviceLifeTimeEstB
+ */
+public class UfsWearInformationProvider implements WearInformationProvider {
+    private static File DEFAULT_FILE =
+        new File("/sys/devices/soc/624000.ufshc/health");
+
+    private File mFile;
+
+    public UfsWearInformationProvider() {
+        this(DEFAULT_FILE);
+    }
+
+    @VisibleForTesting
+    public UfsWearInformationProvider(@NonNull File file) {
+        mFile = file;
+    }
+
+    @Nullable
+    @Override
+    public WearInformation load() {
+        List<String> lifetimeData;
+        try {
+            lifetimeData = java.nio.file.Files.readAllLines(mFile.toPath());
+        } catch (IOException e) {
+            Log.w(CarLog.TAG_STORAGE, "error reading " + mFile, e);
+            return null;
+        }
+        if (lifetimeData == null || lifetimeData.size() < 4) {
+            return null;
+        }
+
+        Pattern infoPattern = Pattern.compile(
+                "Health Descriptor\\[Byte offset 0x\\d+\\]: (\\w+) = 0x([0-9a-fA-F]+)");
+
+        Optional<Integer> lifetimeA = Optional.empty();
+        Optional<Integer> lifetimeB = Optional.empty();
+        Optional<Integer> eol = Optional.empty();
+
+        for(String lifetimeInfo : lifetimeData) {
+            Scanner scanner = new Scanner(lifetimeInfo);
+            if (null == scanner.findInLine(infoPattern)) {
+                continue;
+            }
+            MatchResult match = scanner.match();
+            if (match.groupCount() != 2) {
+                continue;
+            }
+            String name = match.group(1);
+            String value = "0x" + match.group(2);
+            try {
+                switch (name) {
+                    case "bPreEOLInfo":
+                        eol = Optional.of(Integer.decode(value));
+                        break;
+                    case "bDeviceLifeTimeEstA":
+                        lifetimeA = Optional.of(Integer.decode(value));
+                        break;
+                    case "bDeviceLifeTimeEstB":
+                        lifetimeB = Optional.of(Integer.decode(value));
+                        break;
+                }
+            } catch (NumberFormatException e) {
+                Log.w(CarLog.TAG_STORAGE,
+                    "trying to decode key " + name + " value " + value + " didn't parse properly", e);
+            }
+        }
+
+        if (!lifetimeA.isPresent() || !lifetimeB.isPresent() || !eol.isPresent()) {
+            return null;
+        }
+
+        return new WearInformation(convertLifetime(lifetimeA.get()),
+            convertLifetime(lifetimeB.get()),
+            adjustEol(eol.get()));
+    }
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/service/src/com/android/car/storagemonitoring/UidIoStatsProvider.java
similarity index 63%
copy from libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
copy to service/src/com/android/car/storagemonitoring/UidIoStatsProvider.java
index ca0ac83..21798c2 100644
--- a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
+++ b/service/src/com/android/car/storagemonitoring/UidIoStatsProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.car.storagemonitoring;
 
-package com.android.car.vehiclemonitor;
+import android.annotation.Nullable;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.util.SparseArray;
 
-/**
- * Listener for vehicle monitor service.
- * @hide
- */
-oneway interface IVehicleMonitorListener {
-    void onAppViolation(int pid, int uid, int action, int violation) = 0;
+public interface UidIoStatsProvider {
+    @Nullable
+    SparseArray<UidIoStatsRecord> load();
 }
diff --git a/service/src/com/android/car/storagemonitoring/WearEstimateRecord.java b/service/src/com/android/car/storagemonitoring/WearEstimateRecord.java
new file mode 100644
index 0000000..fd4aae7
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/WearEstimateRecord.java
@@ -0,0 +1,178 @@
+/*
+ * 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.storagemonitoring;
+
+import android.annotation.NonNull;
+import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.util.JsonWriter;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Objects;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * This class represents a wear estimate record as stored by CarStorageMonitoringService.
+ *
+ * Because it is meant to map 1:1 to on-disk records, it is not directly convertible to a
+ * WearEstimateChange because it does not include information about "acceptable degradation".
+ */
+public class WearEstimateRecord {
+
+    private final WearEstimate mOldWearEstimate;
+    private final WearEstimate mNewWearEstimate;
+    private final long mTotalCarServiceUptime;
+    private final Instant mUnixTimestamp;
+
+    public WearEstimateRecord(@NonNull WearEstimate oldWearEstimate,
+        @NonNull WearEstimate newWearEstimate,
+        long totalCarServiceUptime,
+        @NonNull Instant unixTimestamp) {
+        mOldWearEstimate = Objects.requireNonNull(oldWearEstimate);
+        mNewWearEstimate = Objects.requireNonNull(newWearEstimate);
+        mTotalCarServiceUptime = totalCarServiceUptime;
+        mUnixTimestamp = Objects.requireNonNull(unixTimestamp);
+    }
+
+    WearEstimateRecord(@NonNull JSONObject json) throws JSONException {
+        mOldWearEstimate = new WearEstimate(json.getJSONObject("oldWearEstimate"));
+        mNewWearEstimate = new WearEstimate(json.getJSONObject("newWearEstimate"));
+        mTotalCarServiceUptime = json.getLong("totalCarServiceUptime");
+        mUnixTimestamp = Instant.ofEpochMilli(json.getLong("unixTimestamp"));
+
+    }
+
+    void writeToJson(@NonNull JsonWriter jsonWriter) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("oldWearEstimate"); mOldWearEstimate.writeToJson(jsonWriter);
+        jsonWriter.name("newWearEstimate"); mNewWearEstimate.writeToJson(jsonWriter);
+        jsonWriter.name("totalCarServiceUptime").value(mTotalCarServiceUptime);
+        jsonWriter.name("unixTimestamp").value(mUnixTimestamp.toEpochMilli());
+        jsonWriter.endObject();
+    }
+
+    public WearEstimate getOldWearEstimate() {
+        return mOldWearEstimate;
+    }
+
+    public WearEstimate getNewWearEstimate() {
+        return mNewWearEstimate;
+    }
+
+    public long getTotalCarServiceUptime() {
+        return mTotalCarServiceUptime;
+    }
+
+    public Instant getUnixTimestamp() {
+        return mUnixTimestamp;
+    }
+
+    WearEstimateChange toWearEstimateChange(boolean isAcceptableDegradation) {
+        return new WearEstimateChange(mOldWearEstimate,
+                mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp, isAcceptableDegradation);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof WearEstimateRecord) {
+            WearEstimateRecord wer = (WearEstimateRecord)other;
+            if (!wer.mOldWearEstimate.equals(mOldWearEstimate)) return false;
+            if (!wer.mNewWearEstimate.equals(mNewWearEstimate)) return false;
+            if (wer.mTotalCarServiceUptime != mTotalCarServiceUptime) return false;
+            if (!wer.mUnixTimestamp.equals(mUnixTimestamp)) return false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks whether this record tracks the same change as the provided estimate.
+     * That means the two objects have the same values for:
+     * <ul>
+     *  <li>old wear indicators</li>
+     *  <li>new wear indicators</li>
+     *  <li>uptime at event</li>
+     * </ul>
+     */
+    public boolean isSameAs(@NonNull WearEstimateChange wearEstimateChange) {
+        if (!mOldWearEstimate.equals(wearEstimateChange.oldEstimate)) return false;
+        if (!mNewWearEstimate.equals(wearEstimateChange.newEstimate)) return false;
+        return (mTotalCarServiceUptime == wearEstimateChange.uptimeAtChange);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mOldWearEstimate,
+                mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("WearEstimateRecord {," +
+            "mOldWearEstimate = %s, " +
+            "mNewWearEstimate = %s, " +
+            "mTotalCarServiceUptime = %d, " +
+            "mUnixTimestamp = %s}",
+            mOldWearEstimate, mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp);
+    }
+
+    public static final class Builder {
+        private WearEstimate mOldWearEstimate = null;
+        private WearEstimate mNewWearEstimate = null;
+        private long mTotalCarServiceUptime = -1;
+        private Instant mUnixTimestamp = null;
+
+        private Builder() {}
+
+        public static Builder newBuilder() {
+            return new Builder();
+        }
+
+        public Builder fromWearEstimate(@NonNull WearEstimate wearEstimate) {
+            mOldWearEstimate = Objects.requireNonNull(wearEstimate);
+            return this;
+        }
+
+        public Builder toWearEstimate(@NonNull WearEstimate wearEstimate) {
+            mNewWearEstimate = Objects.requireNonNull(wearEstimate);
+            return this;
+        }
+
+        public Builder atUptime(long uptime) {
+            if (uptime < 0) {
+                throw new IllegalArgumentException("uptime must be >= 0");
+            }
+            mTotalCarServiceUptime = uptime;
+            return this;
+        }
+
+        public Builder atTimestamp(@NonNull Instant now) {
+            mUnixTimestamp = Objects.requireNonNull(now);
+            return this;
+        }
+
+        public WearEstimateRecord build() {
+            if (mOldWearEstimate == null || mNewWearEstimate == null ||
+                    mTotalCarServiceUptime < 0 || mUnixTimestamp == null) {
+                throw new IllegalStateException("malformed builder state");
+            }
+            return new WearEstimateRecord(
+                    mOldWearEstimate, mNewWearEstimate, mTotalCarServiceUptime, mUnixTimestamp);
+        }
+    }
+}
diff --git a/service/src/com/android/car/storagemonitoring/WearHistory.java b/service/src/com/android/car/storagemonitoring/WearHistory.java
new file mode 100644
index 0000000..38982aa
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/WearHistory.java
@@ -0,0 +1,147 @@
+/*
+ * 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.storagemonitoring;
+
+import android.annotation.NonNull;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.util.JsonWriter;
+import com.android.car.CarStorageMonitoringService;
+import com.android.car.R;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiPredicate;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * This class represents the entire history of flash wear changes as tracked
+ * by CarStorageMonitoringService. It is a set of WearEstimateRecords.
+ *
+ * This is convertible to a list of WearEstimateChanges given policy about what constitutes
+ * acceptable or not acceptable degradation across change events. This policy is subject to
+ * modifications across OS versions, and as such it is not suitable for permanent storage.
+ */
+public class WearHistory {
+    private final List<WearEstimateRecord> mWearHistory = new ArrayList<>();
+
+    public WearHistory() {}
+
+    WearHistory(@NonNull JSONObject jsonObject) throws JSONException {
+        final JSONArray wearHistory = jsonObject.getJSONArray("wearHistory");
+        for (int i = 0; i < wearHistory.length(); ++i) {
+            JSONObject wearRecordJson = wearHistory.getJSONObject(i);
+            WearEstimateRecord wearRecord = new WearEstimateRecord(wearRecordJson);
+            add(wearRecord);
+        }
+    }
+
+    public static WearHistory fromRecords(@NonNull WearEstimateRecord... records) {
+        WearHistory wearHistory = new WearHistory();
+        Arrays.stream(records).forEach(wearHistory::add);
+        return wearHistory;
+    }
+
+    public static WearHistory fromJson(@NonNull File in) throws IOException, JSONException {
+        JSONObject jsonObject = new JSONObject(new String(Files.readAllBytes(in.toPath())));
+        return new WearHistory(jsonObject);
+    }
+
+    public void writeToJson(@NonNull JsonWriter out) throws IOException {
+        out.beginObject();
+        out.name("wearHistory").beginArray();
+        for (WearEstimateRecord wearRecord : mWearHistory) {
+            wearRecord.writeToJson(out);
+        }
+        out.endArray();
+        out.endObject();
+    }
+
+    public boolean add(@NonNull WearEstimateRecord record) {
+        if (record != null && mWearHistory.add(record)) {
+            mWearHistory.sort((WearEstimateRecord o1, WearEstimateRecord o2) ->
+                Long.valueOf(o1.getTotalCarServiceUptime()).compareTo(
+                    o2.getTotalCarServiceUptime()));
+            return true;
+        }
+        return false;
+    }
+
+    public int size() {
+        return mWearHistory.size();
+    }
+
+    public WearEstimateRecord get(int i) {
+        return mWearHistory.get(i);
+    }
+
+    public WearEstimateRecord getLast() {
+        return get(size() - 1);
+    }
+
+    public List<WearEstimateChange> toWearEstimateChanges(
+            long acceptableHoursPerOnePercentFlashWear) {
+        // current technology allows us to detect wear in 10% increments
+        final int WEAR_PERCENTAGE_INCREMENT = 10;
+        final long acceptableWearRate = WEAR_PERCENTAGE_INCREMENT *
+                Duration.ofHours(acceptableHoursPerOnePercentFlashWear).toMillis();
+        final int numRecords = size();
+
+        if (numRecords == 0) return Collections.emptyList();
+
+        List<WearEstimateChange> result = new ArrayList<>();
+        result.add(get(0).toWearEstimateChange(true));
+
+        for (int i = 1; i < numRecords; ++i) {
+            WearEstimateRecord previousRecord = get(i - 1);
+            WearEstimateRecord currentRecord = get(i);
+            final long timeForChange =
+                    currentRecord.getTotalCarServiceUptime() -
+                            previousRecord.getTotalCarServiceUptime();
+            final boolean isAcceptableDegradation = timeForChange >= acceptableWearRate;
+            result.add(currentRecord.toWearEstimateChange(isAcceptableDegradation));
+        }
+
+        return Collections.unmodifiableList(result);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof WearHistory) {
+            WearHistory wi = (WearHistory)other;
+            return wi.mWearHistory.equals(mWearHistory);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mWearHistory.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return mWearHistory.stream().map(WearEstimateRecord::toString).reduce(
+            "WearHistory[size = " + size() + "] -> ",
+            (String s, String t) -> s + ", " + t);
+    }
+}
diff --git a/service/src/com/android/car/storagemonitoring/WearInformation.java b/service/src/com/android/car/storagemonitoring/WearInformation.java
new file mode 100644
index 0000000..da38afe
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/WearInformation.java
@@ -0,0 +1,91 @@
+/*
+ * 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.storagemonitoring;
+
+import static java.util.Objects.hash;
+
+import android.car.storagemonitoring.WearEstimate;
+import java.time.Instant;
+
+public final class WearInformation {
+    public static final int UNKNOWN_LIFETIME_ESTIMATE = -1;
+
+    public static final int UNKNOWN_PRE_EOL_INFO = 0;
+    public static final int PRE_EOL_INFO_NORMAL = 1;
+    public static final int PRE_EOL_INFO_WARNING = 2;
+    public static final int PRE_EOL_INFO_URGENT = 3;
+
+    private static final String UNKNOWN = "unknown";
+    private static final String[] PRE_EOL_STRINGS = new String[] {
+        UNKNOWN, "normal", "warning", "urgent"
+    };
+
+    /**
+     * A lower bound on the lifetime estimate for "type A" memory cells, expressed as a percentage.
+     */
+    public final int lifetimeEstimateA;
+
+    /**
+     * A lower bound on the lifetime estimate for "type B" memory cells, expressed as a percentage.
+     */
+    public final int lifetimeEstimateB;
+
+    /**
+     * An estimate of the lifetime based on reserved block consumption.
+     */
+    public final int preEolInfo;
+
+    public WearInformation(int lifetimeA, int lifetimeB, int preEol) {
+        lifetimeEstimateA = lifetimeA;
+        lifetimeEstimateB = lifetimeB;
+        preEolInfo = preEol;
+    }
+
+    @Override
+    public int hashCode() {
+        return hash(lifetimeEstimateA, lifetimeEstimateB, preEolInfo);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof WearInformation) {
+            WearInformation wi = (WearInformation)other;
+            return (wi.lifetimeEstimateA == lifetimeEstimateA) &&
+                (wi.lifetimeEstimateB == lifetimeEstimateB) &&
+                (wi.preEolInfo == preEolInfo);
+        } else {
+            return false;
+        }
+    }
+
+    private String lifetimeToString(int lifetime) {
+        if (lifetime == UNKNOWN_LIFETIME_ESTIMATE) return UNKNOWN;
+
+        return lifetime + "%";
+    }
+
+    @Override
+    public String toString() {
+        return String.format("lifetime estimate: A = %s, B = %s; pre EOL info: %s",
+                lifetimeToString(lifetimeEstimateA),
+                lifetimeToString(lifetimeEstimateB),
+                PRE_EOL_STRINGS[preEolInfo]);
+    }
+
+    public WearEstimate toWearEstimate() {
+        return new WearEstimate(lifetimeEstimateA, lifetimeEstimateB);
+    }
+}
diff --git a/service/src/com/android/car/storagemonitoring/WearInformationProvider.java b/service/src/com/android/car/storagemonitoring/WearInformationProvider.java
new file mode 100644
index 0000000..65574d7
--- /dev/null
+++ b/service/src/com/android/car/storagemonitoring/WearInformationProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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.storagemonitoring;
+
+import android.annotation.Nullable;
+
+public interface WearInformationProvider {
+    @Nullable
+    WearInformation load();
+
+    default int convertLifetime(int lifetime) {
+        if ((lifetime <= 0) || (lifetime > 11)) return WearInformation.UNKNOWN_LIFETIME_ESTIMATE;
+        return 10 * (lifetime - 1);
+    }
+
+    default int adjustEol(int eol) {
+        if ((eol <= 0) || (eol > 3)) return WearInformation.UNKNOWN_PRE_EOL_INFO;
+        return eol;
+    }
+
+}
diff --git a/service/src/com/android/car/systeminterface/DisplayInterface.java b/service/src/com/android/car/systeminterface/DisplayInterface.java
new file mode 100644
index 0000000..f798b0d
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/DisplayInterface.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.car.systeminterface;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import com.android.car.CarLog;
+import com.android.car.CarPowerManagementService;
+
+/**
+ * Interface that abstracts display operations
+ */
+public interface DisplayInterface {
+    void setDisplayState(boolean on);
+    void startDisplayStateMonitoring(CarPowerManagementService service);
+    void stopDisplayStateMonitoring();
+
+    class DefaultImpl implements DisplayInterface {
+        private final DisplayManager mDisplayManager;
+        private final PowerManager mPowerManager;
+        private final WakeLockInterface mWakeLockInterface;
+        private CarPowerManagementService mService;
+        private boolean mDisplayStateSet;
+        private final DisplayManager.DisplayListener mDisplayListener = new DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+                //ignore
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                //ignore
+            }
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                if (displayId == Display.DEFAULT_DISPLAY) {
+                    handleMainDisplayChanged();
+                }
+            }
+        };
+
+        DefaultImpl(Context context, WakeLockInterface wakeLockInterface) {
+            mWakeLockInterface = wakeLockInterface;
+            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+            mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        }
+
+        private void handleMainDisplayChanged() {
+            boolean isOn = isMainDisplayOn();
+            CarPowerManagementService service;
+            synchronized (this) {
+                if (mDisplayStateSet == isOn) { // same as what is set
+                    return;
+                }
+                service = mService;
+            }
+            service.handleMainDisplayChanged(isOn);
+        }
+
+        private boolean isMainDisplayOn() {
+            Display disp = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            return disp.getState() == Display.STATE_ON;
+        }
+
+        @Override
+        public void startDisplayStateMonitoring(CarPowerManagementService service) {
+            synchronized (this) {
+                mService = service;
+                mDisplayStateSet = isMainDisplayOn();
+            }
+            mDisplayManager.registerDisplayListener(mDisplayListener, service.getHandler());
+        }
+
+        @Override
+        public void stopDisplayStateMonitoring() {
+            mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        }
+
+        @Override
+        public void setDisplayState(boolean on) {
+            synchronized (this) {
+                mDisplayStateSet = on;
+            }
+            if (on) {
+                mWakeLockInterface.switchToFullWakeLock();
+                Log.i(CarLog.TAG_POWER, "on display");
+                mPowerManager.wakeUp(SystemClock.uptimeMillis());
+            } else {
+                mWakeLockInterface.switchToPartialWakeLock();
+                Log.i(CarLog.TAG_POWER, "off display");
+                mPowerManager.goToSleep(SystemClock.uptimeMillis());
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/IOInterface.java b/service/src/com/android/car/systeminterface/IOInterface.java
new file mode 100644
index 0000000..2ce9174
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/IOInterface.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.car.systeminterface;
+
+import android.content.Context;
+import java.io.File;
+
+/**
+ * Interface that abstracts I/O operations
+ */
+public interface IOInterface {
+    File getFilesDir();
+
+    class DefaultImpl implements IOInterface {
+        private final File mFilesDir;
+
+        DefaultImpl(Context context) {
+            mFilesDir = context.getFilesDir();
+        }
+
+        @Override
+        public File getFilesDir() {
+            return mFilesDir;
+        }
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java b/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java
new file mode 100644
index 0000000..5825f8f
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/StorageMonitoringInterface.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.car.systeminterface;
+
+import com.android.car.storagemonitoring.EMmcWearInformationProvider;
+import com.android.car.storagemonitoring.ProcfsUidIoStatsProvider;
+import com.android.car.storagemonitoring.UfsWearInformationProvider;
+import com.android.car.storagemonitoring.UidIoStatsProvider;
+import com.android.car.storagemonitoring.WearInformationProvider;
+
+/**
+ * Interface that abstracts storage monitoring operations
+ */
+public interface StorageMonitoringInterface {
+    default WearInformationProvider[] getFlashWearInformationProviders() {
+        return new WearInformationProvider[] {
+            new EMmcWearInformationProvider(),
+            new UfsWearInformationProvider()
+        };
+    }
+
+    default UidIoStatsProvider getUidIoStatsProvider() {
+        return new ProcfsUidIoStatsProvider();
+    }
+
+    class DefaultImpl implements StorageMonitoringInterface {}
+}
diff --git a/service/src/com/android/car/systeminterface/SystemInterface.java b/service/src/com/android/car/systeminterface/SystemInterface.java
new file mode 100644
index 0000000..1e984f9
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/SystemInterface.java
@@ -0,0 +1,234 @@
+/*
+ * 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 com.android.car.systeminterface;
+
+import android.content.Context;
+import com.android.car.CarPowerManagementService;
+import com.android.car.procfsinspector.ProcessInfo;
+import com.android.car.storagemonitoring.UidIoStatsProvider;
+import com.android.car.storagemonitoring.WearInformationProvider;
+import java.io.File;
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * This class contains references to all the different wrapper interfaces between
+ * CarService and the Android OS APIs.
+ */
+public final class SystemInterface implements DisplayInterface, IOInterface,
+        StorageMonitoringInterface, SystemStateInterface, TimeInterface,
+        WakeLockInterface {
+    private final DisplayInterface mDisplayInterface;
+    private final IOInterface mIOInterface;
+    private final StorageMonitoringInterface mStorageMonitoringInterface;
+    private final SystemStateInterface mSystemStateInterface;
+    private final TimeInterface mTimeInterface;
+    private final WakeLockInterface mWakeLockInterface;
+
+    SystemInterface(DisplayInterface displayInterface,
+            IOInterface ioInterface,
+            StorageMonitoringInterface storageMonitoringInterface,
+            SystemStateInterface systemStateInterface,
+            TimeInterface timeInterface,
+            WakeLockInterface wakeLockInterface) {
+        mDisplayInterface = displayInterface;
+        mIOInterface = ioInterface;
+        mStorageMonitoringInterface = storageMonitoringInterface;
+        mSystemStateInterface = systemStateInterface;
+        mTimeInterface = timeInterface;
+        mWakeLockInterface = wakeLockInterface;
+    }
+
+    public DisplayInterface getDisplayInterface() { return mDisplayInterface; }
+    public IOInterface getIOInterface() { return mIOInterface; }
+    public SystemStateInterface getSystemStateInterface() { return mSystemStateInterface; }
+    public TimeInterface getTimeInterface() { return mTimeInterface; }
+    public WakeLockInterface getWakeLockInterface() { return mWakeLockInterface; }
+
+    @Override
+    public File getFilesDir() {
+        return mIOInterface.getFilesDir();
+    }
+
+    @Override
+    public void releaseAllWakeLocks() {
+        mWakeLockInterface.releaseAllWakeLocks();
+    }
+
+    @Override
+    public void switchToPartialWakeLock() {
+        mWakeLockInterface.switchToPartialWakeLock();
+    }
+
+    @Override
+    public void switchToFullWakeLock() {
+        mWakeLockInterface.switchToFullWakeLock();
+    }
+
+    @Override
+    public long getUptime() {
+        return mTimeInterface.getUptime();
+    }
+
+    @Override
+    public long getUptime(boolean includeDeepSleepTime) {
+        return mTimeInterface.getUptime(includeDeepSleepTime);
+    }
+
+    @Override
+    public void scheduleAction(Runnable r, long delayMs) {
+        mTimeInterface.scheduleAction(r, delayMs);
+    }
+
+    @Override
+    public List<ProcessInfo> getRunningProcesses() {
+        return mSystemStateInterface.getRunningProcesses();
+    }
+
+    @Override
+    public void cancelAllActions() {
+        mTimeInterface.cancelAllActions();
+    }
+
+    @Override
+    public void setDisplayState(boolean on) {
+        mDisplayInterface.setDisplayState(on);
+    }
+
+    @Override
+    public void startDisplayStateMonitoring(CarPowerManagementService service) {
+        mDisplayInterface.startDisplayStateMonitoring(service);
+    }
+
+    @Override
+    public void stopDisplayStateMonitoring() {
+        mDisplayInterface.stopDisplayStateMonitoring();
+    }
+
+    @Override
+    public WearInformationProvider[] getFlashWearInformationProviders() {
+        return mStorageMonitoringInterface.getFlashWearInformationProviders();
+    }
+
+    @Override
+    public UidIoStatsProvider getUidIoStatsProvider() {
+        return mStorageMonitoringInterface.getUidIoStatsProvider();
+    }
+
+    @Override
+    public void shutdown() {
+        mSystemStateInterface.shutdown();
+    }
+
+    @Override
+    public void enterDeepSleep(int wakeupTimeSec) {
+        mSystemStateInterface.enterDeepSleep(wakeupTimeSec);
+    }
+
+    @Override
+    public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
+        mSystemStateInterface.scheduleActionForBootCompleted(action, delay);
+    }
+
+    @Override
+    public boolean isWakeupCausedByTimer() {
+        return mSystemStateInterface.isWakeupCausedByTimer();
+    }
+
+    @Override
+    public boolean isSystemSupportingDeepSleep() {
+        return mSystemStateInterface.isSystemSupportingDeepSleep();
+    }
+
+    public final static class Builder {
+        private DisplayInterface mDisplayInterface;
+        private IOInterface mIOInterface;
+        private StorageMonitoringInterface mStorageMonitoringInterface;
+        private SystemStateInterface mSystemStateInterface;
+        private TimeInterface mTimeInterface;
+        private WakeLockInterface mWakeLockInterface;
+
+        private Builder() {}
+
+        public static Builder newSystemInterface() {
+            return new Builder();
+        }
+
+        public static Builder defaultSystemInterface(Context context) {
+            context = Objects.requireNonNull(context);
+            Builder builder = newSystemInterface();
+            builder.withWakeLockInterface(new WakeLockInterface.DefaultImpl(context));
+            builder.withDisplayInterface(new DisplayInterface.DefaultImpl(context,
+                    builder.mWakeLockInterface));
+            builder.withIOInterface(new IOInterface.DefaultImpl(context));
+            builder.withStorageMonitoringInterface(new StorageMonitoringInterface.DefaultImpl());
+            builder.withSystemStateInterface(new SystemStateInterface.DefaultImpl(context));
+            return builder.withTimeInterface(new TimeInterface.DefaultImpl());
+        }
+
+        public static Builder fromBuilder(Builder otherBuilder) {
+            return newSystemInterface()
+                    .withDisplayInterface(otherBuilder.mDisplayInterface)
+                    .withIOInterface(otherBuilder.mIOInterface)
+                    .withStorageMonitoringInterface(otherBuilder.mStorageMonitoringInterface)
+                    .withSystemStateInterface(otherBuilder.mSystemStateInterface)
+                    .withTimeInterface(otherBuilder.mTimeInterface)
+                    .withWakeLockInterface(otherBuilder.mWakeLockInterface);
+        }
+
+        public Builder withDisplayInterface(DisplayInterface displayInterface) {
+            mDisplayInterface = displayInterface;
+            return this;
+        }
+
+        public Builder withIOInterface(IOInterface ioInterface) {
+            mIOInterface = ioInterface;
+            return this;
+        }
+
+        public Builder withStorageMonitoringInterface(StorageMonitoringInterface
+                storageMonitoringInterface) {
+            mStorageMonitoringInterface = storageMonitoringInterface;
+            return this;
+        }
+
+        public Builder withSystemStateInterface(SystemStateInterface systemStateInterface) {
+            mSystemStateInterface = systemStateInterface;
+            return this;
+        }
+
+        public Builder withTimeInterface(TimeInterface timeInterface) {
+            mTimeInterface = timeInterface;
+            return this;
+        }
+
+        public Builder withWakeLockInterface(WakeLockInterface wakeLockInterface) {
+            mWakeLockInterface = wakeLockInterface;
+            return this;
+        }
+
+        public SystemInterface build() {
+            return new SystemInterface(Objects.requireNonNull(mDisplayInterface),
+                Objects.requireNonNull(mIOInterface),
+                Objects.requireNonNull(mStorageMonitoringInterface),
+                Objects.requireNonNull(mSystemStateInterface),
+                Objects.requireNonNull(mTimeInterface),
+                Objects.requireNonNull(mWakeLockInterface));
+        }
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/SystemStateInterface.java b/service/src/com/android/car/systeminterface/SystemStateInterface.java
new file mode 100644
index 0000000..f7e1506
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/SystemStateInterface.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.car.systeminterface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Pair;
+import com.android.car.procfsinspector.ProcessInfo;
+import com.android.car.procfsinspector.ProcfsInspector;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Interface that abstracts system status (booted, sleeping, ...) operations
+ */
+public interface SystemStateInterface {
+    void shutdown();
+    void enterDeepSleep(int wakeupTimeSec);
+    void scheduleActionForBootCompleted(Runnable action, Duration delay);
+
+    default boolean isWakeupCausedByTimer() {
+        //TODO bug: 32061842, check wake up reason and do necessary operation information should
+        // come from kernel. it can be either power on or wake up for maintenance
+        // power on will involve GPIO trigger from power controller
+        // its own wakeup will involve timer expiration.
+        return false;
+    }
+
+    default boolean isSystemSupportingDeepSleep() {
+        //TODO should return by checking some kernel suspend control sysfs, bug: 32061842
+        return false;
+    }
+
+    default List<ProcessInfo> getRunningProcesses() {
+        return ProcfsInspector.readProcessTable();
+    }
+
+    class DefaultImpl implements SystemStateInterface {
+        private final static Duration MIN_BOOT_COMPLETE_ACTION_DELAY = Duration.ofSeconds(10);
+
+        private final Context mContext;
+        private final PowerManager mPowerManager;
+        private List<Pair<Runnable, Duration>> mActionsList = new ArrayList<>();
+        private ScheduledExecutorService mExecutorService;
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+                    for (Pair<Runnable, Duration> action : mActionsList) {
+                        mExecutorService.schedule(action.first,
+                            action.second.toMillis(), TimeUnit.MILLISECONDS);
+                    }
+                }
+            }
+        };
+
+        DefaultImpl(Context context) {
+            mContext = context;
+            mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        }
+
+        @Override
+        public void shutdown() {
+            mPowerManager.shutdown(false /* no confirm*/, null, true /* true */);
+        }
+
+        @Override
+        public void enterDeepSleep(int wakeupTimeSec) {
+            //TODO set wake up time, bug: 32061842
+            mPowerManager.goToSleep(SystemClock.uptimeMillis(),
+                PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN,
+                PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+        }
+
+        @Override
+        public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
+            if (MIN_BOOT_COMPLETE_ACTION_DELAY.compareTo(delay) < 0) {
+                // TODO: consider adding some degree of randomness here
+                delay = MIN_BOOT_COMPLETE_ACTION_DELAY;
+            }
+            if (mActionsList.isEmpty()) {
+                final int corePoolSize = 1;
+                mExecutorService = Executors.newScheduledThreadPool(corePoolSize);
+                IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+                mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+            }
+            mActionsList.add(Pair.create(action, delay));
+        }
+
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/TimeInterface.java b/service/src/com/android/car/systeminterface/TimeInterface.java
new file mode 100644
index 0000000..dea1153
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/TimeInterface.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.android.car.systeminterface;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+
+import android.os.SystemClock;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Interface that abstracts time operations
+ */
+public interface TimeInterface {
+    public static final boolean INCLUDE_DEEP_SLEEP_TIME = true;
+    public static final boolean EXCLUDE_DEEP_SLEEP_TIME = false;
+
+    default long getUptime() {
+        return getUptime(EXCLUDE_DEEP_SLEEP_TIME);
+    }
+    default long getUptime(boolean includeDeepSleepTime) {
+        return includeDeepSleepTime ?
+            SystemClock.elapsedRealtime() :
+            SystemClock.uptimeMillis();
+    }
+
+    void scheduleAction(Runnable r, long delayMs);
+    void cancelAllActions();
+
+    class DefaultImpl implements TimeInterface {
+        private final ScheduledExecutorService mExecutor = newSingleThreadScheduledExecutor();
+
+        @Override
+        public void scheduleAction(Runnable r, long delayMs) {
+            mExecutor.scheduleAtFixedRate(r, delayMs, delayMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void cancelAllActions() {
+            mExecutor.shutdownNow();
+        }
+    }
+}
diff --git a/service/src/com/android/car/systeminterface/WakeLockInterface.java b/service/src/com/android/car/systeminterface/WakeLockInterface.java
new file mode 100644
index 0000000..c7a5f8e
--- /dev/null
+++ b/service/src/com/android/car/systeminterface/WakeLockInterface.java
@@ -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.
+ */
+
+package com.android.car.systeminterface;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import com.android.car.CarLog;
+
+/**
+ * Interface that abstracts wake lock operations
+ */
+public interface WakeLockInterface {
+    void releaseAllWakeLocks();
+    void switchToPartialWakeLock();
+    void switchToFullWakeLock();
+
+    class DefaultImpl implements WakeLockInterface {
+        private final WakeLock mPartialWakeLock;
+        private final WakeLock mFullWakeLock;
+
+        DefaultImpl(Context context) {
+            PowerManager powerManager =
+                    (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            mFullWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK,
+                CarLog.TAG_POWER);
+            mPartialWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                CarLog.TAG_POWER);
+        }
+
+        @Override
+        public void switchToPartialWakeLock() {
+            if (!mPartialWakeLock.isHeld()) {
+                mPartialWakeLock.acquire();
+            }
+            if (mFullWakeLock.isHeld()) {
+                mFullWakeLock.release();
+            }
+        }
+
+        @Override
+        public void switchToFullWakeLock() {
+            if (!mFullWakeLock.isHeld()) {
+                mFullWakeLock.acquire();
+            }
+            if (mPartialWakeLock.isHeld()) {
+                mPartialWakeLock.release();
+            }
+        }
+
+        @Override
+        public void releaseAllWakeLocks() {
+            if (mPartialWakeLock.isHeld()) {
+                mPartialWakeLock.release();
+            }
+            if (mFullWakeLock.isHeld()) {
+                mFullWakeLock.release();
+            }
+        }
+    }
+}
diff --git a/libvehiclemonitor/java/Android.mk b/tests/DefaultStorageMonitoringCompanionApp/Android.mk
similarity index 60%
copy from libvehiclemonitor/java/Android.mk
copy to tests/DefaultStorageMonitoringCompanionApp/Android.mk
index 1ced365..8eb2a55 100644
--- a/libvehiclemonitor/java/Android.mk
+++ b/tests/DefaultStorageMonitoringCompanionApp/Android.mk
@@ -1,4 +1,4 @@
-# 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.
@@ -18,9 +18,22 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE := libvehiclemonitor-java
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := DefaultStorageMonitoringCompanionApp
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+LOCAL_PROGUARD_ENABLED := disabled
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
diff --git a/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml b/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml
new file mode 100644
index 0000000..45380ce
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.car.defaultstoragemonitoringcompanionapp">
+
+  <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
+
+  <application
+      android:allowBackup="true"
+      android:icon="@mipmap/ic_launcher"
+      android:label="@string/app_name"
+      android:roundIcon="@mipmap/ic_launcher_round"
+      android:supportsRtl="true"
+      android:theme="@style/AppTheme">
+    <activity
+        android:name=".MainActivity"
+        android:exported="true"
+        android:permission="android.car.permission.STORAGE_MONITORING">
+    </activity>
+  </application>
+
+</manifest>
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/layout/activity_main.xml b/tests/DefaultStorageMonitoringCompanionApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..017ebcc
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/notification"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:minLines="10"
+        android:textSize="24dp"
+        android:textColor="#ff0000"
+        android:text="Gathering data..."/>
+
+</RelativeLayout>
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-hdpi/ic_launcher.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-hdpi/ic_launcher_round.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9a078e3
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-mdpi/ic_launcher.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-mdpi/ic_launcher_round.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..efc028a
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xhdpi/ic_launcher.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xhdpi/ic_launcher_round.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3af2608
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxhdpi/ic_launcher.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxhdpi/ic_launcher_round.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bec2e6
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxxhdpi/ic_launcher.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxxhdpi/ic_launcher_round.png b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..34947cd
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/values/colors.xml b/tests/DefaultStorageMonitoringCompanionApp/res/values/colors.xml
new file mode 100644
index 0000000..5a077b3
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="colorPrimary">#3F51B5</color>
+  <color name="colorPrimaryDark">#303F9F</color>
+  <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/values/strings.xml b/tests/DefaultStorageMonitoringCompanionApp/res/values/strings.xml
new file mode 100644
index 0000000..8c0a9bb
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+  <string name="app_name">Flash Storage Wear Notification</string>
+</resources>
diff --git a/tests/DefaultStorageMonitoringCompanionApp/res/values/styles.xml b/tests/DefaultStorageMonitoringCompanionApp/res/values/styles.xml
new file mode 100644
index 0000000..a7a0615
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/res/values/styles.xml
@@ -0,0 +1,8 @@
+<resources>
+
+  <!-- Base application theme. -->
+  <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
+    <!-- Customize your theme here. -->
+  </style>
+
+</resources>
diff --git a/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java b/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java
new file mode 100644
index 0000000..bcec38b
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/MainActivity.java
@@ -0,0 +1,172 @@
+/*
+ * 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.defaultstoragemonitoringcompanionapp;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.widget.TextView;
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.util.List;
+import java.util.Locale;
+
+public class MainActivity extends Activity {
+    private static final boolean DEBUG = false;
+    private static final String TAG = MainActivity.class.getSimpleName();
+
+    private final ServiceConnection mCarConnectionListener = new ServiceConnection() {
+        private String wearLevelToString(int wearLevel) {
+            if (wearLevel == WearEstimate.UNKNOWN) return "unknown";
+            return wearLevel + "%";
+        }
+
+        private String durationPairToString(long value1, String name1,
+            long value2, String name2) {
+            if (value1 > 0) {
+                String s = value1 + " " + name1;
+                if (value2 > 0) {
+                    s += " and " + value2 + " " + name2;
+                }
+                return s;
+            }
+            return null;
+        }
+
+        private String durationToString(Duration duration) {
+            final long days = duration.toDays();
+            duration = duration.minusDays(days);
+            final long hours = duration.toHours();
+            duration = duration.minusHours(hours);
+            final long minutes = duration.toMinutes();
+
+            // for a week or more, just return days
+            if (days >= 7) {
+                return days + " days";
+            }
+
+            // otherwise try a few pairs of units
+            final String daysHours = durationPairToString(days, "days", hours, "hours");
+            if (daysHours != null) return daysHours;
+            final String hoursMinutes = durationPairToString(hours, "hours", minutes, "minutes");
+            if (hoursMinutes != null) return hoursMinutes;
+
+            // either minutes, or less than a minute
+            if (minutes > 0) {
+                return minutes + " minutes";
+            } else {
+                return "less than a minute";
+            }
+        }
+
+        private String wearChangeToString(WearEstimateChange wearEstimateChange) {
+            final int oldLevel = Math.max(wearEstimateChange.oldEstimate.typeA,
+                wearEstimateChange.oldEstimate.typeB);
+            final int newLevel = Math.max(wearEstimateChange.newEstimate.typeA,
+                wearEstimateChange.newEstimate.typeB);
+
+            final String oldLevelString = wearLevelToString(oldLevel);
+            final String newLevelString = wearLevelToString(newLevel);
+
+            final DateTimeFormatter formatter =
+                DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withZone(
+                    ZoneId.systemDefault()).withLocale(Locale.getDefault());
+
+            final String wallClockTimeAtChange = formatter.format(wearEstimateChange.dateAtChange);
+            final String uptimeAtChange = durationToString(
+                Duration.ofMillis(wearEstimateChange.uptimeAtChange));
+
+            return String.format(
+                "Wear level went from %s to %s.\nThe vehicle has been running for %s.\nWall clock time: %s",
+                oldLevelString,
+                newLevelString,
+                uptimeAtChange,
+                wallClockTimeAtChange);
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, "Connected to " + name.flattenToString());
+
+            try {
+                CarStorageMonitoringManager storageMonitoringManager =
+                    (CarStorageMonitoringManager) mCar.getCarManager(
+                        Car.STORAGE_MONITORING_SERVICE);
+                Log.d(TAG, "Acquired a CarStorageMonitoringManager " + storageMonitoringManager);
+                List<WearEstimateChange> wearEstimateChanges = storageMonitoringManager
+                    .getWearEstimateHistory();
+                if (wearEstimateChanges.isEmpty()) {
+                    finish();
+                }
+
+                WearEstimateChange currentChange = wearEstimateChanges
+                    .get(wearEstimateChanges.size() - 1);
+
+                if (!DEBUG && currentChange.isAcceptableDegradation) {
+                    finish();
+                }
+
+                    mNotificationTextView.setText(wearChangeToString(currentChange));
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Failed to get a connection", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, "Disconnected from " + name.flattenToString());
+
+            mCar = null;
+        }
+    };
+
+    private final Handler mHandler = new Handler();
+
+    private Car mCar = null;
+    private TextView mNotificationTextView = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        mNotificationTextView = findViewById(R.id.notification);
+
+        mCar = Car.createCar(this, mCarConnectionListener);
+        mCar.connect();
+
+        mHandler.postDelayed(this::finish, 30000);
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mCar != null) {
+            mCar.disconnect();
+        }
+        super.onDestroy();
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 2a96898..db09cba 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
     <uses-permission android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"/>
     <uses-permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"/>
+    <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
new file mode 100644
index 0000000..bf36b4a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
@@ -0,0 +1,57 @@
+<?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">
+    <TextView
+        android:id="@+id/storage_wear_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Storage Wear Information"
+        android:textSize="24dp"
+        android:textColor="#ff0000"
+        android:minLines="5"/>
+    <ListView
+        android:id="@+id/storage_events_list"
+        android:layout_width="match_parent"
+        android:layout_height="75dp"/>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/write_one_megabyte"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Write 1M"/>
+        <Button
+            android:id="@+id/write_one_kilobyte"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Write 1K"/>
+    </LinearLayout>
+    <TextView
+        android:id="@+id/free_disk_space"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Free Disk Space: 1 byte"
+        android:textSize="24dp"
+        android:textColor="#ff0000"
+        android:minLines="5"/>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/wear_estimate_change_textview.xml b/tests/EmbeddedKitchenSinkApp/res/layout/wear_estimate_change_textview.xml
new file mode 100644
index 0000000..0b09562
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/wear_estimate_change_textview.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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/wear_estimate_change_textview"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:text="Wear Estimate Change"
+    android:textSize="18dp"
+    android:textColor="#ff0000"
+    android:minLines="2"/>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
index 7dddd1f..064d06d 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -35,7 +36,7 @@
 import android.util.SparseIntArray;
 
 import com.android.car.ICarImpl;
-import com.android.car.SystemInterface;
+import com.android.car.systeminterface.SystemInterface;
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
@@ -66,7 +67,8 @@
     private CarEmulator(Context context) {
         mHalEmulator = new MockedVehicleHal();
         ICarImpl carService = new ICarImpl(context, mHalEmulator,
-                SystemInterface.getDefault(context), null /* error notifier */);
+                SystemInterface.Builder.defaultSystemInterface(context).build(),
+                null /* error notifier */);
         mCar = new Car(context, carService, null /* Handler */);
     }
 
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 e68c3fa..c1dd824 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -49,6 +49,7 @@
 import com.google.android.car.kitchensink.radio.RadioTestFragment;
 import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
 import com.google.android.car.kitchensink.setting.CarServiceSettingsActivity;
+import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
 import com.google.android.car.kitchensink.touch.TouchTestFragment;
 import com.google.android.car.kitchensink.volume.VolumeTestFragment;
 import java.util.ArrayList;
@@ -147,6 +148,7 @@
             add("orientation test", OrientationTestFragment.class);
             add("bluetooth headset",BluetoothHeadsetFragment.class);
             add("bluetooth messaging test", MapMceTestFragment.class);
+            add("storage lifetime", StorageLifetimeFragment.class);
             add("car service settings", () -> {
                 Intent intent = new Intent(KitchenSinkActivity.this,
                     CarServiceSettingsActivity.class);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
new file mode 100644
index 0000000..336cc33
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
@@ -0,0 +1,141 @@
+/*
+ * 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.storagelifetime;
+
+import android.annotation.Nullable;
+import android.car.Car;
+import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.database.DataSetObserver;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.StatFs;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.google.android.car.kitchensink.KitchenSinkActivity;
+import com.google.android.car.kitchensink.R;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+public class StorageLifetimeFragment extends Fragment {
+    private static final String FILE_NAME = "storage.bin";
+    private static final String TAG = "CAR.STORAGELIFETIME.KS";
+
+    private static final int KILOBYTE = 1024;
+    private static final int MEGABYTE = 1024 * 1024;
+
+    private StatFs mStatFs;
+    private KitchenSinkActivity mActivity;
+    private TextView mStorageWearInfo;
+    private ListView mStorageChangesHistory;
+    private TextView mFreeSpaceInfo;
+    private CarStorageMonitoringManager mStorageManager;
+
+    // TODO(egranata): put this somewhere more useful than KitchenSink
+    private static String preEolToString(int preEol) {
+        switch (preEol) {
+            case 1: return "normal";
+            case 2: return "warning";
+            case 3: return "urgent";
+            default:
+                return "unknown";
+        }
+    }
+
+    private void writeBytesToFile(int size) {
+        try {
+            byte[] data = new byte[size];
+            SecureRandom.getInstanceStrong().nextBytes(data);
+            Path filePath = new File(mActivity.getFilesDir(), FILE_NAME).toPath();
+            if (Files.notExists(filePath)) {
+                Files.createFile(filePath);
+            }
+            Files.write(filePath,
+                data,
+                StandardOpenOption.APPEND);
+        } catch (NoSuchAlgorithmException | IOException e) {
+            Log.w(TAG, "could not append data", e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater,
+            @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.storagewear, container, false);
+        mActivity = (KitchenSinkActivity) getHost();
+        mStorageWearInfo = view.findViewById(R.id.storage_wear_info);
+        mStorageChangesHistory = view.findViewById(R.id.storage_events_list);
+        mFreeSpaceInfo = view.findViewById(R.id.free_disk_space);
+
+        view.findViewById(R.id.write_one_kilobyte).setOnClickListener(
+            v -> writeBytesToFile(KILOBYTE));
+
+        view.findViewById(R.id.write_one_megabyte).setOnClickListener(
+            v -> writeBytesToFile(MEGABYTE));
+
+        return view;
+    }
+
+    private void reloadInfo() {
+        try {
+            mStatFs = new StatFs(mActivity.getFilesDir().getAbsolutePath());
+
+            mStorageManager =
+                (CarStorageMonitoringManager) mActivity.getCar().getCarManager(
+                        Car.STORAGE_MONITORING_SERVICE);
+
+            mStorageWearInfo.setText("Wear estimate: " +
+                mStorageManager.getWearEstimate() + "\nPre EOL indicator: " +
+                preEolToString(mStorageManager.getPreEolIndicatorStatus()));
+
+            mStorageChangesHistory.setAdapter(new ArrayAdapter(mActivity,
+                    R.layout.wear_estimate_change_textview,
+                    mStorageManager.getWearEstimateHistory().toArray()));
+
+            mFreeSpaceInfo.setText("Available blocks: " + mStatFs.getAvailableBlocksLong() +
+                "\nBlock size: " + mStatFs.getBlockSizeLong() + " bytes" +
+                "\nfor a total free space of: " +
+                (mStatFs.getBlockSizeLong() * mStatFs.getAvailableBlocksLong() / MEGABYTE) + "MB");
+        } catch (android.car.CarNotConnectedException|
+                 android.support.car.CarNotConnectedException e) {
+            Log.e(TAG, "Car not connected or not supported", e);
+        }
+
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        reloadInfo();
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
index 0d65500..874bc64 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
@@ -18,11 +18,14 @@
 import android.car.Car;
 import android.car.CarNotConnectedException;
 import android.car.media.CarAudioManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.ServiceConnection;
 import android.media.AudioManager;
 import android.media.IVolumeController;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.support.annotation.Nullable;
@@ -36,10 +39,9 @@
 import android.widget.Button;
 import android.widget.ListView;
 
-import com.google.android.car.kitchensink.CarEmulator;
 import com.google.android.car.kitchensink.R;
 
-public class VolumeTestFragment extends Fragment{
+public class VolumeTestFragment extends Fragment {
     private static final String TAG = "CarVolumeTest";
     private static final int MSG_VOLUME_CHANGED = 0;
     private static final int MSG_REQUEST_FOCUS = 1;
@@ -52,7 +54,6 @@
 
     private CarAudioManager mCarAudioManager;
     private Car mCar;
-    private CarEmulator mCarEmulator;
 
     private Button mVolumeUp;
     private Button mVolumeDown;
@@ -140,6 +141,26 @@
             default: return "Unknown";
         }
     }
+    private final ServiceConnection mCarConnectionCallback =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder binder) {
+                    Log.d(TAG, "Connected to Car Service");
+                    try {
+                        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+                        initVolumeInfo();
+                        mCarAudioManager.setVolumeController(mVolumeController);
+
+                    } catch (CarNotConnectedException e) {
+                        Log.e(TAG, "Car is not connected!", e);
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    Log.d(TAG, "Disconnect from Car Service");
+                }
+            };
 
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -160,33 +181,8 @@
             }
         });
 
-        mCarEmulator = CarEmulator.create(getContext());
-        mCar = mCarEmulator.getCar();
-        try {
-            mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
-            initVolumeInfo();
-            mCarAudioManager.setVolumeController(mVolumeController);
-        } catch (CarNotConnectedException e) {
-            throw new RuntimeException(e); // Should never occur in car emulator.
-        }
-
-        mVolumeUp = (Button) v.findViewById(R.id.volume_up);
-        mVolumeDown = (Button) v.findViewById(R.id.volume_down);
-
-        mVolumeUp.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mCarEmulator.injectKey(KeyEvent.KEYCODE_VOLUME_UP);
-            }
-        });
-
-        mVolumeDown.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mCarEmulator.injectKey(KeyEvent.KEYCODE_VOLUME_DOWN);
-            }
-        });
-
+        mCar = Car.createCar(getActivity(), mCarConnectionCallback);
+        mCar.connect();
         return v;
     }
 
@@ -214,7 +210,8 @@
             return;
         }
         try {
-            mCarAudioManager.setStreamVolume(logicalStream, volume, 0);
+            mCarAudioManager.setStreamVolume(logicalStream, volume,
+                    AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND);
         } catch (CarNotConnectedException e) {
             Log.e(TAG, "car not connected", e);
         }
diff --git a/libvehiclemonitor/java/Android.mk b/tests/VmsPublisherClientSample/Android.mk
similarity index 64%
copy from libvehiclemonitor/java/Android.mk
copy to tests/VmsPublisherClientSample/Android.mk
index 1ced365..2aa6c40 100644
--- a/libvehiclemonitor/java/Android.mk
+++ b/tests/VmsPublisherClientSample/Android.mk
@@ -1,4 +1,4 @@
-# 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.
@@ -18,9 +18,20 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE := libvehiclemonitor-java
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := VmsPublisherClientSample
+
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+LOCAL_PRIVILEGED_MODULE := true
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_CERTIFICATE := testkey
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/VmsPublisherClientSample/AndroidManifest.xml b/tests/VmsPublisherClientSample/AndroidManifest.xml
new file mode 100644
index 0000000..fdc1a31
--- /dev/null
+++ b/tests/VmsPublisherClientSample/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          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'/>
+
+    <application android:label="@string/app_name"
+                 android:icon="@mipmap/ic_launcher"
+                 android:directBootAware="true">
+        <service android:name=".VmsPublisherClientSampleService"
+                 android:exported="true"
+                 android:singleUser="true">
+        </service>
+    </application>
+</manifest>
diff --git a/tests/VmsPublisherClientSample/res/mipmap-hdpi/ic_launcher.png b/tests/VmsPublisherClientSample/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/VmsPublisherClientSample/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsPublisherClientSample/res/mipmap-mdpi/ic_launcher.png b/tests/VmsPublisherClientSample/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/VmsPublisherClientSample/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsPublisherClientSample/res/mipmap-xhdpi/ic_launcher.png b/tests/VmsPublisherClientSample/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/VmsPublisherClientSample/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsPublisherClientSample/res/mipmap-xxhdpi/ic_launcher.png b/tests/VmsPublisherClientSample/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/VmsPublisherClientSample/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsPublisherClientSample/res/mipmap-xxxhdpi/ic_launcher.png b/tests/VmsPublisherClientSample/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/VmsPublisherClientSample/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsPublisherClientSample/res/values/strings.xml b/tests/VmsPublisherClientSample/res/values/strings.xml
new file mode 100644
index 0000000..df8bf05
--- /dev/null
+++ b/tests/VmsPublisherClientSample/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?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">VmsPublisherClientSample</string>
+</resources>
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
new file mode 100644
index 0000000..e235f1e
--- /dev/null
+++ b/tests/VmsPublisherClientSample/src/com/google/android/car/vms/publisher/VmsPublisherClientSampleService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.vms.publisher;
+
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsPublisherClientService;
+import android.car.vms.VmsSubscriptionState;
+import android.os.Handler;
+import android.os.Message;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This service is launched during the initialization of the VMS publisher service.
+ * Once onVmsPublisherServiceReady is invoked, it starts publishing a single byte every second.
+ */
+public class VmsPublisherClientSampleService extends VmsPublisherClientService {
+    public static final int PUBLISH_EVENT = 0;
+    public static final VmsLayer TEST_LAYER = new VmsLayer(0, 0, 0);
+    public static final int PUBLISHER_ID = 1;
+
+    private byte mCounter = 0;
+    private AtomicBoolean mInitialized = new AtomicBoolean(false);
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == PUBLISH_EVENT && mInitialized.get()) {
+                periodicPublish();
+            }
+        }
+    };
+
+    /**
+     * Notifies that the publisher services are ready to be used: {@link #publish(VmsLayer, byte[])}
+     * and {@link #getSubscriptions()}.
+     */
+    @Override
+    public void onVmsPublisherServiceReady() {
+        VmsSubscriptionState subscriptionState = getSubscriptions();
+        onVmsSubscriptionChange(subscriptionState);
+    }
+
+    @Override
+    public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
+        if (mInitialized.compareAndSet(false, true)) {
+            for (VmsLayer layer : subscriptionState.getLayers()) {
+                if (layer.equals(TEST_LAYER)) {
+                    mHandler.sendEmptyMessage(PUBLISH_EVENT);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mInitialized.set(false);
+        mHandler.removeMessages(PUBLISH_EVENT);
+    }
+
+    private void periodicPublish() {
+        publish(TEST_LAYER, PUBLISHER_ID, new byte[]{mCounter});
+        ++mCounter;
+        mHandler.sendEmptyMessageDelayed(PUBLISH_EVENT, 1000);
+    }
+}
diff --git a/libvehiclemonitor/java/Android.mk b/tests/VmsSubscriberClientSample/Android.mk
similarity index 60%
copy from libvehiclemonitor/java/Android.mk
copy to tests/VmsSubscriberClientSample/Android.mk
index 1ced365..f59e267 100644
--- a/libvehiclemonitor/java/Android.mk
+++ b/tests/VmsSubscriberClientSample/Android.mk
@@ -1,4 +1,4 @@
-# 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.
@@ -18,9 +18,22 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE := libvehiclemonitor-java
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := VmsSubscriberClientSample
+
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+LOCAL_PRIVILEGED_MODULE := true
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include packages/services/Car/car-support-lib/car-support.mk
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/VmsSubscriberClientSample/AndroidManifest.xml b/tests/VmsSubscriberClientSample/AndroidManifest.xml
new file mode 100644
index 0000000..bc798d7
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="com.google.android.car.vms.subscriber"
+          android:sharedUserId="android.uid.system">
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion='25'/>
+
+    <application android:label="@string/app_name"
+                 android:icon="@mipmap/ic_launcher">
+        <meta-data
+            android:name="android.car.application"
+            android:resource="@xml/automotive_app_desc"/>
+        <activity android:name=".VmsSubscriberClientSampleActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/VmsSubscriberClientSample/res/layout/activity_main.xml b/tests/VmsSubscriberClientSample/res/layout/activity_main.xml
new file mode 100644
index 0000000..ce05a4d
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/layout/activity_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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="vms.apps.android.google.com.java.myapplication.MainActivity">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text=""
+        android:id="@+id/textview"/>
+</RelativeLayout>
diff --git a/tests/VmsSubscriberClientSample/res/mipmap-hdpi/ic_launcher.png b/tests/VmsSubscriberClientSample/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsSubscriberClientSample/res/mipmap-mdpi/ic_launcher.png b/tests/VmsSubscriberClientSample/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsSubscriberClientSample/res/mipmap-xhdpi/ic_launcher.png b/tests/VmsSubscriberClientSample/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsSubscriberClientSample/res/mipmap-xxhdpi/ic_launcher.png b/tests/VmsSubscriberClientSample/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsSubscriberClientSample/res/mipmap-xxxhdpi/ic_launcher.png b/tests/VmsSubscriberClientSample/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/VmsSubscriberClientSample/res/values-w820dp/dimens.xml b/tests/VmsSubscriberClientSample/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..308a194
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+  <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+  <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/VmsSubscriberClientSample/res/values/colors.xml b/tests/VmsSubscriberClientSample/res/values/colors.xml
new file mode 100644
index 0000000..5a077b3
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="colorPrimary">#3F51B5</color>
+  <color name="colorPrimaryDark">#303F9F</color>
+  <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/VmsSubscriberClientSample/res/values/dimens.xml b/tests/VmsSubscriberClientSample/res/values/dimens.xml
new file mode 100644
index 0000000..acf94cc
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<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/VmsSubscriberClientSample/res/values/strings.xml b/tests/VmsSubscriberClientSample/res/values/strings.xml
new file mode 100644
index 0000000..24df55e
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+  <string name="app_name">VmsSubscriberClientSample</string>
+</resources>
diff --git a/tests/VmsSubscriberClientSample/res/values/styles.xml b/tests/VmsSubscriberClientSample/res/values/styles.xml
new file mode 100644
index 0000000..a7a0615
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/values/styles.xml
@@ -0,0 +1,8 @@
+<resources>
+
+  <!-- Base application theme. -->
+  <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
+    <!-- Customize your theme here. -->
+  </style>
+
+</resources>
diff --git a/tests/VmsSubscriberClientSample/res/xml/automotive_app_desc.xml b/tests/VmsSubscriberClientSample/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..b10ddd0
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/res/xml/automotive_app_desc.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<automotiveApp>
+    <uses name="service" />
+    <uses name="projection" />
+    <uses name="activity" class="com.google.android.car.vms.subscriber.VmsSubscriberClientSampleActivity" />
+</automotiveApp>
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
new file mode 100644
index 0000000..3ac0707
--- /dev/null
+++ b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.vms.subscriber;
+
+import android.app.Activity;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsSubscriberManager;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.car.Car;
+import android.support.car.CarConnectionCallback;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Connects to the Car service during onCreate. CarConnectionCallback.onConnected is invoked when
+ * the connection is ready. Then, it subscribes to a VMS layer/version and updates the TextView when
+ * a message is received.
+ */
+public class VmsSubscriberClientSampleActivity extends Activity {
+    private static final String TAG = "VmsSampleActivity";
+    // The layer id and version should match the ones defined in
+    // com.google.android.car.vms.publisher.VmsPublisherClientSampleService
+    public static final VmsLayer TEST_LAYER = new VmsLayer(0, 0, 0);
+
+    private Car mCarApi;
+    private TextView mTextView;
+    private VmsSubscriberManager mVmsSubscriberManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        mTextView = (TextView) findViewById(R.id.textview);
+        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            mCarApi = Car.createCar(this, mCarConnectionCallback);
+            mCarApi.connect();
+        } else {
+            Log.d(TAG, "No automotive feature.");
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mCarApi != null) {
+            mCarApi.disconnect();
+        }
+        Log.i(TAG, "onDestroy");
+    }
+
+    private final CarConnectionCallback mCarConnectionCallback = new CarConnectionCallback() {
+        @Override
+        public void onConnected(Car car) {
+            Log.d(TAG, "Connected to Car Service");
+            mVmsSubscriberManager = getVmsSubscriberManager();
+            configureSubscriptions(mVmsSubscriberManager);
+        }
+
+        @Override
+        public void onDisconnected(Car car) {
+            Log.d(TAG, "Disconnect from Car Service");
+        }
+
+        private VmsSubscriberManager getVmsSubscriberManager() {
+            try {
+                return (VmsSubscriberManager) mCarApi.getCarManager(
+                        android.car.Car.VMS_SUBSCRIBER_SERVICE);
+            } catch (android.support.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!", e);
+            }
+            return null;
+        }
+
+        private void configureSubscriptions(VmsSubscriberManager vmsSubscriberManager) {
+            try {
+                vmsSubscriberManager.registerClientCallback(mClientCallback);
+                vmsSubscriberManager.subscribe(TEST_LAYER);
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!", e);
+            }
+        }
+    };
+
+    private final VmsSubscriberManager.VmsSubscriberClientCallback mClientCallback =
+            new VmsSubscriberManager.VmsSubscriberClientCallback() {
+                @Override
+                public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
+                    mTextView.setText(String.valueOf(payload[0]));
+                }
+
+                @Override
+                public void onLayersAvailabilityChanged(List<VmsLayer> availableLayers) {
+                    mTextView.setText(String.valueOf(availableLayers));
+                }
+            };
+}
diff --git a/tests/carservice_test/Android.mk b/tests/carservice_test/Android.mk
index 4be5354..9203cbd 100644
--- a/tests/carservice_test/Android.mk
+++ b/tests/carservice_test/Android.mk
@@ -41,7 +41,8 @@
                                vehicle-hal-support-lib \
                                car-systemtest \
                                android-support-test \
-                               android.hardware.automotive.vehicle-V2.0-java-static
+                               android.hardware.automotive.vehicle-V2.0-java \
+                               com.android.car.test.utils
 
 LOCAL_JAVA_LIBRARIES := android.car android.test.runner
 
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 6a1e2bf..a34c1c6 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -24,24 +24,29 @@
     <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.car.permission.STORAGE_MONITORING" />
     <instrumentation android:name="android.test.InstrumentationTestRunner"
             android:targetPackage="com.android.car.test"
             android:label="Tests for Car APIs"/>
 
     <application android:label="CarServiceTest">
         <uses-library android:name="android.test.runner" />
-        <service android:name="com.android.car.test.TestAppBlockingPolicyService"
+        <service android:name="com.android.car.TestAppBlockingPolicyService"
             android:permission="android.car.permission.CONTROL_APP_BLOCKING">
             <intent-filter>
                 <action android:name="android.car.content.pm.CarAppBlockingPolicyService"/>
             </intent-filter>
         </service>
-        <activity android:name="com.android.car.test.SystemActivityMonitoringServiceTest$ActivityA" />
-        <activity android:name="com.android.car.test.SystemActivityMonitoringServiceTest$ActivityB"
-            android:taskAffinity="com.android.car.carservicetest.activity"/>
-        <activity android:name="com.android.car.test.SystemActivityMonitoringServiceTest$ActivityC"
-            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="com.android.car.SimpleVmsPublisherClientService"
+                 android:exported="true"
+        />
+        <service android:name="com.android.car.VmsPublisherClientMockService"
+                 android:exported="true" />
+
+        <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$ActivityA"/>
+        <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$ActivityB"/>
+        <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$ActivityC"/>
+        <activity android:name="com.android.car.SystemActivityMonitoringServiceTest$BlockingActivity"
+                  android:taskAffinity="com.android.car.carservicetest.block"/>
     </application>
 </manifest>
diff --git a/tests/carservice_test/src/com/android/car/test/AppFocusTest.java b/tests/carservice_test/src/com/android/car/AppFocusTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/AppFocusTest.java
rename to tests/carservice_test/src/com/android/car/AppFocusTest.java
index b63383e..d9bce26 100644
--- a/tests/carservice_test/src/com/android/car/test/AppFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/AppFocusTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.CarAppFocusManager;
diff --git a/tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java b/tests/carservice_test/src/com/android/car/AudioRoutingPolicyTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java
rename to tests/carservice_test/src/com/android/car/AudioRoutingPolicyTest.java
index 7f4aeac..357da1d 100644
--- a/tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java
+++ b/tests/carservice_test/src/com/android/car/AudioRoutingPolicyTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag;
 import android.hardware.automotive.vehicle.V2_0.VehicleAudioRoutingPolicyIndex;
diff --git a/tests/carservice_test/src/com/android/car/test/AudioTestUtils.java b/tests/carservice_test/src/com/android/car/AudioTestUtils.java
similarity index 98%
rename from tests/carservice_test/src/com/android/car/test/AudioTestUtils.java
rename to tests/carservice_test/src/com/android/car/AudioTestUtils.java
index 8c3de7c..459daa2 100644
--- a/tests/carservice_test/src/com/android/car/test/AudioTestUtils.java
+++ b/tests/carservice_test/src/com/android/car/AudioTestUtils.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.media.AudioAttributes;
 import android.media.AudioFocusRequest;
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java b/tests/carservice_test/src/com/android/car/CarAudioExtFocusTest.java
similarity index 97%
rename from tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java
rename to tests/carservice_test/src/com/android/car/CarAudioExtFocusTest.java
index 71e0d04..46e1df6 100644
--- a/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/CarAudioExtFocusTest.java
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS;
-import static com.android.car.test.AudioTestUtils.doRequestFocus;
+import static com.android.car.AudioTestUtils.doRequestFocus;
 import static java.lang.Integer.toHexString;
 
 import android.car.Car;
@@ -227,7 +227,7 @@
     public void testMediaNavFocus() throws Exception {
         //music start
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -249,7 +249,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
@@ -293,7 +293,7 @@
     public void testMediaExternalMediaNavFocus() throws Exception {
         // android music
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -323,7 +323,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK,
@@ -369,7 +369,7 @@
         assertNotNull(carAudioManager);
         AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
                 CarAudioManager.CAR_AUDIO_USAGE_RADIO);
-        int res = doRequestFocus(mAudioManager, listenerRadio,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerRadio,
                 radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -389,7 +389,7 @@
         AudioFocusListener listenerNav = new AudioFocusListener();
         AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource(
                 CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
-        res = doRequestFocus(mAudioManager, listenerNav,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav,
                 extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -438,7 +438,7 @@
     public void testMediaExternalNav() throws Exception {
         // android music
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -458,7 +458,7 @@
         AudioFocusListener listenerNav = new AudioFocusListener();
         AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource(
                 CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
-        res = doRequestFocus(mAudioManager, listenerNav,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav,
                 extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -514,7 +514,7 @@
         AudioFocusListener listenerIntNav = new AudioFocusListener();
         AudioAttributes intNavAttributes = mCarAudioManager.getAudioAttributesForCarUsage(
                 CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE);
-        int res = doRequestFocus(mAudioManager, listenerIntNav, intNavAttributes,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerIntNav, intNavAttributes,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -534,7 +534,7 @@
         AudioFocusListener listenerExtNav = new AudioFocusListener();
         AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource(
                 CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE);
-        res = doRequestFocus(mAudioManager, listenerExtNav,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerExtNav,
                 extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -582,7 +582,7 @@
     public void testMediaExternalRadioNavMediaFocus() throws Exception {
         // android music
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -605,7 +605,7 @@
         assertNotNull(carAudioManager);
         AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
                 CarAudioManager.CAR_AUDIO_USAGE_RADIO);
-        res = doRequestFocus(mAudioManager, listenerRadio,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerRadio,
                 radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -627,7 +627,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        res = doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN,
@@ -763,7 +763,7 @@
         assertNotNull(carAudioManager);
         AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(mediaUsage);
         Log.i(TAG, "request media Focus");
-        int res = doRequestFocus(mAudioManager, listenerMedia,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMedia,
                 radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -807,7 +807,7 @@
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
         Log.i(TAG, "request nav Focus");
-        res = doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK,
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java b/tests/carservice_test/src/com/android/car/CarAudioFocusSystemSoundTest.java
similarity index 97%
rename from tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java
rename to tests/carservice_test/src/com/android/car/CarAudioFocusSystemSoundTest.java
index afafb28..5e0d2e7 100644
--- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusSystemSoundTest.java
+++ b/tests/carservice_test/src/com/android/car/CarAudioFocusSystemSoundTest.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 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 static com.android.car.AudioTestUtils.doRequestFocus;
 
 import com.google.android.collect.Lists;
 
@@ -137,7 +137,7 @@
         assertNotNull(carAudioManager);
         AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
                 CarAudioManager.CAR_AUDIO_USAGE_RADIO);
-        int res = doRequestFocus(mAudioManager, listenerRadio,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerRadio,
                 radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -191,7 +191,7 @@
     public void testMusicSystemSound() throws Exception {
         // music start
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -247,7 +247,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        int res = doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/CarAudioFocusTest.java
similarity index 96%
rename from tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
rename to tests/carservice_test/src/com/android/car/CarAudioFocusTest.java
index 6f5da5f..982924f 100644
--- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/CarAudioFocusTest.java
@@ -13,10 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 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;
@@ -136,7 +135,7 @@
     public void testMediaNavFocus() throws Exception {
         //music start
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -156,7 +155,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
@@ -194,7 +193,7 @@
     public void testMediaExternalMediaNavFocus() throws Exception {
         // android music
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -222,7 +221,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK,
@@ -259,7 +258,7 @@
     public void testMediaExternalRadioNavMediaFocus() throws Exception {
         // android music
         AudioFocusListener listenerMusic = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -280,7 +279,7 @@
         assertNotNull(carAudioManager);
         AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
                 CarAudioManager.CAR_AUDIO_USAGE_RADIO);
-        res = doRequestFocus(mAudioManager, listenerRadio,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerRadio,
                 radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -300,7 +299,7 @@
                 setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
-        res = doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN,
@@ -361,7 +360,7 @@
             int context)
             throws Exception {
         AudioFocusListener lister = new AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, lister,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, lister,
                 streamType,
                 androidFocus);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -427,7 +426,7 @@
         assertNotNull(carAudioManager);
         AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(mediaUsage);
         Log.i(TAG, "request media Focus");
-        int res = doRequestFocus(mAudioManager, listenerMedia,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMedia,
                 radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
         int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -462,7 +461,7 @@
                 setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
                 build();
         Log.i(TAG, "request nav Focus");
-        res = doRequestFocus(mAudioManager, listenerNav, navAttrib,
+        res = AudioTestUtils.doRequestFocus(mAudioManager, listenerNav, navAttrib,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
         request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
         assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK,
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioManagerTest.java b/tests/carservice_test/src/com/android/car/CarAudioManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarAudioManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarAudioManagerTest.java
index 07646d4..a66cc60 100644
--- a/tests/carservice_test/src/com/android/car/test/CarAudioManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarAudioManagerTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.media.CarAudioManager;
diff --git a/tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java b/tests/carservice_test/src/com/android/car/CarCabinManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarCabinManagerTest.java
index cef163e..58b784b 100644
--- a/tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarCabinManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.hardware.CarPropertyValue;
diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticConstantsTest.java b/tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarDiagnosticConstantsTest.java
rename to tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java
index 2ed2658..33be912 100644
--- a/tests/carservice_test/src/com/android/car/test/CarDiagnosticConstantsTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDiagnosticConstantsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java
similarity index 98%
rename from tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java
index b77a844..eb7ef57 100644
--- a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDiagnosticManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import static java.lang.Integer.toHexString;
 
@@ -43,6 +43,7 @@
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
@@ -190,6 +191,7 @@
     @Override
     protected synchronized void configureMockedHal() {
         java.util.Collection<Integer> numVendorSensors = Arrays.asList(0, 0);
+        java.util.Collection<Integer> selectiveClear = Collections.singletonList(1);
         addProperty(VehicleProperty.OBD2_LIVE_FRAME, mLiveFrameEventBuilder.build())
                 .setConfigArray(numVendorSensors);
         addProperty(
@@ -199,7 +201,8 @@
                 .setConfigArray(numVendorSensors);
         addProperty(
                 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR,
-                mFreezeFrameProperties.mFreezeFrameClearHandler);
+                mFreezeFrameProperties.mFreezeFrameClearHandler)
+                .setConfigArray(selectiveClear);
     }
 
     @Override
@@ -732,6 +735,7 @@
         assertTrue(mCarDiagnosticManager.isFreezeFrameNotificationSupported());
         assertTrue(mCarDiagnosticManager.isGetFreezeFrameSupported());
         assertTrue(mCarDiagnosticManager.isClearFreezeFramesSupported());
+        assertTrue(mCarDiagnosticManager.isSelectiveClearFreezeFramesSupported());
     }
 
     class Listener implements CarDiagnosticManager.OnDiagnosticEventListener {
diff --git a/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java b/tests/carservice_test/src/com/android/car/CarHvacManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarHvacManagerTest.java
index 07f879e..9c89694 100644
--- a/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarHvacManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.hardware.CarPropertyValue;
diff --git a/tests/carservice_test/src/com/android/car/test/CarInfoManagerTest.java b/tests/carservice_test/src/com/android/car/CarInfoManagerTest.java
similarity index 98%
rename from tests/carservice_test/src/com/android/car/test/CarInfoManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarInfoManagerTest.java
index 2cdbbb0..fe32615 100644
--- a/tests/carservice_test/src/com/android/car/test/CarInfoManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarInfoManagerTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.CarInfoManager;
diff --git a/tests/carservice_test/src/com/android/car/test/CarPackageManagerTest.java b/tests/carservice_test/src/com/android/car/CarPackageManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarPackageManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarPackageManagerTest.java
index c455bc4..ffbf5a4 100644
--- a/tests/carservice_test/src/com/android/car/test/CarPackageManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPackageManagerTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.CarNotConnectedException;
diff --git a/tests/carservice_test/src/com/android/car/test/CarPowerManagementTest.java b/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
similarity index 83%
rename from tests/carservice_test/src/com/android/car/test/CarPowerManagementTest.java
rename to tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
index d5e5bf8..b0ac2e7 100644
--- a/tests/carservice_test/src/com/android/car/test/CarPowerManagementTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPowerManagementTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerBootupReason;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerSetState;
@@ -26,6 +26,8 @@
 import android.os.SystemClock;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.android.car.systeminterface.DisplayInterface;
+import com.android.car.systeminterface.SystemInterface;
 import com.google.android.collect.Lists;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
@@ -40,6 +42,13 @@
 public class CarPowerManagementTest extends MockedCarTestBase {
 
     private final PowerStatePropertyHandler mPowerStateHandler = new PowerStatePropertyHandler();
+    private final MockDisplayInterface mMockDisplayInterface = new MockDisplayInterface();
+
+    @Override
+    protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
+        SystemInterface.Builder builder = super.getSystemInterfaceBuilder();
+        return builder.withDisplayInterface(mMockDisplayInterface);
+    }
 
     private void setupPowerPropertyAndStart(boolean allowSleep) {
         addProperty(VehicleProperty.AP_POWER_STATE, mPowerStateHandler)
@@ -70,9 +79,9 @@
         assertBootComplete();
         for (int i = 0; i < 2; i++) {
             mPowerStateHandler.sendPowerState(VehicleApPowerState.ON_DISP_OFF, 0);
-            waitForFakeDisplayState(false);
+            mMockDisplayInterface.waitForDisplayState(false);
             mPowerStateHandler.sendPowerState(VehicleApPowerState.ON_FULL, 0);
-            waitForFakeDisplayState(true);
+            mMockDisplayInterface.waitForDisplayState(true);
         }
     }
 
@@ -107,6 +116,33 @@
         assertEquals(0, first[1]);
     }
 
+    private final class MockDisplayInterface implements DisplayInterface {
+        private boolean mDisplayOn = true;
+        private final Semaphore mDisplayStateWait = new Semaphore(0);
+
+        @Override
+        public synchronized void setDisplayState(boolean on) {
+            mDisplayOn = on;
+            mDisplayStateWait.release();
+        }
+
+        boolean waitForDisplayState(boolean expectedState)
+            throws Exception {
+            if (expectedState == mDisplayOn) {
+                return true;
+            }
+            mDisplayStateWait.tryAcquire(MockedCarTestBase.SHORT_WAIT_TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS);
+            return expectedState == mDisplayOn;
+        }
+
+        @Override
+        public void startDisplayStateMonitoring(CarPowerManagementService service) {}
+
+        @Override
+        public void stopDisplayStateMonitoring() {}
+    }
+
     private class PowerStatePropertyHandler implements VehicleHalPropertyHandler {
 
         private int mPowerState = VehicleApPowerState.ON_FULL;
diff --git a/tests/carservice_test/src/com/android/car/test/CarProjectionManagerTest.java b/tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarProjectionManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java
index adf288c..773a296 100644
--- a/tests/carservice_test/src/com/android/car/test/CarProjectionManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarProjectionManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.CarProjectionManager;
diff --git a/tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java b/tests/carservice_test/src/com/android/car/CarRadioManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarRadioManagerTest.java
index 169a370..7288259 100644
--- a/tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarRadioManagerTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.hardware.radio.CarRadioEvent;
diff --git a/tests/carservice_test/src/com/android/car/test/CarSensorManagerTest.java b/tests/carservice_test/src/com/android/car/CarSensorManagerTest.java
similarity index 99%
rename from tests/carservice_test/src/com/android/car/test/CarSensorManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarSensorManagerTest.java
index daddfdb..00120a3 100644
--- a/tests/carservice_test/src/com/android/car/test/CarSensorManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarSensorManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import android.car.Car;
 import android.car.CarNotConnectedException;
diff --git a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
new file mode 100644
index 0000000..24d75a5
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
@@ -0,0 +1,435 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.Car;
+import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.car.storagemonitoring.UidIoStats;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.JsonWriter;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import com.android.car.storagemonitoring.UidIoStatsProvider;
+import com.android.car.storagemonitoring.WearEstimateRecord;
+import com.android.car.storagemonitoring.WearHistory;
+import com.android.car.storagemonitoring.WearInformation;
+import com.android.car.storagemonitoring.WearInformationProvider;
+import com.android.car.systeminterface.StorageMonitoringInterface;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.systeminterface.TimeInterface;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Test the public entry points for the CarStorageMonitoringManager */
+@MediumTest
+public class CarStorageMonitoringTest extends MockedCarTestBase {
+    private static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
+
+    private static final WearInformation DEFAULT_WEAR_INFORMATION =
+        new WearInformation(30, 0, WearInformation.PRE_EOL_INFO_NORMAL);
+
+    private static final class TestData {
+        static final TestData DEFAULT = new TestData(0, DEFAULT_WEAR_INFORMATION, null, null);
+
+        final long uptime;
+        @NonNull
+        final WearInformation wearInformation;
+        @Nullable
+        final WearHistory wearHistory;
+        @NonNull
+        final UidIoStatsRecord[] ioStats;
+
+        TestData(long uptime,
+                @Nullable WearInformation wearInformation,
+                @Nullable WearHistory wearHistory,
+                @Nullable UidIoStatsRecord[] ioStats) {
+            if (wearInformation == null) wearInformation = DEFAULT_WEAR_INFORMATION;
+            if (ioStats == null) ioStats = new UidIoStatsRecord[0];
+            this.uptime = uptime;
+            this.wearInformation = wearInformation;
+            this.wearHistory = wearHistory;
+            this.ioStats = ioStats;
+        }
+    }
+
+    private static final Map<String, TestData> PER_TEST_DATA =
+            new HashMap<String, TestData>() {{
+                put("testReadWearHistory",
+                    new TestData(6500, DEFAULT_WEAR_INFORMATION,
+                        WearHistory.fromRecords(
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
+                                .toWearEstimate(new WearEstimate(10, 0))
+                                .atUptime(1000)
+                                .atTimestamp(Instant.ofEpochMilli(5000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(10, 0))
+                                .toWearEstimate(new WearEstimate(20, 0))
+                                .atUptime(4000)
+                                .atTimestamp(Instant.ofEpochMilli(12000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(20, 0))
+                                .toWearEstimate(new WearEstimate(30, 0))
+                                .atUptime(6500)
+                                .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
+
+                put("testNotAcceptableWearEvent",
+                    new TestData(2520006499L,
+                        new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
+                        WearHistory.fromRecords(
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
+                                .toWearEstimate(new WearEstimate(10, 0))
+                                .atUptime(1000)
+                                .atTimestamp(Instant.ofEpochMilli(5000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(10, 0))
+                                .toWearEstimate(new WearEstimate(20, 0))
+                                .atUptime(4000)
+                                .atTimestamp(Instant.ofEpochMilli(12000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(20, 0))
+                                .toWearEstimate(new WearEstimate(30, 0))
+                                .atUptime(6500)
+                                .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
+
+                put("testAcceptableWearEvent",
+                    new TestData(2520006501L,
+                        new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
+                        WearHistory.fromRecords(
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
+                                .toWearEstimate(new WearEstimate(10, 0))
+                                .atUptime(1000)
+                                .atTimestamp(Instant.ofEpochMilli(5000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(10, 0))
+                                .toWearEstimate(new WearEstimate(20, 0))
+                                .atUptime(4000)
+                                .atTimestamp(Instant.ofEpochMilli(12000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(20, 0))
+                                .toWearEstimate(new WearEstimate(30, 0))
+                                .atUptime(6500)
+                                .atTimestamp(Instant.ofEpochMilli(17000)).build()), null));
+
+                put("testBootIoStats",
+                    new TestData(1000L,
+                        new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
+                        null,
+                        new UidIoStatsRecord[]{
+                            new UidIoStatsRecord(0, 5000, 6000, 3000, 1000, 1,
+                                0, 0, 0, 0, 0),
+                            new UidIoStatsRecord(1000, 200, 5000, 0, 4000, 0,
+                                1000, 0, 500, 0, 0)}));
+
+                put("testAggregateIoStats",
+                    new TestData(1000L,
+                        new WearInformation(0, 0, WearInformation.PRE_EOL_INFO_NORMAL),
+                        null,
+                        new UidIoStatsRecord[]{
+                            new UidIoStatsRecord(0, 5000, 6000, 3000, 1000, 1,
+                                0, 0, 0, 0, 0),
+                            new UidIoStatsRecord(1000, 200, 5000, 0, 4000, 0,
+                                1000, 0, 500, 0, 0)}));
+
+            }};
+
+    private final MockSystemStateInterface mMockSystemStateInterface =
+            new MockSystemStateInterface();
+    private final MockStorageMonitoringInterface mMockStorageMonitoringInterface =
+            new MockStorageMonitoringInterface();
+    private final MockTimeInterface mMockTimeInterface =
+            new MockTimeInterface();
+
+    private CarStorageMonitoringManager mCarStorageMonitoringManager;
+
+    @Override
+    protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
+        SystemInterface.Builder builder = super.getSystemInterfaceBuilder();
+        return builder.withSystemStateInterface(mMockSystemStateInterface)
+            .withStorageMonitoringInterface(mMockStorageMonitoringInterface)
+            .withTimeInterface(mMockTimeInterface);
+    }
+
+    @Override
+    protected synchronized void configureFakeSystemInterface() {
+        try {
+            final String testName = getName();
+            final TestData wearData = PER_TEST_DATA.getOrDefault(testName, TestData.DEFAULT);
+            final WearHistory wearHistory = wearData.wearHistory;
+
+            mMockStorageMonitoringInterface.setWearInformation(wearData.wearInformation);
+
+            if (wearHistory != null) {
+                File wearHistoryFile = new File(getFakeSystemInterface().getFilesDir(),
+                    CarStorageMonitoringService.WEAR_INFO_FILENAME);
+                try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(wearHistoryFile))) {
+                    wearHistory.writeToJson(jsonWriter);
+                }
+            }
+
+            if (wearData.uptime > 0) {
+                File uptimeFile = new File(getFakeSystemInterface().getFilesDir(),
+                    CarStorageMonitoringService.UPTIME_TRACKER_FILENAME);
+                try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(uptimeFile))) {
+                    jsonWriter.beginObject();
+                    jsonWriter.name("uptime").value(wearData.uptime);
+                    jsonWriter.endObject();
+                }
+            }
+
+            Arrays.stream(wearData.ioStats).forEach(
+                    mMockStorageMonitoringInterface::addIoStatsRecord);
+
+        } catch (IOException e) {
+            Log.e(TAG, "failed to configure fake system interface", e);
+            fail("failed to configure fake system interface instance");
+        }
+
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mMockSystemStateInterface.executeBootCompletedActions();
+
+        mCarStorageMonitoringManager =
+            (CarStorageMonitoringManager) getCar().getCarManager(Car.STORAGE_MONITORING_SERVICE);
+    }
+
+    public void testReadPreEolInformation() throws Exception {
+        assertEquals(DEFAULT_WEAR_INFORMATION.preEolInfo,
+                mCarStorageMonitoringManager.getPreEolIndicatorStatus());
+    }
+
+    public void testReadWearEstimate() throws Exception {
+        final WearEstimate wearEstimate = mCarStorageMonitoringManager.getWearEstimate();
+
+        assertNotNull(wearEstimate);
+        assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateA, wearEstimate.typeA);
+        assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateB, wearEstimate.typeB);
+    }
+
+    public void testReadWearHistory() throws Exception {
+        final List<WearEstimateChange> wearEstimateChanges =
+                mCarStorageMonitoringManager.getWearEstimateHistory();
+
+        assertNotNull(wearEstimateChanges);
+        assertFalse(wearEstimateChanges.isEmpty());
+
+        final WearHistory expectedWearHistory = PER_TEST_DATA.get(getName()).wearHistory;
+
+        assertEquals(expectedWearHistory.size(), wearEstimateChanges.size());
+        for (int i = 0; i < wearEstimateChanges.size(); ++i) {
+            final WearEstimateRecord expected = expectedWearHistory.get(i);
+            final WearEstimateChange actual = wearEstimateChanges.get(i);
+
+            assertTrue(expected.isSameAs(actual));
+        }
+    }
+
+    private void checkLastWearEvent(boolean isAcceptable) throws Exception {
+        final List<WearEstimateChange> wearEstimateChanges =
+            mCarStorageMonitoringManager.getWearEstimateHistory();
+
+        assertNotNull(wearEstimateChanges);
+        assertFalse(wearEstimateChanges.isEmpty());
+
+        final TestData wearData = PER_TEST_DATA.get(getName());
+
+        final WearInformation expectedCurrentWear = wearData.wearInformation;
+        final WearEstimate expectedPreviousWear = wearData.wearHistory.getLast().getNewWearEstimate();
+
+        final WearEstimateChange actualCurrentWear =
+                wearEstimateChanges.get(wearEstimateChanges.size() - 1);
+
+        assertEquals(isAcceptable, actualCurrentWear.isAcceptableDegradation);
+        assertEquals(expectedCurrentWear.toWearEstimate(), actualCurrentWear.newEstimate);
+        assertEquals(expectedPreviousWear, actualCurrentWear.oldEstimate);
+    }
+
+    public void testNotAcceptableWearEvent() throws Exception {
+        checkLastWearEvent(false);
+    }
+
+    public void testAcceptableWearEvent() throws Exception {
+        checkLastWearEvent(true);
+    }
+
+    public void testBootIoStats() throws Exception {
+        final List<UidIoStats> bootIoStats =
+            mCarStorageMonitoringManager.getBootIoStats();
+
+        assertNotNull(bootIoStats);
+        assertFalse(bootIoStats.isEmpty());
+
+        final UidIoStatsRecord[] bootIoRecords = PER_TEST_DATA.get(getName()).ioStats;
+
+        bootIoStats.forEach(uidIoStats -> assertTrue(Arrays.stream(bootIoRecords).anyMatch(
+                ioRecord -> uidIoStats.representsSameMetrics(ioRecord))));
+    }
+
+    public void testAggregateIoStats() throws Exception {
+        UidIoStatsRecord oldRecord1000 = mMockStorageMonitoringInterface.getIoStatsRecord(1000);
+
+        UidIoStatsRecord newRecord1000 = new UidIoStatsRecord(1000,
+            oldRecord1000.foreground_rchar,
+            oldRecord1000.foreground_wchar + 50,
+            oldRecord1000.foreground_read_bytes,
+            oldRecord1000.foreground_write_bytes + 100,
+            oldRecord1000.foreground_fsync + 1,
+            oldRecord1000.background_rchar,
+            oldRecord1000.background_wchar,
+            oldRecord1000.background_read_bytes,
+            oldRecord1000.background_write_bytes,
+            oldRecord1000.background_fsync);
+
+        mMockStorageMonitoringInterface.addIoStatsRecord(newRecord1000);
+
+        UidIoStatsRecord record2000 = new UidIoStatsRecord(2000,
+            1024,
+            2048,
+            0,
+            1024,
+            1,
+            0,
+            0,
+            0,
+            0,
+            0);
+
+        mMockStorageMonitoringInterface.addIoStatsRecord(record2000);
+
+        mMockTimeInterface.tick();
+
+        List<UidIoStats> aggregateIoStats = mCarStorageMonitoringManager.getAggregateIoStats();
+
+        assertNotNull(aggregateIoStats);
+        assertFalse(aggregateIoStats.isEmpty());
+
+        aggregateIoStats.forEach(serviceIoStat -> {
+            UidIoStatsRecord mockIoStat = mMockStorageMonitoringInterface.getIoStatsRecord(
+                    serviceIoStat.uid);
+
+            assertNotNull(mockIoStat);
+
+            assertTrue(serviceIoStat.representsSameMetrics(mockIoStat));
+        });
+    }
+
+    static final class MockStorageMonitoringInterface implements StorageMonitoringInterface,
+        WearInformationProvider {
+        private WearInformation mWearInformation = null;
+        private SparseArray<UidIoStatsRecord> mIoStats = new SparseArray<>();
+        private UidIoStatsProvider mIoStatsProvider = () -> mIoStats;
+
+        void setWearInformation(WearInformation wearInformation) {
+            mWearInformation = wearInformation;
+        }
+
+        void addIoStatsRecord(UidIoStatsRecord record) {
+            mIoStats.append(record.uid, record);
+        }
+
+        UidIoStatsRecord getIoStatsRecord(int uid) {
+            return mIoStats.get(uid);
+        }
+
+        void deleteIoStatsRecord(int uid) {
+            mIoStats.delete(uid);
+        }
+
+        @Override
+        public WearInformation load() {
+            return mWearInformation;
+        }
+
+        @Override
+        public WearInformationProvider[] getFlashWearInformationProviders() {
+            return new WearInformationProvider[] {this};
+        }
+
+        @Override
+        public UidIoStatsProvider getUidIoStatsProvider() {
+            return mIoStatsProvider;
+        }
+    }
+
+    static final class MockTimeInterface implements TimeInterface {
+        private final List<Pair<Runnable, Long>> mActionsList = new ArrayList<>();
+
+        @Override
+        public long getUptime(boolean includeDeepSleepTime) {
+            return 0;
+        }
+
+        @Override
+        public void scheduleAction(Runnable r, long delayMs) {
+            mActionsList.add(Pair.create(r, delayMs));
+            mActionsList.sort(Comparator.comparing(d -> d.second));
+        }
+
+        @Override
+        public void cancelAllActions() {
+            mActionsList.clear();
+        }
+
+        void tick() {
+            mActionsList.forEach(pair -> pair.first.run());
+        }
+    }
+
+    static final class MockSystemStateInterface implements SystemStateInterface {
+        private final List<Pair<Runnable, Duration>> mActionsList = new ArrayList<>();
+
+        @Override
+        public void shutdown() {}
+
+        @Override
+        public void enterDeepSleep(int wakeupTimeSec) {}
+
+        @Override
+        public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
+            mActionsList.add(Pair.create(action, delay));
+            mActionsList.sort(Comparator.comparing(d -> d.second));
+        }
+
+        void executeBootCompletedActions() {
+            for (Pair<Runnable, Duration> action : mActionsList) {
+                action.first.run();
+            }
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/test/CarVendorExtensionManagerTest.java b/tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java
similarity index 98%
rename from tests/carservice_test/src/com/android/car/test/CarVendorExtensionManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java
index fa33e56..942649d 100644
--- a/tests/carservice_test/src/com/android/car/test/CarVendorExtensionManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarVendorExtensionManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.test;
+package com.android.car;
 
 import static com.android.car.CarServiceUtils.toByteArray;
 
@@ -246,7 +246,7 @@
         @Override
         public synchronized void get(VehiclePropValue requestedPropValue, getCallback cb) {
             if (!isVendorProperty(requestedPropValue.prop)) {
-                cb.onValues(StatusCode.INVALID_ARG, null);
+                super.get(requestedPropValue, cb);
                 return;
             }
             VehiclePropValue result = new VehiclePropValue();
diff --git a/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java b/tests/carservice_test/src/com/android/car/CarVolumeServiceTest.java
similarity index 90%
rename from tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java
rename to tests/carservice_test/src/com/android/car/CarVolumeServiceTest.java
index 2e92a59..0d9a607 100644
--- a/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/CarVolumeServiceTest.java
@@ -13,9 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
-
-import static com.android.car.test.AudioTestUtils.doRequestFocus;
+package com.android.car;
 
 import android.car.Car;
 import android.car.CarNotConnectedException;
@@ -40,13 +38,12 @@
 import android.util.SparseIntArray;
 import android.view.KeyEvent;
 
-import com.android.car.VolumeUtils;
+import com.google.android.collect.Lists;
+
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 import com.android.internal.annotations.GuardedBy;
 
-import com.google.android.collect.Lists;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -54,7 +51,6 @@
 public class CarVolumeServiceTest extends MockedCarTestBase {
     private static final String TAG = CarVolumeServiceTest.class.getSimpleName();
 
-    private static final int MIN_VOL = 1;
     private static final int MAX_VOL = 20;
     private static final long TIMEOUT_MS = 3000;
     private static final long POLL_INTERVAL_MS = 50;
@@ -101,7 +97,7 @@
             }
         }
         SingleChannelVolumeHandler handler =
-                startVolumeEmulation(supportedAudioContext, maxs);
+                startVolumeEmulation(true, supportedAudioContext, maxs);
         mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
         return handler;
     }
@@ -114,7 +110,7 @@
         // give focus to music, now current context becomes VehicleAudioContextFlag.MUSIC_FLAG
         CarAudioFocusTest.AudioFocusListener listenerMusic =
                 new CarAudioFocusTest.AudioFocusListener();
-        int res = doRequestFocus(mAudioManager, listenerMusic,
+        int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                 AudioManager.STREAM_MUSIC,
                 AudioManager.AUDIOFOCUS_GAIN);
         assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -138,7 +134,7 @@
         }
     }
 
-    public void testVolumeSet() throws Exception {
+    public void testVolumeSet() {
         try {
             setupExternalVolumeEmulation(false);
             int callVol = 10;
@@ -154,8 +150,27 @@
 
             volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, MAX_VOL),
                     createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol));
-        } catch (CarNotConnectedException e) {
-            fail("Car not connected");
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    public void testMultipleVolumeControllers() {
+        try {
+            startVolumeEmulation(false, 0, new ArrayList<>());
+            mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+            mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+            VolumeController volumeController1 = new VolumeController();
+            VolumeController volumeController2 = new VolumeController();
+            mCarAudioManager.setVolumeController(volumeController1);
+            mCarAudioManager.setVolumeController(volumeController2);
+
+            mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    1, AudioManager.FLAG_SHOW_UI);
+            volumeChangeVerificationPoll(AudioManager.STREAM_MUSIC, true, volumeController1);
+            volumeChangeVerificationPoll(AudioManager.STREAM_MUSIC, true, volumeController2);
+        } catch (Exception e) {
+            fail(e.getMessage());
         }
     }
 
@@ -168,7 +183,7 @@
             // first give focus to system sound
             CarAudioFocusTest.AudioFocusListener listenerMusic =
                     new CarAudioFocusTest.AudioFocusListener();
-            int res = doRequestFocus(mAudioManager, listenerMusic,
+            int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                     AudioManager.STREAM_SYSTEM,
                     AudioManager.AUDIOFOCUS_GAIN);
             assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -184,7 +199,7 @@
             AudioAttributes callAttrib = (new AudioAttributes.Builder()).
                     setUsage(AudioAttributes.USAGE_ALARM).
                     build();
-            res = doRequestFocus(mAudioManager, listenerAlarm, callAttrib,
+            res = AudioTestUtils.doRequestFocus(mAudioManager, listenerAlarm, callAttrib,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
             assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
             request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
@@ -207,7 +222,7 @@
         }
     }
 
-    public void testVolumeKeys() throws Exception {
+    public void testVolumeKeys() {
         try {
             setupExternalVolumeEmulation(false);
             int musicVol = 10;
@@ -217,7 +232,7 @@
 
             CarAudioFocusTest.AudioFocusListener listenerMusic =
                     new CarAudioFocusTest.AudioFocusListener();
-            int res = doRequestFocus(mAudioManager, listenerMusic,
+            int res = AudioTestUtils.doRequestFocus(mAudioManager, listenerMusic,
                     AudioManager.STREAM_MUSIC,
                     AudioManager.AUDIOFOCUS_GAIN);
             assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
@@ -239,7 +254,7 @@
             AudioAttributes callAttrib = (new AudioAttributes.Builder()).
                     setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).
                     build();
-            doRequestFocus(mAudioManager, listenerCall, callAttrib,
+            AudioTestUtils.doRequestFocus(mAudioManager, listenerCall, callAttrib,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
             request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
             mAudioFocusPropertyHandler.sendAudioFocusState(
@@ -250,7 +265,7 @@
             callVol++;
             volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol),
                     createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol));
-        } catch (CarNotConnectedException | InterruptedException e) {
+        } catch (Exception e) {
             fail(e.toString());
         }
     }
@@ -411,8 +426,8 @@
                 }
             };
 
-    private SingleChannelVolumeHandler startVolumeEmulation(int supportedAudioVolumeContext,
-            List<Integer> maxs) {
+    private SingleChannelVolumeHandler startVolumeEmulation(boolean supportExternalVolume,
+            int supportedAudioVolumeContext, List<Integer> maxs) {
         SingleChannelVolumeHandler singleChannelVolumeHandler =
                 new SingleChannelVolumeHandler(maxs);
         int zones = (1<<maxs.size()) - 1;
@@ -425,9 +440,11 @@
                         0  /* reserved */);
         audioVolumeConfigArray.addAll(maxs);
 
-        addProperty(VehicleProperty.AUDIO_VOLUME, singleChannelVolumeHandler)
-                        .setConfigArray(audioVolumeConfigArray)
-                        .setSupportedAreas(zones);
+        if (supportExternalVolume) {
+            addProperty(VehicleProperty.AUDIO_VOLUME, singleChannelVolumeHandler)
+                    .setConfigArray(audioVolumeConfigArray)
+                    .setSupportedAreas(zones);
+        }
 
         addProperty(VehicleProperty.HW_KEY_INPUT, mHWKeyHandler)
                 .setAccess(VehiclePropertyAccess.READ);
diff --git a/tests/carservice_test/src/com/android/car/test/GarageModeTest.java b/tests/carservice_test/src/com/android/car/GarageModeTest.java
similarity index 96%
rename from tests/carservice_test/src/com/android/car/test/GarageModeTest.java
rename to tests/carservice_test/src/com/android/car/GarageModeTest.java
index 19ccb6e..06abdc8 100644
--- a/tests/carservice_test/src/com/android/car/test/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/GarageModeTest.java
@@ -13,17 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.settings.CarSettings;
 import android.content.Context;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
-import com.android.car.CarPowerManagementService;
-import com.android.car.DeviceIdleControllerWrapper;
-import com.android.car.GarageModeService;
-
 @MediumTest
 public class GarageModeTest extends AndroidTestCase {
 
diff --git a/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
similarity index 64%
rename from tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java
rename to tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index a060496..e6ed401 100644
--- a/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.test.CarTestManager;
 import android.car.test.CarTestManagerBinderWrapper;
@@ -21,7 +21,9 @@
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.automotive.vehicle.V2_0.VehicleDrivingStatus;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
 import android.os.Binder;
@@ -31,20 +33,31 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import com.android.car.CarPowerManagementService;
-import com.android.car.ICarImpl;
-import com.android.car.SystemInterface;
+import android.util.SparseArray;
+
+import com.android.car.systeminterface.DisplayInterface;
+import com.android.car.systeminterface.IOInterface;
+import com.android.car.systeminterface.StorageMonitoringInterface;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemInterface.Builder;
+import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.systeminterface.TimeInterface;
+import com.android.car.systeminterface.WakeLockInterface;
+import com.android.car.test.utils.TemporaryDirectory;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal;
 import com.android.car.vehiclehal.test.MockedVehicleHal.DefaultPropertyHandler;
 import com.android.car.vehiclehal.test.MockedVehicleHal.StaticPropertyHandler;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder;
 
+import java.io.File;
+import java.io.IOException;
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Base class for testing with mocked vehicle HAL (=car).
@@ -59,11 +72,16 @@
     private android.car.Car mCar;
     private ICarImpl mCarImpl;
     private MockedVehicleHal mMockedVehicleHal;
-    private FakeSystemInterface mFakeSystemInterface;
+    private SystemInterface mFakeSystemInterface;
+    private final MockIOInterface mMockIOInterface = new MockIOInterface();
 
     private final Semaphore mWaitForMain = new Semaphore(0);
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
 
+    private final Map<VehiclePropConfigBuilder, VehicleHalPropertyHandler> mHalConfig =
+            new HashMap<>();
+    private final SparseArray<VehiclePropConfigBuilder> mPropToConfigBuilder = new SparseArray<>();
+
     private static final IBinder mCarServiceToken = new Binder();
     private static boolean mRealCarServiceReleased = false;
 
@@ -75,9 +93,25 @@
         return mMockedVehicleHal;
     }
 
+    protected synchronized SystemInterface getFakeSystemInterface() {
+        return mFakeSystemInterface;
+    }
+
     protected synchronized void configureMockedHal() {
     }
 
+    protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() {
+        return Builder.newSystemInterface()
+                .withSystemStateInterface(new MockSystemStateInterface())
+                .withDisplayInterface(new MockDisplayInterface())
+                .withIOInterface(mMockIOInterface)
+                .withStorageMonitoringInterface(new MockStorageMonitoringInterface())
+                .withTimeInterface(new MockTimeInterface())
+                .withWakeLockInterface(new MockWakeLockInterface());
+    }
+
+    protected synchronized void configureFakeSystemInterface() {}
+
     @Override
     protected synchronized void setUp() throws Exception {
         super.setUp();
@@ -85,9 +119,15 @@
         releaseRealCarService(getContext());
 
         mMockedVehicleHal = createMockedVehicleHal();
+        addProperty(VehicleProperty.DRIVING_STATUS,
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.DRIVING_STATUS)
+                        .addIntValue(VehicleDrivingStatus.UNRESTRICTED)
+                        .build());
         configureMockedHal();
 
-        mFakeSystemInterface = new FakeSystemInterface();
+        mFakeSystemInterface = getSystemInterfaceBuilder().build();
+        configureFakeSystemInterface();
+
         Context context = getCarServiceContext();
         mCarImpl = new ICarImpl(context, mMockedVehicleHal, mFakeSystemInterface,
                 null /* error notifier */);
@@ -103,6 +143,8 @@
 
         mCar.disconnect();
         mCarImpl.release();
+
+        mMockIOInterface.tearDown();
     }
 
     protected Context getCarServiceContext() throws NameNotFoundException {
@@ -127,26 +169,23 @@
         mCarImpl.init();
     }
 
-    private final Map<VehiclePropConfigBuilder, VehicleHalPropertyHandler> mHalConfig =
-            new HashMap<>();
-
     protected synchronized VehiclePropConfigBuilder addProperty(int propertyId,
             VehicleHalPropertyHandler propertyHandler) {
         VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId);
-        mHalConfig.put(builder, propertyHandler);
+        setConfigBuilder(builder, propertyHandler);
         return builder;
     }
 
     protected synchronized VehiclePropConfigBuilder addProperty(int propertyId) {
         VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId);
-        mHalConfig.put(builder, new DefaultPropertyHandler(builder.build(), null));
+        setConfigBuilder(builder, new DefaultPropertyHandler(builder.build(), null));
         return builder;
     }
 
     protected synchronized VehiclePropConfigBuilder addProperty(int propertyId,
             VehiclePropValue value) {
         VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId);
-        mHalConfig.put(builder, new DefaultPropertyHandler(builder.build(), value));
+        setConfigBuilder(builder, new DefaultPropertyHandler(builder.build(), value));
         return builder;
     }
 
@@ -156,10 +195,23 @@
                 .setChangeMode(VehiclePropertyChangeMode.STATIC)
                 .setAccess(VehiclePropertyAccess.READ);
 
-        mHalConfig.put(builder, new StaticPropertyHandler(value));
+        setConfigBuilder(builder, new StaticPropertyHandler(value));
         return builder;
     }
 
+    private void setConfigBuilder(VehiclePropConfigBuilder builder,
+            VehicleHalPropertyHandler propertyHandler) {
+        int propId = builder.build().prop;
+
+        // Override previous property config if exists.
+        VehiclePropConfigBuilder prevBuilder = mPropToConfigBuilder.get(propId);
+        if (prevBuilder != null) {
+            mHalConfig.remove(prevBuilder);
+        }
+        mPropToConfigBuilder.put(propId, builder);
+        mHalConfig.put(builder, propertyHandler);
+    }
+
     protected synchronized android.car.Car getCar() {
         return mCar;
     }
@@ -179,10 +231,6 @@
         mWaitForMain.acquire();
     }
 
-    protected boolean waitForFakeDisplayState(boolean expectedState) throws Exception {
-        return mFakeSystemInterface.waitForDisplayState(expectedState, SHORT_WAIT_TIMEOUT_MS);
-    }
-
     public static <T> void assertArrayEquals(T[] expected, T[] actual) {
         if (!Arrays.equals(expected, actual)) {
             fail("expected:<" + Arrays.toString(expected) +
@@ -239,58 +287,77 @@
         }
     }
 
-    private static class FakeSystemInterface extends SystemInterface {
-
-        private boolean mDisplayOn = true;
-        private final Semaphore mDisplayStateWait = new Semaphore(0);
+    static final class MockDisplayInterface implements DisplayInterface {
 
         @Override
-        public synchronized void setDisplayState(boolean on) {
-            mDisplayOn = on;
-            mDisplayStateWait.release();
-        }
-
-        boolean waitForDisplayState(boolean expectedState, long timeoutMs)
-                throws Exception {
-            if (expectedState == mDisplayOn) {
-                return true;
-            }
-            mDisplayStateWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
-            return expectedState == mDisplayOn;
-        }
+        public void setDisplayState(boolean on) {}
 
         @Override
-        public void releaseAllWakeLocks() {
-        }
+        public void startDisplayStateMonitoring(CarPowerManagementService service) {}
 
         @Override
-        public void shutdown() { }
-
-        @Override
-        public void enterDeepSleep(int wakeupTimeSec) { }
-
-        @Override
-        public boolean isSystemSupportingDeepSleep() {
-            return false;
-        }
-
-        @Override
-        public void switchToPartialWakeLock() {
-        }
-
-        @Override
-        public void switchToFullWakeLock() {
-        }
-
-        @Override
-        public void startDisplayStateMonitoring(CarPowerManagementService service) {
-        }
-
-        @Override
-        public void stopDisplayStateMonitoring() {
-        }
-
-        @Override
-        public boolean isWakeupCausedByTimer() { return false; }
+        public void stopDisplayStateMonitoring() {}
     }
+
+    static final class MockIOInterface implements IOInterface {
+        private TemporaryDirectory mFilesDir = null;
+
+        @Override
+        public File getFilesDir() {
+            if (mFilesDir == null) {
+                try {
+                    mFilesDir = new TemporaryDirectory(TAG);
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to create temporary directory", e);
+                    fail("failed to create temporary directory. exception was: " + e);
+                }
+            }
+            return mFilesDir.getDirectory();
+        }
+
+        public void tearDown() {
+            if (mFilesDir != null) {
+                try {
+                    mFilesDir.close();
+                } catch (Exception e) {
+                    Log.w(TAG, "could not remove temporary directory", e);
+                }
+            }
+        }
+    }
+
+    static final class MockStorageMonitoringInterface implements StorageMonitoringInterface {}
+
+    static final class MockSystemStateInterface implements SystemStateInterface {
+        @Override
+        public void shutdown() {}
+
+        @Override
+        public void enterDeepSleep(int wakeupTimeSec) {}
+
+        @Override
+        public void scheduleActionForBootCompleted(Runnable action, Duration delay) {}
+    }
+
+    static final class MockTimeInterface implements TimeInterface {
+
+        @Override
+        public void scheduleAction(Runnable r, long delayMs) {}
+
+        @Override
+        public void cancelAllActions() {}
+    }
+
+    static final class MockWakeLockInterface implements WakeLockInterface {
+
+        @Override
+        public void releaseAllWakeLocks() {}
+
+        @Override
+        public void switchToPartialWakeLock() {}
+
+        @Override
+        public void switchToFullWakeLock() {}
+    }
+
 }
diff --git a/tests/carservice_test/src/com/android/car/SimpleVmsPublisherClientService.java b/tests/carservice_test/src/com/android/car/SimpleVmsPublisherClientService.java
new file mode 100644
index 0000000..53491b1
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/SimpleVmsPublisherClientService.java
@@ -0,0 +1,38 @@
+/*
+ * 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.vms.VmsPublisherClientService;
+import android.car.vms.VmsSubscriptionState;
+
+/**
+ * This service is launched during the tests in VmsPublisherClientServiceTest.
+ */
+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.MOCK_PUBLISHER_ID,
+                VmsPublisherClientServiceTest.PAYLOAD);
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
new file mode 100644
index 0000000..9907130
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.android.car;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class SystemActivityMonitoringServiceTest extends AndroidTestCase {
+    private static final long ACTIVITY_TIME_OUT = 5000;
+
+    private SystemActivityMonitoringService mService;
+    private Semaphore mSemaphore = new Semaphore(0);
+
+    private final TopTaskInfoContainer[] mTopTaskInfo = new TopTaskInfoContainer[1];
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mService = new SystemActivityMonitoringService(getContext());
+        mService.registerActivityLaunchListener(topTask -> {
+            if (!getTestContext().getPackageName().equals(topTask.topActivity.getPackageName())) {
+                return; // Ignore activities outside of this test case.
+            }
+            synchronized (mTopTaskInfo) {
+                mTopTaskInfo[0] = topTask;
+            }
+            mSemaphore.release();
+        });
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        mService.registerActivityLaunchListener(null);
+        mService = null;
+    }
+
+    public void testActivityLaunch() throws Exception {
+        ComponentName activityA = toComponentName(getTestContext(), ActivityA.class);
+        startActivity(getContext(), activityA);
+        assertTopTaskActivity(activityA);
+
+        ComponentName activityB = toComponentName(getTestContext(), ActivityB.class);
+        startActivity(getContext(), activityB);
+        assertTopTaskActivity(activityB);
+    }
+
+    public void testActivityBlocking() throws Exception {
+        ComponentName blackListedActivity = toComponentName(getTestContext(), ActivityC.class);
+        ComponentName blockingActivity = toComponentName(getTestContext(), BlockingActivity.class);
+        Intent blockingIntent = new Intent();
+        blockingIntent.setComponent(blockingActivity);
+
+        // start a black listed activity
+        startActivity(getContext(), blackListedActivity);
+        assertTopTaskActivity(blackListedActivity);
+
+        // Instead of start activity, invoke blockActivity.
+        mService.blockActivity(mTopTaskInfo[0], blockingIntent);
+        assertTopTaskActivity(blockingActivity);
+    }
+
+    /** Activity that closes itself after some timeout to clean up the screen. */
+    public static class TempActivity extends Activity {
+        @Override
+        protected void onResume() {
+            super.onResume();
+            getMainThreadHandler().postDelayed(this::finish, ACTIVITY_TIME_OUT);
+        }
+    }
+
+    public static class ActivityA extends TempActivity {}
+    public static class ActivityB extends TempActivity {}
+    public static class ActivityC extends TempActivity {}
+    public static class BlockingActivity extends TempActivity {}
+
+    private void assertTopTaskActivity(ComponentName activity) throws Exception{
+        assertTrue(mSemaphore.tryAcquire(2, TimeUnit.SECONDS));
+        synchronized (mTopTaskInfo) {
+            assertEquals(activity, mTopTaskInfo[0].topActivity);
+        }
+    }
+
+    private static ComponentName toComponentName(Context ctx, Class<?> cls) {
+        return ComponentName.createRelative(ctx, cls.getName());
+    }
+
+    private static void startActivity(Context ctx, ComponentName name) {
+        Intent intent = new Intent();
+        intent.setComponent(name);
+        ctx.startActivity(intent);
+    }
+}
\ No newline at end of file
diff --git a/tests/carservice_test/src/com/android/car/test/TestAppBlockingPolicyService.java b/tests/carservice_test/src/com/android/car/TestAppBlockingPolicyService.java
similarity index 98%
rename from tests/carservice_test/src/com/android/car/test/TestAppBlockingPolicyService.java
rename to tests/carservice_test/src/com/android/car/TestAppBlockingPolicyService.java
index dc1ad8a..ee875c4 100644
--- a/tests/carservice_test/src/com/android/car/test/TestAppBlockingPolicyService.java
+++ b/tests/carservice_test/src/com/android/car/TestAppBlockingPolicyService.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.test;
+package com.android.car;
 
 import android.car.content.pm.AppBlockingPackageInfo;
 import android.car.content.pm.CarAppBlockingPolicy;
@@ -56,6 +56,7 @@
         } catch (NameNotFoundException e) {
             return null;
         }
+
         AppBlockingPackageInfo selfInfo = new AppBlockingPackageInfo(packageName, 0, 0, 0,
                 signatures, null);
         AppBlockingPackageInfo[] whitelists = new AppBlockingPackageInfo[] { selfInfo };
diff --git a/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java b/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
new file mode 100644
index 0000000..286c004
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsHalServiceSubscriptionEventTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 static org.junit.Assume.assumeTrue;
+
+import android.car.VehicleAreaType;
+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_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_0.VmsSubscriptionsStateIntegerValuesIndex;
+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;
+
+@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 {
+        super.setUp();
+        mHal = getMockedVehicleHal();
+        mHalHandlerSemaphore = new Semaphore(0);
+    }
+
+    @Override
+    protected synchronized void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testEmptySubscriptions() throws Exception {
+        List<VmsLayer> layers = new ArrayList<>();
+        subscriptionTestLogic(layers);
+    }
+
+    public void testOneSubscription() throws Exception {
+        List<VmsLayer> layers = Arrays.asList(new VmsLayer(8, 0, 3));
+        subscriptionTestLogic(layers);
+    }
+
+    public void testManySubscriptions() throws Exception {
+        List<VmsLayer> layers = Arrays.asList(
+                new VmsLayer(8, 1, 3),
+                new VmsLayer(5, 2, 1),
+                new VmsLayer(3, 3, 9),
+                new VmsLayer(2, 4, 7),
+                new VmsLayer(9, 5, 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(VmsSubscriptionsStateIntegerValuesIndex.MESSAGE_TYPE);
+        int sequenceNumber = v.get(VmsSubscriptionsStateIntegerValuesIndex.SEQUENCE_NUMBER);
+        int numberLayers = v.get(VmsSubscriptionsStateIntegerValuesIndex.NUMBER_OF_LAYERS);
+        assertEquals(VmsMessageType.SUBSCRIPTIONS_RESPONSE, messageType);
+        //TODO(asafro): This assertion makes no sense. need to fix.
+        //assertEquals(layers.size(), sequenceNumber);
+        assertEquals(layers.size(), numberLayers);
+        List<VmsLayer> receivedLayers = new ArrayList<>();
+        int start = VmsSubscriptionsStateIntegerValuesIndex.SUBSCRIPTIONS_START;
+        int end = VmsSubscriptionsStateIntegerValuesIndex.SUBSCRIPTIONS_START + 3 * numberLayers;
+        while (start < end) {
+            int type = v.get(start++);
+            int subtype = v.get(start++);
+            int version = v.get(start++);
+            receivedLayers.add(new VmsLayer(type, subtype, 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(VmsMessageWithLayerIntegerValuesIndex.MESSAGE_TYPE);
+        int layerId = v.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE);
+        int layerVersion = v.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_VERSION);
+        int fused = v.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_SUBTYPE);
+        assertEquals(VmsMessageType.SUBSCRIBE, messsageType);
+        assertEquals(layer.getType(), layerId);
+        assertEquals(layer.getVersion(), layerVersion);
+    }
+
+    private VehiclePropValue createHalSubscribeRequest(VmsLayer layer) {
+        return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .addIntValue(VmsMessageType.SUBSCRIBE)
+                .addIntValue(layer.getType())
+                .addIntValue(layer.getSubtype())
+                .addIntValue(layer.getVersion())
+                .build();
+    }
+
+    private VehiclePropValue createHalSubscriptionRequest() {
+        return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .addIntValue(VmsMessageType.SUBSCRIPTIONS_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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java b/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java
new file mode 100644
index 0000000..f6b3be5
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsOperationRecorderTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
+import android.car.vms.VmsOperationRecorder;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+
+@MediumTest
+public class VmsOperationRecorderTest extends TestCase {
+
+    /**
+     * Capture messages that VmsOperationRecorder.Writer would normally pass to Log.d(...).
+     */
+    class TestWriter extends VmsOperationRecorder.Writer {
+        public String mMsg;
+
+        @Override
+        public boolean isEnabled() {
+            return true;
+        }
+
+        @Override
+        public void write(String msg) {
+            super.write(msg);
+            mMsg = msg;
+        }
+    }
+
+    private TestWriter mWriter;
+    private VmsOperationRecorder mRecorder;
+    private static final String TAG = "VmsOperationRecorderTest";
+
+    private static final VmsLayer layer1 = new VmsLayer(1, 3, 2);
+    private static final VmsLayer layer2 = new VmsLayer(2, 4, 3);
+    private static final VmsLayer layer3 = new VmsLayer(3, 5, 4);
+
+    private static final VmsLayerDependency layerDependency1 = new VmsLayerDependency(layer3);
+    private static final VmsLayerDependency layerDependency2 = new VmsLayerDependency(layer1,
+            new HashSet<VmsLayer>(Arrays.asList(layer2, layer3)));
+
+    private static final VmsLayersOffering layersOffering0 = new VmsLayersOffering(
+            new HashSet<VmsLayerDependency>(), 66);
+    private static final VmsLayersOffering layersOffering1 = new VmsLayersOffering(
+            new HashSet<>(Arrays.asList(layerDependency1)), 66);
+    private static final VmsLayersOffering layersOffering2 = new VmsLayersOffering(
+            new HashSet<>(Arrays.asList(layerDependency1, layerDependency2)), 66);
+
+    public void setUp() {
+        mWriter = new TestWriter();
+        mRecorder = new VmsOperationRecorder(mWriter);
+    }
+
+    public void testSubscribe() throws Exception {
+        mRecorder.subscribe(layer1);
+        assertJsonMsgEquals("{'subscribe':{'layer':{'subtype':3,'type':1,'version':2}}}");
+    }
+
+    public void testUnsubscribe() throws Exception {
+        mRecorder.unsubscribe(layer1);
+        assertJsonMsgEquals("{'unsubscribe':{'layer':{'type':1,'subtype':3,'version':2}}}");
+    }
+
+    public void testStartMonitoring() throws Exception {
+        mRecorder.startMonitoring();
+        assertJsonMsgEquals("{'startMonitoring':{}}");
+    }
+
+    public void testStopMonitoring() throws Exception {
+        mRecorder.stopMonitoring();
+        assertJsonMsgEquals("{'stopMonitoring':{}}");
+    }
+
+    public void testSetLayersOffering0() throws Exception {
+        mRecorder.setLayersOffering(layersOffering0);
+        assertJsonMsgEquals("{'setLayersOffering':{}}");
+    }
+
+    public void testSetLayersOffering2() throws Exception {
+        mRecorder.setLayersOffering(layersOffering2);
+        assertJsonMsgEquals("{'setLayersOffering':{'layerDependency':["
+                + "{'layer':{'type':3,'subtype':5,'version':4}},"
+                + "{'layer':{'type':1,'subtype':3,'version':2},'dependency':["
+                + "{'type':2,'subtype':4,'version':3},{'type':3,'subtype':5,'version':4}]}"
+                + "]}}");
+    }
+
+    public void testGetPublisherId() throws Exception {
+        mRecorder.getPublisherId(9);
+        assertJsonMsgEquals("{'getPublisherId':{'publisherId':9}}");
+    }
+
+    public void testAddSubscription() throws Exception {
+        mRecorder.addSubscription(42, layer1);
+        assertJsonMsgEquals(
+                "{'addSubscription':{'sequenceNumber':42,'layer':{'type':1,'subtype':3,'version':2}}}"
+        );
+    }
+
+    public void testRemoveSubscription() throws Exception {
+        mRecorder.removeSubscription(42, layer1);
+        assertJsonMsgEquals("{'removeSubscription':"
+                + "{'sequenceNumber':42,'layer':{'type':1,'subtype':3,'version':2}}}");
+    }
+
+    public void testAddPromiscuousSubscription() throws Exception {
+        mRecorder.addPromiscuousSubscription(42);
+        assertJsonMsgEquals("{'addPromiscuousSubscription':{'sequenceNumber':42}}");
+    }
+
+    public void testRemovePromiscuousSubscription() throws Exception {
+        mRecorder.removePromiscuousSubscription(42);
+        assertJsonMsgEquals("{'removePromiscuousSubscription':{'sequenceNumber':42}}");
+    }
+
+    public void testAddHalSubscription() throws Exception {
+        mRecorder.addHalSubscription(42, layer1);
+        assertJsonMsgEquals("{'addHalSubscription':"
+                + "{'sequenceNumber':42,'layer':{'type':1,'subtype':3,'version':2}}}");
+    }
+
+    public void testRemoveHalSubscription() throws Exception {
+        mRecorder.removeHalSubscription(42, layer1);
+        assertJsonMsgEquals("{'removeHalSubscription':"
+                + "{'sequenceNumber':42,'layer':{'type':1,'subtype':3,'version':2}}}");
+    }
+
+    public void testSetPublisherLayersOffering() throws Exception {
+        mRecorder.setPublisherLayersOffering(layersOffering1);
+        assertJsonMsgEquals("{'setPublisherLayersOffering':{'layerDependency':["
+                + "{'layer':{'type':3,'subtype':5,'version':4}}]}}");
+    }
+
+    public void testSetHalPublisherLayersOffering() throws Exception {
+        mRecorder.setHalPublisherLayersOffering(layersOffering1);
+        assertJsonMsgEquals("{'setHalPublisherLayersOffering':{'layerDependency':["
+                + "{'layer':{'type':3,'subtype':5,'version':4}}]}}");
+    }
+
+    public void testSubscribeToPublisher() throws Exception {
+        mRecorder.subscribe(layer1, 99);
+        assertJsonMsgEquals(
+                "{'subscribe':{'publisherId':99, 'layer':{'type':1,'subtype':3,'version':2}}}");
+    }
+
+    public void testUnsubscribeToPublisher() throws Exception {
+        mRecorder.unsubscribe(layer1, 99);
+        assertJsonMsgEquals(
+                "{'unsubscribe':{'publisherId':99, 'layer':{'type':1,'subtype':3,'version':2}}}}");
+    }
+
+    private void assertJsonMsgEquals(String expectJson) throws Exception {
+        // Escaping double quotes in a JSON string is really noisy. The test data uses single
+        // quotes instead, which gets replaced here.
+        JSONObject expect = new JSONObject(expectJson.replace("'", "\""));
+        JSONObject got = new JSONObject(mWriter.mMsg);
+        assertTrue(similar(expect, got));
+    }
+
+    /*
+     * Determine if two JSONObjects are similar.
+     * They must contain the same set of names which must be associated with
+     * similar values.
+     */
+    private boolean similar(JSONObject expect, JSONObject got) {
+        try {
+            if (!expect.keySet().equals(got.keySet())) {
+                return false;
+            }
+
+            for (String key : expect.keySet()) {
+                Object valueExpect = expect.get(key);
+                Object valueGot = got.get(key);
+
+                if (valueExpect == valueGot) {
+                    continue;
+                }
+
+                if (valueExpect == null) {
+                    return false;
+                }
+
+                if (valueExpect instanceof JSONObject) {
+                    return similar((JSONObject) valueExpect, (JSONObject) valueGot);
+                } else if (valueExpect instanceof JSONArray) {
+                    // Equal JSONArray have the same length and one contains the other.
+                    JSONArray expectArray = (JSONArray) valueExpect;
+                    JSONArray gotArray = (JSONArray) valueGot;
+
+                    if (expectArray.length() != gotArray.length()) {
+                        return false;
+                    }
+
+                    for (int i = 0; i < expectArray.length(); i++) {
+                        boolean gotContainsSimilar = false;
+                        for (int j = 0; j < gotArray.length(); j++) {
+                            if (similar((JSONObject) expectArray.get(i),
+                                    (JSONObject) gotArray.get(j))) {
+                                gotContainsSimilar = true;
+                                break;
+                            }
+                        }
+                        if (!gotContainsSimilar) {
+                            return false;
+                        }
+                    }
+                } else if (!valueExpect.equals(valueGot)) {
+                    return false;
+                }
+            }
+
+        } catch (JSONException e) {
+            Log.d(TAG, "Could not compare JSONObjects: " + e);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherClientMockService.java b/tests/carservice_test/src/com/android/car/VmsPublisherClientMockService.java
new file mode 100644
index 0000000..32d224b
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsPublisherClientMockService.java
@@ -0,0 +1,91 @@
+/*
+ * 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.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.HashSet;
+import java.util.Set;
+
+/**
+ * This service is launched during the tests in VmsPublisherSubscriberTest. It publishes a property
+ * that is going to be verified in the test.
+ *
+ * The service makes offering for pre-defined layers which verifies availability notifications for
+ * subscribers without them actively being subscribed to a layer, and also echos all the
+ * subscription requests with an offering for that layer.
+ * For example, without any subscription request from any client, this service will make offering
+ * to layer X. If a client will subscribe later to layer Y, this service will respond with offering
+ * to both layers X and Y.
+ *
+ * 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.
+ */
+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");
+        int publisherId = getPublisherId(VmsPublisherSubscriberTest.PAYLOAD);
+        publishIfNeeded(subscriptionState);
+        declareOffering(subscriptionState, publisherId);
+    }
+
+    private void publishIfNeeded(VmsSubscriptionState subscriptionState) {
+        for (VmsLayer layer : subscriptionState.getLayers()) {
+            if (layer.equals(VmsPublisherSubscriberTest.LAYER)) {
+                publish(VmsPublisherSubscriberTest.LAYER,
+                        VmsPublisherSubscriberTest.EXPECTED_PUBLISHER_ID,
+                        VmsPublisherSubscriberTest.PAYLOAD);
+            }
+        }
+    }
+
+    private void declareOffering(VmsSubscriptionState subscriptionState, int publisherId) {
+        Set<VmsLayerDependency> dependencies = new HashSet<>();
+
+        // Add all layers from the subscription state.
+        for( VmsLayer layer : subscriptionState.getLayers()) {
+            dependencies.add(new VmsLayerDependency(layer));
+        }
+
+        // Add default test layers.
+        dependencies.add(new VmsLayerDependency(VmsPublisherSubscriberTest.LAYER));
+
+        VmsLayersOffering offering = new VmsLayersOffering(dependencies, publisherId);
+        setLayersOffering(offering);
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherClientServiceTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherClientServiceTest.java
new file mode 100644
index 0000000..0a53092
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsPublisherClientServiceTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.annotation.ArrayRes;
+import android.car.VehicleAreaType;
+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.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
+import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerIntegerValuesIndex;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+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;
+
+@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;
+    private static final int MOCK_PUBLISHER_LAYER_SUBTYPE = 56;
+    public static final int MOCK_PUBLISHER_ID = 1234;
+    public static final VmsLayer MOCK_PUBLISHER_LAYER =
+            new VmsLayer(MOCK_PUBLISHER_LAYER_ID,
+                    MOCK_PUBLISHER_LAYER_SUBTYPE,
+                    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[] {
+                            javaClassToComponent(
+                                    getContext(), SimpleVmsPublisherClientService.class)
+                    };
+                }
+
+                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_SUBTYPE)
+                .addIntValue(MOCK_PUBLISHER_LAYER_VERSION)
+                .build();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        /**
+         * 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 {
+        super.tearDown();
+    }
+
+    static String javaClassToComponent(Context context, Class<?> clazz) {
+        return context.getPackageName() +  "/" + clazz.getName();
+    }
+
+    /**
+     * 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 {
+        //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(VmsMessageWithLayerIntegerValuesIndex.MESSAGE_TYPE);
+        int layerId = rawValue.int32Values.get(VmsMessageWithLayerIntegerValuesIndex.LAYER_TYPE);
+        int layerVersion = rawValue.int32Values.get(VmsMessageWithLayerIntegerValuesIndex.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 semaphore so the test can continue.
+            ArrayList<Integer> int32Values = value.value.int32Values;
+            if (int32Values.get(VmsBaseMessageIntegerValuesIndex.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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherPermissionsTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherPermissionsTest.java
new file mode 100644
index 0000000..14a2f18
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsPublisherPermissionsTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.annotation.ArrayRes;
+import android.car.VehicleAreaType;
+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_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+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.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+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 static final int MOCK_PUBLISHER_LAYER_FUSION_INT_VALUE = 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)
+                .addIntValue(MOCK_PUBLISHER_LAYER_FUSION_INT_VALUE)
+                .build();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        /**
+         * 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 {
+        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 {
+        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.MESSAGE_TYPE) ==
+                    VmsMessageType.DATA) {
+                mHalHandlerSemaphore.release();
+            }
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java b/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java
new file mode 100644
index 0000000..89dcba9
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsPublisherSubscriberTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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 static com.android.car.VmsPublisherClientServiceTest.javaClassToComponent;
+
+import android.annotation.ArrayRes;
+import android.car.Car;
+import android.car.VehicleAreaType;
+import android.car.vms.VmsAssociatedLayer;
+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_0.VehicleProperty;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.car.vehiclehal.test.MockedVehicleHal;
+
+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;
+
+@MediumTest
+public class VmsPublisherSubscriberTest extends MockedCarTestBase {
+    private static final int LAYER_ID = 88;
+    private static final int LAYER_VERSION = 19;
+    private static final int LAYER_SUBTYPE = 55;
+    private static final String TAG = "VmsPubSubTest";
+
+    // The expected publisher ID is 0 since it the expected assigned ID from the VMS core.
+    public static final int EXPECTED_PUBLISHER_ID = 0;
+    public static final VmsLayer LAYER = new VmsLayer(LAYER_ID, LAYER_SUBTYPE, LAYER_VERSION);
+    public static final VmsAssociatedLayer ASSOCIATED_LAYER =
+            new VmsAssociatedLayer(LAYER, new HashSet<>(Arrays.asList(EXPECTED_PUBLISHER_ID)));
+    public static final byte[] PAYLOAD = new byte[]{2, 3, 5, 7, 11, 13, 17};
+
+    private static final List<VmsAssociatedLayer> AVAILABLE_ASSOCIATED_LAYERS =
+            new ArrayList<>(Arrays.asList(ASSOCIATED_LAYER));
+
+
+    private static final int SUBSCRIBED_LAYER_ID = 89;
+    public static final VmsLayer SUBSCRIBED_LAYER =
+            new VmsLayer(SUBSCRIBED_LAYER_ID, LAYER_SUBTYPE, LAYER_VERSION);
+    public static final VmsAssociatedLayer ASSOCIATED_SUBSCRIBED_LAYER =
+            new VmsAssociatedLayer(SUBSCRIBED_LAYER, new HashSet<>(Arrays.asList(EXPECTED_PUBLISHER_ID)));
+    private static final List<VmsAssociatedLayer> AVAILABLE_ASSOCIATED_LAYERS_WITH_SUBSCRIBED_LAYER =
+            new ArrayList<>(Arrays.asList(ASSOCIATED_LAYER, ASSOCIATED_SUBSCRIBED_LAYER));
+
+
+    private HalHandler mHalHandler;
+    // Used to block until a value is propagated to the TestClientCallback.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[] {
+                            javaClassToComponent(getContext(), VmsPublisherClientMockService.class)
+                    };
+                }
+
+                return super.getStringArray(id);
+            }
+        };
+        ContextWrapper wrapper = new ContextWrapper(context) {
+            @Override
+            public Resources getResources() {
+                return resources;
+            }
+        };
+        return wrapper;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mSubscriberSemaphore = new Semaphore(0);
+        mAvailabilitySemaphore = new Semaphore(0);
+    }
+
+    @Override
+    protected synchronized void tearDown() throws Exception {
+        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 {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(LAYER);
+
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(LAYER, clientCallback.getLayer());
+        assertTrue(Arrays.equals(PAYLOAD, clientCallback.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 {
+        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.
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        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.
+     * In this test the client subscribes to a layer and verifies that it gets the
+     * notification that it is available.
+     */
+    public void testAvailabilityWithSubscription() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+            Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIBED_LAYER);
+
+        assertTrue(mAvailabilitySemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(AVAILABLE_ASSOCIATED_LAYERS_WITH_SUBSCRIBED_LAYER, clientCallback.getAvailableLayers());
+    }
+
+    /**
+     * The Mock service offers all the subscribed layers as available layers, so in this
+     * test the client subscribes to a layer and verifies that it gets the notification that it
+     * is available.
+     */
+    public void testAvailabilityWithoutSubscription() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+
+        assertTrue(mAvailabilitySemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(AVAILABLE_ASSOCIATED_LAYERS, clientCallback.getAvailableLayers());
+    }
+
+    private class HalHandler implements MockedVehicleHal.VehicleHalPropertyHandler {
+    }
+
+    private class TestClientCallback implements VmsSubscriberManager.VmsSubscriberClientCallback {
+        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 onLayersAvailabilityChanged(List<VmsLayer> availableLayers) {
+            mAvailableLayers = availableLayers;
+            mAvailabilitySemaphore.release();
+        }
+
+        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/VmsSubscriberManagerTest.java b/tests/carservice_test/src/com/android/car/VmsSubscriberManagerTest.java
new file mode 100644
index 0000000..954afee
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/VmsSubscriberManagerTest.java
@@ -0,0 +1,598 @@
+/*
+ * 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 static org.junit.Assume.assumeTrue;
+
+import android.car.Car;
+import android.car.VehicleAreaType;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsSubscriberManager;
+import android.car.vms.VmsSubscriberManager.VmsSubscriberClientCallback;
+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_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.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.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class VmsSubscriberManagerTest extends MockedCarTestBase {
+    private static final String TAG = "VmsSubscriberManagerTest";
+    private static final int PUBLISHER_ID = 17;
+    private static final int WRONG_PUBLISHER_ID = 26;
+    private static final Set<Integer> PUBLISHERS_LIST = new HashSet<Integer>(Arrays.asList(PUBLISHER_ID));
+
+    private static final int SUBSCRIPTION_LAYER_ID = 2;
+    private static final int SUBSCRIPTION_LAYER_VERSION = 3;
+    private static final int MOCK_PUBLISHER_LAYER_SUBTYPE = 444;
+    private static final VmsLayer SUBSCRIPTION_LAYER = new VmsLayer(SUBSCRIPTION_LAYER_ID,
+            MOCK_PUBLISHER_LAYER_SUBTYPE,
+            SUBSCRIPTION_LAYER_VERSION);
+    private static final VmsAssociatedLayer SUBSCRIPTION_ASSOCIATED_LAYER =
+            new VmsAssociatedLayer(SUBSCRIPTION_LAYER, PUBLISHERS_LIST);
+
+    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,
+                    MOCK_PUBLISHER_LAYER_SUBTYPE,
+                    SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1);
+
+    private static final VmsAssociatedLayer SUBSCRIPTION_DEPENDANT_ASSOCIATED_LAYER_1 =
+            new VmsAssociatedLayer(SUBSCRIPTION_DEPENDANT_LAYER_1, PUBLISHERS_LIST);
+
+    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,
+                    MOCK_PUBLISHER_LAYER_SUBTYPE,
+                    SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2);
+
+    private static final VmsAssociatedLayer SUBSCRIPTION_DEPENDANT_ASSOCIATED_LAYER_2 =
+            new VmsAssociatedLayer(SUBSCRIPTION_DEPENDANT_LAYER_2, PUBLISHERS_LIST);
+
+    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 TestClientCallback.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 {
+        super.setUp();
+        mSubscriberSemaphore = new Semaphore(0);
+        mHalHandlerSemaphore = new Semaphore(0);
+    }
+
+    @Override
+    protected synchronized void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribe() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        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, clientCallback.getLayer());
+        byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
+        assertTrue(Arrays.equals(expectedPayload, clientCallback.getPayload()));
+    }
+
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribeToPublisher() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(WRONG_PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribeFromPublisher() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE); //<-
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        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, clientCallback.getLayer());
+        byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
+        assertTrue(Arrays.equals(expectedPayload, clientCallback.getPayload()));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagate to a subscriber.
+    public void testUnsubscribe() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+        vmsSubscriberManager.unsubscribe(SUBSCRIPTION_LAYER);
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagate to a subscriber.
+    public void testSubscribeFromWrongPublisher() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(WRONG_PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagate to a subscriber.
+    public void testUnsubscribeFromPublisher() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+        vmsSubscriberManager.unsubscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribeAll() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.startMonitoring();
+
+        // Inject a value and wait for its callback in TestClientCallback.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(MOCK_PUBLISHER_LAYER_SUBTYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        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, clientCallback.getLayer());
+        byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
+        assertTrue(Arrays.equals(expectedPayload, clientCallback.getPayload()));
+    }
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSimpleAvailableLayers() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+
+        // Inject a value and wait for its callback in TestClientCallback.onLayersAvailabilityChanged.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        /*
+        Offering:
+        Layer             | Dependency
+        ===============================
+        (2, 3, 444), [17] | {}
+
+        Expected availability:
+        {(2, 3, 444 [17])}
+         */
+        v.value.int32Values.addAll(
+                Arrays.asList(
+                        VmsMessageType.OFFERING, // MessageType
+                        PUBLISHER_ID,
+                        1, // Number of offered layers
+
+                        SUBSCRIPTION_LAYER_ID,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_LAYER_VERSION,
+                        0 // number of dependencies for layer
+                )
+        );
+
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        List<VmsAssociatedLayer> expectedAvailableLayers =
+                new ArrayList<>(Arrays.asList(SUBSCRIPTION_ASSOCIATED_LAYER));
+        assertTrue(expectedAvailableLayers.containsAll(clientCallback.getAvailableLayers()));
+        assertEquals(expectedAvailableLayers.size(), clientCallback.getAvailableLayers().size());
+    }
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber after it has
+    // subscribed to a layer.
+    public void testSimpleAvailableLayersAfterSubscription() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+
+        // Inject a value and wait for its callback in TestClientCallback.onLayersAvailabilityChanged.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        /*
+        Offering:
+        Layer             | Dependency
+        ===============================
+        (2, 3, 444), [17] | {}
+
+        Expected availability:
+        {(2, 3, 444 [17])}
+         */
+        v.value.int32Values.addAll(
+                Arrays.asList(
+                        VmsMessageType.OFFERING, // MessageType
+                        PUBLISHER_ID,
+                        1, // Number of offered layers
+
+                        SUBSCRIPTION_LAYER_ID,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_LAYER_VERSION,
+                        0 // number of dependencies for layer
+                )
+        );
+
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        List<VmsAssociatedLayer> expectedAvailableLayers =
+                new ArrayList<>(Arrays.asList(SUBSCRIPTION_ASSOCIATED_LAYER));
+        assertTrue(expectedAvailableLayers.containsAll(clientCallback.getAvailableLayers()));
+        assertEquals(expectedAvailableLayers.size(), clientCallback.getAvailableLayers().size());
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagates to a subscriber after
+    // it has unregistered its callback.
+    public void testSimpleAvailableLayersAfterUnregister() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.unregisterClientCallback();
+
+
+        // Inject a value and wait for its callback in TestClientCallback.onLayersAvailabilityChanged.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        /*
+        Offering:
+        Layer             | Dependency
+        ===============================
+        (2, 3, 444), [17] | {}
+
+        Expected availability:
+        {(2, 3, 444 [17])}
+         */
+        v.value.int32Values.addAll(
+                Arrays.asList(
+                        VmsMessageType.OFFERING, // MessageType
+                        PUBLISHER_ID,
+                        1, // Number of offered layers
+
+                        SUBSCRIPTION_LAYER_ID,
+                        SUBSCRIPTION_LAYER_VERSION,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        0 // number of dependencies for layer
+                )
+        );
+
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagates to a subscriber after
+    // it has unregistered its callback.
+    public void testSomething() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+        try {
+            vmsSubscriberManager.unregisterClientCallback();
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+        fail();
+    }
+
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testComplexAvailableLayers() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestClientCallback clientCallback = new TestClientCallback();
+        vmsSubscriberManager.registerClientCallback(clientCallback);
+
+        // Inject a value and wait for its callback in TestClientCallback.onLayersAvailabilityChanged.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        /*
+        Offering:
+        Layer  | Dependency
+        ====================
+        (2, 3, 444), [17] | {}
+        (4, 5, 444), [17] | {(2, 3)}
+        (6, 7, 444), [17] | {(2, 3), (4, 5)}
+        (6, 7, 444), [17] | {(100, 200)}
+
+        Expected availability:
+        {(2, 3, 444 [17]), (4, 5, 444 [17]), (6, 7, 444 [17])}
+         */
+
+        v.value.int32Values.addAll(
+                Arrays.asList(
+                        VmsMessageType.OFFERING, // MessageType
+                        PUBLISHER_ID,
+                        4, // Number of offered layers
+
+                        SUBSCRIPTION_LAYER_ID,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_LAYER_VERSION,
+                        0, // number of dependencies for layer
+
+                        SUBSCRIPTION_DEPENDANT_LAYER_ID_1,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1,
+                        1, // number of dependencies for layer
+                        SUBSCRIPTION_LAYER_ID,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_LAYER_VERSION,
+
+                        SUBSCRIPTION_DEPENDANT_LAYER_ID_2,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2,
+                        2, // number of dependencies for layer
+                        SUBSCRIPTION_LAYER_ID,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_LAYER_VERSION,
+                        SUBSCRIPTION_DEPENDANT_LAYER_ID_1,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1,
+
+                        SUBSCRIPTION_DEPENDANT_LAYER_ID_2,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2,
+                        1, // number of dependencies for layer
+                        SUBSCRIPTION_UNSUPPORTED_LAYER_ID,
+                        MOCK_PUBLISHER_LAYER_SUBTYPE,
+                        SUBSCRIPTION_UNSUPPORTED_LAYER_VERSION
+                )
+        );
+
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        List<VmsAssociatedLayer> expectedAvailableLayers =
+                new ArrayList<>(Arrays.asList(
+                        SUBSCRIPTION_ASSOCIATED_LAYER,
+                        SUBSCRIPTION_DEPENDANT_ASSOCIATED_LAYER_1,
+                        SUBSCRIPTION_DEPENDANT_ASSOCIATED_LAYER_2
+                ));
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertTrue(expectedAvailableLayers.containsAll(clientCallback.getAvailableLayers()));
+        assertEquals(expectedAvailableLayers.size(), clientCallback.getAvailableLayers().size());
+    }
+
+    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 TestClientCallback implements VmsSubscriberClientCallback {
+        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 onLayersAvailabilityChanged(List<VmsLayer> availableLayers) {
+            Log.d(TAG, "onLayersAvailabilityChanged: Layers: " + availableLayers);
+            mAvailableLayers.addAll(availableLayers);
+            mSubscriberSemaphore.release();
+        }
+
+        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/SystemActivityMonitoringServiceTest.java b/tests/carservice_test/src/com/android/car/test/SystemActivityMonitoringServiceTest.java
deleted file mode 100644
index 3345605..0000000
--- a/tests/carservice_test/src/com/android/car/test/SystemActivityMonitoringServiceTest.java
+++ /dev/null
@@ -1,227 +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 com.android.car.test;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.hardware.automotive.vehicle.V2_0.VehicleDrivingStatus;
-import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
-import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
-import android.os.SystemClock;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import com.android.car.SystemActivityMonitoringService;
-import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
-import com.android.car.vehiclehal.VehiclePropValueBuilder;
-import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-@MediumTest
-public class SystemActivityMonitoringServiceTest extends MockedCarTestBase {
-    private static final long TIMEOUT_MS = 3000;
-    private static final long POLL_INTERVAL_MS = 50;
-    private static final Semaphore sAvailable = new Semaphore(0);
-
-    private final DrivingStatusHandler mDrivingStatusHandler = new DrivingStatusHandler();
-
-    @Override
-    protected synchronized void configureMockedHal() {
-        addProperty(VehicleProperty.DRIVING_STATUS, mDrivingStatusHandler)
-                .setAccess(VehiclePropertyAccess.READ);
-    }
-
-    private void init(boolean drivingStatusRestricted) {
-        // Set no restriction to driving status, to avoid CarPackageManagerService to launch a
-        // blocking activity.
-        mDrivingStatusHandler.setDrivingStatusRestricted(drivingStatusRestricted);
-
-        // Due to asynchronous nature of Car Service initialization, if we won't wait we may inject
-        // an event while SensorHalService is not subscribed yet.
-        assertTrue(getMockedVehicleHal()
-                .waitForSubscriber(VehicleProperty.DRIVING_STATUS, TIMEOUT_MS));
-
-        VehiclePropValue injectValue =
-                VehiclePropValueBuilder.newBuilder(VehicleProperty.DRIVING_STATUS)
-                        .setTimestamp(SystemClock.elapsedRealtimeNanos())
-                        .addIntValue(0)
-                        .build();
-        getMockedVehicleHal().injectEvent(injectValue);
-    }
-
-    public void testActivityLaunch() {
-        init(false);
-        List<TopTaskInfoContainer> taskList = new ArrayList<>();
-        SystemActivityMonitoringService systemActivityMonitoringService =
-                new SystemActivityMonitoringService(getContext());
-        systemActivityMonitoringService.registerActivityLaunchListener(
-                new SystemActivityMonitoringService.ActivityLaunchListener() {
-                    @Override
-                    public void onActivityLaunch(
-                            SystemActivityMonitoringService.TopTaskInfoContainer topTask) {
-                        taskList.add(topTask);
-                    }
-                });
-        getContext().startActivity(new Intent(getContext(), ActivityA.class));
-        verifyTopActivityPolling(taskList, 0, new ComponentName(getContext().getPackageName(),
-                ActivityA.class.getName()));
-        sAvailable.release();
-
-        verifyTopActivityPolling(taskList, 1, new ComponentName(getContext().getPackageName(),
-                ActivityB.class.getName()));
-        sAvailable.release();
-
-        verifyTopActivityPolling(taskList, 2, new ComponentName(getContext().getPackageName(),
-                ActivityC.class.getName()));
-    }
-
-    public void testActivityBlocking() {
-        init(false);
-        Semaphore blocked = new Semaphore(0);
-        List<TopTaskInfoContainer> taskList = new ArrayList<>();
-        SystemActivityMonitoringService systemActivityMonitoringService =
-                new SystemActivityMonitoringService(getContext());
-
-        ComponentName blackListedActivity = new ComponentName(getContext().getPackageName(),
-                ActivityC.class.getName());
-        ComponentName blockingActivity = new ComponentName(getContext().getPackageName(),
-                BlockingActivity.class.getName());
-        Intent newActivityIntent = new Intent();
-        newActivityIntent.setComponent(blockingActivity);
-
-        systemActivityMonitoringService.registerActivityLaunchListener(
-                new SystemActivityMonitoringService.ActivityLaunchListener() {
-                    @Override
-                    public void onActivityLaunch(
-                            SystemActivityMonitoringService.TopTaskInfoContainer topTask) {
-                        taskList.add(topTask);
-                        if (topTask.topActivity.equals(blackListedActivity)) {
-                            systemActivityMonitoringService.blockActivity(topTask,
-                                    newActivityIntent);
-                            blocked.release();
-                        }
-                    }
-                });
-        // start a black listed activity
-        getContext().startActivity(new Intent(getContext(), ActivityC.class));
-        // wait for the listener to call blockActivity()
-        try {
-            blocked.tryAcquire(2, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        // We should first receive the blackListedActivity launch,
-        // and later the blockActivity launch
-        verifyTopActivityPolling(taskList, 0, blackListedActivity);
-        verifyTopActivityPolling(taskList, 1, blockingActivity);
-    }
-
-    private void verifyTopActivityPolling(
-            List<TopTaskInfoContainer> topTaskList, int i, ComponentName activity) {
-        boolean activityVerified = false;
-        int timeElapsedMs = 0;
-        try {
-            while (!activityVerified && timeElapsedMs <= TIMEOUT_MS) {
-                Thread.sleep(POLL_INTERVAL_MS);
-                timeElapsedMs += POLL_INTERVAL_MS;
-                if (topTaskList.size() <= i) continue;
-                TopTaskInfoContainer topTask = topTaskList.get(i);
-                if (topTask != null && topTask.topActivity.equals(activity)) {
-                    activityVerified = true;
-                    break;
-                }
-            }
-            assertEquals(true, activityVerified);
-        } catch (Exception e) {
-            fail(e.toString());
-        }
-    }
-
-    public static class ActivityA extends Activity {
-        @Override
-        protected void onPostResume() {
-            super.onPostResume();
-            // Wait until the activity launch event is consumed by the listener.
-            try {
-                if (!sAvailable.tryAcquire(2, TimeUnit.SECONDS)) {
-                    fail("Time out");
-                }
-            } catch (Exception e) {
-                fail(e.toString());
-            }
-            Intent intent = new Intent(this, ActivityB.class);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startActivity(intent);
-        }
-    }
-
-    public static class ActivityB extends Activity {
-        @Override
-        protected void onPostResume() {
-            super.onPostResume();
-            // Wait until the activity launch event is consumed by the listener.
-            try {
-                if (!sAvailable.tryAcquire(2, TimeUnit.SECONDS)) {
-                    fail("Time out");
-                }
-            } catch (Exception e) {
-                fail(e.toString());
-            }
-            Intent intent = new Intent(this, ActivityC.class);
-            startActivity(intent);
-        }
-    }
-
-    public static class ActivityC extends Activity {
-    }
-
-    public static class BlockingActivity extends Activity {
-    }
-
-    private class DrivingStatusHandler implements VehicleHalPropertyHandler {
-        int mDrivingStatus = VehicleDrivingStatus.UNRESTRICTED;
-
-        public void setDrivingStatusRestricted(boolean restricted) {
-            mDrivingStatus = restricted ? VehicleDrivingStatus.NO_VIDEO
-                    : VehicleDrivingStatus.UNRESTRICTED;
-        }
-
-        @Override
-        public void onPropertySet(VehiclePropValue value) {
-        }
-
-        @Override
-        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
-            return VehiclePropValueBuilder.newBuilder(VehicleProperty.DRIVING_STATUS)
-                    .setTimestamp(SystemClock.elapsedRealtimeNanos())
-                    .addIntValue(mDrivingStatus)
-                    .build();
-        }
-
-        @Override
-        public void onPropertySubscribe(int property, int zones, float sampleRate) {
-        }
-
-        @Override
-        public void onPropertyUnsubscribe(int property) {
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/Android.mk b/tests/carservice_unit_test/Android.mk
index 1ec2008..1cf134c 100644
--- a/tests/carservice_unit_test/Android.mk
+++ b/tests/carservice_unit_test/Android.mk
@@ -34,6 +34,10 @@
 LOCAL_INSTRUMENTATION_FOR := CarService
 
 LOCAL_JAVA_LIBRARIES := android.car android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test android-support-test mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := junit \
+                               legacy-android-test \
+                               android-support-test \
+                               mockito-target-minus-junit4 \
+                               com.android.car.test.utils
 
 include $(BUILD_PACKAGE)
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index 78f13e3..db24049 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -25,6 +25,15 @@
 import com.android.car.hal.PowerHalService;
 import com.android.car.hal.PowerHalService.PowerState;
 
+import com.android.car.systeminterface.DisplayInterface;
+import com.android.car.systeminterface.IOInterface;
+import com.android.car.systeminterface.SystemInterface;
+import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.systeminterface.WakeLockInterface;
+import com.android.car.test.utils.TemporaryDirectory;
+import java.io.File;
+import java.io.IOException;
+import java.time.Duration;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -34,8 +43,13 @@
     private static final long WAIT_TIMEOUT_MS = 2000;
     private static final long WAIT_TIMEOUT_LONG_MS = 5000;
 
+    private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
+    private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
+    private final MockWakeLockInterface mWakeLockInterface = new MockWakeLockInterface();
+    private final MockIOInterface mIOInterface = new MockIOInterface();
+
     private MockedPowerHalService mPowerHal;
-    private SystemInterfaceImpl mSystemInterface;
+    private SystemInterface mSystemInterface;
     private CarPowerManagementService mService;
     private final PowerEventListener mPowerEventListener = new PowerEventListener();
     private PowerEventProcessingHandlerImpl mPowerEventProcessingHandler;
@@ -45,7 +59,11 @@
         super.setUp();
         mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
                 true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
-        mSystemInterface = new SystemInterfaceImpl();
+        mSystemInterface = SystemInterface.Builder.defaultSystemInterface(getContext())
+            .withDisplayInterface(mDisplayInterface)
+            .withSystemStateInterface(mSystemStateInterface)
+            .withWakeLockInterface(mWakeLockInterface)
+            .withIOInterface(mIOInterface).build();
     }
 
     @Override
@@ -54,6 +72,7 @@
         if (mService != null) {
             mService.release();
         }
+        mIOInterface.tearDown();
     }
 
     public void testBootComplete() throws Exception {
@@ -75,15 +94,15 @@
         assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
         // it will call display on for initial state
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_ON_DISP_OFF, 0));
-        assertFalse(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
     }
 
     public void testDisplayOn() throws Exception {
         // start with display off
         mSystemInterface.setDisplayState(false);
-        mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS);
+        mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS);
         mService = new CarPowerManagementService(mPowerHal, mSystemInterface);
         mService.init();
         mService.registerPowerEventListener(mPowerEventListener);
@@ -93,7 +112,7 @@
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
 
         // display should be turned on as it started with off state.
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
     }
 
     public void testShutdown() throws Exception {
@@ -105,14 +124,14 @@
         mService.registerPowerEventProcessingHandler(mPowerEventProcessingHandler);
         assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
 
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
                 PowerHalService.FLAG_SHUTDOWN_IMMEDIATELY));
         assertStateReceived(PowerHalService.SET_SHUTDOWN_START, wakeupTime);
-        assertFalse(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
         mPowerEventListener.waitForShutdown(WAIT_TIMEOUT_MS);
-        mSystemInterface.waitForShutdown(WAIT_TIMEOUT_MS);
+        mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
     }
 
     public void testShutdownWithProcessing() throws Exception {
@@ -126,15 +145,15 @@
         mService.registerPowerEventProcessingHandler(mPowerEventProcessingHandler);
         assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
 
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE, 0));
         mPowerEventProcessingHandler.waitForPrepareShutdown(WAIT_TIMEOUT_MS);
         assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START,
                 WAIT_TIMEOUT_LONG_MS, wakeupTime);
-        assertFalse(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
         mPowerEventListener.waitForShutdown(WAIT_TIMEOUT_MS);
-        mSystemInterface.waitForShutdown(WAIT_TIMEOUT_MS);
+        mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
     }
 
     public void testSleepEntryAndWakeup() throws Exception {
@@ -146,14 +165,14 @@
         mService.registerPowerEventProcessingHandler(mPowerEventProcessingHandler);
         assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
 
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
                 PowerHalService.FLAG_SHUTDOWN_PARAM_CAN_SLEEP));
-        assertFalse(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
         mPowerEventListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
-        int wakeupTimeReceived = mSystemInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
+        int wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
         assertEquals(wakeupTime, wakeupTimeReceived);
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
         mPowerEventListener.waitForSleepExit(WAIT_TIMEOUT_MS);
@@ -171,19 +190,19 @@
         mService.registerPowerEventProcessingHandler(mPowerEventProcessingHandler);
         assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
 
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
                 PowerHalService.FLAG_SHUTDOWN_PARAM_CAN_SLEEP));
         mPowerEventProcessingHandler.waitForPrepareShutdown(WAIT_TIMEOUT_MS);
-        assertFalse(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
         assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY,
                 WAIT_TIMEOUT_LONG_MS, 0);
         mPowerEventListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
         // set power on here without notification. PowerManager should check the state after sleep
         // exit
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_ON_DISP_OFF, 0), false);
-        int wakeupTimeReceived = mSystemInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
+        int wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
         assertEquals(wakeupTime, wakeupTimeReceived);
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
         mPowerEventListener.waitForSleepExit(WAIT_TIMEOUT_MS);
@@ -201,35 +220,35 @@
         mService.registerPowerEventProcessingHandler(mPowerEventProcessingHandler);
         assertStateReceived(MockedPowerHalService.SET_BOOT_COMPLETE, 0);
         mPowerEventProcessingHandler.waitForPowerOn(WAIT_TIMEOUT_MS);
-        assertTrue(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertTrue(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
 
         mPowerHal.setCurrentPowerState(new PowerState(PowerHalService.STATE_SHUTDOWN_PREPARE,
                 PowerHalService.FLAG_SHUTDOWN_PARAM_CAN_SLEEP));
         mPowerEventProcessingHandler.waitForPrepareShutdown(WAIT_TIMEOUT_MS);
-        assertFalse(mSystemInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
+        assertFalse(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS));
         assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY,
                 WAIT_TIMEOUT_LONG_MS, 0);
         mPowerEventListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
-        mSystemInterface.setWakeupCausedByTimer(true);
-        int wakeupTimeReceived = mSystemInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
+        mSystemStateInterface.setWakeupCausedByTimer(true);
+        int wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
         assertEquals(wakeupTime, wakeupTimeReceived);
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
         mPowerEventListener.waitForSleepExit(WAIT_TIMEOUT_MS);
         // second processing after wakeup
-        assertFalse(mSystemInterface.getDisplayState());
+        assertFalse(mDisplayInterface.getDisplayState());
         mPowerEventProcessingHandler.waitForPrepareShutdown(WAIT_TIMEOUT_MS);
         assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY,
                 WAIT_TIMEOUT_LONG_MS, 0);
         mPowerEventListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
         // PM will shutdown system as it was not woken-up due to timer and it is not power on.
-        mSystemInterface.setWakeupCausedByTimer(false);
-        wakeupTimeReceived = mSystemInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
+        mSystemStateInterface.setWakeupCausedByTimer(false);
+        wakeupTimeReceived = mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
         assertEquals(wakeupTime, wakeupTimeReceived);
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
         assertStateReceived(PowerHalService.SET_SHUTDOWN_START, wakeupTime);
         mPowerEventListener.waitForShutdown(WAIT_TIMEOUT_MS);
-        mSystemInterface.waitForShutdown(WAIT_TIMEOUT_MS);
-        assertFalse(mSystemInterface.getDisplayState());
+        mSystemStateInterface.waitForShutdown(WAIT_TIMEOUT_MS);
+        assertFalse(mDisplayInterface.getDisplayState());
     }
 
     private void assertStateReceived(int expectedState, int expectedParam) throws Exception {
@@ -259,15 +278,9 @@
         }
     }
 
-    private static class SystemInterfaceImpl extends SystemInterface {
-
+    private static final class MockDisplayInterface implements DisplayInterface {
         private boolean mDisplayOn = true;
         private final Semaphore mDisplayStateWait = new Semaphore(0);
-        private final Semaphore mShutdownWait = new Semaphore(0);
-        private final Semaphore mSleepWait = new Semaphore(0);
-        private final Semaphore mSleepExitWait = new Semaphore(0);
-        private int mWakeupTime;
-        private boolean mWakeupCausedByTimer = false;
 
         @Override
         public synchronized void setDisplayState(boolean on) {
@@ -285,8 +298,18 @@
         }
 
         @Override
-        public void releaseAllWakeLocks() {
-        }
+        public void startDisplayStateMonitoring(CarPowerManagementService service) {}
+
+        @Override
+        public void stopDisplayStateMonitoring() {}
+    }
+
+    private static final class MockSystemStateInterface implements SystemStateInterface {
+        private final Semaphore mShutdownWait = new Semaphore(0);
+        private final Semaphore mSleepWait = new Semaphore(0);
+        private final Semaphore mSleepExitWait = new Semaphore(0);
+        private int mWakeupTime;
+        private boolean mWakeupCausedByTimer = false;
 
         @Override
         public void shutdown() {
@@ -307,11 +330,6 @@
             }
         }
 
-        @Override
-        public boolean isSystemSupportingDeepSleep() {
-            return true;
-        }
-
         public int waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
             waitForSemaphore(mSleepWait, timeoutMs);
             mSleepExitWait.release();
@@ -319,23 +337,10 @@
         }
 
         @Override
-        public void switchToPartialWakeLock() {
-        }
+        public void scheduleActionForBootCompleted(Runnable action, Duration delay) {}
 
         @Override
-        public void switchToFullWakeLock() {
-        }
-
-        @Override
-        public void startDisplayStateMonitoring(CarPowerManagementService service) {
-        }
-
-        @Override
-        public void stopDisplayStateMonitoring() {
-        }
-
-        @Override
-        public synchronized boolean isWakeupCausedByTimer() {
+        public boolean isWakeupCausedByTimer() {
             Log.i(TAG, "isWakeupCausedByTimer:" + mWakeupCausedByTimer);
             return mWakeupCausedByTimer;
         }
@@ -343,6 +348,50 @@
         public synchronized void setWakeupCausedByTimer(boolean set) {
             mWakeupCausedByTimer = set;
         }
+
+        @Override
+        public boolean isSystemSupportingDeepSleep() {
+            return true;
+        }
+    }
+
+    private static final class MockWakeLockInterface implements WakeLockInterface {
+
+        @Override
+        public void releaseAllWakeLocks() {}
+
+        @Override
+        public void switchToPartialWakeLock() {}
+
+        @Override
+        public void switchToFullWakeLock() {}
+    }
+
+    private static final class MockIOInterface implements IOInterface {
+        private TemporaryDirectory mFilesDir;
+
+        @Override
+        public File getFilesDir() {
+            if (mFilesDir == null) {
+                try {
+                    mFilesDir = new TemporaryDirectory(TAG);
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to create temporary directory", e);
+                    fail("failed to create temporary directory. exception was: " + e);
+                }
+            }
+            return mFilesDir.getDirectory();
+        }
+
+        public void tearDown() {
+            if (mFilesDir != null) {
+                try {
+                    mFilesDir.close();
+                } catch (Exception e) {
+                    Log.w(TAG, "could not remove temporary directory", e);
+                }
+            }
+        }
     }
 
     private class PowerEventListener implements PowerServiceEventListener {
diff --git a/tests/carservice_unit_test/src/com/android/car/SlidingWindowTest.java b/tests/carservice_unit_test/src/com/android/car/SlidingWindowTest.java
new file mode 100644
index 0000000..5d372d9
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/SlidingWindowTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import junit.framework.TestCase;
+
+@MediumTest
+public class SlidingWindowTest extends TestCase {
+    static final String TAG = SlidingWindowTest.class.getSimpleName();
+
+    private static <T> boolean sameContents(Iterator<T> t1, Iterator<T> t2) {
+        while(t1.hasNext()) {
+            if (!t2.hasNext()) {
+                return false;
+            }
+            T e1 = t1.next();
+            T e2 = t2.next();
+            if (!e1.equals(e2)) {
+                return false;
+            }
+        }
+        return !t2.hasNext();
+    }
+
+    public void testAdd() {
+        final List<Integer> elements = Arrays.asList(3, 4);
+        SlidingWindow<Integer> window = new SlidingWindow<>(5);
+        elements.forEach(window::add);
+        assertTrue(sameContents(elements.iterator(), window.iterator()));
+    }
+
+    public void testAddOverflow() {
+        final List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+        final List<Integer> expectedElements = Arrays.asList(6, 7, 8, 9, 10);
+        SlidingWindow<Integer> window = new SlidingWindow<>(5);
+        window.addAll(elements);
+        assertTrue(sameContents(expectedElements.iterator(), window.iterator()));
+    }
+
+    public void testStream() {
+        final List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+        final List<Integer> expectedElements = Arrays.asList(6, 7, 8, 9, 10);
+        SlidingWindow<Integer> window = new SlidingWindow<>(5);
+        window.addAll(elements);
+        for (Integer e : expectedElements) {
+            assertTrue(window.stream().anyMatch(e::equals));
+        }
+    }
+
+    public void testCount() {
+        final List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+        SlidingWindow<Integer> window = new SlidingWindow<>(5);
+        window.addAll(elements);
+        assertEquals(3, window.count((Integer e) -> (e % 2) == 0));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/UptimeTrackerTest.java b/tests/carservice_unit_test/src/com/android/car/UptimeTrackerTest.java
new file mode 100644
index 0000000..1b46825
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/UptimeTrackerTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+import com.android.car.systeminterface.TimeInterface;
+import com.android.car.test.utils.TemporaryFile;
+
+import junit.framework.TestCase;
+
+@MediumTest
+public class UptimeTrackerTest extends TestCase {
+    static final String TAG = UptimeTrackerTest.class.getSimpleName();
+
+    static final class MockTimeInterface implements TimeInterface {
+        private long mCurrentTime = 0;
+        private Runnable mRunnable = null;
+
+        MockTimeInterface incrementTime(long by) {
+            mCurrentTime += by;
+            return this;
+        }
+
+        MockTimeInterface tick() {
+            if (mRunnable != null) {
+                mRunnable.run();
+            }
+            return this;
+        }
+
+        @Override
+        public long getUptime(boolean includeDeepSleepTime) {
+            return mCurrentTime;
+        }
+
+        @Override
+        public void scheduleAction(Runnable r, long delayMs) {
+            if (mRunnable != null) {
+                throw new IllegalStateException("task already scheduled");
+            }
+            mRunnable = r;
+        }
+
+        @Override
+        public void cancelAllActions() {
+            mRunnable = null;
+        }
+    }
+
+    private static final long SNAPSHOT_INTERVAL = 0; // actual time doesn't matter for this test
+
+    public void testUptimeTrackerFromCleanSlate() throws Exception {
+        MockTimeInterface timeInterface = new MockTimeInterface();
+        try (TemporaryFile uptimeFile = new TemporaryFile(TAG)) {
+            UptimeTracker uptimeTracker = new UptimeTracker(uptimeFile.getFile(),
+                SNAPSHOT_INTERVAL, timeInterface);
+
+            assertEquals(0, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(5000).tick();
+            assertEquals(5000, uptimeTracker.getTotalUptime());
+
+            timeInterface.tick();
+            assertEquals(5000, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(1000).tick();
+            assertEquals(6000, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(400).tick();
+            assertEquals(6400, uptimeTracker.getTotalUptime());
+        }
+    }
+
+    public void testUptimeTrackerWithHistoricalState() throws Exception {
+        MockTimeInterface timeInterface = new MockTimeInterface();
+        try (TemporaryFile uptimeFile = new TemporaryFile(TAG)) {
+            uptimeFile.write("{\"uptime\" : 5000}");
+            UptimeTracker uptimeTracker = new UptimeTracker(uptimeFile.getFile(),
+                SNAPSHOT_INTERVAL, timeInterface);
+
+            assertEquals(5000, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(5000).tick();
+            assertEquals(10000, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(1000).tick();
+            assertEquals(11000, uptimeTracker.getTotalUptime());
+        }
+    }
+
+    public void testUptimeTrackerAcrossHistoricalState() throws Exception {
+        MockTimeInterface timeInterface = new MockTimeInterface();
+        try (TemporaryFile uptimeFile = new TemporaryFile(TAG)) {
+            uptimeFile.write("{\"uptime\" : 5000}");
+            UptimeTracker uptimeTracker = new UptimeTracker(uptimeFile.getFile(),
+                SNAPSHOT_INTERVAL, timeInterface);
+
+            assertEquals(5000, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(5000).tick();
+            assertEquals(10000, uptimeTracker.getTotalUptime());
+
+            timeInterface.incrementTime(500).tick();
+            uptimeTracker.onDestroy();
+            timeInterface.cancelAllActions();
+
+            uptimeTracker = new UptimeTracker(uptimeFile.getFile(),
+                SNAPSHOT_INTERVAL, timeInterface);
+
+            timeInterface.incrementTime(3000).tick();
+            assertEquals(13500, uptimeTracker.getTotalUptime());
+        }
+    }
+
+    public void testUptimeTrackerShutdown() throws Exception {
+        MockTimeInterface timeInterface = new MockTimeInterface();
+        try (TemporaryFile uptimeFile = new TemporaryFile(TAG)) {
+            UptimeTracker uptimeTracker = new UptimeTracker(uptimeFile.getFile(),
+                SNAPSHOT_INTERVAL, timeInterface);
+
+            timeInterface.incrementTime(6000);
+            uptimeTracker.onDestroy();
+            timeInterface.cancelAllActions();
+
+            uptimeTracker = new UptimeTracker(uptimeFile.getFile(),
+                SNAPSHOT_INTERVAL, timeInterface);
+            assertEquals(6000, uptimeTracker.getTotalUptime());
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java b/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
new file mode 100644
index 0000000..4a09d4f
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
@@ -0,0 +1,266 @@
+/*
+ * 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.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+public class VmsLayersAvailabilityTest extends AndroidTestCase {
+
+    private static final VmsLayer LAYER_X = new VmsLayer(1, 1, 2);
+    private static final VmsLayer LAYER_Y = new VmsLayer(3, 2, 4);
+    private static final VmsLayer LAYER_Z = new VmsLayer(5, 3, 6);
+
+    private static final int PUBLISHER_ID_1 = 19;
+    private static final int PUBLISHER_ID_2 = 28;
+
+    private static final Set<Integer> PUBLISHERS_1 = new HashSet<>(Arrays.asList(PUBLISHER_ID_1));
+    private static final Set<Integer> PUBLISHERS_2 = new HashSet<>(Arrays.asList(PUBLISHER_ID_2));
+    private static final Set<Integer> PUBLISHERS_1_AND_2 =
+            new HashSet<>(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2));
+
+    private static final VmsLayerDependency X_DEPENDS_ON_Y =
+            new VmsLayerDependency(LAYER_X, new HashSet<VmsLayer>(Arrays.asList(LAYER_Y)));
+
+    private static final VmsLayerDependency X_DEPENDS_ON_Z =
+            new VmsLayerDependency(LAYER_X, new HashSet<VmsLayer>(Arrays.asList(LAYER_Z)));
+
+    private static final VmsLayerDependency Y_DEPENDS_ON_Z =
+            new VmsLayerDependency(LAYER_Y, new HashSet<VmsLayer>(Arrays.asList(LAYER_Z)));
+
+    private static final VmsLayerDependency Y_DEPENDS_ON_X =
+            new VmsLayerDependency(LAYER_Y, new HashSet<VmsLayer>(Arrays.asList(LAYER_X)));
+
+    private static final VmsLayerDependency Z_DEPENDS_ON_X =
+            new VmsLayerDependency(LAYER_Z, new HashSet<VmsLayer>(Arrays.asList(LAYER_X)));
+
+    private static final VmsLayerDependency Z_DEPENDS_ON_NOTHING =
+            new VmsLayerDependency(LAYER_Z);
+
+    private static final VmsLayerDependency X_DEPENDS_ON_SELF =
+            new VmsLayerDependency(LAYER_X, new HashSet<VmsLayer>(Arrays.asList(LAYER_X)));
+
+    private Set<VmsLayersOffering> mOfferings;
+    private VmsLayersAvailability mLayersAvailability;
+
+    @Override
+    protected void setUp() throws Exception {
+        mLayersAvailability = new VmsLayersAvailability();
+        mOfferings = new HashSet<>();
+        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<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_2));
+
+        VmsLayersOffering offering =
+                new VmsLayersOffering(new HashSet<>(Arrays.asList(new VmsLayerDependency(LAYER_X))),
+                        PUBLISHER_ID_2);
+
+        mOfferings.add(offering);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers, mLayersAvailability.getAvailableLayers());
+    }
+
+    public void testChainOfDependenciesSatisfied() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_1));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1));
+
+        VmsLayersOffering offering =
+                new VmsLayersOffering(
+                        new HashSet<>(Arrays.asList(X_DEPENDS_ON_Y, Y_DEPENDS_ON_Z, Z_DEPENDS_ON_NOTHING)),
+                        PUBLISHER_ID_1);
+
+        mOfferings.add(offering);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+    }
+
+    public void testChainOfDependenciesSatisfiedTwoOfferings() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_1));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1));
+
+        VmsLayersOffering offering1 =
+                new VmsLayersOffering(
+                        new HashSet<>(Arrays.asList(X_DEPENDS_ON_Y, Y_DEPENDS_ON_Z)),
+                        PUBLISHER_ID_1);
+
+        VmsLayersOffering offering2 =
+                new VmsLayersOffering(new HashSet<>(Arrays.asList(Z_DEPENDS_ON_NOTHING)),
+                        PUBLISHER_ID_1);
+
+        mOfferings.add(offering1);
+        mOfferings.add(offering2);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+    }
+
+    public void testChainOfDependencieNotSatisfied() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        VmsLayersOffering offering =
+                new VmsLayersOffering(new HashSet<>(Arrays.asList(X_DEPENDS_ON_Y, Y_DEPENDS_ON_Z)),
+                        PUBLISHER_ID_1);
+
+        mOfferings.add(offering);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+
+        Set<VmsAssociatedLayer> expectedUnavailableAssociatedLayers = new HashSet<>();
+        expectedUnavailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+        expectedUnavailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_1));
+
+
+        assertEquals(expectedUnavailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getUnavailableLayers()));
+    }
+
+    public void testOneOfMultipleDependencySatisfied() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1));
+
+
+        VmsLayersOffering offering =
+                new VmsLayersOffering(
+                        new HashSet<>(Arrays.asList(
+                                X_DEPENDS_ON_Y, X_DEPENDS_ON_Z, Z_DEPENDS_ON_NOTHING)),
+                        PUBLISHER_ID_1);
+
+        mOfferings.add(offering);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+    }
+
+    public void testCyclicDependency() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+
+        VmsLayersOffering offering =
+                new VmsLayersOffering(
+                        new HashSet<>(
+                                Arrays.asList(X_DEPENDS_ON_Y, Y_DEPENDS_ON_Z, Z_DEPENDS_ON_X)),
+                        PUBLISHER_ID_1);
+
+        mOfferings.add(offering);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+    }
+
+    public void testAlmostCyclicDependency() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1_AND_2));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_2));
+
+        VmsLayersOffering offering1 =
+                new VmsLayersOffering(
+                        new HashSet<>(Arrays.asList(X_DEPENDS_ON_Y, Z_DEPENDS_ON_NOTHING)),
+                        PUBLISHER_ID_1);
+
+        VmsLayersOffering offering2 =
+                new VmsLayersOffering(new HashSet<>(Arrays.asList(Y_DEPENDS_ON_Z, Z_DEPENDS_ON_X)),
+                        PUBLISHER_ID_2);
+
+        mOfferings.add(offering1);
+        mOfferings.add(offering2);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers, mLayersAvailability.getAvailableLayers());
+    }
+
+    public void testCyclicDependencyAndLayerWithoutDependency() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+        expectedAvailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Z, PUBLISHERS_1));
+
+        VmsLayersOffering offering1 =
+                new VmsLayersOffering(
+                        new HashSet<>(
+                                Arrays.asList(X_DEPENDS_ON_Y, Z_DEPENDS_ON_NOTHING)),
+                        PUBLISHER_ID_1);
+
+        VmsLayersOffering offering2 =
+                new VmsLayersOffering(new HashSet<>(Arrays.asList(Y_DEPENDS_ON_X)), PUBLISHER_ID_2);
+
+        mOfferings.add(offering1);
+        mOfferings.add(offering2);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+
+        Set<VmsAssociatedLayer> expectedUnavailableAssociatedLayers = new HashSet<>();
+        expectedUnavailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+        expectedUnavailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_Y, PUBLISHERS_2));
+
+        assertEquals(expectedUnavailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getUnavailableLayers()));
+    }
+
+    public void testSelfDependency() throws Exception {
+        Set<VmsAssociatedLayer> expectedAvailableAssociatedLayers = new HashSet<>();
+
+        VmsLayersOffering offering =
+                new VmsLayersOffering(new HashSet<>(Arrays.asList(X_DEPENDS_ON_SELF)),
+                        PUBLISHER_ID_1);
+
+        mOfferings.add(offering);
+        mLayersAvailability.setPublishersOffering(mOfferings);
+
+        assertEquals(expectedAvailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getAvailableLayers()));
+
+        Set<VmsAssociatedLayer> expectedUnavailableAssociatedLayers = new HashSet<>();
+        expectedUnavailableAssociatedLayers.add(new VmsAssociatedLayer(LAYER_X, PUBLISHERS_1));
+
+        assertEquals(expectedUnavailableAssociatedLayers,
+                new HashSet<VmsAssociatedLayer>(mLayersAvailability.getUnavailableLayers()));
+    }
+}
\ No newline at end of file
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..3f922c9
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/VmsPublishersInfoTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.Arrays;
+import java.util.Map;
+
+@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/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java b/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
new file mode 100644
index 0000000..8fe849b
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.vms.IVmsSubscriberClient;
+import android.car.vms.VmsAssociatedLayer;
+import android.car.vms.VmsLayer;
+import android.car.vms.VmsSubscriptionState;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+
+@SmallTest
+public class VmsRoutingTest extends AndroidTestCase {
+    private static VmsLayer LAYER_WITH_SUBSCRIPTION_1 = new VmsLayer(1, 1, 2);
+    private static VmsLayer LAYER_WITH_SUBSCRIPTION_2 = new VmsLayer(1, 3, 3);
+    private static VmsLayer LAYER_WITHOUT_SUBSCRIPTION =
+            new VmsLayer(1, 7, 4);
+    private static int PUBLISHER_ID_1 = 123;
+    private static int PUBLISHER_ID_2 = 456;
+    private static int PUBLISHER_ID_UNLISTED = 789;
+    private VmsRouting mRouting;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRouting = new VmsRouting();
+    }
+
+    public void testAddingSubscribersAndHalLayersNoOverlap() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a HAL subscription.
+        mRouting.addHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        // Verify expected subscriptions are in routing manager.
+        Set<VmsLayer> expectedSubscriptions = new HashSet<>();
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_1);
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_2);
+        VmsSubscriptionState subscriptionState = mRouting.getSubscriptionState();
+        assertEquals(2, subscriptionState.getSequenceNumber());
+        assertEquals(expectedSubscriptions,
+                new HashSet<>(subscriptionState.getLayers()));
+
+        // Verify there is only a single subscriber.
+        assertEquals(1,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1).size());
+    }
+
+    public void testAddingSubscribersAndHalLayersWithOverlap() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_2);
+
+        // Add a HAL subscription to a layer there is already another subscriber for.
+        mRouting.addHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        // Verify expected subscriptions are in routing manager.
+        Set<VmsLayer> expectedSubscriptions = new HashSet<>();
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_1);
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_2);
+        VmsSubscriptionState subscriptionState = mRouting.getSubscriptionState();
+        assertEquals(3, subscriptionState.getSequenceNumber());
+        assertEquals(expectedSubscriptions,
+                new HashSet<>(subscriptionState.getLayers()));
+    }
+
+    public void testAddingAndRemovingLayers() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a HAL subscription.
+        mRouting.addHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        // Remove a subscription to a layer.
+        mRouting.removeSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Update the HAL subscription
+        mRouting.removeHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        // Verify there are no subscribers in the routing manager.
+        VmsSubscriptionState subscriptionState = mRouting.getSubscriptionState();
+        assertEquals(4, subscriptionState.getSequenceNumber());
+        assertTrue(subscriptionState.getLayers().isEmpty());
+    }
+
+    public void testAddingBothTypesOfSubscribers() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriberForLayer = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriberForLayer, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a subscription without a layer.
+        MockVmsSubscriber subscriberWithoutLayer = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriberWithoutLayer);
+
+        // Verify 2 subscribers for the layer.
+        assertEquals(2,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1).size());
+
+        // Add the subscriber with layer as also a subscriber without layer
+        mRouting.addSubscription(subscriberForLayer);
+
+        // The number of subscribers for the layer should remain the same as before.
+        assertEquals(2,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1).size());
+    }
+
+    public void testOnlyRelevantSubscribers() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriberForLayer = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriberForLayer, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a subscription without a layer.
+        MockVmsSubscriber subscriberWithoutLayer = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriberWithoutLayer);
+
+        // Verify that only the subscriber without layer is returned.
+        Set<MockVmsSubscriber> expectedListeneres = new HashSet<MockVmsSubscriber>();
+        expectedListeneres.add(subscriberWithoutLayer);
+        assertEquals(expectedListeneres,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITHOUT_SUBSCRIPTION, PUBLISHER_ID_1));
+    }
+
+    public void testAddingSubscribersAndHalLayersAndSubscribersToPublishers() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a HAL subscription.
+        mRouting.addHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_2);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_2, PUBLISHER_ID_2);
+
+        // Verify expected subscriptions are in routing manager.
+        Set<VmsLayer> expectedSubscriptions = new HashSet<>();
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_1);
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_2);
+
+        Set<VmsAssociatedLayer> expectedSubscriptionsToPublishers = new HashSet<>();
+        expectedSubscriptionsToPublishers.add(new VmsAssociatedLayer(LAYER_WITH_SUBSCRIPTION_1,
+                new HashSet(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2))));
+        expectedSubscriptionsToPublishers.add(new VmsAssociatedLayer(LAYER_WITH_SUBSCRIPTION_2,
+                new HashSet(Arrays.asList(PUBLISHER_ID_2))));
+
+        VmsSubscriptionState subscriptionState = mRouting.getSubscriptionState();
+        assertEquals(5, subscriptionState.getSequenceNumber());
+        assertEquals(expectedSubscriptions,
+                new HashSet<>(subscriptionState.getLayers()));
+
+        assertEquals(expectedSubscriptionsToPublishers,
+                subscriptionState.getAssociatedLayers());
+
+        // Verify there is only a single subscriber.
+        assertEquals(1,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1).size());
+    }
+
+    public void testAddingSubscriberToPublishersAndGetListeneresToDifferentPublisher()
+            throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_2);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1);
+
+        Set<IVmsSubscriberClient> subscribers;
+        // Need to route a layer 1 message from publisher 2 so there are no subscribers.
+        subscribers =
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1,
+                        PUBLISHER_ID_2);
+        assertEquals(0, subscribers.size());
+
+        // Need to route a layer 1 message from publisher 1 so there is one subscriber.
+        subscribers =
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1,
+                        PUBLISHER_ID_1);
+        assertEquals(1, subscribers.size());
+        assertTrue(subscribers.contains(subscriber));
+
+        // Verify all the messages for LAYER_WITH_SUBSCRIPTION_2 have subscribers since the
+        // subscription was done without specifying a specific publisher.
+        subscribers =
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_2,
+                        PUBLISHER_ID_UNLISTED);
+        assertEquals(1, subscribers.size());
+        assertTrue(subscribers.contains(subscriber));
+    }
+
+
+    public void testRemovalOfSubscribersToPublishers() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a HAL subscription.
+        mRouting.addHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_2);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_2, PUBLISHER_ID_2);
+        mRouting.removeSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_2, PUBLISHER_ID_2);
+
+        // Verify expected subscriptions are in routing manager.
+        Set<VmsLayer> expectedSubscriptions = new HashSet<>();
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_1);
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_2);
+
+
+        Set<VmsAssociatedLayer> expectedSubscriptionsToPublishers = new HashSet<>();
+        expectedSubscriptionsToPublishers.add(new VmsAssociatedLayer(LAYER_WITH_SUBSCRIPTION_1,
+                new HashSet(Arrays.asList(PUBLISHER_ID_1, PUBLISHER_ID_2))));
+
+        VmsSubscriptionState subscriptionState = mRouting.getSubscriptionState();
+        assertEquals(6, subscriptionState.getSequenceNumber());
+        assertEquals(expectedSubscriptions,
+                new HashSet<>(subscriptionState.getLayers()));
+
+        assertEquals(expectedSubscriptionsToPublishers,
+                subscriptionState.getAssociatedLayers());
+
+        // Verify there is only a single subscriber.
+        assertEquals(1,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1).size());
+    }
+
+    public void testRemovalOfSubscribersToPublishersClearListForPublisher() throws Exception {
+        // Add a subscription to a layer.
+        MockVmsSubscriber subscriber = new MockVmsSubscriber();
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1);
+
+        // Add a HAL subscription.
+        mRouting.addHalSubscription(LAYER_WITH_SUBSCRIPTION_2);
+
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_2);
+        mRouting.addSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_2, PUBLISHER_ID_2);
+        mRouting.removeSubscription(subscriber, LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1);
+
+        // Verify expected subscriptions are in routing manager.
+        Set<VmsLayer> expectedSubscriptions = new HashSet<>();
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_1);
+        expectedSubscriptions.add(LAYER_WITH_SUBSCRIPTION_2);
+
+        Set<VmsAssociatedLayer> expectedSubscriptionsToPublishers = new HashSet<>();
+        expectedSubscriptionsToPublishers.add(new VmsAssociatedLayer(LAYER_WITH_SUBSCRIPTION_1,
+                new HashSet(Arrays.asList(PUBLISHER_ID_2))));
+        expectedSubscriptionsToPublishers.add(new VmsAssociatedLayer(LAYER_WITH_SUBSCRIPTION_2,
+                new HashSet(Arrays.asList(PUBLISHER_ID_2))));
+
+        VmsSubscriptionState subscriptionState = mRouting.getSubscriptionState();
+        assertEquals(6, subscriptionState.getSequenceNumber());
+        assertEquals(expectedSubscriptions,
+                new HashSet<>(subscriptionState.getLayers()));
+
+        assertEquals(expectedSubscriptionsToPublishers,
+                subscriptionState.getAssociatedLayers());
+
+        // Verify there is only a single subscriber.
+        assertEquals(1,
+                mRouting.getSubscribersForLayerFromPublisher(
+                        LAYER_WITH_SUBSCRIPTION_1, PUBLISHER_ID_1).size());
+    }
+
+    class MockVmsSubscriber extends IVmsSubscriberClient.Stub {
+        @Override
+        public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
+        }
+
+        @Override
+        public void onLayersAvailabilityChanged(List<VmsAssociatedLayer> availableLayers) {
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
new file mode 100644
index 0000000..4e98a10
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
@@ -0,0 +1,479 @@
+/*
+ * 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.storagemonitoring;
+
+import android.car.storagemonitoring.UidIoStats;
+import android.car.storagemonitoring.UidIoStats.PerStateMetrics;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.util.SparseArray;
+import com.android.car.test.utils.TemporaryFile;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+
+import java.io.FileWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.time.Instant;
+import java.util.List;
+import junit.framework.TestCase;
+import org.json.JSONObject;
+
+/**
+ * Tests the storage monitoring API in CarService.
+ */
+@MediumTest
+public class CarStorageMonitoringTest extends TestCase {
+    static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
+
+    public void testEMmcWearInformationProvider() throws Exception {
+        try (TemporaryFile lifetimeFile = new TemporaryFile(TAG)) {
+            try (TemporaryFile eolFile = new TemporaryFile(TAG)) {
+                lifetimeFile.write("0x05 0x00");
+                eolFile.write("01");
+
+                EMmcWearInformationProvider wearInfoProvider = new EMmcWearInformationProvider(
+                        lifetimeFile.getFile(), eolFile.getFile());
+
+                WearInformation wearInformation = wearInfoProvider.load();
+
+                assertNotNull(wearInformation);
+                assertEquals(40, wearInformation.lifetimeEstimateA);
+                assertEquals(WearInformation.UNKNOWN_LIFETIME_ESTIMATE,
+                        wearInformation.lifetimeEstimateB);
+
+                assertEquals(WearInformation.PRE_EOL_INFO_NORMAL, wearInformation.preEolInfo);
+            }
+        }
+    }
+
+    public void testUfsWearInformationProvider() throws Exception {
+        try (TemporaryFile lifetimeFile = new TemporaryFile(TAG)) {
+            lifetimeFile.write("ufs version: 1.0\n" +
+                    "Health Descriptor[Byte offset 0x2]: bPreEOLInfo = 0x2\n" +
+                    "Health Descriptor[Byte offset 0x1]: bDescriptionIDN = 0x1\n" +
+                    "Health Descriptor[Byte offset 0x3]: bDeviceLifeTimeEstA = 0x0\n" +
+                    "Health Descriptor[Byte offset 0x5]: VendorPropInfo = somedatahere\n" +
+                    "Health Descriptor[Byte offset 0x4]: bDeviceLifeTimeEstB = 0xA\n");
+
+            UfsWearInformationProvider wearInfoProvider = new UfsWearInformationProvider(
+                lifetimeFile.getFile());
+
+            WearInformation wearInformation = wearInfoProvider.load();
+
+            assertNotNull(wearInformation);
+            assertEquals(90, wearInformation.lifetimeEstimateB);
+            assertEquals(WearInformation.PRE_EOL_INFO_WARNING, wearInformation.preEolInfo);
+            assertEquals(WearInformation.UNKNOWN_LIFETIME_ESTIMATE,
+                    wearInformation.lifetimeEstimateA);
+        }
+    }
+
+    public void testWearEstimateEquality() {
+        WearEstimate wearEstimate1 = new WearEstimate(10, 20);
+        WearEstimate wearEstimate2 = new WearEstimate(10, 20);
+        WearEstimate wearEstimate3 = new WearEstimate(20, 30);
+        assertEquals(wearEstimate1, wearEstimate1);
+        assertEquals(wearEstimate1, wearEstimate2);
+        assertNotSame(wearEstimate1, wearEstimate3);
+    }
+
+    public void testWearEstimateParcel() throws Exception {
+        WearEstimate originalWearEstimate = new WearEstimate(10, 20);
+        Parcel p = Parcel.obtain();
+        originalWearEstimate.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        WearEstimate newWearEstimate = new WearEstimate(p);
+        assertEquals(originalWearEstimate, newWearEstimate);
+        p.recycle();
+    }
+
+    public void testWearEstimateChangeEquality() {
+        WearEstimateChange wearEstimateChange1 = new WearEstimateChange(
+                new WearEstimate(10, 20),
+                new WearEstimate(20, 30),
+                5000L,
+                Instant.now(),
+                false);
+        WearEstimateChange wearEstimateChange2 = new WearEstimateChange(
+            new WearEstimate(10, 20),
+            new WearEstimate(20, 30),
+            5000L,
+            wearEstimateChange1.dateAtChange,
+            false);
+        assertEquals(wearEstimateChange1, wearEstimateChange1);
+        assertEquals(wearEstimateChange1, wearEstimateChange2);
+        WearEstimateChange wearEstimateChange3 = new WearEstimateChange(
+            new WearEstimate(10, 30),
+            new WearEstimate(20, 30),
+            3000L,
+            Instant.now(),
+            true);
+        assertNotSame(wearEstimateChange1, wearEstimateChange3);
+    }
+
+    public void testWearEstimateChangeParcel() throws Exception {
+        WearEstimateChange originalWearEstimateChange = new WearEstimateChange(
+                new WearEstimate(10, 0),
+                new WearEstimate(20, 10),
+                123456789L,
+                Instant.now(),
+                false);
+        Parcel p = Parcel.obtain();
+        originalWearEstimateChange.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        WearEstimateChange newWearEstimateChange = new WearEstimateChange(p);
+        assertEquals(originalWearEstimateChange, newWearEstimateChange);
+        p.recycle();
+    }
+
+    public void testWearEstimateJson() throws Exception {
+        WearEstimate originalWearEstimate = new WearEstimate(20, 0);
+        StringWriter stringWriter = new StringWriter(1024);
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        originalWearEstimate.writeToJson(jsonWriter);
+        StringReader stringReader = new StringReader(stringWriter.toString());
+        JsonReader jsonReader = new JsonReader(stringReader);
+        WearEstimate newWearEstimate = new WearEstimate(jsonReader);
+        assertEquals(originalWearEstimate, newWearEstimate);
+    }
+
+    public void testWearEstimateRecordJson() throws Exception {
+        try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
+            WearEstimateRecord originalWearEstimateRecord = new WearEstimateRecord(new WearEstimate(10, 20),
+                new WearEstimate(10, 30), 5000, Instant.ofEpochMilli(1000));
+            try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(temporaryFile.getFile()))) {
+                originalWearEstimateRecord.writeToJson(jsonWriter);
+            }
+            JSONObject jsonObject = new JSONObject(
+                    new String(Files.readAllBytes(temporaryFile.getPath())));
+            WearEstimateRecord newWearEstimateRecord = new WearEstimateRecord(jsonObject);
+            assertEquals(originalWearEstimateRecord, newWearEstimateRecord);
+        }
+    }
+
+    public void testWearEstimateRecordEquality() throws Exception {
+        WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(WearEstimate.UNKNOWN_ESTIMATE,
+                new WearEstimate(10, 20), 5000, Instant.ofEpochMilli(2000));
+        WearEstimateRecord wearEstimateRecord2 = new WearEstimateRecord(WearEstimate.UNKNOWN_ESTIMATE,
+            new WearEstimate(10, 20), 5000, Instant.ofEpochMilli(2000));
+        WearEstimateRecord wearEstimateRecord3 = new WearEstimateRecord(WearEstimate.UNKNOWN_ESTIMATE,
+            new WearEstimate(10, 40), 5000, Instant.ofEpochMilli(1000));
+
+        assertEquals(wearEstimateRecord1, wearEstimateRecord1);
+        assertEquals(wearEstimateRecord1, wearEstimateRecord2);
+        assertNotSame(wearEstimateRecord1, wearEstimateRecord3);
+    }
+
+    public void testWearHistoryJson() throws Exception {
+        try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
+            WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(
+                WearEstimate.UNKNOWN_ESTIMATE,
+                new WearEstimate(10, 20), 5000, Instant.ofEpochMilli(2000));
+            WearEstimateRecord wearEstimateRecord2 = new WearEstimateRecord(
+                wearEstimateRecord1.getOldWearEstimate(),
+                new WearEstimate(10, 40), 9000, Instant.ofEpochMilli(16000));
+            WearEstimateRecord wearEstimateRecord3 = new WearEstimateRecord(
+                wearEstimateRecord2.getOldWearEstimate(),
+                new WearEstimate(20, 40), 12000, Instant.ofEpochMilli(21000));
+            WearHistory originalWearHistory = WearHistory.fromRecords(wearEstimateRecord1,
+                wearEstimateRecord2, wearEstimateRecord3);
+            try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(temporaryFile.getFile()))) {
+                originalWearHistory.writeToJson(jsonWriter);
+            }
+            JSONObject jsonObject = new JSONObject(
+                new String(Files.readAllBytes(temporaryFile.getPath())));
+            WearHistory newWearHistory = new WearHistory(jsonObject);
+            assertEquals(originalWearHistory, newWearHistory);
+        }
+    }
+
+    public void testWearHistoryEquality() throws Exception {
+        WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(
+            WearEstimate.UNKNOWN_ESTIMATE,
+            new WearEstimate(10, 20), 5000, Instant.ofEpochMilli(2000));
+        WearEstimateRecord wearEstimateRecord2 = new WearEstimateRecord(
+            wearEstimateRecord1.getOldWearEstimate(),
+            new WearEstimate(10, 40), 9000, Instant.ofEpochMilli(16000));
+        WearEstimateRecord wearEstimateRecord3 = new WearEstimateRecord(
+            wearEstimateRecord2.getOldWearEstimate(),
+            new WearEstimate(20, 40), 12000, Instant.ofEpochMilli(21000));
+        WearEstimateRecord wearEstimateRecord4 = new WearEstimateRecord(
+            wearEstimateRecord3.getOldWearEstimate(),
+            new WearEstimate(30, 50), 17000, Instant.ofEpochMilli(34000));
+        WearEstimateRecord wearEstimateRecord5 = new WearEstimateRecord(
+            wearEstimateRecord3.getOldWearEstimate(),
+            new WearEstimate(20, 50), 15000, Instant.ofEpochMilli(34000));
+
+        WearHistory wearHistory1 = WearHistory.fromRecords(wearEstimateRecord1,
+            wearEstimateRecord2, wearEstimateRecord3, wearEstimateRecord4);
+        WearHistory wearHistory2 = WearHistory.fromRecords(wearEstimateRecord4,
+            wearEstimateRecord1, wearEstimateRecord2, wearEstimateRecord3);
+        WearHistory wearHistory3 = WearHistory.fromRecords(wearEstimateRecord1,
+            wearEstimateRecord2, wearEstimateRecord3, wearEstimateRecord5);
+
+        assertEquals(wearHistory1, wearHistory1);
+        assertEquals(wearHistory1, wearHistory2);
+        assertNotSame(wearHistory1, wearHistory3);
+    }
+
+    public void testWearHistoryToChanges() {
+        WearEstimateRecord wearEstimateRecord1 = new WearEstimateRecord(
+            WearEstimate.UNKNOWN_ESTIMATE,
+            new WearEstimate(10, 20), 3600000, Instant.ofEpochMilli(2000));
+        WearEstimateRecord wearEstimateRecord2 = new WearEstimateRecord(
+            wearEstimateRecord1.getOldWearEstimate(),
+            new WearEstimate(10, 40), 172000000, Instant.ofEpochMilli(16000));
+        WearEstimateRecord wearEstimateRecord3 = new WearEstimateRecord(
+            wearEstimateRecord2.getOldWearEstimate(),
+            new WearEstimate(20, 40), 172000001, Instant.ofEpochMilli(21000));
+
+        WearHistory wearHistory = WearHistory.fromRecords(wearEstimateRecord1,
+            wearEstimateRecord2, wearEstimateRecord3);
+
+        List<WearEstimateChange> wearEstimateChanges = wearHistory.toWearEstimateChanges(1);
+
+        assertEquals(3, wearEstimateChanges.size());
+        WearEstimateChange unknownToOne = wearEstimateChanges.get(0);
+        WearEstimateChange oneToTwo = wearEstimateChanges.get(1);
+        WearEstimateChange twoToThree = wearEstimateChanges.get(2);
+
+        assertEquals(unknownToOne.oldEstimate, wearEstimateRecord1.getOldWearEstimate());
+        assertEquals(unknownToOne.newEstimate, wearEstimateRecord1.getNewWearEstimate());
+        assertEquals(unknownToOne.uptimeAtChange, wearEstimateRecord1.getTotalCarServiceUptime());
+        assertEquals(unknownToOne.dateAtChange, wearEstimateRecord1.getUnixTimestamp());
+        assertTrue(unknownToOne.isAcceptableDegradation);
+
+        assertEquals(oneToTwo.oldEstimate, wearEstimateRecord2.getOldWearEstimate());
+        assertEquals(oneToTwo.newEstimate, wearEstimateRecord2.getNewWearEstimate());
+        assertEquals(oneToTwo.uptimeAtChange, wearEstimateRecord2.getTotalCarServiceUptime());
+        assertEquals(oneToTwo.dateAtChange, wearEstimateRecord2.getUnixTimestamp());
+        assertTrue(oneToTwo.isAcceptableDegradation);
+
+        assertEquals(twoToThree.oldEstimate, wearEstimateRecord3.getOldWearEstimate());
+        assertEquals(twoToThree.newEstimate, wearEstimateRecord3.getNewWearEstimate());
+        assertEquals(twoToThree.uptimeAtChange, wearEstimateRecord3.getTotalCarServiceUptime());
+        assertEquals(twoToThree.dateAtChange, wearEstimateRecord3.getUnixTimestamp());
+        assertFalse(twoToThree.isAcceptableDegradation);
+    }
+
+    public void testUidIoStatEntry() throws Exception {
+        try (TemporaryFile statsFile = new TemporaryFile(TAG)) {
+            statsFile.write("0 256797495 181736102 362132480 947167232 0 0 0 0 250 0\n"
+                + "1006 489007 196802 0 20480 51474 2048 1024 2048 1 1\n");
+
+            ProcfsUidIoStatsProvider statsProvider = new ProcfsUidIoStatsProvider(
+                    statsFile.getPath());
+
+            SparseArray<UidIoStatsRecord> entries = statsProvider.load();
+
+            assertNotNull(entries);
+            assertEquals(2, entries.size());
+
+            UidIoStats entry = new UidIoStats(entries.get(0), 1234);
+            assertNotNull(entry);
+            assertEquals(0, entry.uid);
+            assertEquals(1234, entry.runtimeMillis);
+            assertEquals(256797495, entry.foreground.bytesRead);
+            assertEquals(181736102, entry.foreground.bytesWritten);
+            assertEquals(362132480, entry.foreground.bytesReadFromStorage);
+            assertEquals(947167232, entry.foreground.bytesWrittenToStorage);
+            assertEquals(250, entry.foreground.fsyncCalls);
+            assertEquals(0, entry.background.bytesRead);
+            assertEquals(0, entry.background.bytesWritten);
+            assertEquals(0, entry.background.bytesReadFromStorage);
+            assertEquals(0, entry.background.bytesWrittenToStorage);
+            assertEquals(0, entry.background.fsyncCalls);
+
+            entry = new UidIoStats(entries.get(1006), 4321);
+            assertNotNull(entry);
+            assertEquals(1006, entry.uid);
+            assertEquals(4321, entry.runtimeMillis);
+            assertEquals(489007, entry.foreground.bytesRead);
+            assertEquals(196802, entry.foreground.bytesWritten);
+            assertEquals(0, entry.foreground.bytesReadFromStorage);
+            assertEquals(20480, entry.foreground.bytesWrittenToStorage);
+            assertEquals(1, entry.foreground.fsyncCalls);
+            assertEquals(51474, entry.background.bytesRead);
+            assertEquals(2048, entry.background.bytesWritten);
+            assertEquals(1024, entry.background.bytesReadFromStorage);
+            assertEquals(2048, entry.background.bytesWrittenToStorage);
+            assertEquals(1, entry.background.fsyncCalls);
+        }
+    }
+
+    public void testUidIoStatEntryMissingFields() throws Exception {
+        try (TemporaryFile statsFile = new TemporaryFile(TAG)) {
+            statsFile.write("0 256797495 181736102 362132480 947167232 0 0 0 0 250 0\n"
+                + "1 2 3 4 5 6 7 8 9\n");
+
+            ProcfsUidIoStatsProvider statsProvider = new ProcfsUidIoStatsProvider(
+                statsFile.getPath());
+
+            SparseArray<UidIoStatsRecord> entries = statsProvider.load();
+
+            assertNull(entries);
+        }
+    }
+
+    public void testUidIoStatEntryNonNumericFields() throws Exception {
+        try (TemporaryFile statsFile = new TemporaryFile(TAG)) {
+            statsFile.write("0 256797495 181736102 362132480 947167232 0 0 0 0 250 0\n"
+                + "notanumber 489007 196802 0 20480 51474 2048 1024 2048 1 1\n");
+
+            ProcfsUidIoStatsProvider statsProvider = new ProcfsUidIoStatsProvider(
+                statsFile.getPath());
+
+            SparseArray<UidIoStatsRecord> entries = statsProvider.load();
+
+            assertNull(entries);
+        }
+    }
+
+    public void testUidIoStatEntryEquality() throws Exception {
+        UidIoStats statEntry1 = new UidIoStats(10, 1234,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(100, 200, 300, 400, 500));
+        UidIoStats statEntry2 = new UidIoStats(10, 1234,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(100, 200, 300, 400, 500));
+        UidIoStats statEntry3 = new UidIoStats(30, 4567,
+            new PerStateMetrics(1, 20, 30, 42, 50),
+            new PerStateMetrics(10, 200, 300, 420, 500));
+        UidIoStats statEntry4 = new UidIoStats(11, 6541,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(100, 200, 300, 400, 500));
+        UidIoStats statEntry5 = new UidIoStats(10, 1234,
+            new PerStateMetrics(10, 20, 30, 40, 0),
+            new PerStateMetrics(100, 200, 300, 400, 500));
+
+        assertEquals(statEntry1, statEntry1);
+        assertEquals(statEntry1, statEntry2);
+        assertNotSame(statEntry1, statEntry3);
+        assertNotSame(statEntry1, statEntry4);
+        assertNotSame(statEntry1, statEntry5);
+    }
+
+    public void testUidIoStatEntryParcel() throws Exception {
+        UidIoStats statEntry = new UidIoStats(10, 5000,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(100, 200, 300, 400, 500));
+        Parcel p = Parcel.obtain();
+        statEntry.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        UidIoStats other = new UidIoStats(p);
+        assertEquals(other, statEntry);
+    }
+
+    public void testUidIoStatEntryJson() throws Exception {
+        try (TemporaryFile temporaryFile = new TemporaryFile(TAG)) {
+            UidIoStats statEntry = new UidIoStats(10, 1200,
+                new PerStateMetrics(10, 20, 30, 40, 50),
+                new PerStateMetrics(100, 200, 300, 400, 500));
+            try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(temporaryFile.getFile()))) {
+                statEntry.writeToJson(jsonWriter);
+            }
+            JSONObject jsonObject = new JSONObject(
+                new String(Files.readAllBytes(temporaryFile.getPath())));
+            UidIoStats other = new UidIoStats(jsonObject);
+            assertEquals(statEntry, other);
+        }
+    }
+
+
+    public void testUidIoStatEntryDelta() throws Exception {
+        UidIoStats statEntry1 = new UidIoStats(10, 1000,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(60, 70, 80, 90, 100));
+
+        UidIoStats statEntry2 = new UidIoStats(10,2000,
+            new PerStateMetrics(110, 120, 130, 140, 150),
+            new PerStateMetrics(260, 370, 480, 500, 110));
+
+        UidIoStats statEntry3 = new UidIoStats(30, 3000,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(100, 200, 300, 400, 500));
+
+
+        UidIoStats delta21 = statEntry2.delta(statEntry1);
+        assertNotNull(delta21);
+        assertEquals(statEntry1.uid, delta21.uid);
+
+        assertEquals(1000, delta21.runtimeMillis);
+        assertEquals(100, delta21.foreground.bytesRead);
+        assertEquals(100, delta21.foreground.bytesWritten);
+        assertEquals(100, delta21.foreground.bytesReadFromStorage);
+        assertEquals(100, delta21.foreground.bytesWrittenToStorage);
+        assertEquals(100, delta21.foreground.fsyncCalls);
+
+        assertEquals(200, delta21.background.bytesRead);
+        assertEquals(300, delta21.background.bytesWritten);
+        assertEquals(400, delta21.background.bytesReadFromStorage);
+        assertEquals(410, delta21.background.bytesWrittenToStorage);
+        assertEquals(10, delta21.background.fsyncCalls);
+
+        try {
+            UidIoStats delta31 = statEntry3.delta(statEntry1);
+            fail("delta only allowed on stats for matching user ID");
+        } catch (IllegalArgumentException e) {
+            // test passed
+        }
+    }
+
+    public void testUidIoStatsRecordDelta() throws Exception {
+        UidIoStats statEntry = new UidIoStats(10, 1000,
+            new PerStateMetrics(10, 20, 30, 40, 50),
+            new PerStateMetrics(60, 70, 80, 90, 100));
+
+        UidIoStatsRecord statRecord = new UidIoStatsRecord(10,
+            20, 20, 30, 50, 70,
+            80, 70, 80, 100, 110);
+
+        UidIoStatsRecord delta = statRecord.delta(statEntry);
+
+        assertNotNull(delta);
+        assertEquals(statRecord.uid, delta.uid);
+
+        assertEquals(10, delta.foreground_rchar);
+        assertEquals(0, delta.foreground_wchar);
+        assertEquals(0, delta.foreground_read_bytes);
+        assertEquals(10, delta.foreground_write_bytes);
+        assertEquals(20, delta.foreground_fsync);
+
+        assertEquals(20, delta.background_rchar);
+        assertEquals(0, delta.background_wchar);
+        assertEquals(0, delta.background_read_bytes);
+        assertEquals(10, delta.background_write_bytes);
+        assertEquals(10, delta.background_fsync);
+
+        statRecord = new UidIoStatsRecord(30,
+            20, 20, 30, 50, 70,
+            80, 70, 80, 100, 110);
+
+        try {
+            statRecord.delta(statEntry);
+            fail("delta only allowed on records for matching user ID");
+        } catch (IllegalArgumentException e) {
+            // test passed
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java
new file mode 100644
index 0000000..c694f50
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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.storagemonitoring;
+
+import android.car.storagemonitoring.UidIoStats;
+import android.car.storagemonitoring.UidIoStatsRecord;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.SparseArray;
+import com.android.car.procfsinspector.ProcessInfo;
+import com.android.car.systeminterface.SystemStateInterface;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import junit.framework.TestCase;
+
+/**
+ * Tests IoStatsTracker functionality.
+ */
+@MediumTest
+public class IoStatsTrackerTest extends TestCase {
+    private static final int SAMPLE_WINDOW_MS = 1000;
+    private static final List<UidIoStats> EMPTY = Collections.emptyList();
+    private static final String TAG = IoStatsTrackerTest.class.getSimpleName();
+
+    public void testNewUsersAppear() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        assertEquals(0, ioStatsTracker.getCurrentSample().size());
+        assertEquals(0, ioStatsTracker.getTotal().size());
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        UserActivity user1 = new UserActivity(1);
+        user1.foreground_rchar = 30;
+        user1.background_wchar = 50;
+
+        UidIoStatsRecord process0 = user0.updateSystemState(mockSystemStateInterface);
+        UidIoStatsRecord process1 = user1.updateSystemState(mockSystemStateInterface);
+
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(2, ioStatsTracker.getCurrentSample().size());
+        assertEquals(2, ioStatsTracker.getTotal().size());
+
+        assertTrue(ioStatsTracker.getCurrentSample().get(0).representsSameMetrics(process0));
+        assertTrue(ioStatsTracker.getCurrentSample().get(1).representsSameMetrics(process1));
+
+        assertTrue(ioStatsTracker.getTotal().get(0).representsSameMetrics(process0));
+        assertTrue(ioStatsTracker.getTotal().get(1).representsSameMetrics(process1));
+    }
+
+    public void testUserMetricsChange() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        user0.foreground_rchar = 60;
+        user0.foreground_wchar = 10;
+        UidIoStatsRecord process0 = user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(1, ioStatsTracker.getCurrentSample().size());
+        assertEquals(1, ioStatsTracker.getTotal().size());
+
+        UidIoStats sample0 = ioStatsTracker.getCurrentSample().get(0);
+        UidIoStats total0 = ioStatsTracker.getTotal().get(0);
+
+        assertNotNull(sample0);
+        assertNotNull(total0);
+
+        assertTrue(total0.representsSameMetrics(process0));
+
+        assertEquals(10, sample0.foreground.bytesRead);
+        assertEquals(10, sample0.foreground.bytesWritten);
+        assertEquals(0, sample0.background.bytesWritten);
+    }
+
+    public void testUpdateNoIoProcessActive() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        user0.spawnProcess();
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(1, ioStatsTracker.getCurrentSample().size());
+        assertEquals(1, ioStatsTracker.getTotal().size());
+
+        UidIoStats sample0 = ioStatsTracker.getCurrentSample().get(0);
+        UidIoStats total0 = ioStatsTracker.getTotal().get(0);
+
+        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
+        assertEquals(2 * SAMPLE_WINDOW_MS, total0.runtimeMillis);
+
+        assertEquals(0, sample0.foreground.bytesRead);
+        assertEquals(0, sample0.background.bytesWritten);
+    }
+
+    public void testUpdateNoIoProcessInactive() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        user0.killProcess();
+        UidIoStatsRecord record0 = user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(0, ioStatsTracker.getCurrentSample().size());
+        assertEquals(1, ioStatsTracker.getTotal().size());
+
+        UidIoStats total0 = ioStatsTracker.getTotal().get(0);
+        assertEquals(SAMPLE_WINDOW_MS, total0.runtimeMillis);
+        assertTrue(total0.representsSameMetrics(record0));
+    }
+
+    public void testUpdateIoHappens() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        user0.foreground_rchar = 60;
+        UidIoStatsRecord record0 = user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(1, ioStatsTracker.getCurrentSample().size());
+        assertEquals(1, ioStatsTracker.getTotal().size());
+
+        UidIoStats sample0 = ioStatsTracker.getCurrentSample().get(0);
+        UidIoStats total0 = ioStatsTracker.getTotal().get(0);
+
+        assertTrue(total0.representsSameMetrics(record0));
+        assertEquals(2 * SAMPLE_WINDOW_MS, total0.runtimeMillis);
+        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
+        assertEquals(10, sample0.foreground.bytesRead);
+        assertEquals(0, sample0.background.bytesWritten);
+    }
+
+    public void testUpdateGoAwayComeBackProcess() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(0, ioStatsTracker.getCurrentSample().size());
+        assertEquals(1, ioStatsTracker.getTotal().size());
+
+        user0.spawnProcess();
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(1, ioStatsTracker.getCurrentSample().size());
+        UidIoStats sample0 = ioStatsTracker.getCurrentSample().get(0);
+        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
+    }
+
+    public void testUpdateGoAwayComeBackIo() throws Exception {
+        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
+        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
+            SAMPLE_WINDOW_MS, mockSystemStateInterface);
+
+        UserActivity user0 = new UserActivity(0);
+        user0.foreground_rchar = 50;
+        user0.background_wchar = 10;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(0, ioStatsTracker.getCurrentSample().size());
+        assertEquals(1, ioStatsTracker.getTotal().size());
+
+        user0.foreground_fsync = 1;
+
+        user0.updateSystemState(mockSystemStateInterface);
+        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
+
+        assertEquals(1, ioStatsTracker.getCurrentSample().size());
+
+        UidIoStats sample0 = ioStatsTracker.getCurrentSample().get(0);
+        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
+        assertEquals(1, sample0.foreground.fsyncCalls);
+    }
+
+    private static final class UserActivity {
+        private final int mUid;
+        private boolean mHasProcess;
+
+        private long foreground_rchar;
+        private long foreground_wchar;
+        private long foreground_read_bytes;
+        private long foreground_write_bytes;
+        private long foreground_fsync;
+
+        private long background_rchar;
+        private long background_wchar;
+        private long background_read_bytes;
+        private long background_write_bytes;
+        private long background_fsync;
+
+        UserActivity(int uid) {
+            mUid = uid;
+        }
+
+        void spawnProcess() {
+            mHasProcess = true;
+        }
+        void killProcess() {
+            mHasProcess = false;
+        }
+
+        UidIoStatsRecord updateSystemState(MockSystemStateInterface systemState) {
+            UidIoStatsRecord uidIoStatsRecord = new UidIoStatsRecord(mUid,
+                foreground_rchar,
+                foreground_wchar,
+                foreground_read_bytes,
+                foreground_write_bytes,
+                foreground_fsync,
+                background_rchar,
+                background_wchar,
+                background_read_bytes,
+                background_write_bytes,
+                background_fsync);
+
+            systemState.addIoRecord(uidIoStatsRecord);
+            if (mHasProcess) {
+                systemState.addProcess(new ProcessInfo(1, mUid));
+            } else {
+                systemState.removeUserProcesses(mUid);
+            }
+
+            return uidIoStatsRecord;
+        }
+    }
+
+    private final class MockSystemStateInterface implements SystemStateInterface {
+        private final List<ProcessInfo> mProcesses = new ArrayList<>();
+        private final SparseArray<UidIoStatsRecord> mIoRecords = new SparseArray<>();
+
+        @Override
+        public void shutdown() {
+        }
+
+        @Override
+        public void enterDeepSleep(int wakeupTimeSec) {
+        }
+
+        @Override
+        public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
+        }
+
+        @Override
+        public boolean isWakeupCausedByTimer() {
+            return false;
+        }
+
+        @Override
+        public boolean isSystemSupportingDeepSleep() {
+            return false;
+        }
+
+        @Override
+        public synchronized List<ProcessInfo> getRunningProcesses() {
+            return mProcesses;
+        }
+
+        synchronized void addProcess(ProcessInfo processInfo) {
+            mProcesses.add(processInfo);
+        }
+
+        synchronized void removeUserProcesses(int uid) {
+            mProcesses.removeAll(
+                    mProcesses.stream().filter(pi -> pi.uid == uid).collect(Collectors.toList()));
+        }
+
+        synchronized void addIoRecord(UidIoStatsRecord record) {
+            mIoRecords.put(record.uid, record);
+        }
+
+        synchronized void clear() {
+            mProcesses.clear();
+            mIoRecords.clear();
+        }
+    }
+}
diff --git a/libvehiclemonitor/java/Android.mk b/tests/common_utils/Android.mk
similarity index 78%
copy from libvehiclemonitor/java/Android.mk
copy to tests/common_utils/Android.mk
index 1ced365..19302c6 100644
--- a/libvehiclemonitor/java/Android.mk
+++ b/tests/common_utils/Android.mk
@@ -1,4 +1,4 @@
-# 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.
@@ -12,15 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-#
 
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE := libvehiclemonitor-java
+LOCAL_MODULE := com.android.car.test.utils
+
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/common_utils/src/com/android/car/test/utils/TemporaryDirectory.java b/tests/common_utils/src/com/android/car/test/utils/TemporaryDirectory.java
new file mode 100644
index 0000000..fa5727d
--- /dev/null
+++ b/tests/common_utils/src/com/android/car/test/utils/TemporaryDirectory.java
@@ -0,0 +1,80 @@
+/*
+ * 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.utils;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/**
+ * A utility class that creates a temporary directory.
+ * The directory, and any files contained within, are automatically deleted when calling close().
+ *
+ * Example usage:
+ *
+ * try (TemporaryDirectory tf = new TemporaryDirectory("myTest")) {
+ *     ...
+ * } // directory gets deleted here
+ */
+public class TemporaryDirectory implements AutoCloseable {
+    private Path mDirectory;
+
+    private static final class DeletingVisitor extends SimpleFileVisitor<Path> {
+        FileVisitResult consume(Path path) throws IOException {
+            Files.delete(path);
+            return FileVisitResult.CONTINUE;
+        }
+
+        @Override
+        public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+                throws IOException {
+            return consume(path);
+        }
+
+        @Override
+        public FileVisitResult postVisitDirectory(Path path, IOException e)
+                throws IOException {
+            return consume(path);
+        }
+    }
+
+    private static final SimpleFileVisitor<Path> DELETE = new DeletingVisitor();
+
+    public TemporaryDirectory(@Nullable String prefix) throws IOException {
+        if (prefix == null) {
+            prefix = TemporaryDirectory.class.getSimpleName();
+        }
+        prefix += String.valueOf(SystemClock.elapsedRealtimeNanos());
+
+        mDirectory = Files.createTempDirectory(prefix);
+    }
+
+    @Override
+    public void close() throws Exception {
+        Files.walkFileTree(mDirectory, DELETE);
+    }
+
+    public File getDirectory() {
+        return mDirectory.toFile();
+    }
+}
diff --git a/tests/common_utils/src/com/android/car/test/utils/TemporaryFile.java b/tests/common_utils/src/com/android/car/test/utils/TemporaryFile.java
new file mode 100644
index 0000000..518f4a9
--- /dev/null
+++ b/tests/common_utils/src/com/android/car/test/utils/TemporaryFile.java
@@ -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.
+ */
+
+package com.android.car.test.utils;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * A utility class that creates a temporary file.
+ * The file is automatically deleted when calling close().
+ *
+ * Example usage:
+ *
+ * try (TemporaryFile tf = new TemporaryFile("myTest")) {
+ *     ...
+ * } // file gets deleted here
+ */
+public final class TemporaryFile implements AutoCloseable {
+    private File mFile;
+
+    public TemporaryFile(@Nullable String prefix) throws IOException {
+        if (prefix == null) {
+            prefix = TemporaryFile.class.getSimpleName();
+        }
+        mFile = File.createTempFile(prefix, String.valueOf(SystemClock.elapsedRealtimeNanos()));
+    }
+
+    @Override
+    public void close() throws Exception {
+        Files.delete(mFile.toPath());
+    }
+
+    public void write(String s) throws IOException {
+        BufferedWriter writer = new BufferedWriter(new FileWriter(mFile));
+        writer.write(s);
+        writer.close();
+    }
+
+    public File getFile() {
+        return mFile;
+    }
+
+    public Path getPath() { return mFile.toPath(); }
+}
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
index 6675713..196a6d4 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import android.annotation.Nullable;
@@ -47,6 +48,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -259,6 +261,27 @@
 
         final int EXPECTED_INVOCATIONS = 1000;  // How many time get/set will be called.
         final int EXPECTED_DURATION_MS = 2500;
+        // This is a stress test and it can be flaky because it shares resources with all currently
+        // running process. Let's have this number of attempt before giving up.
+        final int ATTEMPTS = 3;
+
+        for (int curAttempt = 0; curAttempt < ATTEMPTS; curAttempt++) {
+            long missingInvocations = stressTestHvacProperties(mgr, cfg,
+                    EXPECTED_INVOCATIONS, EXPECTED_DURATION_MS);
+            if (missingInvocations == 0) return;  // All done.
+
+            Log.w(TAG, "Failed to invoke get/set " + EXPECTED_INVOCATIONS
+                            + " within " + EXPECTED_DURATION_MS + "ms"
+                            + ", actually invoked: "
+                            + (EXPECTED_INVOCATIONS - missingInvocations));
+        }
+        fail("Failed to invoke get/set " + EXPECTED_INVOCATIONS + " within "
+                + EXPECTED_DURATION_MS + "ms. Number of attempts: " + ATTEMPTS
+                + ". See logs for details.");
+    }
+
+    private long stressTestHvacProperties(CarHvacManager mgr, CarPropertyConfig<Float> cfg,
+            int EXPECTED_INVOCATIONS, int EXPECTED_DURATION_MS) throws InterruptedException {
         CountDownLatch counter = new CountDownLatch(EXPECTED_INVOCATIONS);
 
         List<Thread> threads = new ArrayList<>(Lists.newArrayList(
@@ -271,15 +294,12 @@
 
         counter.await(EXPECTED_DURATION_MS, TimeUnit.MILLISECONDS);
         long missingInvocations = counter.getCount();
-        assertTrue("Failed to invoke get/set " + EXPECTED_INVOCATIONS
-                + " within " + EXPECTED_DURATION_MS + "ms"
-                        + ", actually invoked: " + (EXPECTED_INVOCATIONS - missingInvocations),
-                missingInvocations == 0);
 
         for (Thread t : threads) {
             t.join(10000);  // Let thread join to not interfere with other test.
             assertFalse(t.isAlive());
         }
+        return missingInvocations;
     }
 
     private void invokeSetAndGetForHvacFloat(CarHvacManager mgr,
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
index 5221907..fb97e73 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
@@ -81,14 +81,12 @@
         return readVhalProperty(vehicle, request, f);
     }
 
-    @Nullable
     static IVehicle getVehicle() throws RemoteException {
         IVehicle service;
         try {
-            service = android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
+            service = IVehicle.getService();
         } catch (NoSuchElementException ex) {
-            Log.d(TAG, "Couldn't connect to vehicle@2.1, connecting to vehicle@2.0...");
-            service =  IVehicle.getService();
+            throw new RuntimeException("Couldn't connect to vehicle@2.0", ex);
         }
         Log.d(TAG, "Connected to IVehicle service: " + service);
         return service;
diff --git a/tools/emulator/diagnostic_injector.py b/tools/emulator/diagnostic_injector.py
index 7678576..1082e5b 100755
--- a/tools/emulator/diagnostic_injector.py
+++ b/tools/emulator/diagnostic_injector.py
@@ -25,7 +25,7 @@
 import sys
 import time
 
-import vhal_consts_2_1 as c
+import vhal_consts_2_0 as c
 
 # vhal_emulator depends on a custom Python package that requires installation
 # give user guidance should the import fail
diff --git a/tools/emulator/driving_info_generator.py b/tools/emulator/driving_info_generator.py
new file mode 100644
index 0000000..6d30dbd
--- /dev/null
+++ b/tools/emulator/driving_info_generator.py
@@ -0,0 +1,201 @@
+# 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.
+#
+
+import datetime
+import time
+
+from xml.dom import minidom
+
+import vhal_consts_2_0 as c
+
+# interval of generating driving information
+SAMPLE_INTERVAL_SECONDS = 0.5
+
+RPM_LOW = 1000
+RPM_HIGH = 3000
+
+REVERSE_DURATION_SECONDS = 10
+PARK_DURATION_SECONDS = 10
+
+# roughly 5 miles/hour
+REVERSE_SPEED_METERS_PER_SECOND = 2.3
+
+UTC_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
+
+
+def speed2Gear(speed):
+    """
+        Get the current gear based on speed of vehicle. The conversion may not be strictly real but
+        are close enough to a normal vehicle. Assume the vehicle is moving forward.
+    """
+    if speed < 4.4:
+        # 0 - 10 mph
+        return c.VEHICLEGEAR_GEAR_1
+    elif speed < 11.2:
+        # 10 - 25 mph
+        return c.VEHICLEGEAR_GEAR_2
+    elif speed < 20.1:
+        # 25 - 45 mph
+        return c.VEHICLEGEAR_GEAR_3
+    elif speed < 26.8:
+        # 45 - 60 mph
+        return c.VEHICLEGEAR_GEAR_4
+    else:
+        # > 60 mph
+        return c.VEHICLEGEAR_GEAR_5
+
+class GpxFrame(object):
+    """
+        A class representing a track point from GPX file
+    """
+    def __init__(self, trkptDom):
+        timeElements = trkptDom.getElementsByTagName('time')
+        if timeElements:
+            # time value in GPX is in UTC format: YYYY-MM-DDTHH:MM:SS, need to parse it
+            self.datetime = datetime.datetime.strptime(timeElements[0].firstChild.nodeValue,
+                                                          UTC_TIME_FORMAT)
+        speedElements = trkptDom.getElementsByTagName('speed')
+        if speedElements:
+            self.speedInMps = float(speedElements[0].firstChild.nodeValue)
+
+class DrivingInfoGenerator(object):
+    """
+        A class that generates driving information like speed, odometer, rpm, etc. It is taking a
+        GPX file which describes a real route that consists of a sequence of location data. And then
+        derive driving information from those location data.
+
+        Assume it is a car with automatic transmission, so that current gear does not
+        necessarily match selected gear.
+    """
+
+    def __init__(self, gpxFile):
+        self.gpxDom = minidom.parse(gpxFile)
+        # Speed of vehicle (meter / second)
+        self.speedInMps = 0
+        # Fixed RPM with average value during driving
+        self.rpm = RPM_LOW
+        # Odometer (kilometer)
+        self.odometerInKm = 0
+        # Gear selection
+        self.selectedGear = c.VEHICLEGEAR_GEAR_PARK
+        # Current gear
+        self.currentGear = c.VEHICLEGEAR_GEAR_PARK
+        # Timestamp while driving on route defined in GPX file
+        self.datetime = 0
+
+    def _generateFrame(self, listener):
+        """
+            Handle newly generated vehicle property with listener
+        """
+        listener.handle(c.VEHICLEPROPERTY_PERF_VEHICLE_SPEED, 0, self.speedInMps, "PERF_VEHICLE_SPEED")
+        listener.handle(c.VEHICLEPROPERTY_ENGINE_RPM, 0, self.rpm, "ENGINE_RPM")
+        listener.handle(c.VEHICLEPROPERTY_PERF_ODOMETER, 0, self.odometerInKm, "PERF_ODOMETER")
+        listener.handle(c.VEHICLEPROPERTY_CURRENT_GEAR, 0, self.currentGear, "CURRENT_GEAR")
+
+    def _generateFromGpxFrame(self, gpxFrame, listener):
+        """
+            Generate a sequence of vehicle property frames from current track point to the next one.
+            The frequency of frames are pre-defined.
+
+            Some assumptions here:
+                - Two track points are very close to each other (e.g. 1 second driving distance)
+                - It is a straight line between two track point
+                - Speed is changing linearly between two track point
+
+            Given the info:
+                timestamp1 : speed1
+                timestamp2 : speed2
+
+            Vehicle properties in each frame are derived like this:
+                - Speed is calculated based on linear model
+                - Odometer is calculated based on speed and time
+                - RPM will be set to a low value if not accelerating, otherwise set to a high value
+                - Current gear will be set according to speed
+        """
+
+        duration = (gpxFrame.datetime - self.datetime).total_seconds()
+        speedIncrement = (gpxFrame.speedInMps - self.speedInMps) / duration * SAMPLE_INTERVAL_SECONDS
+        self.rpm = RPM_HIGH if speedIncrement > 0 else RPM_LOW
+
+        timeElapsed = 0
+        while timeElapsed < duration:
+            self._generateFrame(listener)
+            if timeElapsed + SAMPLE_INTERVAL_SECONDS < duration:
+                self.odometerInKm += (self.speedInMps + speedIncrement / 2.0) * SAMPLE_INTERVAL_SECONDS / 1000
+                self.speedInMps += speedIncrement
+                time.sleep(SAMPLE_INTERVAL_SECONDS)
+            else:
+                timeLeft = duration - timeElapsed
+                self.odometerInKm += (self.speedInMps + gpxFrame.speedInMps) / 2.0 * timeLeft / 1000
+                self.speedInMps = gpxFrame.speedInMps
+                time.sleep(timeLeft)
+
+            self.currentGear = speed2Gear(self.speedInMps)
+            timeElapsed += SAMPLE_INTERVAL_SECONDS
+
+        self.datetime = gpxFrame.datetime
+
+    def _generateInReverseMode(self, duration, listener):
+        print "Vehicle is reversing"
+        listener.handle(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_REVERSE,
+                        "GEAR_SELECTION")
+        listener.handle(c.VEHICLEPROPERTY_CURRENT_GEAR, 0, c.VEHICLEGEAR_GEAR_REVERSE,
+                        "CURRENT_GEAR")
+        self.rpm = RPM_LOW
+        self.speedInMps = REVERSE_SPEED_METERS_PER_SECOND
+        curTime = 0
+        while curTime < duration:
+            self._generateFrame(listener)
+            self.odometerInKm += self.speedInMps * SAMPLE_INTERVAL_SECONDS / 1000
+            curTime += SAMPLE_INTERVAL_SECONDS
+            time.sleep(SAMPLE_INTERVAL_SECONDS)
+        # After reverse is done, set speed to 0
+        self.speedInMps = .0
+
+    def _generateInParkMode(self, duration, listener):
+        print "Vehicle is parked"
+        listener.handle(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_PARK,
+                        "GEAR_SELECTION")
+        listener.handle(c.VEHICLEPROPERTY_CURRENT_GEAR, 0, c.VEHICLEGEAR_GEAR_PARK,
+                        "CURRENT_GEAR")
+        # Assume in park mode, engine is still on
+        self.rpm = RPM_LOW
+        self.speedInMps = .0
+        curTime = 0
+        while curTime < duration:
+            self._generateFrame(listener)
+            curTime += SAMPLE_INTERVAL_SECONDS
+            time.sleep(SAMPLE_INTERVAL_SECONDS)
+
+    def generate(self, listener):
+        # First, car is parked (probably in garage)
+        self._generateInParkMode(PARK_DURATION_SECONDS, listener)
+        # Second, car will reverse (out of garage)
+        self._generateInReverseMode(REVERSE_DURATION_SECONDS, listener)
+
+        trk = self.gpxDom.getElementsByTagName('trk')[0]
+        trkseg = trk.getElementsByTagName('trkseg')[0]
+        trkpts = trkseg.getElementsByTagName('trkpt')
+
+        print "Vehicle start moving forward"
+        listener.handle(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_DRIVE,
+                        "GEAR_SELECTION")
+
+        firstGpxFrame = GpxFrame(trkpts[0])
+        self.speedInMps = firstGpxFrame.speedInMps
+        self.datetime = firstGpxFrame.datetime
+
+        for i in xrange(1, len(trkpts)):
+            self._generateFromGpxFrame(GpxFrame(trkpts[i]), listener)
diff --git a/tools/emulator/gui.py b/tools/emulator/gui.py
index 594526a..3a0161d 100755
--- a/tools/emulator/gui.py
+++ b/tools/emulator/gui.py
@@ -24,7 +24,7 @@
 
 import VehicleHalProto_pb2
 from vhal_emulator import Vhal
-import vhal_consts_2_1 as c
+import vhal_consts_2_0 as c
 
 
 # Define a simple thread that receives messages from a vhal object (v) and prints them
diff --git a/tools/emulator/user_action_generator.py b/tools/emulator/user_action_generator.py
new file mode 100644
index 0000000..3d08e17
--- /dev/null
+++ b/tools/emulator/user_action_generator.py
@@ -0,0 +1,219 @@
+# 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.
+#
+
+import time
+
+import vhal_consts_2_0 as c
+
+# A list of user actions as a subset of VHAL properties to simulate
+userActions = [
+    c.VEHICLEPROPERTY_HVAC_POWER_ON,
+    c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET,
+    c.VEHICLEPROPERTY_HVAC_FAN_SPEED,
+    c.VEHICLEPROPERTY_HVAC_FAN_DIRECTION,
+    c.VEHICLEPROPERTY_HVAC_AUTO_ON,
+    c.VEHICLEPROPERTY_HVAC_RECIRC_ON,
+    c.VEHICLEPROPERTY_HVAC_AC_ON,
+    c.VEHICLEPROPERTY_HVAC_DEFROSTER
+]
+
+propDesc = {
+    c.VEHICLEPROPERTY_HVAC_POWER_ON: 'HVAC_POWER_ON',
+    c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET: 'HVAC_TEMPERATURE_SET',
+    c.VEHICLEPROPERTY_HVAC_FAN_SPEED: 'HVAC_FAN_SPEED',
+    c.VEHICLEPROPERTY_HVAC_FAN_DIRECTION: 'HVAC_FAN_DIRECTION',
+    c.VEHICLEPROPERTY_HVAC_AUTO_ON: 'HVAC_AUTO_ON',
+    c.VEHICLEPROPERTY_HVAC_RECIRC_ON: 'HVAC_AUTO_RECIRC_ON',
+    c.VEHICLEPROPERTY_HVAC_AC_ON: 'HVAC_AC_ON',
+    c.VEHICLEPROPERTY_HVAC_DEFROSTER: 'HVAC_DEFROSTER'
+}
+
+SHORT_SLEEP_TIME_SEC = 2
+LONG_SLEEP_TIME_SEC = 30
+
+MIN_TEMPERATURE_C = 22
+MAX_TEMPERATURE_C = 28
+
+MIN_FAN_SPEED = 1
+MAX_FAN_SPEED = 5
+
+class ActionPropConfig(object):
+    """
+        A configuration class that parse the vhal property config message and hold the
+        configuration values
+    """
+    def __init__(self, vhalConfig):
+        self.supportedAreas = self._getSupportedAreas(vhalConfig)
+        self.areaConfigs = self._getAreaConfigs(vhalConfig, self.supportedAreas,
+                                                vhalConfig.value_type)
+
+    def _getSupportedAreas(self, vhalConfig):
+        supportedAreas = []
+        areas = vhalConfig.supported_areas
+        while areas != 0:
+            area = areas & (areas - 1)
+            area ^= areas
+            areas &= (areas - 1)
+            supportedAreas.append(area)
+
+        return supportedAreas
+
+    def _getAreaConfigs(self, vhalConfig, supportedAreas, valueType):
+        areaConfigs = {}
+        configs = vhalConfig.area_configs
+        if not configs:
+            return None
+
+        if len(configs) == 1:
+            for area in supportedAreas:
+                areaConfigs[area] = AreaConfig(configs[0], valueType)
+        else:
+            for config in configs:
+                areaConfigs[config.area_id] = AreaConfig(config, valueType)
+
+        return areaConfigs
+
+class AreaConfig(object):
+    """
+        A configuration class is representing an area config of a vhal property.
+    """
+    def __init__(self, vhalAreaConfig, valueType):
+        """
+            The class is initialized by parsing the vhal area config object
+        """
+        if valueType == c.VEHICLEPROPERTYTYPE_INT32:
+            self.min = vhalAreaConfig.min_int32_value
+            self.max = vhalAreaConfig.max_int32_value
+        elif valueType == c.VEHICLEPROPERTYTYPE_INT64:
+            self.min = vhalAreaConfig.min_int64_value
+            self.max = vhalAreaConfig.max_int64_value
+        elif valueType == c.VEHICLEPROPERTYTYPE_FLOAT:
+            self.min = vhalAreaConfig.min_float_value
+            self.max = vhalAreaConfig.max_float_value
+        else:
+            self.min = None
+            self.max = None
+
+class UserActionGenerator(object):
+    """
+        A class generate user action related vhal properties in a deterministic algorithm based on
+        pre-fetched vhal configuration
+    """
+    def __init__(self, vhal):
+        self.configs = self._getConfig(vhal)
+
+
+    def _getConfig(self, vhal):
+        """
+            Get vhal configuration for properties that need to be simulated
+        """
+        vhalConfig = {}
+        for actionProp in userActions:
+            vhal.getConfig(actionProp)
+            vhalConfig[actionProp] = ActionPropConfig(vhal.rxMsg().config[0])
+        return vhalConfig
+
+    def _adjustContinuousProperty(self, prop, begin, end, listener):
+        """
+            The method generate continuous property value from "begin" value to "end" value
+            (exclusive).
+        """
+        config = self.configs[prop]
+        for area in config.supportedAreas:
+            areaConfig = config.areaConfigs[area]
+            if begin < end:
+                begin = areaConfig.min if begin < areaConfig.min else begin
+                end = areaConfig.max if end > areaConfig.max else end
+            else:
+                begin = areaConfig.max if begin > areaConfig.max else begin
+                end = areaConfig.min if end < areaConfig.min else end
+
+            for value in self._range(begin, end):
+                listener.handle(prop, area, value, propDesc[prop])
+                time.sleep(0.2)
+
+    def _setProperty(self, prop, value, listener):
+        """
+            This method generates single property value (e.g. boolean or integer value)
+        """
+        config = self.configs[prop]
+        for area in config.supportedAreas:
+            listener.handle(prop, area, value, propDesc[prop])
+            time.sleep(1)
+
+    def _range(self, begin, end):
+        if begin < end:
+            i = begin
+            while i < end:
+                yield i
+                i += 1
+        else:
+            i = begin
+            while i > end:
+                yield i
+                i += -1
+
+    def generate(self, listener):
+        """
+            Main method that simulate user in-car actions such as HVAC
+        """
+        listener.handle(c.VEHICLEPROPERTY_HVAC_POWER_ON, c.VEHICLEAREAZONE_ROW_1,
+                        1, 'HVAC_POWER_ON')
+        time.sleep(SHORT_SLEEP_TIME_SEC)
+
+        while True:
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_AC_ON, 1, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_FAN_DIRECTION,
+                              c.VEHICLEHVACFANDIRECTION_FACE, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._adjustContinuousProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET,
+                                           MAX_TEMPERATURE_C, MIN_TEMPERATURE_C, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._adjustContinuousProperty(c.VEHICLEPROPERTY_HVAC_FAN_SPEED,
+                                           MIN_FAN_SPEED, MAX_FAN_SPEED, listener)
+            time.sleep(LONG_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_AUTO_ON, 1, listener)
+            time.sleep(LONG_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_AUTO_ON, 0, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_FAN_DIRECTION,
+                              c.VEHICLEHVACFANDIRECTION_FACE_AND_FLOOR, listener)
+            time.sleep(LONG_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_DEFROSTER, 1, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_FAN_DIRECTION,
+                              c.VEHICLEHVACFANDIRECTION_DEFROST, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._adjustContinuousProperty(c.VEHICLEPROPERTY_HVAC_TEMPERATURE_SET,
+                                           MIN_TEMPERATURE_C, MAX_TEMPERATURE_C, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
+
+            self._adjustContinuousProperty(c.VEHICLEPROPERTY_HVAC_FAN_SPEED,
+                                           MAX_FAN_SPEED, MIN_FAN_SPEED, listener)
+            time.sleep(LONG_SLEEP_TIME_SEC)
+
+            self._setProperty(c.VEHICLEPROPERTY_HVAC_AC_ON, 0, listener)
+            time.sleep(SHORT_SLEEP_TIME_SEC)
diff --git a/tools/emulator/vhal_const_generate.py b/tools/emulator/vhal_const_generate.py
index 55bcd1b..df9713b 100755
--- a/tools/emulator/vhal_const_generate.py
+++ b/tools/emulator/vhal_const_generate.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3.4
+#!/usr/bin/env python3
 #
 # Copyright (C) 2017 The Android Open Source Project
 #
@@ -94,23 +94,15 @@
     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 = os.path.join(vhal_location, '2.0', 'types.hal')
+    if not(os.path.exists(vhal_20_file)):
+        print("Vehicle HAL was not found at %s. lunch may provide a correct environment, or files moved" % vhal_location)
+        sys.exit(1)
+
+    print("Generating constants from Vehicle HAL 2.0 (%s)" % (vhal_20_file))
+    vhal_20_doc = parser.parse(vhal_20_file)
+
     vhal_20_file = open(os.path.join(script_directory, 'vhal_consts_2_0.py'), 'w')
 
     printHeader(vhal_20_file)
@@ -131,17 +123,4 @@
     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 59c430c..4eb3a1e 100644
--- a/tools/emulator/vhal_consts_2_0.py
+++ b/tools/emulator/vhal_consts_2_0.py
@@ -15,223 +15,9 @@
 # DO NOT EDIT MANUALLY
 # This file was autogenerated by vhal_const_generate.py
 
-# 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
-
-# VehicleAudioFocusIndex
-VEHICLEAUDIOFOCUSINDEX_FOCUS = 0x0
-VEHICLEAUDIOFOCUSINDEX_STREAMS = 0x1
-VEHICLEAUDIOFOCUSINDEX_EXTERNAL_FOCUS_STATE = 0x2
-VEHICLEAUDIOFOCUSINDEX_AUDIO_CONTEXTS = 0x3
-
-# Obd2CommonIgnitionMonitors
-OBD2COMMONIGNITIONMONITORS_COMPONENTS_AVAILABLE = 0x1
-OBD2COMMONIGNITIONMONITORS_COMPONENTS_INCOMPLETE = 0x2
-OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_AVAILABLE = 0x4
-OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_INCOMPLETE = 0x8
-OBD2COMMONIGNITIONMONITORS_MISFIRE_AVAILABLE = 0x10
-OBD2COMMONIGNITIONMONITORS_MISFIRE_INCOMPLETE = 0x20
-
-# Obd2FuelType
-OBD2FUELTYPE_NOT_AVAILABLE = 0x0
-OBD2FUELTYPE_GASOLINE = 0x1
-OBD2FUELTYPE_METHANOL = 0x2
-OBD2FUELTYPE_ETHANOL = 0x3
-OBD2FUELTYPE_DIESEL = 0x4
-OBD2FUELTYPE_LPG = 0x5
-OBD2FUELTYPE_CNG = 0x6
-OBD2FUELTYPE_PROPANE = 0x7
-OBD2FUELTYPE_ELECTRIC = 0x8
-OBD2FUELTYPE_BIFUEL_RUNNING_GASOLINE = 0x9
-OBD2FUELTYPE_BIFUEL_RUNNING_METHANOL = 0xa
-OBD2FUELTYPE_BIFUEL_RUNNING_ETHANOL = 0xb
-OBD2FUELTYPE_BIFUEL_RUNNING_LPG = 0xc
-OBD2FUELTYPE_BIFUEL_RUNNING_CNG = 0xd
-OBD2FUELTYPE_BIFUEL_RUNNING_PROPANE = 0xe
-OBD2FUELTYPE_BIFUEL_RUNNING_ELECTRIC = 0xf
-OBD2FUELTYPE_BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION = 0x10
-OBD2FUELTYPE_HYBRID_GASOLINE = 0x11
-OBD2FUELTYPE_HYBRID_ETHANOL = 0x12
-OBD2FUELTYPE_HYBRID_DIESEL = 0x13
-OBD2FUELTYPE_HYBRID_ELECTRIC = 0x14
-OBD2FUELTYPE_HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION = 0x15
-OBD2FUELTYPE_HYBRID_REGENERATIVE = 0x16
-OBD2FUELTYPE_BIFUEL_RUNNING_DIESEL = 0x17
-
 # VehicleAudioVolumeState
 VEHICLEAUDIOVOLUMESTATE_STATE_OK = 0x0
-VEHICLEAUDIOVOLUMESTATE_LIMIT_REACHED = 0x1
-
-# VmsMessageWithLayerIntegerValuesIndex
-VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_TYPE = 0x1
-VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_SUBTYPE = 0x2
-VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_VERSION = 0x3
-
-# Obd2SparkIgnitionMonitors
-OBD2SPARKIGNITIONMONITORS_EGR_AVAILABLE = 0x40
-OBD2SPARKIGNITIONMONITORS_EGR_INCOMPLETE = 0x80
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_AVAILABLE = 0x100
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_INCOMPLETE = 0x200
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_AVAILABLE = 0x400
-OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_INCOMPLETE = 0x800
-OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_AVAILABLE = 0x1000
-OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_INCOMPLETE = 0x2000
-OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_AVAILABLE = 0x4000
-OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_INCOMPLETE = 0x8000
-OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_AVAILABLE = 0x10000
-OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_INCOMPLETE = 0x20000
-OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_AVAILABLE = 0x40000
-OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_INCOMPLETE = 0x80000
-OBD2SPARKIGNITIONMONITORS_CATALYST_AVAILABLE = 0x100000
-OBD2SPARKIGNITIONMONITORS_CATALYST_INCOMPLETE = 0x200000
-
-# VehicleArea
-VEHICLEAREA_GLOBAL = 0x1000000
-VEHICLEAREA_ZONE = 0x2000000
-VEHICLEAREA_WINDOW = 0x3000000
-VEHICLEAREA_MIRROR = 0x4000000
-VEHICLEAREA_SEAT = 0x5000000
-VEHICLEAREA_DOOR = 0x6000000
-VEHICLEAREA_MASK = 0xf000000
-
-# Obd2SecondaryAirStatus
-OBD2SECONDARYAIRSTATUS_UPSTREAM = 0x1
-OBD2SECONDARYAIRSTATUS_DOWNSTREAM_OF_CATALYCIC_CONVERTER = 0x2
-OBD2SECONDARYAIRSTATUS_FROM_OUTSIDE_OR_OFF = 0x4
-OBD2SECONDARYAIRSTATUS_PUMP_ON_FOR_DIAGNOSTICS = 0x8
-
-# VehicleAudioHwVariantConfigFlag
-VEHICLEAUDIOHWVARIANTCONFIGFLAG_INTERNAL_RADIO_FLAG = 0x1
-
-# VmsMessageWithLayerAndPublisherIdIntegerValuesIndex
-VMSMESSAGEWITHLAYERANDPUBLISHERIDINTEGERVALUESINDEX_PUBLISHER_ID = 0x4
-
-# Obd2CompressionIgnitionMonitors
-OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_AVAILABLE = 0x40
-OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_INCOMPLETE = 0x80
-OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_AVAILABLE = 0x100
-OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_INCOMPLETE = 0x200
-OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_AVAILABLE = 0x400
-OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_INCOMPLETE = 0x800
-OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_AVAILABLE = 0x1000
-OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_INCOMPLETE = 0x2000
-OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_AVAILABLE = 0x4000
-OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_INCOMPLETE = 0x8000
-OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_AVAILABLE = 0x10000
-OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_INCOMPLETE = 0x20000
-
-# VmsOfferingMessageIntegerValuesIndex
-VMSOFFERINGMESSAGEINTEGERVALUESINDEX_PUBLISHER_ID = 0x1
-VMSOFFERINGMESSAGEINTEGERVALUESINDEX_NUMBER_OF_OFFERS = 0x2
-VMSOFFERINGMESSAGEINTEGERVALUESINDEX_OFFERING_START = 0x3
-
-# 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
-
-# Obd2IgnitionMonitorKind
-OBD2IGNITIONMONITORKIND_SPARK = 0x0
-OBD2IGNITIONMONITORKIND_COMPRESSION = 0x1
-
-# VmsAvailabilityStateIntegerValuesIndex
-VMSAVAILABILITYSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
-VMSAVAILABILITYSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x2
-VMSAVAILABILITYSTATEINTEGERVALUESINDEX_LAYERS_START = 0x3
-
-# VehicleApPowerStateConfigFlag
-VEHICLEAPPOWERSTATECONFIGFLAG_ENABLE_DEEP_SLEEP_FLAG = 0x1
-VEHICLEAPPOWERSTATECONFIGFLAG_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2
-
-# VehicleHvacFanDirection
-VEHICLEHVACFANDIRECTION_FACE = 0x1
-VEHICLEHVACFANDIRECTION_FLOOR = 0x2
-VEHICLEHVACFANDIRECTION_FACE_AND_FLOOR = 0x3
-VEHICLEHVACFANDIRECTION_DEFROST = 0x4
-VEHICLEHVACFANDIRECTION_DEFROST_AND_FLOOR = 0x5
-
-# VehicleHwKeyInputAction
-VEHICLEHWKEYINPUTACTION_ACTION_DOWN = 0x0
-VEHICLEHWKEYINPUTACTION_ACTION_UP = 0x1
-
-# Wheel
-WHEEL_UNKNOWN = 0x0
-WHEEL_LEFT_FRONT = 0x1
-WHEEL_RIGHT_FRONT = 0x2
-WHEEL_LEFT_REAR = 0x4
-WHEEL_RIGHT_REAR = 0x8
-
-# VehicleAudioVolumeCapabilityFlag
-VEHICLEAUDIOVOLUMECAPABILITYFLAG_PERSISTENT_STORAGE = 0x1
-VEHICLEAUDIOVOLUMECAPABILITYFLAG_MASTER_VOLUME_ONLY = 0x2
-
-# VehicleAudioVolumeIndex
-VEHICLEAUDIOVOLUMEINDEX_INDEX_STREAM = 0x0
-VEHICLEAUDIOVOLUMEINDEX_INDEX_VOLUME = 0x1
-VEHICLEAUDIOVOLUMEINDEX_INDEX_STATE = 0x2
-
-# VehicleRadioConstants
-VEHICLERADIOCONSTANTS_VEHICLE_RADIO_PRESET_MIN_VALUE = 0x1
-
-# 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
-
-# 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
-
-# VehicleAudioRoutingPolicyIndex
-VEHICLEAUDIOROUTINGPOLICYINDEX_STREAM = 0x0
-VEHICLEAUDIOROUTINGPOLICYINDEX_CONTEXTS = 0x1
-
-# VmsSubscriptionsStateIntegerValuesIndex
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_LAYERS = 0x2
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x3
-VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SUBSCRIPTIONS_START = 0x4
+VEHICLEAUDIOVOLUMESTATE_STATE_LIMIT_REACHED = 0x1
 
 # SubscribeFlags
 SUBSCRIBEFLAGS_UNDEFINED = 0x0
@@ -239,60 +25,40 @@
 SUBSCRIBEFLAGS_SET_CALL = 0x2
 SUBSCRIBEFLAGS_DEFAULT = 0x1
 
-# VehicleAudioExtFocusFlag
-VEHICLEAUDIOEXTFOCUSFLAG_NONE_FLAG = 0x0
-VEHICLEAUDIOEXTFOCUSFLAG_PERMANENT_FLAG = 0x1
-VEHICLEAUDIOEXTFOCUSFLAG_TRANSIENT_FLAG = 0x2
-VEHICLEAUDIOEXTFOCUSFLAG_PLAY_ONLY_FLAG = 0x4
-VEHICLEAUDIOEXTFOCUSFLAG_MUTE_MEDIA_FLAG = 0x8
-
-# 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
-
-# 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
-
-# VehiclePropertyAccess
-VEHICLEPROPERTYACCESS_NONE = 0x0
-VEHICLEPROPERTYACCESS_READ = 0x1
-VEHICLEPROPERTYACCESS_WRITE = 0x2
-VEHICLEPROPERTYACCESS_READ_WRITE = 0x3
-
-# Obd2FuelSystemStatus
-OBD2FUELSYSTEMSTATUS_OPEN_INSUFFICIENT_ENGINE_TEMPERATURE = 0x1
-OBD2FUELSYSTEMSTATUS_CLOSED_LOOP = 0x2
-OBD2FUELSYSTEMSTATUS_OPEN_ENGINE_LOAD_OR_DECELERATION = 0x4
-OBD2FUELSYSTEMSTATUS_OPEN_SYSTEM_FAILURE = 0x8
-OBD2FUELSYSTEMSTATUS_CLOSED_LOOP_BUT_FEEDBACK_FAULT = 0x10
-
-# VehicleTurnSignal
-VEHICLETURNSIGNAL_NONE = 0x0
-VEHICLETURNSIGNAL_RIGHT = 0x1
-VEHICLETURNSIGNAL_LEFT = 0x2
-VEHICLETURNSIGNAL_EMERGENCY = 0x4
-
-# VehicleAudioStreamFlag
-VEHICLEAUDIOSTREAMFLAG_STREAM0_FLAG = 0x1
-VEHICLEAUDIOSTREAMFLAG_STREAM1_FLAG = 0x2
-VEHICLEAUDIOSTREAMFLAG_STREAM2_FLAG = 0x4
-
-# VehicleApPowerBootupReason
-VEHICLEAPPOWERBOOTUPREASON_USER_POWER_ON = 0x0
-VEHICLEAPPOWERBOOTUPREASON_USER_UNLOCK = 0x1
-VEHICLEAPPOWERBOOTUPREASON_TIMER = 0x2
+# DiagnosticIntegerSensorIndex
+DIAGNOSTICINTEGERSENSORINDEX_FUEL_SYSTEM_STATUS = 0x0
+DIAGNOSTICINTEGERSENSORINDEX_MALFUNCTION_INDICATOR_LIGHT_ON = 0x1
+DIAGNOSTICINTEGERSENSORINDEX_IGNITION_MONITORS_SUPPORTED = 0x2
+DIAGNOSTICINTEGERSENSORINDEX_IGNITION_SPECIFIC_MONITORS = 0x3
+DIAGNOSTICINTEGERSENSORINDEX_INTAKE_AIR_TEMPERATURE = 0x4
+DIAGNOSTICINTEGERSENSORINDEX_COMMANDED_SECONDARY_AIR_STATUS = 0x5
+DIAGNOSTICINTEGERSENSORINDEX_NUM_OXYGEN_SENSORS_PRESENT = 0x6
+DIAGNOSTICINTEGERSENSORINDEX_RUNTIME_SINCE_ENGINE_START = 0x7
+DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 0x8
+DIAGNOSTICINTEGERSENSORINDEX_WARMUPS_SINCE_CODES_CLEARED = 0x9
+DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 0xa
+DIAGNOSTICINTEGERSENSORINDEX_ABSOLUTE_BAROMETRIC_PRESSURE = 0xb
+DIAGNOSTICINTEGERSENSORINDEX_CONTROL_MODULE_VOLTAGE = 0xc
+DIAGNOSTICINTEGERSENSORINDEX_AMBIENT_AIR_TEMPERATURE = 0xd
+DIAGNOSTICINTEGERSENSORINDEX_TIME_WITH_MALFUNCTION_LIGHT_ON = 0xe
+DIAGNOSTICINTEGERSENSORINDEX_TIME_SINCE_TROUBLE_CODES_CLEARED = 0xf
+DIAGNOSTICINTEGERSENSORINDEX_MAX_FUEL_AIR_EQUIVALENCE_RATIO = 0x10
+DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_VOLTAGE = 0x11
+DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_CURRENT = 0x12
+DIAGNOSTICINTEGERSENSORINDEX_MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x13
+DIAGNOSTICINTEGERSENSORINDEX_MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 0x14
+DIAGNOSTICINTEGERSENSORINDEX_FUEL_TYPE = 0x15
+DIAGNOSTICINTEGERSENSORINDEX_FUEL_RAIL_ABSOLUTE_PRESSURE = 0x16
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_OIL_TEMPERATURE = 0x17
+DIAGNOSTICINTEGERSENSORINDEX_DRIVER_DEMAND_PERCENT_TORQUE = 0x18
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_ACTUAL_PERCENT_TORQUE = 0x19
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_REFERENCE_PERCENT_TORQUE = 0x1a
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_IDLE = 0x1b
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT1 = 0x1c
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT2 = 0x1d
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT3 = 0x1e
+DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT4 = 0x1f
+DIAGNOSTICINTEGERSENSORINDEX_LAST_SYSTEM_INDEX = 0x1f
 
 # VehicleAudioFocusState
 VEHICLEAUDIOFOCUSSTATE_STATE_GAIN = 0x1
@@ -302,9 +68,26 @@
 VEHICLEAUDIOFOCUSSTATE_STATE_LOSS = 0x5
 VEHICLEAUDIOFOCUSSTATE_STATE_LOSS_TRANSIENT_EXLCUSIVE = 0x6
 
-# VehicleAudioStream
-VEHICLEAUDIOSTREAM_STREAM0 = 0x0
-VEHICLEAUDIOSTREAM_STREAM1 = 0x1
+# Obd2CommonIgnitionMonitors
+OBD2COMMONIGNITIONMONITORS_COMPONENTS_AVAILABLE = 0x1
+OBD2COMMONIGNITIONMONITORS_COMPONENTS_INCOMPLETE = 0x2
+OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_AVAILABLE = 0x4
+OBD2COMMONIGNITIONMONITORS_FUEL_SYSTEM_INCOMPLETE = 0x8
+OBD2COMMONIGNITIONMONITORS_MISFIRE_AVAILABLE = 0x10
+OBD2COMMONIGNITIONMONITORS_MISFIRE_INCOMPLETE = 0x20
+
+# VehicleHvacFanDirection
+VEHICLEHVACFANDIRECTION_FACE = 0x1
+VEHICLEHVACFANDIRECTION_FLOOR = 0x2
+VEHICLEHVACFANDIRECTION_FACE_AND_FLOOR = 0x3
+VEHICLEHVACFANDIRECTION_DEFROST = 0x4
+VEHICLEHVACFANDIRECTION_DEFROST_AND_FLOOR = 0x5
+
+# VehiclePropertyOperation
+VEHICLEPROPERTYOPERATION_GENERIC = 0x0
+VEHICLEPROPERTYOPERATION_SET = 0x1
+VEHICLEPROPERTYOPERATION_GET = 0x2
+VEHICLEPROPERTYOPERATION_SUBSCRIBE = 0x3
 
 # DiagnosticFloatSensorIndex
 DIAGNOSTICFLOATSENSORINDEX_CALCULATED_ENGINE_LOAD = 0x0
@@ -380,10 +163,9 @@
 DIAGNOSTICFLOATSENSORINDEX_ENGINE_FUEL_RATE = 0x46
 DIAGNOSTICFLOATSENSORINDEX_LAST_SYSTEM_INDEX = 0x46
 
-# VehicleAreaMirror
-VEHICLEAREAMIRROR_DRIVER_LEFT = 0x1
-VEHICLEAREAMIRROR_DRIVER_RIGHT = 0x2
-VEHICLEAREAMIRROR_DRIVER_CENTER = 0x4
+# VehicleAudioVolumeLimitIndex
+VEHICLEAUDIOVOLUMELIMITINDEX_STREAM = 0x0
+VEHICLEAUDIOVOLUMELIMITINDEX_MAX_VOLUME = 0x1
 
 # VehicleAreaWindow
 VEHICLEAREAWINDOW_FRONT_WINDSHIELD = 0x1
@@ -396,40 +178,97 @@
 VEHICLEAREAWINDOW_ROW_3_LEFT = 0x1000
 VEHICLEAREAWINDOW_ROW_3_RIGHT = 0x2000
 
-# VehiclePropertyOperation
-VEHICLEPROPERTYOPERATION_GENERIC = 0x0
-VEHICLEPROPERTYOPERATION_SET = 0x1
-VEHICLEPROPERTYOPERATION_GET = 0x2
-VEHICLEPROPERTYOPERATION_SUBSCRIBE = 0x3
+# 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
 
-# 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
+# VehicleAreaMirror
+VEHICLEAREAMIRROR_DRIVER_LEFT = 0x1
+VEHICLEAREAMIRROR_DRIVER_RIGHT = 0x2
+VEHICLEAREAMIRROR_DRIVER_CENTER = 0x4
 
-# StatusCode
-STATUSCODE_OK = 0x0
-STATUSCODE_TRY_AGAIN = 0x1
-STATUSCODE_INVALID_ARG = 0x2
-STATUSCODE_NOT_AVAILABLE = 0x3
-STATUSCODE_ACCESS_DENIED = 0x4
-STATUSCODE_INTERNAL_ERROR = 0x5
+# 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
+VEHICLEAUDIOCONTEXTFLAG_RINGTONE_FLAG = 0x2000
 
-# 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
+# VmsMessageWithLayerAndPublisherIdIntegerValuesIndex
+VMSMESSAGEWITHLAYERANDPUBLISHERIDINTEGERVALUESINDEX_PUBLISHER_ID = 0x4
+
+# VehiclePropertyAccess
+VEHICLEPROPERTYACCESS_NONE = 0x0
+VEHICLEPROPERTYACCESS_READ = 0x1
+VEHICLEPROPERTYACCESS_WRITE = 0x2
+VEHICLEPROPERTYACCESS_READ_WRITE = 0x3
+
+# VehicleApPowerStateIndex
+VEHICLEAPPOWERSTATEINDEX_STATE = 0x0
+VEHICLEAPPOWERSTATEINDEX_ADDITIONAL = 0x1
+
+# VmsBaseMessageIntegerValuesIndex
+VMSBASEMESSAGEINTEGERVALUESINDEX_MESSAGE_TYPE = 0x0
+
+# VehicleAudioStream
+VEHICLEAUDIOSTREAM_STREAM0 = 0x0
+VEHICLEAUDIOSTREAM_STREAM1 = 0x1
+
+# VehiclePropertyChangeMode
+VEHICLEPROPERTYCHANGEMODE_STATIC = 0x0
+VEHICLEPROPERTYCHANGEMODE_ON_CHANGE = 0x1
+VEHICLEPROPERTYCHANGEMODE_CONTINUOUS = 0x2
+VEHICLEPROPERTYCHANGEMODE_POLL = 0x3
+VEHICLEPROPERTYCHANGEMODE_ON_SET = 0x4
+
+# Obd2CompressionIgnitionMonitors
+OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_AVAILABLE = 0x40
+OBD2COMPRESSIONIGNITIONMONITORS_EGR_OR_VVT_INCOMPLETE = 0x80
+OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_AVAILABLE = 0x100
+OBD2COMPRESSIONIGNITIONMONITORS_PM_FILTER_INCOMPLETE = 0x200
+OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_AVAILABLE = 0x400
+OBD2COMPRESSIONIGNITIONMONITORS_EXHAUST_GAS_SENSOR_INCOMPLETE = 0x800
+OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_AVAILABLE = 0x1000
+OBD2COMPRESSIONIGNITIONMONITORS_BOOST_PRESSURE_INCOMPLETE = 0x2000
+OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_AVAILABLE = 0x4000
+OBD2COMPRESSIONIGNITIONMONITORS_NOx_SCR_INCOMPLETE = 0x8000
+OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_AVAILABLE = 0x10000
+OBD2COMPRESSIONIGNITIONMONITORS_NMHC_CATALYST_INCOMPLETE = 0x20000
+
+# VehiclePropertyGroup
+VEHICLEPROPERTYGROUP_SYSTEM = 0x10000000
+VEHICLEPROPERTYGROUP_VENDOR = 0x20000000
+VEHICLEPROPERTYGROUP_MASK = 0xf0000000
+
+# 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
 
 # VmsMessageType
 VMSMESSAGETYPE_SUBSCRIBE = 0x1
@@ -445,6 +284,32 @@
 VMSMESSAGETYPE_SUBSCRIPTIONS_CHANGE = 0xb
 VMSMESSAGETYPE_DATA = 0xc
 
+# 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
+
+# Wheel
+WHEEL_UNKNOWN = 0x0
+WHEEL_LEFT_FRONT = 0x1
+WHEEL_RIGHT_FRONT = 0x2
+WHEEL_LEFT_REAR = 0x4
+WHEEL_RIGHT_REAR = 0x8
+
 # VehicleProperty
 VEHICLEPROPERTY_INVALID = 0x0
 VEHICLEPROPERTY_INFO_VIN = 0x11100100
@@ -457,6 +322,7 @@
 VEHICLEPROPERTY_ENGINE_COOLANT_TEMP = 0x11600301
 VEHICLEPROPERTY_ENGINE_OIL_TEMP = 0x11600304
 VEHICLEPROPERTY_ENGINE_RPM = 0x11600305
+VEHICLEPROPERTY_WHEEL_TICK = 0x11e00306
 VEHICLEPROPERTY_GEAR_SELECTION = 0x11400400
 VEHICLEPROPERTY_CURRENT_GEAR = 0x11400401
 VEHICLEPROPERTY_PARKING_BRAKE_ON = 0x11200402
@@ -465,6 +331,8 @@
 VEHICLEPROPERTY_NIGHT_MODE = 0x11200407
 VEHICLEPROPERTY_TURN_SIGNAL_STATE = 0x11400408
 VEHICLEPROPERTY_IGNITION_STATE = 0x11400409
+VEHICLEPROPERTY_ABS_ACTIVE = 0x1120040a
+VEHICLEPROPERTY_TRACTION_CONTROL_ACTIVE = 0x1120040b
 VEHICLEPROPERTY_HVAC_FAN_SPEED = 0x12400500
 VEHICLEPROPERTY_HVAC_FAN_DIRECTION = 0x12400501
 VEHICLEPROPERTY_HVAC_TEMPERATURE_CURRENT = 0x12600502
@@ -481,8 +349,9 @@
 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_HVAC_FAN_DIRECTION_AVAILABLE = 0x12400511
+VEHICLEPROPERTY_HVAC_AUTO_RECIRC_ON = 0x12200512
 VEHICLEPROPERTY_ENV_OUTSIDE_TEMPERATURE = 0x11600703
 VEHICLEPROPERTY_ENV_CABIN_TEMPERATURE = 0x11600704
 VEHICLEPROPERTY_RADIO_PRESET = 0x11410801
@@ -544,10 +413,6 @@
 VEHICLEPROPERTY_WINDOW_VENT_POS = 0x11400bc2
 VEHICLEPROPERTY_WINDOW_VENT_MOVE = 0x11400bc3
 VEHICLEPROPERTY_WINDOW_LOCK = 0x11200bc4
-VEHICLEPROPERTY_WHEEL_TICK = 0x11e00306
-VEHICLEPROPERTY_ABS_ACTIVE = 0x1120040a
-VEHICLEPROPERTY_TRACTION_CONTROL_ACTIVE = 0x1120040b
-VEHICLEPROPERTY_HVAC_AUTO_RECIRC_ON = 0x12200512
 VEHICLEPROPERTY_VEHICLE_MAP_SERVICE = 0x11e00c00
 VEHICLEPROPERTY_OBD2_LIVE_FRAME = 0x11e00d00
 VEHICLEPROPERTY_OBD2_FREEZE_FRAME = 0x11e00d01
@@ -562,76 +427,25 @@
 VEHICLEIGNITIONSTATE_ON = 0x4
 VEHICLEIGNITIONSTATE_START = 0x5
 
-# VehiclePropertyGroup
-VEHICLEPROPERTYGROUP_SYSTEM = 0x10000000
-VEHICLEPROPERTYGROUP_VENDOR = 0x20000000
-VEHICLEPROPERTYGROUP_MASK = 0xf0000000
+# VehicleAudioVolumeCapabilityFlag
+VEHICLEAUDIOVOLUMECAPABILITYFLAG_PERSISTENT_STORAGE = 0x1
+VEHICLEAUDIOVOLUMECAPABILITYFLAG_MASTER_VOLUME_ONLY = 0x2
 
-# 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
+# VehicleHwKeyInputAction
+VEHICLEHWKEYINPUTACTION_ACTION_DOWN = 0x0
+VEHICLEHWKEYINPUTACTION_ACTION_UP = 0x1
 
-# VmsBaseMessageIntegerValuesIndex
-VMSBASEMESSAGEINTEGERVALUESINDEX_MESSAGE_TYPE = 0x0
+# VehicleAudioStreamFlag
+VEHICLEAUDIOSTREAMFLAG_STREAM0_FLAG = 0x1
+VEHICLEAUDIOSTREAMFLAG_STREAM1_FLAG = 0x2
+VEHICLEAUDIOSTREAMFLAG_STREAM2_FLAG = 0x4
 
-# DiagnosticIntegerSensorIndex
-DIAGNOSTICINTEGERSENSORINDEX_FUEL_SYSTEM_STATUS = 0x0
-DIAGNOSTICINTEGERSENSORINDEX_MALFUNCTION_INDICATOR_LIGHT_ON = 0x1
-DIAGNOSTICINTEGERSENSORINDEX_IGNITION_MONITORS_SUPPORTED = 0x2
-DIAGNOSTICINTEGERSENSORINDEX_IGNITION_SPECIFIC_MONITORS = 0x3
-DIAGNOSTICINTEGERSENSORINDEX_INTAKE_AIR_TEMPERATURE = 0x4
-DIAGNOSTICINTEGERSENSORINDEX_COMMANDED_SECONDARY_AIR_STATUS = 0x5
-DIAGNOSTICINTEGERSENSORINDEX_NUM_OXYGEN_SENSORS_PRESENT = 0x6
-DIAGNOSTICINTEGERSENSORINDEX_RUNTIME_SINCE_ENGINE_START = 0x7
-DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 0x8
-DIAGNOSTICINTEGERSENSORINDEX_WARMUPS_SINCE_CODES_CLEARED = 0x9
-DIAGNOSTICINTEGERSENSORINDEX_DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 0xa
-DIAGNOSTICINTEGERSENSORINDEX_ABSOLUTE_BAROMETRIC_PRESSURE = 0xb
-DIAGNOSTICINTEGERSENSORINDEX_CONTROL_MODULE_VOLTAGE = 0xc
-DIAGNOSTICINTEGERSENSORINDEX_AMBIENT_AIR_TEMPERATURE = 0xd
-DIAGNOSTICINTEGERSENSORINDEX_TIME_WITH_MALFUNCTION_LIGHT_ON = 0xe
-DIAGNOSTICINTEGERSENSORINDEX_TIME_SINCE_TROUBLE_CODES_CLEARED = 0xf
-DIAGNOSTICINTEGERSENSORINDEX_MAX_FUEL_AIR_EQUIVALENCE_RATIO = 0x10
-DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_VOLTAGE = 0x11
-DIAGNOSTICINTEGERSENSORINDEX_MAX_OXYGEN_SENSOR_CURRENT = 0x12
-DIAGNOSTICINTEGERSENSORINDEX_MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x13
-DIAGNOSTICINTEGERSENSORINDEX_MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 0x14
-DIAGNOSTICINTEGERSENSORINDEX_FUEL_TYPE = 0x15
-DIAGNOSTICINTEGERSENSORINDEX_FUEL_RAIL_ABSOLUTE_PRESSURE = 0x16
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_OIL_TEMPERATURE = 0x17
-DIAGNOSTICINTEGERSENSORINDEX_DRIVER_DEMAND_PERCENT_TORQUE = 0x18
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_ACTUAL_PERCENT_TORQUE = 0x19
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_REFERENCE_PERCENT_TORQUE = 0x1a
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_IDLE = 0x1b
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT1 = 0x1c
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT2 = 0x1d
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT3 = 0x1e
-DIAGNOSTICINTEGERSENSORINDEX_ENGINE_PERCENT_TORQUE_DATA_POINT4 = 0x1f
-DIAGNOSTICINTEGERSENSORINDEX_LAST_SYSTEM_INDEX = 0x1f
-
-# VehiclePropertyChangeMode
-VEHICLEPROPERTYCHANGEMODE_STATIC = 0x0
-VEHICLEPROPERTYCHANGEMODE_ON_CHANGE = 0x1
-VEHICLEPROPERTYCHANGEMODE_CONTINUOUS = 0x2
-VEHICLEPROPERTYCHANGEMODE_POLL = 0x3
-VEHICLEPROPERTYCHANGEMODE_ON_SET = 0x4
-
-# VehicleApPowerState
-VEHICLEAPPOWERSTATE_OFF = 0x0
-VEHICLEAPPOWERSTATE_DEEP_SLEEP = 0x1
-VEHICLEAPPOWERSTATE_ON_DISP_OFF = 0x2
-VEHICLEAPPOWERSTATE_ON_FULL = 0x3
-VEHICLEAPPOWERSTATE_SHUTDOWN_PREPARE = 0x4
-
-# VehicleInstrumentClusterType
-VEHICLEINSTRUMENTCLUSTERTYPE_NONE = 0x0
-VEHICLEINSTRUMENTCLUSTERTYPE_HAL_INTERFACE = 0x1
-VEHICLEINSTRUMENTCLUSTERTYPE_EXTERNAL_DISPLAY = 0x2
+# Obd2FuelSystemStatus
+OBD2FUELSYSTEMSTATUS_OPEN_INSUFFICIENT_ENGINE_TEMPERATURE = 0x1
+OBD2FUELSYSTEMSTATUS_CLOSED_LOOP = 0x2
+OBD2FUELSYSTEMSTATUS_OPEN_ENGINE_LOAD_OR_DECELERATION = 0x4
+OBD2FUELSYSTEMSTATUS_OPEN_SYSTEM_FAILURE = 0x8
+OBD2FUELSYSTEMSTATUS_CLOSED_LOOP_BUT_FEEDBACK_FAULT = 0x10
 
 # VehicleUnit
 VEHICLEUNIT_SHOULD_NOT_USE = 0x0
@@ -650,22 +464,209 @@
 VEHICLEUNIT_SECS = 0x53
 VEHICLEUNIT_YEAR = 0x59
 
-# VehicleAudioVolumeLimitIndex
-VEHICLEAUDIOVOLUMELIMITINDEX_STREAM = 0x0
-VEHICLEAUDIOVOLUMELIMITINDEX_MAX_VOLUME = 0x1
+# VehicleAudioFocusIndex
+VEHICLEAUDIOFOCUSINDEX_FOCUS = 0x0
+VEHICLEAUDIOFOCUSINDEX_STREAMS = 0x1
+VEHICLEAUDIOFOCUSINDEX_EXTERNAL_FOCUS_STATE = 0x2
+VEHICLEAUDIOFOCUSINDEX_AUDIO_CONTEXTS = 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
+
+# VehicleAudioVolumeIndex
+VEHICLEAUDIOVOLUMEINDEX_STREAM = 0x0
+VEHICLEAUDIOVOLUMEINDEX_VOLUME = 0x1
+VEHICLEAUDIOVOLUMEINDEX_STATE = 0x2
+
+# 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
+
+# VmsAvailabilityStateIntegerValuesIndex
+VMSAVAILABILITYSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
+VMSAVAILABILITYSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x2
+VMSAVAILABILITYSTATEINTEGERVALUESINDEX_LAYERS_START = 0x3
+
+# VmsSubscriptionsStateIntegerValuesIndex
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SEQUENCE_NUMBER = 0x1
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_LAYERS = 0x2
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_NUMBER_OF_ASSOCIATED_LAYERS = 0x3
+VMSSUBSCRIPTIONSSTATEINTEGERVALUESINDEX_SUBSCRIPTIONS_START = 0x4
+
+# VmsMessageWithLayerIntegerValuesIndex
+VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_TYPE = 0x1
+VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_SUBTYPE = 0x2
+VMSMESSAGEWITHLAYERINTEGERVALUESINDEX_LAYER_VERSION = 0x3
+
+# 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
+
+# VehicleArea
+VEHICLEAREA_GLOBAL = 0x1000000
+VEHICLEAREA_ZONE = 0x2000000
+VEHICLEAREA_WINDOW = 0x3000000
+VEHICLEAREA_MIRROR = 0x4000000
+VEHICLEAREA_SEAT = 0x5000000
+VEHICLEAREA_DOOR = 0x6000000
+VEHICLEAREA_MASK = 0xf000000
+
+# VehicleTurnSignal
+VEHICLETURNSIGNAL_NONE = 0x0
+VEHICLETURNSIGNAL_RIGHT = 0x1
+VEHICLETURNSIGNAL_LEFT = 0x2
+VEHICLETURNSIGNAL_EMERGENCY = 0x4
+
+# VehicleDisplay
+VEHICLEDISPLAY_MAIN = 0x0
+VEHICLEDISPLAY_INSTRUMENT_CLUSTER = 0x1
+
+# VehicleAudioRoutingPolicyIndex
+VEHICLEAUDIOROUTINGPOLICYINDEX_STREAM = 0x0
+VEHICLEAUDIOROUTINGPOLICYINDEX_CONTEXTS = 0x1
+
+# Obd2IgnitionMonitorKind
+OBD2IGNITIONMONITORKIND_SPARK = 0x0
+OBD2IGNITIONMONITORKIND_COMPRESSION = 0x1
+
+# VehicleApPowerBootupReason
+VEHICLEAPPOWERBOOTUPREASON_USER_POWER_ON = 0x0
+VEHICLEAPPOWERBOOTUPREASON_USER_UNLOCK = 0x1
+VEHICLEAPPOWERBOOTUPREASON_TIMER = 0x2
+
+# Obd2FuelType
+OBD2FUELTYPE_NOT_AVAILABLE = 0x0
+OBD2FUELTYPE_GASOLINE = 0x1
+OBD2FUELTYPE_METHANOL = 0x2
+OBD2FUELTYPE_ETHANOL = 0x3
+OBD2FUELTYPE_DIESEL = 0x4
+OBD2FUELTYPE_LPG = 0x5
+OBD2FUELTYPE_CNG = 0x6
+OBD2FUELTYPE_PROPANE = 0x7
+OBD2FUELTYPE_ELECTRIC = 0x8
+OBD2FUELTYPE_BIFUEL_RUNNING_GASOLINE = 0x9
+OBD2FUELTYPE_BIFUEL_RUNNING_METHANOL = 0xa
+OBD2FUELTYPE_BIFUEL_RUNNING_ETHANOL = 0xb
+OBD2FUELTYPE_BIFUEL_RUNNING_LPG = 0xc
+OBD2FUELTYPE_BIFUEL_RUNNING_CNG = 0xd
+OBD2FUELTYPE_BIFUEL_RUNNING_PROPANE = 0xe
+OBD2FUELTYPE_BIFUEL_RUNNING_ELECTRIC = 0xf
+OBD2FUELTYPE_BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION = 0x10
+OBD2FUELTYPE_HYBRID_GASOLINE = 0x11
+OBD2FUELTYPE_HYBRID_ETHANOL = 0x12
+OBD2FUELTYPE_HYBRID_DIESEL = 0x13
+OBD2FUELTYPE_HYBRID_ELECTRIC = 0x14
+OBD2FUELTYPE_HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION = 0x15
+OBD2FUELTYPE_HYBRID_REGENERATIVE = 0x16
+OBD2FUELTYPE_BIFUEL_RUNNING_DIESEL = 0x17
+
+# VehicleAudioExtFocusFlag
+VEHICLEAUDIOEXTFOCUSFLAG_NONE_FLAG = 0x0
+VEHICLEAUDIOEXTFOCUSFLAG_PERMANENT_FLAG = 0x1
+VEHICLEAUDIOEXTFOCUSFLAG_TRANSIENT_FLAG = 0x2
+VEHICLEAUDIOEXTFOCUSFLAG_PLAY_ONLY_FLAG = 0x4
+VEHICLEAUDIOEXTFOCUSFLAG_MUTE_MEDIA_FLAG = 0x8
 
 # VehicleApPowerStateShutdownParam
 VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_IMMEDIATELY = 0x1
 VEHICLEAPPOWERSTATESHUTDOWNPARAM_CAN_SLEEP = 0x2
 VEHICLEAPPOWERSTATESHUTDOWNPARAM_SHUTDOWN_ONLY = 0x3
 
-# VehicleApPowerStateIndex
-VEHICLEAPPOWERSTATEINDEX_STATE = 0x0
-VEHICLEAPPOWERSTATEINDEX_ADDITIONAL = 0x1
+# VehicleInstrumentClusterType
+VEHICLEINSTRUMENTCLUSTERTYPE_NONE = 0x0
+VEHICLEINSTRUMENTCLUSTERTYPE_HAL_INTERFACE = 0x1
+VEHICLEINSTRUMENTCLUSTERTYPE_EXTERNAL_DISPLAY = 0x2
 
-# VehicleDisplay
-VEHICLEDISPLAY_MAIN = 0x0
-VEHICLEDISPLAY_INSTRUMENT_CLUSTER = 0x1
+# Obd2SparkIgnitionMonitors
+OBD2SPARKIGNITIONMONITORS_EGR_AVAILABLE = 0x40
+OBD2SPARKIGNITIONMONITORS_EGR_INCOMPLETE = 0x80
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_AVAILABLE = 0x100
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_HEATER_INCOMPLETE = 0x200
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_AVAILABLE = 0x400
+OBD2SPARKIGNITIONMONITORS_OXYGEN_SENSOR_INCOMPLETE = 0x800
+OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_AVAILABLE = 0x1000
+OBD2SPARKIGNITIONMONITORS_AC_REFRIGERANT_INCOMPLETE = 0x2000
+OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_AVAILABLE = 0x4000
+OBD2SPARKIGNITIONMONITORS_SECONDARY_AIR_SYSTEM_INCOMPLETE = 0x8000
+OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_AVAILABLE = 0x10000
+OBD2SPARKIGNITIONMONITORS_EVAPORATIVE_SYSTEM_INCOMPLETE = 0x20000
+OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_AVAILABLE = 0x40000
+OBD2SPARKIGNITIONMONITORS_HEATED_CATALYST_INCOMPLETE = 0x80000
+OBD2SPARKIGNITIONMONITORS_CATALYST_AVAILABLE = 0x100000
+OBD2SPARKIGNITIONMONITORS_CATALYST_INCOMPLETE = 0x200000
+
+# VehicleApPowerStateConfigFlag
+VEHICLEAPPOWERSTATECONFIGFLAG_ENABLE_DEEP_SLEEP_FLAG = 0x1
+VEHICLEAPPOWERSTATECONFIGFLAG_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2
+
+# VmsOfferingMessageIntegerValuesIndex
+VMSOFFERINGMESSAGEINTEGERVALUESINDEX_PUBLISHER_ID = 0x1
+VMSOFFERINGMESSAGEINTEGERVALUESINDEX_NUMBER_OF_OFFERS = 0x2
+VMSOFFERINGMESSAGEINTEGERVALUESINDEX_OFFERING_START = 0x3
+
+# 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
+
+# VehicleApPowerState
+VEHICLEAPPOWERSTATE_OFF = 0x0
+VEHICLEAPPOWERSTATE_DEEP_SLEEP = 0x1
+VEHICLEAPPOWERSTATE_ON_DISP_OFF = 0x2
+VEHICLEAPPOWERSTATE_ON_FULL = 0x3
+VEHICLEAPPOWERSTATE_SHUTDOWN_PREPARE = 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
+
+# Obd2SecondaryAirStatus
+OBD2SECONDARYAIRSTATUS_UPSTREAM = 0x1
+OBD2SECONDARYAIRSTATUS_DOWNSTREAM_OF_CATALYCIC_CONVERTER = 0x2
+OBD2SECONDARYAIRSTATUS_FROM_OUTSIDE_OR_OFF = 0x4
+OBD2SECONDARYAIRSTATUS_PUMP_ON_FOR_DIAGNOSTICS = 0x8
+
+# VehicleRadioConstants
+VEHICLERADIOCONSTANTS_VEHICLE_RADIO_PRESET_MIN_VALUE = 0x1
+
+# VehicleAudioHwVariantConfigFlag
+VEHICLEAUDIOHWVARIANTCONFIGFLAG_INTERNAL_RADIO_FLAG = 0x1
+
+# StatusCode
+STATUSCODE_OK = 0x0
+STATUSCODE_TRY_AGAIN = 0x1
+STATUSCODE_INVALID_ARG = 0x2
+STATUSCODE_NOT_AVAILABLE = 0x3
+STATUSCODE_ACCESS_DENIED = 0x4
+STATUSCODE_INTERNAL_ERROR = 0x5
 
 # Create a container of value_type constants to be used by vhal_emulator
 class vhal_types_2_0:
diff --git a/tools/emulator/vhal_consts_2_1.py b/tools/emulator/vhal_consts_2_1.py
deleted file mode 100644
index e5dbcff..0000000
--- a/tools/emulator/vhal_consts_2_1.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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 *
diff --git a/tools/emulator/vhal_emulator_test.py b/tools/emulator/vhal_emulator_test.py
index ea4030d..3cf2e8b 100755
--- a/tools/emulator/vhal_emulator_test.py
+++ b/tools/emulator/vhal_emulator_test.py
@@ -37,7 +37,7 @@
 sys.dont_write_bytecode = True
 
 import VehicleHalProto_pb2
-import vhal_consts_2_1
+import vhal_consts_2_0
 import vhal_emulator
 import logging
 
@@ -49,12 +49,12 @@
     _vhal = 0                       # Handle to VHAL object that communicates over socket to DUT
     # TODO: b/38203109 - Fix OBD2 values, implement handling for complex properties
     _skipProps = [
-                    vhal_consts_2_1.VEHICLEPROPERTY_OBD2_LIVE_FRAME,
-                    vhal_consts_2_1.VEHICLEPROPERTY_OBD2_FREEZE_FRAME,
-                    vhal_consts_2_1.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_INFO,
-                    vhal_consts_2_1.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_CLEAR,
-                    vhal_consts_2_1.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE,
-                    vhal_consts_2_1.VEHICLEPROPERTY_WHEEL_TICK,     # Need to support complex properties
+                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_LIVE_FRAME,
+                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME,
+                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_INFO,
+                    vhal_consts_2_0.VEHICLEPROPERTY_OBD2_FREEZE_FRAME_CLEAR,
+                    vhal_consts_2_0.VEHICLEPROPERTY_VEHICLE_MAP_SERVICE,
+                    vhal_consts_2_0.VEHICLEPROPERTY_WHEEL_TICK,     # Need to support complex properties
                     0x21E00666      # FakeDataControllingProperty - an internal test property
                  ]
 
@@ -70,7 +70,7 @@
         elif valType in self._types.TYPE_BYTES:
             # Generate array of integers counting from 0
             testValue = list(range(len(origValue)))
-        elif valType == vhal_consts_2_1.VEHICLEPROPERTYTYPE_BOOLEAN:
+        elif valType == vhal_consts_2_0.VEHICLEPROPERTYTYPE_BOOLEAN:
             testValue = origValue ^ 1
         elif valType in self._types.TYPE_INT32:
             try:
@@ -113,7 +113,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_1.VEHICLEPROPERTYTYPE_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]
@@ -305,5 +305,5 @@
         self._configs = self._vhal.rxMsg().config
 
 if __name__ == '__main__':
-    v = VhalTest(vhal_consts_2_1.vhal_types_2_0)
+    v = VhalTest(vhal_consts_2_0.vhal_types_2_0)
     v.runTests()
diff --git a/tools/emulator/vhal_prop_simulator.py b/tools/emulator/vhal_prop_simulator.py
new file mode 100755
index 0000000..4b1b1a8
--- /dev/null
+++ b/tools/emulator/vhal_prop_simulator.py
@@ -0,0 +1,67 @@
+#!/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.
+#
+
+import argparse
+import time
+
+from threading import Thread
+
+import driving_info_generator
+import user_action_generator
+import vhal_consts_2_0 as c
+
+from vhal_emulator import Vhal
+
+DEFAULT_TIMEOUT_SEC = 60 * 60 # 1 hour
+
+class VhalPropSimulator(object):
+    """
+        A class simulates vhal properties by calling each generator in a separate thread. It is
+        itself a listener passed to each generator to handle vhal event
+    """
+
+    def __init__(self, device, gpxFile,):
+        self.vhal = Vhal(c.vhal_types_2_0, device)
+        self.gpxFile = gpxFile
+
+    def handle(self, prop, area_id, value, desc=None):
+        """
+            handle generated VHAL property by injecting through vhal emulator.
+        """
+        print "Generated property %s with value: %s" % (desc, value)
+        self.vhal.setProperty(prop, area_id, value)
+
+    def _startGeneratorThread(self, generator):
+        thread = Thread(target=generator.generate, args=(self,))
+        thread.daemon = True
+        thread.start()
+
+    def run(self, timeout):
+        self._startGeneratorThread(user_action_generator.UserActionGenerator(self.vhal))
+        self._startGeneratorThread(driving_info_generator.DrivingInfoGenerator(self.gpxFile))
+        time.sleep(float(timeout))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Vhal Property Simulator')
+    parser.add_argument('-s', action='store', dest='deviceid', default=None)
+    parser.add_argument('--timeout', action='store', dest='timeout', default=DEFAULT_TIMEOUT_SEC)
+    parser.add_argument('--gpx', action='store', dest='gpxFile', default=None)
+    args = parser.parse_args()
+
+    simulator = VhalPropSimulator(device=args.deviceid, gpxFile=args.gpxFile)
+    simulator.run(args.timeout)
diff --git a/vehicle_monitor_service/Android.mk b/vehicle_monitor_service/Android.mk
deleted file mode 100644
index bd9f2ac..0000000
--- a/vehicle_monitor_service/Android.mk
+++ /dev/null
@@ -1,65 +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.
-#
-#
-LOCAL_PATH:= $(call my-dir)
-##################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
-    find . -name "*.cpp" -and -not -name ".*"))
-
-LOCAL_SRC_FILES := $(filter-out main_vehiclemonitor.cpp, $(LOCAL_SRC_FILES))
-
-LOCAL_C_INCLUDES += \
-    frameworks/base/include \
-    packages/services/Car/libvehiclemonitor/include
-
-LOCAL_SHARED_LIBRARIES := \
-    libbinder
-
-LOCAL_MODULE := libvehiclemonitorservice
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS  += -Werror
-
-include $(BUILD_STATIC_LIBRARY)
-
-##################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    main_vehiclemonitor.cpp
-
-LOCAL_C_INCLUDES += \
-    frameworks/base/include \
-    packages/services/Car/libvehiclemonitor/include
-
-LOCAL_SHARED_LIBRARIES := \
-    libbinder \
-    liblog \
-    libutils \
-    libvehiclemonitor-native
-
-LOCAL_WHOLE_STATIC_LIBRARIES := \
-    libvehiclemonitorservice
-
-LOCAL_STRIP_MODULE := keep_symbols
-
-LOCAL_MODULE := vehicle_monitor_service
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS  += -Werror
-
-include $(BUILD_EXECUTABLE)
diff --git a/vehicle_monitor_service/ProcessMonitor.cpp b/vehicle_monitor_service/ProcessMonitor.cpp
deleted file mode 100644
index 521473f..0000000
--- a/vehicle_monitor_service/ProcessMonitor.cpp
+++ /dev/null
@@ -1,290 +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.
- */
-#define LOG_TAG "ProcessMonitor"
-
-#include <mutex>
-#include <sys/param.h>
-#include <dirent.h>
-
-#include "ProcessMonitor.h"
-
-#define DBG_VERBOSE
-#ifdef DBG_VERBOSE
-#define LOG_VERBOSE(x...) ALOGD(x)
-#else
-#define LOG_VERBOSE(x...)
-#endif
-
-#define MAX_LINE 256
-#define SELF_IO  "/proc/self/io"
-#define NUM_PROC_DUMP 10
-
-namespace android {
-
-static bool procDeltaCpuCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
-                            const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
-    return a.second->delta_time > b.second->delta_time;
-}
-
-static bool procDeltaMemCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
-                            const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
-    return a.second->delta_rss > b.second->delta_rss;
-}
-
-static bool procDeltaWbytesCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
-                               const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
-    return a.second->delta_wbytes > b.second->delta_wbytes;
-}
-
-static bool procCpuCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
-                       const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
-    return (a.second->utime + a.second->utime) > (b.second->stime + b.second->stime);
-}
-
-static bool procMemCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
-                      const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
-    return a.second->rss > b.second->rss;
-}
-
-static bool procWbytesCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
-                      const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
-    return a.second->wbytes > b.second->wbytes;
-}
-
-ProcessMonitor::ProcessMonitor() {
-    //TODO: read config from policy files.
-    if (access(SELF_IO, F_OK) == -1) {
-        mIoSupported = false;
-        ALOGE("**** DISK I/O PROFILING DISABLED!!!!****\n"
-              "Kernel doesn't support I/O profiling.");
-    } else {
-        mIoSupported = true;
-    }
-}
-
-ProcessMonitor::~ProcessMonitor() {
-}
-
-void ProcessMonitor::dump(String8& msg) {
-    std::shared_lock<std::shared_timed_mutex> lock(mMutex);
-    msg.append("ProcessMonitor\n");
-    msg.appendFormat("Processes count: %d\n", (int) mProcInfoMap.size());
-    msg.append("Top CPU usage\n");
-    dumpTopProcesses(msg, procCpuCmp);
-    msg.append("Top CPU usage increase\n");
-    dumpTopProcesses(msg, procDeltaCpuCmp);
-    msg.append("Top memory usage\n");
-    dumpTopProcesses(msg, procMemCmp);
-    msg.append("Top memory usage increase\n");
-    dumpTopProcesses(msg, procDeltaMemCmp);
-    if (mIoSupported) {
-        msg.append("Top disk IO \n");
-        dumpTopProcesses(msg, procWbytesCmp);
-        msg.append("Top disk IO increase \n");
-        dumpTopProcesses(msg, procDeltaWbytesCmp);
-    } else {
-        msg.append("Disk IO monitoring not supported.\n");
-    }
-}
-
-void ProcessMonitor::dumpTopProcesses(
-        String8& msg,
-        bool (*procCmpFn) (
-                const std::pair<pid_t, const std::shared_ptr<ProcInfo>>,
-                const std::pair<pid_t, const std::shared_ptr<ProcInfo>>)) {
-
-    std::vector<std::pair<pid_t, std::shared_ptr<ProcInfo>>> topPids(NUM_PROC_DUMP);
-    std::partial_sort_copy(mProcInfoMap.begin(),
-                           mProcInfoMap.end(),
-                           topPids.begin(),
-                           topPids.end(),
-                           *procCmpFn);
-    for (auto it = topPids.begin(); it != topPids.end(); ++it) {
-        msg.appendFormat("(%s) PID: %d: delta_time: %" PRIu64 ", delta_rss: %" PRIu64 ", "
-                         "delta_wbytes: %" PRIu64 ", utime: %" PRIu64" , stime: %" PRIu64 ", "
-                         "rss: %" PRIu64 ", wbytes: %" PRIu64 "\n",
-                         it->second->name.c_str(),
-                         it->first,
-                         it->second->delta_time,
-                         it->second->delta_rss,
-                         it->second->delta_wbytes,
-                         it->second->utime,
-                         it->second->stime,
-                         it->second->rss,
-                         it->second->wbytes);
-    }
-
-}
-
-status_t ProcessMonitor::setAppPriority(uint32_t , uint32_t, uint32_t) {
-    std::unique_lock<std::shared_timed_mutex> lock(mMutex);
-    // TODO implement.
-    return NO_ERROR;
-}
-
-status_t ProcessMonitor::process() {
-    status_t status = updateProcessInfo();
-    if (status != NO_ERROR) {
-        return status;
-    }
-    return improveSystemHealth();
-}
-
-status_t ProcessMonitor::improveSystemHealth() {
-    // TODO: implement policy enforcer. kill apps that abuse system.
-    return NO_ERROR;
-}
-
-status_t ProcessMonitor::updateProcessInfo() {
-    std::unique_lock<std::shared_timed_mutex> lock(mMutex);
-    std::set<pid_t> oldPids;
-    populateExistingPids(oldPids);
-    DIR *procDir;
-    procDir = opendir("/proc");
-    if (!procDir) {
-        ALOGE("Failed to open /proc dir");
-        return PERMISSION_DENIED;
-    }
-    struct dirent *pidDir;
-    pid_t pid;
-    while ((pidDir = readdir(procDir))) {
-        if (!isdigit(pidDir->d_name[0])) {
-            continue;
-        }
-        pid = atoi(pidDir->d_name);
-        updateOrAddProcess(pid);
-        oldPids.erase(pid);
-    }
-    deleteOutdatedPids(oldPids);
-    return NO_ERROR;
-}
-
-void ProcessMonitor::deleteOutdatedPids(std::set<pid_t>& pidSet) {
-    for(auto it = pidSet.begin(); it != pidSet.end(); ++it) {
-        LOG_VERBOSE("Process %d ended. Removing from process map", *it);
-        mProcInfoMap.erase(*it);
-    }
-}
-
-void ProcessMonitor::populateExistingPids(std::set<pid_t>& pidSet) {
-    for(auto it = mProcInfoMap.begin(); it != mProcInfoMap.end(); ++it) {
-        pidSet.insert(it->first);
-    }
-}
-
-void ProcessMonitor::updateOrAddProcess(pid_t pid) {
-    auto pidDataIt = mProcInfoMap.find(pid);
-    std::shared_ptr<ProcInfo> pidData;
-    if (pidDataIt == mProcInfoMap.end()) {
-        pidData = std::make_shared<ProcInfo>();
-        mProcInfoMap.insert(std::pair<pid_t, std::shared_ptr<ProcInfo>>(pid, pidData));
-    } else {
-        pidData = pidDataIt->second;
-    }
-    auto originalPidData = std::make_shared<ProcInfo>(*pidData);
-    readStat(pidData, pid);
-    if (mIoSupported) {
-        readIo(pidData, pid);
-    }
-    readCmdline(pidData, pid);
-    readStatus(pidData, pid);
-    updateDiffs(pidData, originalPidData);
-}
-
-void ProcessMonitor::readStat(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
-    char filename[64];
-    sprintf(filename, "/proc/%d/stat", pid);
-    FILE *file;
-    file = fopen(filename, "r");
-    if (!file) {
-        ALOGD("Failed to open file %s for reading", filename);
-        return;
-    }
-    fscanf(file,
-           "%*d %*s %*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
-           "%" SCNu64
-           "%" SCNu64 "%*d %*d %*d %*d %*d %*d %*d "
-           "%*" SCNu64
-           "%" SCNu64 "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
-           "%*d",
-           &pidData->utime,
-           &pidData->stime,
-           &pidData->rss);
-    fclose(file);
-}
-
-void ProcessMonitor::readIo(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
-    char filename[64];
-    sprintf(filename, "/proc/%d/io", pid);
-    FILE *file;
-    file = fopen(filename, "r");
-    if (!file) {
-        ALOGD("Failed to open file %s for reading", filename);
-        return;
-    }
-    char buf[MAX_LINE];
-    while (fgets(buf, MAX_LINE, file)) {
-        sscanf(buf, "write_bytes: %" PRIu64, &pidData->wbytes);
-    }
-    fclose(file);
-}
-
-void ProcessMonitor::readCmdline(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
-    char filename[64];
-    sprintf(filename, "/proc/%d/cmdline", pid);
-    FILE *file;
-    file = fopen(filename, "r");
-    if (!file) {
-        ALOGD("Failed to open file %s for reading", filename);
-        return;
-    }
-    char buf[MAXPATHLEN];
-    fgets(buf, MAXPATHLEN, file);
-    fclose(file);
-    if (strlen(buf) > 0) {
-        pidData->name.assign(buf);
-    }
-}
-
-void ProcessMonitor::readStatus(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
-    char filename[64];
-    sprintf(filename, "/proc/%d/status", pid);
-    FILE *file;
-    file = fopen(filename, "r");
-    if (!file) {
-        ALOGD("Failed to open file %s for reading", filename);
-        return;
-    }
-    char line[MAX_LINE];
-    unsigned int uid;
-    while (fgets(line, MAX_LINE, file)) {
-        sscanf(line, "Uid: %u", &uid);
-    }
-    fclose(file);
-    pidData->uid = uid;
-
-}
-
-void ProcessMonitor::updateDiffs(std::shared_ptr<ProcInfo> pidData,
-                 std::shared_ptr<ProcInfo> oldPidData) {
-    pidData->delta_utime = pidData->utime - oldPidData->utime;
-    pidData->delta_stime = pidData->stime - oldPidData->stime;
-    pidData->delta_time = pidData->delta_utime + pidData->delta_stime;
-    pidData->delta_rss = pidData->rss - oldPidData->rss;
-    pidData->delta_wbytes = pidData->wbytes - oldPidData->wbytes;
-}
-
-}; // namespace android
diff --git a/vehicle_monitor_service/ProcessMonitor.h b/vehicle_monitor_service/ProcessMonitor.h
deleted file mode 100644
index d9f4d25..0000000
--- a/vehicle_monitor_service/ProcessMonitor.h
+++ /dev/null
@@ -1,94 +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.
- */
-#ifndef CAR_PROCESS_MONITOR_H_
-#define CAR_PROCESS_MONITOR_H_
-
-#include <map>
-#include <memory>
-#include <set>
-#include <string>
-#include <shared_mutex>
-
-#include <inttypes.h>
-#include <cutils/compiler.h>
-
-#include <binder/BinderService.h>
-#include <binder/IBinder.h>
-#include <utils/String8.h>
-
-#include <IVehicleMonitor.h>
-#include <HandlerThread.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-struct ProcInfo {
-    pid_t pid;
-    uid_t uid;
-    std::string name;
-    uint64_t utime;
-    uint64_t stime;
-    uint64_t rss;
-    uint64_t wbytes;
-    int64_t delta_utime;
-    int64_t delta_stime;
-    int64_t delta_time;
-    int64_t delta_rss;
-    int64_t delta_wbytes;
-};
-
-// ----------------------------------------------------------------------------
-
-// This class is used to collect information about running processes.
-// It also enforces certain policies to running apps - ex. kills non-system,
-// non-foreground applications that use too much memory, CPU, or write too much
-// data to disk.
-class ProcessMonitor {
-public:
-    ProcessMonitor();
-    ~ProcessMonitor();
-
-    void dump(String8& msg);
-    status_t setAppPriority(uint32_t pid, uint32_t uid, uint32_t priority);
-    status_t process();
-
-private:
-    status_t updateProcessInfo();
-    void populateExistingPids(std::set<pid_t>& pidSet);
-    void deleteOutdatedPids(std::set<pid_t>& pidSet);
-    void updateOrAddProcess(pid_t pid);
-    void readStat(std::shared_ptr<ProcInfo> pidData, pid_t pid);
-    void readIo(std::shared_ptr<ProcInfo> pidData, pid_t pid);
-    void readCmdline(std::shared_ptr<ProcInfo> pidData, pid_t pid);
-    void readStatus(std::shared_ptr<ProcInfo> pidData, pid_t pid);
-    void updateDiffs(std::shared_ptr<ProcInfo> pidData,
-                     std::shared_ptr<ProcInfo> oldPidData);
-    status_t improveSystemHealth();
-    void dumpTopProcesses(String8& msg,
-        bool (*procCmpFn) (
-                const std::pair<pid_t, const std::shared_ptr<ProcInfo>>,
-                const std::pair<pid_t, const std::shared_ptr<ProcInfo>>));
-
-
-private:
-    std::map<pid_t, std::shared_ptr<ProcInfo>> mProcInfoMap;
-    bool mIoSupported;
-    mutable std::shared_timed_mutex mMutex;
-};
-
-}
-
-#endif /* CAR_PROCESS_MONITOR_H_ */
diff --git a/vehicle_monitor_service/VehicleMonitorService.cpp b/vehicle_monitor_service/VehicleMonitorService.cpp
deleted file mode 100644
index 7368064..0000000
--- a/vehicle_monitor_service/VehicleMonitorService.cpp
+++ /dev/null
@@ -1,142 +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.
- */
-#define LOG_TAG "VehicleMonitor"
-
-#include <assert.h>
-
-#include <binder/PermissionCache.h>
-#include <utils/Errors.h>
-#include <utils/SystemClock.h>
-
-#include "VehicleMonitorService.h"
-
-//#define DBG_VERBOSE
-#ifdef DBG_VERBOSE
-#define LOG_VERBOSE(x...) ALOGD(x)
-#else
-#define LOG_VERBOSE(x...)
-#endif
-
-namespace android {
-
-const nsecs_t monitorInterval = 15000000000; // 15s
-
-VehicleMonitorMessageHandler::VehicleMonitorMessageHandler(const sp<Looper>& looper,
-        VehicleMonitorService& service)
-    : mLooper(looper),
-      mService(service),
-      mLastDispatchTime(0) {
-    mLooper->sendMessageDelayed(monitorInterval, this, Message(COLLECT_DATA));
-}
-
-VehicleMonitorMessageHandler::~VehicleMonitorMessageHandler() {
-}
-
-void VehicleMonitorMessageHandler::dump(String8& msg) {
-    msg.appendFormat("mLastDispatchTime:%" PRId64 "\n", mLastDispatchTime);
-    mProcessMonitor.dump(msg);
-}
-
-void VehicleMonitorMessageHandler::doHandleCollectData() {
-    {
-        std::lock_guard<std::mutex> autoLock(mLock);
-        mLastDispatchTime = elapsedRealtime();
-        mProcessMonitor.process();
-    }
-
-    // TODO: do better timing for sendMessage
-    mLooper->sendMessageDelayed(monitorInterval, this, Message(COLLECT_DATA));
-}
-
-void VehicleMonitorMessageHandler::handleMessage(const Message& message) {
-    switch (message.what) {
-    case COLLECT_DATA:
-        doHandleCollectData();
-        break;
-    default:
-        // TODO?
-        break;
-    }
-}
-
-// ----------------------------------------------------
-VehicleMonitorService* VehicleMonitorService::sInstance = NULL;
-
-status_t VehicleMonitorService::dump(int fd, const Vector<String16>& /*args*/) {
-    static const String16 sDump("android.permission.DUMP");
-    String8 msg;
-    if (!PermissionCache::checkCallingPermission(sDump)) {
-        msg.appendFormat("Permission Denial: "
-                         "can't dump VMS from pid=%d, uid=%d\n",
-                         IPCThreadState::self()->getCallingPid(),
-                         IPCThreadState::self()->getCallingUid());
-        write(fd, msg.string(), msg.size());
-        return NO_ERROR;
-    }
-    msg.appendFormat("*Handler, now in ms:%" PRId64 "\n", elapsedRealtime());
-    mHandler->dump(msg);
-    write(fd, msg.string(), msg.size());
-    return NO_ERROR;
-}
-
-VehicleMonitorService::VehicleMonitorService() {
-    sInstance = this;
-}
-
-VehicleMonitorService::~VehicleMonitorService() {
-    sInstance = NULL;
-}
-
-void VehicleMonitorService::binderDied(const wp<IBinder>& who) {
-    std::lock_guard<std::mutex> autoLock(mLock);
-    sp<IBinder> ibinder = who.promote();
-    ibinder->unlinkToDeath(this);
-    // TODO: reset all priorities set by CarService.
-}
-
-void VehicleMonitorService::release() {
-    std::lock_guard<std::mutex> autoLock(mLock);
-    mHandlerThread->quit();
-}
-
-void VehicleMonitorService::onFirstRef() {
-    std::lock_guard<std::mutex> autoLock(mLock);
-    mHandlerThread = new HandlerThread();
-    status_t r = mHandlerThread->start("VMS.NATIVE_LOOP");
-    if (r != NO_ERROR) {
-        ALOGE("cannot start handler thread, error:%d", r);
-        return;
-    }
-    sp<VehicleMonitorMessageHandler> handler(
-            new VehicleMonitorMessageHandler(mHandlerThread->getLooper(), *this));
-    assert(handler.get() != NULL);
-    mHandler = handler;
-}
-
-status_t VehicleMonitorService::setAppPriority(uint32_t, uint32_t, vehicle_app_priority) {
-    //TODO
-    return NO_ERROR;
-}
-
-status_t VehicleMonitorService::setMonitorListener(
-        const sp<IVehicleMonitorListener> &listener) {
-    sp<IBinder> ibinder = IInterface::asBinder(listener);
-    LOG_VERBOSE("setMonitorListener, binder 0x%x", ibinder.get());
-    //TODO
-    return NO_ERROR;
-}
-
-}; // namespace android
diff --git a/vehicle_monitor_service/VehicleMonitorService.h b/vehicle_monitor_service/VehicleMonitorService.h
deleted file mode 100644
index 57ade9d..0000000
--- a/vehicle_monitor_service/VehicleMonitorService.h
+++ /dev/null
@@ -1,99 +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.
- */
-#ifndef CAR_VEHICLE_MONITOR_SERVICE_H_
-#define CAR_VEHICLE_MONITOR_SERVICE_H_
-
-#include <mutex>
-#include <inttypes.h>
-#include <cutils/compiler.h>
-
-#include <binder/BinderService.h>
-#include <binder/IBinder.h>
-#include <utils/String8.h>
-
-#include <ProcessMonitor.h>
-#include <IVehicleMonitor.h>
-#include <IVehicleMonitorListener.h>
-#include <HandlerThread.h>
-
-namespace android {
-// ----------------------------------------------------------------------------
-
-class VehicleMonitorService;
-
-/**
- * MessageHandler to handle periodic data processing.
- * Init / release is handled in the handler thread to allow upper layer to
- * allocate resource for the thread.
- */
-class VehicleMonitorMessageHandler : public MessageHandler {
-    enum {
-        COLLECT_DATA = 0,
-    };
-
-public:
-    // not passing VMS as sp as this is held by VMS always.
-    VehicleMonitorMessageHandler(
-            const sp<Looper>& mLooper, VehicleMonitorService& service);
-    virtual ~VehicleMonitorMessageHandler();
-
-    void dump(String8& msg);
-
-private:
-    void handleMessage(const Message& message);
-    void doHandleCollectData();
-
-private:
-    mutable std::mutex mLock;
-    const sp<Looper> mLooper;
-    ProcessMonitor mProcessMonitor;
-    VehicleMonitorService& mService;
-    int64_t mLastDispatchTime;
-};
-
-// ----------------------------------------------------------------------------
-class VehicleMonitorService :
-    public BinderService<VehicleMonitorService>,
-    public BnVehicleMonitor,
-    public IBinder::DeathRecipient {
-public:
-    static const char* getServiceName() ANDROID_API {
-        return IVehicleMonitor::SERVICE_NAME;
-    };
-
-    VehicleMonitorService();
-    ~VehicleMonitorService();
-    virtual status_t dump(int fd, const Vector<String16>& args);
-    void release();
-    virtual void binderDied(const wp<IBinder>& who);
-    virtual status_t setAppPriority(
-            uint32_t pid, uint32_t uid, vehicle_app_priority priority);
-    virtual status_t setMonitorListener(
-            const sp<IVehicleMonitorListener> &listener);
-
-private:
-    // RefBase
-    virtual void onFirstRef();
-private:
-    static VehicleMonitorService* sInstance;
-    sp<HandlerThread> mHandlerThread;
-    sp<VehicleMonitorMessageHandler> mHandler;
-    mutable std::mutex mLock;
-};
-
-}
-
-#endif /* CAR_VEHICLE_MONITOR_SERVICE_H_ */
diff --git a/vehicle_monitor_service/main_vehiclemonitor.cpp b/vehicle_monitor_service/main_vehiclemonitor.cpp
deleted file mode 100644
index 2994a07..0000000
--- a/vehicle_monitor_service/main_vehiclemonitor.cpp
+++ /dev/null
@@ -1,37 +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.
- */
-
-#define LOG_TAG "VMS"
-
-#include <signal.h>
-
-#include <binder/IServiceManager.h>
-#include <utils/Log.h>
-
-#include "VehicleMonitorService.h"
-
-using namespace android;
-
-int main(int /*argc*/, char** /*argv*/)
-{
-    ALOGI("vehicle_monitor_service: starting");
-    signal(SIGPIPE, SIG_IGN);
-    // this will wait for service manager if not available yet.
-    sp<IServiceManager> sm = defaultServiceManager();
-    VehicleMonitorService::instantiate();
-    ProcessState::self()->startThreadPool();
-    IPCThreadState::self()->joinThreadPool();
-}