Merge "Script to automatically generate vhal_consts_ for use in the emulator"
diff --git a/car-lib/src/android/car/vms/VmsLayer.java b/car-lib/src/android/car/vms/VmsLayer.java
index 702daec..afd0ae7 100644
--- a/car-lib/src/android/car/vms/VmsLayer.java
+++ b/car-lib/src/android/car/vms/VmsLayer.java
@@ -24,8 +24,10 @@
 
 /**
  * A VMS Layer which can be subscribed to by VMS clients.
- * Consists of the layer ID and the layer version.
+ * Consists of the layer ID and the layer major version.
  *
+ * This class does not contain the minor version since all minor version are backward and forward
+ * compatible and should not be used for routing messages.
  * @hide
  */
 @FutureFeature
diff --git a/car-lib/src/android/car/vms/VmsLayerDependency.java b/car-lib/src/android/car/vms/VmsLayerDependency.java
index bb588eb..e14c7ec 100644
--- a/car-lib/src/android/car/vms/VmsLayerDependency.java
+++ b/car-lib/src/android/car/vms/VmsLayerDependency.java
@@ -80,6 +80,10 @@
             }
         };
 
+    public String toString() {
+        return "VmsLayerDependency{ Layer: " + mLayer + " Dependency: " + mDependency + "}";
+    }
+
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeParcelable(mLayer, flags);
diff --git a/car-lib/src/android/car/vms/VmsLayersOffering.java b/car-lib/src/android/car/vms/VmsLayersOffering.java
index 12b3dd3..51a0b99 100644
--- a/car-lib/src/android/car/vms/VmsLayersOffering.java
+++ b/car-lib/src/android/car/vms/VmsLayersOffering.java
@@ -55,6 +55,11 @@
         };
 
     @Override
+    public String toString() {
+        return "VmsLayersOffering{" + mDependencies+ "}";
+    }
+
+    @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeParcelableList(mDependencies, flags);
     }
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index 2743ff1..85fd2c2 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -106,6 +106,41 @@
         if (DBG) {
             Log.d(TAG, "Publishing for layer : " + layer);
         }
+
+        IBinder token = getTokenForPublisherServiceThreadSafe();
+
+        try {
+            mVmsPublisherService.publish(token, layer, payload);
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to publish message: " + payload, e);
+        }
+        return false;
+    }
+
+    /**
+     * Uses the VmsPublisherService binder to set the layers offering.
+     *
+     * @param offering the layers that the publisher may publish.
+     * @return if the call to VmsPublisherService.setLayersOffering was successful.
+     */
+    public final boolean setLayersOffering(VmsLayersOffering offering) {
+        if (DBG) {
+            Log.d(TAG, "Setting layers offering : " + offering);
+        }
+
+        IBinder token = getTokenForPublisherServiceThreadSafe();
+
+        try {
+            mVmsPublisherService.setLayersOffering(token, offering);
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "unable to set layers offering: " + offering, e);
+        }
+        return false;
+    }
+
+    private IBinder getTokenForPublisherServiceThreadSafe() {
         if (mVmsPublisherService == null) {
             throw new IllegalStateException("VmsPublisherService not set.");
         }
@@ -117,13 +152,7 @@
         if (token == null) {
             throw new IllegalStateException("VmsPublisherService does not have a valid token.");
         }
-        try {
-            mVmsPublisherService.publish(token, layer, payload);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "unable to publish message: " + payload, e);
-        }
-        return false;
+        return token;
     }
 
     /**
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
index 7c37b2d..640948a 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -162,8 +162,7 @@
      * @param layer the layer to subscribe to.
      * @throws IllegalStateException if the listener was not set via {@link #setListener}.
      */
-    public void subscribe(VmsLayer layer)
-            throws CarNotConnectedException {
+    public void subscribe(VmsLayer layer) throws CarNotConnectedException {
         if (DBG) {
             Log.d(TAG, "Subscribing to layer: " + layer);
         }
@@ -186,8 +185,7 @@
         }
     }
 
-    public void subscribeAll()
-        throws CarNotConnectedException {
+    public void subscribeAll() throws CarNotConnectedException {
         if (DBG) {
             Log.d(TAG, "Subscribing passively to all data messages");
         }
@@ -239,7 +237,7 @@
         }
     }
 
-    public void unsubscribeAll() throws CarNotConnectedException {
+    public void unsubscribeAll() {
         if (DBG) {
             Log.d(TAG, "Unsubscribing passively from all data messages");
         }
@@ -248,17 +246,17 @@
             listener = mListener;
         }
         if (listener == null) {
-            Log.w(TAG, "subscribe: listener was not set, " +
+            Log.w(TAG, "unsubscribeAll: listener was not set, " +
                     "setListener must be called first.");
             throw new IllegalStateException("Listener was not set.");
         }
         try {
             mVmsSubscriberService.removeVmsSubscriberClientPassiveListener(mIListener);
         } catch (RemoteException e) {
-            Log.e(TAG, "Could not connect: ", e);
-            throw new CarNotConnectedException(e);
+            Log.e(TAG, "Failed to unregister subscriber ", e);
+            // ignore
         } catch (IllegalStateException ex) {
-            Car.checkCarNotConnectedExceptionFromCarService(ex);
+            Car.hideCarNotConnectedExceptionFromCarService(ex);
         }
     }
 
diff --git a/car-usb-handler/res/values/strings.xml b/car-usb-handler/res/values/strings.xml
index 2242648..be1e7a2 100644
--- a/car-usb-handler/res/values/strings.xml
+++ b/car-usb-handler/res/values/strings.xml
@@ -26,4 +26,5 @@
     <string name="usb_pref_delete_yes">Yes</string>
     <string name="usb_pref_delete_cancel">Cancel</string>
     <string name="usb_resolving_handlers">Getting supported handlers</string>
+    <string name="usb_unknown_device">Unknown USB device</string>
 </resources>
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java
index 5084414..288f598 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceSettings.java
@@ -17,7 +17,6 @@
 
 import android.content.ComponentName;
 import android.hardware.usb.UsbDevice;
-import com.android.internal.util.Preconditions;
 
 /**
  * Settings for USB device.
@@ -34,8 +33,6 @@
     private boolean mDefaultHandler;
 
     UsbDeviceSettings(String serialNumber, int vid, int pid) {
-        Preconditions.checkNotNull(serialNumber);
-
         mSerialNumber = serialNumber;
         mVid = vid;
         mPid = pid;
@@ -96,7 +93,15 @@
      * Checks if setting matches {@code UsbDevice}.
      */
     public boolean matchesDevice(UsbDevice device) {
-        return getSerialNumber().equals(device.getSerialNumber());
+        String deviceSerial = device.getSerialNumber();
+        if (AoapInterface.isDeviceInAoapMode(device)) {
+            return mAoap && deviceSerial.equals(mSerialNumber);
+        } else if (deviceSerial == null) {
+            return mVid == device.getVendorId() && mPid == device.getProductId();
+        } else {
+            return mVid == device.getVendorId() && mPid == device.getProductId()
+                    && deviceSerial.equals(mSerialNumber);
+        }
     }
 
     /**
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
index b705928..5c61a98 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -68,10 +68,10 @@
         public void onReceive(Context context, Intent intent) {
             if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
                 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
-                unsetActiveDeviceIfSerialMatch(device);
+                unsetActiveDeviceIfMatch(device);
             } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
                 UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
-                setActiveDeviceIfSerialMatch(device);
+                setActiveDeviceIfMatch(device);
             }
         }
     };
@@ -79,9 +79,6 @@
     @GuardedBy("this")
     private UsbDevice mActiveDevice;
 
-    @GuardedBy("this")
-    private String mProcessingDeviceSerial;
-
     public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
         mContext = context;
         mCallback = callbacks;
@@ -96,17 +93,17 @@
 
     }
 
-    private synchronized void setActiveDeviceIfSerialMatch(UsbDevice device) {
-        if (device != null && device.getSerialNumber() != null
-                && device.getSerialNumber().equals(mProcessingDeviceSerial)) {
+    private synchronized void setActiveDeviceIfMatch(UsbDevice device) {
+        if (mActiveDevice != null && device != null
+                && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
             mActiveDevice = device;
         }
     }
 
-    private synchronized void unsetActiveDeviceIfSerialMatch(UsbDevice device) {
+    private synchronized void unsetActiveDeviceIfMatch(UsbDevice device) {
         mHandler.requestDeviceRemoved();
-        if (mActiveDevice != null && mActiveDevice.getSerialNumber() != null
-                && mActiveDevice.getSerialNumber().equals(device.getSerialNumber())) {
+        if (mActiveDevice != null && device != null
+                && UsbUtil.isDevicesMatching(device, mActiveDevice)) {
             mActiveDevice = null;
         }
     }
@@ -114,7 +111,6 @@
     private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
         if (mActiveDevice == null) {
             mActiveDevice = device;
-            mProcessingDeviceSerial = device.getSerialNumber();
             return true;
         }
         return false;
@@ -122,7 +118,6 @@
 
     private synchronized void stopDeviceProcessing() {
         mActiveDevice = null;
-        mProcessingDeviceSerial = null;
     }
 
     private synchronized UsbDevice getActiveDevice() {
@@ -131,8 +126,22 @@
 
     private boolean deviceMatchedActiveDevice(UsbDevice device) {
         UsbDevice activeDevice = getActiveDevice();
-        return activeDevice != null && activeDevice.getSerialNumber() != null
-                && activeDevice.getSerialNumber().equals(device.getSerialNumber());
+        return activeDevice != null && UsbUtil.isDevicesMatching(activeDevice, device);
+    }
+
+    private String generateTitle() {
+        String manufacturer = mActiveDevice.getManufacturerName();
+        String product = mActiveDevice.getProductName();
+        if (manufacturer == null && product == null) {
+            return mContext.getString(R.string.usb_unknown_device);
+        }
+        if (manufacturer != null && product != null) {
+            return manufacturer + " " + product;
+        }
+        if (manufacturer != null) {
+            return manufacturer;
+        }
+        return product;
     }
 
     /**
@@ -147,7 +156,7 @@
         mCallback.optionsUpdated(mEmptyList);
         mCallback.processingStateChanged(true);
 
-        UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device.getSerialNumber());
+        UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
         if (settings != null && mUsbResolver.dispatch(
                     mActiveDevice, settings.getHandler(), settings.getAoap())) {
             if (LOCAL_LOGV) {
@@ -156,7 +165,7 @@
             }
             return;
         }
-        mCallback.titleChanged(device.getManufacturerName() + " " + device.getProductName());
+        mCallback.titleChanged(generateTitle());
         mUsbResolver.resolve(device);
     }
 
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
index 157c92f..1b251f8 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
@@ -22,6 +22,7 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.hardware.usb.UsbDevice;
 import android.util.Log;
 import java.util.ArrayList;
 import java.util.List;
@@ -47,26 +48,43 @@
         mDbHelper = new UsbSettingsDbHelper(context);
     }
 
+    private Cursor queryFor(SQLiteDatabase db, UsbDevice device) {
+        String serial = device.getSerialNumber();
+        String selection;
+        String[] selectionArgs;
+        if (AoapInterface.isDeviceInAoapMode(device)) {
+            selection = COLUMN_SERIAL + " = ? AND " + COLUMN_AOAP + " = 1";
+            selectionArgs = new String[] {serial};
+        } else if (serial == null) {
+            selection = COLUMN_SERIAL + " IS NULL AND "
+                    + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?";
+            selectionArgs = new String[] {
+                    Integer.toString(device.getVendorId()),
+                    Integer.toString(device.getProductId())};
+        } else {
+            selection =
+                    COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?";
+            selectionArgs = new String[] {
+                    device.getSerialNumber(),
+                    Integer.toString(device.getVendorId()),
+                    Integer.toString(device.getProductId())};
+        }
+        return db.query(TABLE_USB_SETTINGS, null, selection, selectionArgs, null, null, null);
+    }
+
     /**
      * Returns settings for {@serialNumber} or null if it doesn't exist.
      */
     @Nullable
-    public UsbDeviceSettings getSettings(String serialNumber) {
+    public UsbDeviceSettings getSettings(UsbDevice device) {
         try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
-             Cursor resultCursor = db.query(
-                     TABLE_USB_SETTINGS,
-                     null,
-                     COLUMN_SERIAL + " = ?",
-                     new String[]{serialNumber},
-                     null,
-                     null,
-                     null)) {
+                Cursor resultCursor = queryFor(db, device)) {
             if (resultCursor.getCount() > 1) {
-                throw new RuntimeException("Querying for serial number: " + serialNumber
+                throw new RuntimeException("Querying for device: " + device
                         + " returned " + resultCursor.getCount() + " results");
             }
             if (resultCursor.getCount() == 0) {
-                Log.w(TAG, "Usb setting missing for device serial: " + serialNumber);
+                Log.w(TAG, "Usb setting missing for device: " + device);
                 return null;
             }
             List<UsbDeviceSettings> settings = constructSettings(resultCursor);
@@ -168,7 +186,7 @@
 
 
     private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
-        private static final int DATABASE_VERSION = 1;
+        private static final int DATABASE_VERSION = 2;
         private static final String DATABASE_NAME = "usb_devices.db";
 
         UsbSettingsDbHelper(Context context) {
@@ -177,20 +195,47 @@
 
         @Override
         public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + TABLE_USB_SETTINGS + " ("
+            createTable(db, TABLE_USB_SETTINGS);
+            createSerialIndex(db);
+        }
+
+        private void createTable(SQLiteDatabase db, String tableName) {
+            db.execSQL("CREATE TABLE " + tableName + " ("
                     + COLUMN_SERIAL + " TEXT,"
                     + COLUMN_VID + " INTEGER,"
                     + COLUMN_PID + " INTEGER,"
                     + COLUMN_NAME + " TEXT, "
                     + COLUMN_HANDLER + " TEXT,"
                     + COLUMN_AOAP + " INTEGER,"
-                    + COLUMN_DEFAULT_HANDLER + " INTEGER," + "PRIMARY KEY (" + COLUMN_SERIAL
+                    + COLUMN_DEFAULT_HANDLER + " INTEGER,"
+                    + "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", " + COLUMN_PID
                     + "))");
         }
 
+        private void createSerialIndex(SQLiteDatabase db) {
+            db.execSQL("CREATE INDEX " + TABLE_USB_SETTINGS + "_" + COLUMN_SERIAL + " ON "
+                    + TABLE_USB_SETTINGS + "(" + COLUMN_SERIAL + ")");
+        }
+
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            // Do nothing at this point. Not required for v1 database.
+            for (; oldVersion != newVersion; oldVersion++) {
+                switch (oldVersion) {
+                    case 1:
+                        String tempTableName = "temp_" + TABLE_USB_SETTINGS;
+                        createTable(db, tempTableName);
+                        db.execSQL("INSERT INTO " + tempTableName
+                                + " SELECT * FROM " + TABLE_USB_SETTINGS);
+                        db.execSQL("DROP TABLE " + TABLE_USB_SETTINGS);
+                        db.execSQL("ALTER TABLE " + tempTableName + " RENAME TO "
+                                + TABLE_USB_SETTINGS);
+                        createSerialIndex(db);
+                        break;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown database version " + oldVersion);
+                }
+            }
         }
     }
 }
diff --git a/evs/app/EvsStateControl.cpp b/evs/app/EvsStateControl.cpp
index abed1e5..ec29e39 100644
--- a/evs/app/EvsStateControl.cpp
+++ b/evs/app/EvsStateControl.cpp
@@ -21,7 +21,7 @@
 #include <stdio.h>
 #include <string.h>
 
-#include <android/log.h>
+#include <log/log.h>
 
 
 // TODO:  Seems like it'd be nice if the Vehicle HAL provided such helpers (but how & where?)
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
index bfdb9c0..577b798 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
@@ -72,6 +72,10 @@
         return true;
     }
 
+    public boolean isConnected() {
+        return mConnection.isConnected();
+    }
+
     static int toDigitValue(char c) {
         if ((c >= '0') && (c <= '9')) return c - '0';
         switch (c) {
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java b/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java
index 121b54a..3fffd22 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java
+++ b/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java
@@ -56,6 +56,16 @@
                                 mFloatCommands.add(
                                         Obd2Command.getLiveFrameCommand(
                                                 Obd2Command.getFloatCommand(pid))));
+        Log.i(
+                TAG,
+                String.format(
+                        "connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n"
+                                + "mIntegerCommands = %s\nmFloatCommands = %s\n",
+                        connectionPids,
+                        apiIntegerPids,
+                        apiFloatPids,
+                        mIntegerCommands,
+                        mFloatCommands));
     }
 
     public JsonWriter generate(JsonWriter jsonWriter) throws IOException {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index eae070c..55f93ca 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -47,16 +47,6 @@
     public static final String INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE =
             "system_activity_monitoring";
 
-    // load jni for all services here
-    static {
-        try {
-            System.loadLibrary("jni_car_service");
-        } catch (UnsatisfiedLinkError ex) {
-            // Unable to load native library when loaded from the testing framework.
-            Log.e(CarLog.TAG_SERVICE, "Failed to load jni_car_service library: " + ex.getMessage());
-        }
-    }
-
     private final Context mContext;
     private final VehicleHal mHal;
 
diff --git a/service/src/com/android/car/VmsLayersAvailability.java b/service/src/com/android/car/VmsLayersAvailability.java
index 5f5ac30..d6e89f2 100644
--- a/service/src/com/android/car/VmsLayersAvailability.java
+++ b/service/src/com/android/car/VmsLayersAvailability.java
@@ -83,7 +83,7 @@
     /**
      * Returns a collection of all the layers which may be published.
      */
-    public Collection<VmsLayer> getAvailableLayers() {
+    public Set<VmsLayer> getAvailableLayers() {
         synchronized (mLock) {
             return mAvailableLayers;
         }
@@ -93,7 +93,7 @@
      * Returns a collection of all the layers which publishers could have published if the
      * dependencies were satisfied.
      */
-    public Collection<VmsLayer> getUnavailableLayers() {
+    public Set<VmsLayer> getUnavailableLayers() {
         synchronized (mLock) {
             return mUnavailableLayers;
         }
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 8f7fba3..8bb0167 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -56,7 +56,6 @@
     private final Context mContext;
     private final VmsHalService mHal;
     private final VmsPublisherManager mPublisherManager;
-    private final Map<IBinder, VmsLayersOffering> mRawOffering = new HashMap<>();
 
     public VmsPublisherService(Context context, VmsHalService hal) {
         mContext = context;
@@ -96,12 +95,7 @@
 
     @Override
     public void setLayersOffering(IBinder token, VmsLayersOffering offering) {
-        // Store the raw dependencies
-        mRawOffering.put(token, offering);
-
-        //TODO(asafro): Calculate the new available layers
-
-        //TODO(asafro): Notify the subscribers that there is a change in availability
+        mHal.setPublisherLayersOffering(token, offering);
     }
 
     // Implements IVmsPublisherService interface.
diff --git a/service/src/com/android/car/VmsRouting.java b/service/src/com/android/car/VmsRouting.java
index fc2cbac..2829cc0 100644
--- a/service/src/com/android/car/VmsRouting.java
+++ b/service/src/com/android/car/VmsRouting.java
@@ -20,16 +20,14 @@
 import android.car.vms.IVmsSubscriberClient;
 import android.car.vms.VmsLayer;
 import android.car.vms.VmsSubscriptionState;
-
+import com.android.internal.annotations.GuardedBy;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import com.android.internal.annotations.GuardedBy;
-
 /**
  * Manages all the VMS subscriptions:
  * + Subscriptions to data messages of individual layer + version.
@@ -62,6 +60,7 @@
      * @param layer the layer subscribing to.
      */
     public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer) {
+        //TODO(b/36902947): revise if need to sync, and return value.
         synchronized (mLock) {
             ++mSequenceNumber;
             // Get or create the list of listeners for layer and version.
@@ -142,8 +141,8 @@
     }
 
     /**
-     * Returns all the listeners for a layer and version. This include the subscribers which
-     * explicitly subscribed to this layer and version and the promiscuous subscribers.
+     * Returns a list with all the listeners for a layer and version. This include the subscribers
+     * which explicitly subscribed to this layer and version and the promiscuous subscribers.
      *
      * @param layer to get listeners to.
      * @return a list of the listeners.
@@ -162,6 +161,21 @@
     }
 
     /**
+     * Returns a list with all the listeners.
+     */
+    public Set<IVmsSubscriberClient> getAllListeners() {
+        Set<IVmsSubscriberClient> listeners = new HashSet<>();
+        synchronized (mLock) {
+            for (VmsLayer layer : mLayerSubscriptions.keySet()) {
+                listeners.addAll(mLayerSubscriptions.get(layer));
+            }
+            // Add the promiscuous subscribers.
+            listeners.addAll(mPromiscuousSubscribers);
+        }
+        return listeners;
+    }
+
+    /**
      * Checks if a listener is subscribed to any messages.
      * @param listener that may have subscription.
      * @return true if the listener uis subscribed to messages.
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
index 97ed27f..eabb0a6 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -269,18 +269,13 @@
 
     // Implements VmsHalSubscriberListener interface
     @Override
-    public void onChange(VmsLayer layer, byte[] payload) {
+    public void onDataMessage(VmsLayer layer, byte[] payload) {
         if(DBG) {
             Log.d(TAG, "Publishing a message for layer: " + layer);
         }
 
         Set<IVmsSubscriberClient> listeners = mHal.getListeners(layer);
 
-        // If there are no listeners we're done.
-        if ((listeners == null)) {
-            return;
-        }
-
         for (IVmsSubscriberClient subscriber : listeners) {
             try {
                 subscriber.onVmsMessageReceived(layer, payload);
@@ -290,6 +285,24 @@
                 Log.e(TAG, "onVmsMessageReceived calling failed: ", e);
             }
         }
+    }
 
+    @Override
+    public void onLayersAvaiabilityChange(List<VmsLayer> availableLayers) {
+        if(DBG) {
+            Log.d(TAG, "Publishing layers availability change: " + availableLayers);
+        }
+
+        Set<IVmsSubscriberClient> listeners = mHal.getAllListeners();
+
+        for (IVmsSubscriberClient subscriber : listeners) {
+            try {
+                subscriber.onLayersAvailabilityChange(availableLayers);
+            } catch (RemoteException e) {
+                // If we could not send a record, its likely the connection snapped. Let the binder
+                // death handle the situation.
+                Log.e(TAG, "onLayersAvailabilityChange calling failed: ", e);
+            }
+        }
     }
 }
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index 84e3678..48bc7e9 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -114,8 +114,41 @@
     }
 
     @Override
+    public synchronized void init() {
+        for (int i = 0; i < mVehiclePropertyToConfig.size(); ++i) {
+            int propertyId = mVehiclePropertyToConfig.keyAt(i);
+            switch (propertyId) {
+                case VehicleProperty.OBD2_LIVE_FRAME:
+                case VehicleProperty.OBD2_FREEZE_FRAME:
+                case VehicleProperty.OBD2_FREEZE_FRAME_INFO:
+                    Log.i(CarLog.TAG_DIAGNOSTIC,
+                            String.format("subscribing to property 0x%x", propertyId));
+                    mHal.subscribeProperty(this, propertyId);
+                    break;
+                default:
+                    break;
+            }
+        }
+        super.init();
+    }
+
+    @Override
     public synchronized void release() {
         super.release();
+        for (int i = 0; i < mVehiclePropertyToConfig.size(); ++i) {
+            int propertyId = mVehiclePropertyToConfig.keyAt(i);
+            switch (propertyId) {
+                case VehicleProperty.OBD2_LIVE_FRAME:
+                case VehicleProperty.OBD2_FREEZE_FRAME:
+                case VehicleProperty.OBD2_FREEZE_FRAME_INFO:
+                    Log.i(CarLog.TAG_DIAGNOSTIC,
+                            String.format("unsubscribing from property 0x%x", propertyId));
+                    mHal.unsubscribeProperty(this, propertyId);
+                    break;
+                default:
+                    break;
+            }
+        }
         mDiagnosticCapabilities.clear();
     }
 
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index c23f36a..8a36117 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -22,24 +22,32 @@
 import android.car.annotation.FutureFeature;
 import android.car.vms.IVmsSubscriberClient;
 import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
 import android.car.vms.VmsSubscriptionState;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
-import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsBaseMessageIntegerValuesIndex;
 import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
-import android.os.SystemClock;
+import android.hardware.automotive.vehicle.V2_1.VmsOfferingMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsSimpleMessageIntegerValuesIndex;
+import android.os.Binder;
+import android.os.IBinder;
 import android.util.Log;
 import com.android.car.CarLog;
+import com.android.car.VmsLayersAvailability;
 import com.android.car.VmsRouting;
 import com.android.internal.annotations.GuardedBy;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -49,25 +57,27 @@
  */
 @FutureFeature
 public class VmsHalService extends HalServiceBase {
+
     private static final boolean DBG = true;
     private static final int HAL_PROPERTY_ID = VehicleProperty.VEHICLE_MAP_SERVICE;
     private static final String TAG = "VmsHalService";
-    private static final Set<Integer> SUPPORTED_MESSAGE_TYPES =
-        new HashSet<Integer>(
-            Arrays.asList(
-                VmsMessageType.SUBSCRIBE,
-                VmsMessageType.UNSUBSCRIBE,
-                VmsMessageType.DATA));
 
     private boolean mIsSupported = false;
     private CopyOnWriteArrayList<VmsHalPublisherListener> mPublisherListeners =
         new CopyOnWriteArrayList<>();
     private CopyOnWriteArrayList<VmsHalSubscriberListener> mSubscriberListeners =
         new CopyOnWriteArrayList<>();
+
+    private final IBinder mHalPublisherToken = new Binder();
     private final VehicleHal mVehicleHal;
-    @GuardedBy("mLock")
-    private VmsRouting mRouting = new VmsRouting();
-    private final Object mLock = new Object();
+
+    private final Object mRoutingLock = new Object();
+    private final VmsRouting mRouting = new VmsRouting();
+    private final Object mAvailabilityLock = new Object();
+    @GuardedBy("mAvailabilityLock")
+    private final Map<IBinder, VmsLayersOffering> mOfferings = new HashMap<>();
+    @GuardedBy("mAvailabilityLock")
+    private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
 
     /**
      * The VmsPublisherService implements this interface to receive data from the HAL.
@@ -80,7 +90,11 @@
      * The VmsSubscriberService implements this interface to receive data from the HAL.
      */
     public interface VmsHalSubscriberListener {
-        void onChange(VmsLayer layer, byte[] payload);
+        // Notify listener on a data Message.
+        void onDataMessage(VmsLayer layer, byte[] payload);
+
+        // Notify listener on a change in available layers.
+        void onLayersAvaiabilityChange(List<VmsLayer> availableLayers);
     }
 
     /**
@@ -110,22 +124,22 @@
     }
 
     public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer) {
-        synchronized (mLock) {
+        boolean firstSubscriptionForLayer = false;
+        synchronized (mRoutingLock) {
             // Check if publishers need to be notified about this change in subscriptions.
-            boolean firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
+            firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
 
             // Add the listeners subscription to the layer
             mRouting.addSubscription(listener, layer);
-
-            // Notify the publishers
-            if (firstSubscriptionForLayer) {
-                notifyPublishers(layer, true);
-            }
+        }
+        if (firstSubscriptionForLayer) {
+            notifyPublishers(layer, true);
         }
     }
 
     public void removeSubscription(IVmsSubscriberClient listener, VmsLayer layer) {
-        synchronized (mLock) {
+        boolean layerHasSubscribers = true;
+        synchronized (mRoutingLock) {
             if (!mRouting.hasLayerSubscriptions(layer)) {
                 Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
                 return;
@@ -135,67 +149,72 @@
             mRouting.removeSubscription(listener, layer);
 
             // Check if publishers need to be notified about this change in subscriptions.
-            boolean layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
-
-            // Notify the publishers
-            if (!layerHasSubscribers) {
-                notifyPublishers(layer, false);
-            }
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
+        }
+        if (!layerHasSubscribers) {
+            notifyPublishers(layer, false);
         }
     }
 
     public void addSubscription(IVmsSubscriberClient listener) {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             mRouting.addSubscription(listener);
         }
     }
 
     public void removeSubscription(IVmsSubscriberClient listener) {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             mRouting.removeSubscription(listener);
         }
     }
 
     public void removeDeadListener(IVmsSubscriberClient listener) {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             mRouting.removeDeadListener(listener);
         }
     }
 
     public Set<IVmsSubscriberClient> getListeners(VmsLayer layer) {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             return mRouting.getListeners(layer);
         }
     }
 
+    public Set<IVmsSubscriberClient> getAllListeners() {
+        synchronized (mRoutingLock) {
+            return mRouting.getAllListeners();
+        }
+    }
+
     public boolean isHalSubscribed(VmsLayer layer) {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             return mRouting.isHalSubscribed(layer);
         }
     }
 
     public VmsSubscriptionState getSubscriptionState() {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             return mRouting.getSubscriptionState();
         }
     }
 
     public void addHalSubscription(VmsLayer layer) {
-        synchronized (mLock) {
+        boolean firstSubscriptionForLayer = true;
+        synchronized (mRoutingLock) {
             // Check if publishers need to be notified about this change in subscriptions.
-            boolean firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
+            firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
 
             // Add the listeners subscription to the layer
             mRouting.addHalSubscription(layer);
-
-            if (firstSubscriptionForLayer) {
-                notifyPublishers(layer, true);
-            }
+        }
+        if (firstSubscriptionForLayer) {
+            notifyPublishers(layer, true);
         }
     }
 
     public void removeHalSubscription(VmsLayer layer) {
-        synchronized (mLock) {
+        boolean layerHasSubscribers = true;
+        synchronized (mRoutingLock) {
             if (!mRouting.hasLayerSubscriptions(layer)) {
                 Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
                 return;
@@ -205,21 +224,35 @@
             mRouting.removeHalSubscription(layer);
 
             // Check if publishers need to be notified about this change in subscriptions.
-            boolean layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
-
-            // Notify the publishers
-            if (!layerHasSubscribers) {
-                notifyPublishers(layer, false);
-            }
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
+        }
+        if (!layerHasSubscribers) {
+            notifyPublishers(layer, false);
         }
     }
 
     public boolean containsListener(IVmsSubscriberClient listener) {
-        synchronized (mLock) {
+        synchronized (mRoutingLock) {
             return mRouting.containsListener(listener);
         }
     }
 
+    public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering){
+        Set<VmsLayer> availableLayers = Collections.EMPTY_SET;
+        synchronized (mAvailabilityLock) {
+            updateOffering(publisherToken, offering);
+            availableLayers = mAvailableLayers.getAvailableLayers();
+        }
+        notifySubscribers(availableLayers);
+    }
+
+    public Set<VmsLayer> getAvailableLayers() {
+        //TODO(b/36872877): wrap available layers in VmsAvailabilityState similar to VmsSubscriptionState.
+        synchronized (mAvailabilityLock) {
+            return mAvailableLayers.getAvailableLayers();
+        }
+    }
+
     /**
      * Notify all the publishers and the HAL on subscription changes regardless of who triggered
      * the change.
@@ -227,18 +260,31 @@
      * @param layer          layer which is being subscribed to or unsubscribed from.
      * @param hasSubscribers indicates if the notification is for subscription or unsubscription.
      */
-    public void notifyPublishers(VmsLayer layer, boolean hasSubscribers) {
-        synchronized (mLock) {
-            // notify the HAL
-            setSubscriptionRequest(layer, hasSubscribers);
+    private void notifyPublishers(VmsLayer layer, boolean hasSubscribers) {
+        // notify the HAL
+        setSubscriptionRequest(layer, hasSubscribers);
 
-            // Notify the App publishers
-            for (VmsHalPublisherListener listener : mPublisherListeners) {
-                // Besides the list of layers, also a timestamp is provided to the clients.
-                // They should ignore any notification with a timestamp that is older than the most
-                // recent timestamp they have seen.
-                listener.onChange(getSubscriptionState());
-            }
+        // Notify the App publishers
+        for (VmsHalPublisherListener listener : mPublisherListeners) {
+            // Besides the list of layers, also a timestamp is provided to the clients.
+            // They should ignore any notification with a timestamp that is older than the most
+            // recent timestamp they have seen.
+            listener.onChange(getSubscriptionState());
+        }
+    }
+
+    /**
+     * Notify all the subscribers and the HAL on layers availability change.
+     *
+     * @param availableLayers the layers which publishers claim they made publish.
+     */
+    private void notifySubscribers(Set<VmsLayer> availableLayers) {
+        // notify the HAL
+        setAvailableLayers(availableLayers);
+
+        // Notify the App subscribers
+        for (VmsHalSubscriberListener listener : mSubscriberListeners) {
+            listener.onLayersAvaiabilityChange(new ArrayList<>(availableLayers));
         }
     }
 
@@ -288,42 +334,128 @@
         }
         for (VehiclePropValue v : values) {
             ArrayList<Integer> vec = v.value.int32Values;
-            int messageType = vec.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
-            int layerId = vec.get(VmsMessageIntegerValuesIndex.VMS_LAYER_ID);
-            int layerVersion = vec.get(VmsMessageIntegerValuesIndex.VMS_LAYER_VERSION);
-
-            // Check if message type is supported.
-            if (!SUPPORTED_MESSAGE_TYPES.contains(messageType)) {
-                throw new IllegalArgumentException("Unexpected message type. " +
-                    "Expecting: " + SUPPORTED_MESSAGE_TYPES +
-                    ". Got: " + messageType);
-
-            }
+            int messageType = vec.get(VmsBaseMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
 
             if (DBG) {
-                Log.d(TAG,
-                    "Received message for Type: " + messageType +
-                        " Layer Id: " + layerId +
-                        "Version: " + layerVersion);
+                Log.d(TAG, "Handling VMS message type: " + messageType);
             }
-            // This is a data message intended for subscribers.
-            if (messageType == VmsMessageType.DATA) {
-                // Get the payload.
-                byte[] payload = toByteArray(v.value.bytes);
-
-                // Send the message.
-                for (VmsHalSubscriberListener listener : mSubscriberListeners) {
-                    listener.onChange(new VmsLayer(layerId, layerVersion), payload);
-                }
-            } else if (messageType == VmsMessageType.SUBSCRIBE) {
-                addHalSubscription(new VmsLayer(layerId, layerVersion));
-            } else {
-                // messageType == VmsMessageType.UNSUBSCRIBE
-                removeHalSubscription(new VmsLayer(layerId, layerVersion));
+            switch(messageType) {
+                case VmsMessageType.DATA:
+                    handleDataEvent(vec, toByteArray(v.value.bytes));
+                    break;
+                case VmsMessageType.SUBSCRIBE:
+                    handleSubscribeEvent(vec);
+                    break;
+                case VmsMessageType.UNSUBSCRIBE:
+                    handleUnsubscribeEvent(vec);
+                    break;
+                case VmsMessageType.OFFERING:
+                    handleOfferingEvent(vec);
+                    break;
+                case VmsMessageType.AVAILABILITY:
+                    handleAvailabilityEvent();
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected message type: " + messageType);
             }
         }
     }
 
+    private void handleDataEvent(List<Integer> integerValues, byte[] payload) {
+        int layerId = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID);
+        int layerVersion = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION);
+        if (DBG) {
+            Log.d(TAG,
+                "Handling a data event for Layer Id: " + layerId +
+                    " Version: " + layerVersion);
+        }
+
+        // Send the message.
+        for (VmsHalSubscriberListener listener : mSubscriberListeners) {
+            listener.onDataMessage(new VmsLayer(layerId, layerVersion), payload);
+        }
+    }
+
+    private void handleSubscribeEvent(List<Integer> integerValues) {
+        int layerId = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID);
+        int layerVersion = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION);
+        if (DBG) {
+            Log.d(TAG,
+                "Handling a subscribe event for Layer Id: " + layerId +
+                    " Version: " + layerVersion);
+        }
+        addHalSubscription(new VmsLayer(layerId, layerVersion));
+    }
+
+    private void handleUnsubscribeEvent(List<Integer> integerValues) {
+        int layerId = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID);
+        int layerVersion = integerValues.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION);
+        if (DBG) {
+            Log.d(TAG,
+                "Handling an unsubscribe event for Layer Id: " + layerId +
+                    " Version: " + layerVersion);
+        }
+        removeHalSubscription(new VmsLayer(layerId, layerVersion));
+    }
+
+    private void handleOfferingEvent(List<Integer> integerValues) {
+        int numLayersDependencies =
+            integerValues.get(VmsOfferingMessageIntegerValuesIndex.VMS_NUMBER_OF_LAYERS_DEPENDENCIES);
+        int idx = VmsOfferingMessageIntegerValuesIndex.FIRST_DEPENDENCIES_INDEX;
+
+        List<VmsLayerDependency> offeredLayers = new ArrayList<>();
+
+        // An offering is layerId, LayerVersion, NumDeps, <LayerId, LayerVersion> X NumDeps.
+        for (int i = 0; i < numLayersDependencies; i++) {
+            int layerId = integerValues.get(idx++);
+            int layerVersion = integerValues.get(idx++);
+            VmsLayer offeredLayer = new VmsLayer(layerId, layerVersion);
+
+            int numDependenciesForLayer = integerValues.get(idx++);
+            if (numDependenciesForLayer == 0) {
+                offeredLayers.add(new VmsLayerDependency(offeredLayer));
+            } else {
+                Set<VmsLayer> dependencies = new HashSet<>();
+
+                for (int j = 0; j < numDependenciesForLayer; j++) {
+                    int dependantLayerId = integerValues.get(idx++);
+                    int dependantLayerVersion = integerValues.get(idx++);
+
+                    VmsLayer dependantLayer = new VmsLayer(dependantLayerId, dependantLayerVersion);
+                    dependencies.add(dependantLayer);
+                }
+                offeredLayers.add(new VmsLayerDependency(offeredLayer, dependencies));
+            }
+        }
+        // Store the HAL offering.
+        VmsLayersOffering offering = new VmsLayersOffering(offeredLayers);
+        synchronized (mAvailabilityLock) {
+            updateOffering(mHalPublisherToken, offering);
+        }
+    }
+
+    private void handleAvailabilityEvent() {
+        synchronized (mAvailabilityLock) {
+            Collection<VmsLayer> availableLayers = mAvailableLayers.getAvailableLayers();
+            VehiclePropValue vehiclePropertyValue = toVehiclePropValue(
+                VmsMessageType.AVAILABILITY, availableLayers);
+            setPropertyValue(vehiclePropertyValue);
+        }
+    }
+
+    private void updateOffering(IBinder publisherToken, VmsLayersOffering offering) {
+        Set<VmsLayer> availableLayers = Collections.EMPTY_SET;
+        synchronized (mAvailabilityLock) {
+            mOfferings.put(publisherToken, offering);
+
+            // Update layers availability.
+            mAvailableLayers.setPublishersOffering(mOfferings.values());
+
+            availableLayers = mAvailableLayers.getAvailableLayers();
+        }
+        notifySubscribers(availableLayers);
+    }
+
     @Override
     public void dump(PrintWriter writer) {
         writer.println(TAG);
@@ -339,14 +471,21 @@
      */
     public boolean setSubscriptionRequest(VmsLayer layer, boolean hasSubscribers) {
         VehiclePropValue vehiclePropertyValue = toVehiclePropValue(
-                hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, layer);
+            hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, layer);
         return setPropertyValue(vehiclePropertyValue);
     }
 
     public boolean setDataMessage(VmsLayer layer, byte[] payload) {
         VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.DATA,
-                layer,
-                payload);
+            layer,
+            payload);
+        return setPropertyValue(vehiclePropertyValue);
+    }
+
+    public boolean setAvailableLayers(Collection<VmsLayer> availableLayers) {
+        VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.AVAILABILITY,
+            availableLayers);
+
         return setPropertyValue(vehiclePropertyValue);
     }
 
@@ -361,13 +500,20 @@
     }
 
     /** Creates a {@link VehiclePropValue} */
-    private static VehiclePropValue toVehiclePropValue(int messageType, VmsLayer layer) {
+    private static VehiclePropValue toVehiclePropValue(int messageType) {
         VehiclePropValue vehicleProp = new VehiclePropValue();
         vehicleProp.prop = HAL_PROPERTY_ID;
         vehicleProp.areaId = VehicleAreaType.VEHICLE_AREA_TYPE_NONE;
         VehiclePropValue.RawValue v = vehicleProp.value;
 
         v.int32Values.add(messageType);
+        return vehicleProp;
+    }
+
+    /** Creates a {@link VehiclePropValue} */
+    private static VehiclePropValue toVehiclePropValue(int messageType, VmsLayer layer) {
+        VehiclePropValue vehicleProp = toVehiclePropValue(messageType);
+        VehiclePropValue.RawValue v = vehicleProp.value;
         v.int32Values.add(layer.getId());
         v.int32Values.add(layer.getVersion());
         return vehicleProp;
@@ -375,8 +521,8 @@
 
     /** Creates a {@link VehiclePropValue} with payload */
     private static VehiclePropValue toVehiclePropValue(int messageType,
-            VmsLayer layer,
-            byte[] payload) {
+        VmsLayer layer,
+        byte[] payload) {
         VehiclePropValue vehicleProp = toVehiclePropValue(messageType, layer);
         VehiclePropValue.RawValue v = vehicleProp.value;
         v.bytes.ensureCapacity(payload.length);
@@ -385,4 +531,18 @@
         }
         return vehicleProp;
     }
-}
\ No newline at end of file
+
+    /** Creates a {@link VehiclePropValue} with payload */
+    private static VehiclePropValue toVehiclePropValue(int messageType,
+        Collection<VmsLayer> layers) {
+        VehiclePropValue vehicleProp = toVehiclePropValue(messageType);
+        VehiclePropValue.RawValue v = vehicleProp.value;
+        int numLayers = layers.size();
+        v.int32Values.add(numLayers);
+        for (VmsLayer layer : layers) {
+            v.int32Values.add(layer.getId());
+            v.int32Values.add(layer.getVersion());
+        }
+        return vehicleProp;
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index ae4a0aa..98bfc21 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -197,8 +197,7 @@
         };
 
         public DrawerAdapter() {
-            super(KitchenSinkActivity.this, true /* showDisabledOnListOnEmpty */,
-                    true /* smallLayout */);
+            super(KitchenSinkActivity.this, true /* showDisabledOnListOnEmpty */);
             setTitle(getString(R.string.app_title));
         }
 
diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java
index fbea6d2..cc73421 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientMockService.java
@@ -18,8 +18,12 @@
 
 import android.car.annotation.FutureFeature;
 import android.car.vms.VmsLayer;
+import android.car.vms.VmsLayerDependency;
+import android.car.vms.VmsLayersOffering;
 import android.car.vms.VmsPublisherClientService;
 import android.car.vms.VmsSubscriptionState;
+import java.util.List;
+import java.util.ArrayList;
 
 /**
  * This service is launched during the tests in VmsPublisherSubscriberTest. It publishes a property
@@ -36,12 +40,14 @@
     public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
         // Case when the publisher finished initialization before the subscription request.
         publishIfNeeded(subscriptionState);
+        declareOffering(subscriptionState);
     }
 
     @Override
     public void onVmsPublisherServiceReady() {
         // Case when the subscription request was sent before the publisher was ready.
-        publishIfNeeded(getSubscriptions());
+        VmsSubscriptionState subscriptionState = getSubscriptions();
+        publishIfNeeded(subscriptionState);
     }
 
     private void publishIfNeeded(VmsSubscriptionState subscriptionState) {
@@ -51,4 +57,13 @@
             }
         }
     }
+
+    private void declareOffering(VmsSubscriptionState subscriptionState) {
+        List<VmsLayerDependency> dependencies = new ArrayList<>();
+        for( VmsLayer layer : subscriptionState.getLayers()) {
+            dependencies.add(new VmsLayerDependency(layer));
+        }
+        VmsLayersOffering offering = new VmsLayersOffering(dependencies);
+        setLayersOffering(offering);
+    }
 }
diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
index aa1431e..c22f63a 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
@@ -28,7 +28,8 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
 import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
-import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsBaseMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsSimpleMessageIntegerValuesIndex;
 import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
@@ -139,9 +140,9 @@
         //      the semaphore will not be released.
         assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
         VehiclePropValue.RawValue rawValue = mHalHandler.getValue().value;
-        int messageType = rawValue.int32Values.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
-        int layerId = rawValue.int32Values.get(VmsMessageIntegerValuesIndex.VMS_LAYER_ID);
-        int layerVersion = rawValue.int32Values.get(VmsMessageIntegerValuesIndex.VMS_LAYER_VERSION);
+        int messageType = rawValue.int32Values.get(VmsSimpleMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
+        int layerId = rawValue.int32Values.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_ID);
+        int layerVersion = rawValue.int32Values.get(VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_VERSION);
         byte[] payload = new byte[rawValue.bytes.size()];
         for (int i = 0; i < rawValue.bytes.size(); ++i) {
             payload[i] = rawValue.bytes.get(i);
@@ -161,7 +162,7 @@
 
             // If this is the data message release the semaphone so the test can continue.
             ArrayList<Integer> int32Values = value.value.int32Values;
-            if (int32Values.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE) ==
+            if (int32Values.get(VmsBaseMessageIntegerValuesIndex.VMS_MESSAGE_TYPE) ==
                     VmsMessageType.DATA) {
                 mHalHandlerSemaphore.release();
             }
diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java
index e86b3fa..20fa0b6 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherSubscriberTest.java
@@ -32,7 +32,9 @@
 
 import com.android.car.vehiclehal.test.MockedVehicleHal;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -45,10 +47,12 @@
 
     public static final VmsLayer LAYER = new VmsLayer(LAYER_ID, LAYER_VERSION);
     public static final byte[] PAYLOAD = new byte[]{2, 3, 5, 7, 11, 13, 17};
+    private static final List<VmsLayer> AVAILABLE_LAYERS = new ArrayList<>(Arrays.asList(LAYER));
 
     private HalHandler mHalHandler;
     // Used to block until a value is propagated to the TestListener.onVmsMessageReceived.
     private Semaphore mSubscriberSemaphore;
+    private Semaphore mAvailabilitySemaphore;
 
     @Override
     protected synchronized void configureMockedHal() {
@@ -92,6 +96,7 @@
         if (!VmsTestUtils.canRunTest(TAG)) return;
         super.setUp();
         mSubscriberSemaphore = new Semaphore(0);
+        mAvailabilitySemaphore = new Semaphore(0);
     }
 
     @Override
@@ -121,12 +126,30 @@
         assertTrue(Arrays.equals(PAYLOAD, listener.getPayload()));
     }
 
+    /**
+     * The Mock service offers all the subscribed layers as available layers, so in this
+     * test the listener subscribes to a layer and verifies that it gets the notification that it
+     * is available.
+     */
+    public void testAvailability() throws Exception {
+        if (!VmsTestUtils.canRunTest(TAG)) return;
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+            Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(LAYER);
+
+        assertTrue(mAvailabilitySemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(AVAILABLE_LAYERS, listener.getAvailalbeLayers());
+    }
+
     private class HalHandler implements MockedVehicleHal.VehicleHalPropertyHandler {
     }
 
     private class TestListener implements VmsSubscriberManager.VmsSubscriberClientListener {
         private VmsLayer mLayer;
         private byte[] mPayload;
+        private List<VmsLayer> mAvailableLayers;
 
         @Override
         public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
@@ -139,9 +162,9 @@
 
         @Override
         public void onLayersAvailabilityChange(List<VmsLayer> availableLayers) {
-            //TODO(asafro): test availability changes on publisher update when logic is implemented.
-            //  for that need to add Offering support in VmsPublisherClientService
-            //  and update VmsPublisherClientMockService
+            assertEquals(AVAILABLE_LAYERS, availableLayers);
+            mAvailableLayers = availableLayers;
+            mAvailabilitySemaphore.release();
         }
 
         @Override
@@ -156,5 +179,9 @@
         public byte[] getPayload() {
             return mPayload;
         }
+
+        public List<VmsLayer> getAvailalbeLayers() {
+            return mAvailableLayers;
+        }
     }
 }
diff --git a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
index 79cddad..063d5cf 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Semaphore;
@@ -46,6 +47,20 @@
     private static final VmsLayer SUBSCRIPTION_LAYER = new VmsLayer(SUBSCRIPTION_LAYER_ID,
             SUBSCRIPTION_LAYER_VERSION);
 
+    private static final int SUBSCRIPTION_DEPENDANT_LAYER_ID_1 = 4;
+    private static final int SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1 = 5;
+    private static final VmsLayer SUBSCRIPTION_DEPENDANT_LAYER_1 =
+        new VmsLayer(SUBSCRIPTION_DEPENDANT_LAYER_ID_1, SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1);
+
+    private static final int SUBSCRIPTION_DEPENDANT_LAYER_ID_2 = 6;
+    private static final int SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2 = 7;
+    private static final VmsLayer SUBSCRIPTION_DEPENDANT_LAYER_2 =
+        new VmsLayer(SUBSCRIPTION_DEPENDANT_LAYER_ID_2, SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2);
+
+    private static final int SUBSCRIPTION_UNSUPPORTED_LAYER_ID = 100;
+    private static final int SUBSCRIPTION_UNSUPPORTED_LAYER_VERSION = 200;
+
+
     private HalHandler mHalHandler;
     // Used to block until the HAL property is updated in HalHandler.onPropertySet.
     private Semaphore mHalHandlerSemaphore;
@@ -132,6 +147,120 @@
         assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
     }
 
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSimpleAvailableLayers() throws Exception {
+        if (!VmsTestUtils.canRunTest(TAG)) return;
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+            Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+
+        // Inject a value and wait for its callback in TestListener.onLayersAvailabilityChange.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+            .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+            .setTimestamp(SystemClock.elapsedRealtimeNanos())
+            .build();
+        /*
+        Offering:
+        Layer  | Dependency
+        ====================
+        (2, 3) | {}
+
+        Expected availability:
+        {(2, 3)}
+         */
+        v.value.int32Values.addAll(
+            Arrays.asList(
+                VmsMessageType.OFFERING, // MessageType
+                1, // Number of offered layers
+
+                SUBSCRIPTION_LAYER_ID,
+                SUBSCRIPTION_LAYER_VERSION,
+                0 // number of dependencies for layer
+            )
+        );
+
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        List<VmsLayer> expectedAvailableLayers = new ArrayList<>(Arrays.asList(SUBSCRIPTION_LAYER));
+
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(expectedAvailableLayers, listener.getAvailableLayers());
+    }
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testComplexAvailableLayers() throws Exception {
+        if (!VmsTestUtils.canRunTest(TAG)) return;
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+            Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+
+        // Inject a value and wait for its callback in TestListener.onLayersAvailabilityChange.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+            .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+            .setTimestamp(SystemClock.elapsedRealtimeNanos())
+            .build();
+        /*
+        Offering:
+        Layer  | Dependency
+        ====================
+        (2, 3) | {}
+        (4, 5) | {(2, 3)}
+        (6, 7) | {(2, 3), (4, 5)}
+        (6, 7) | {(100, 200)}
+
+        Expected availability:
+        {(2, 3), (4, 5), (6, 7)}
+         */
+
+        v.value.int32Values.addAll(
+            Arrays.asList(
+                VmsMessageType.OFFERING, // MessageType
+                4, // Number of offered layers
+
+                SUBSCRIPTION_LAYER_ID,
+                SUBSCRIPTION_LAYER_VERSION,
+                0, // number of dependencies for layer
+
+                SUBSCRIPTION_DEPENDANT_LAYER_ID_1,
+                SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1,
+                1, // number of dependencies for layer
+                SUBSCRIPTION_LAYER_ID,
+                SUBSCRIPTION_LAYER_VERSION,
+
+                SUBSCRIPTION_DEPENDANT_LAYER_ID_2,
+                SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2,
+                2, // number of dependencies for layer
+                SUBSCRIPTION_LAYER_ID,
+                SUBSCRIPTION_LAYER_VERSION,
+                SUBSCRIPTION_DEPENDANT_LAYER_ID_1,
+                SUBSCRIPTION_DEPENDANT_LAYER_VERSION_1,
+
+                SUBSCRIPTION_DEPENDANT_LAYER_ID_2,
+                SUBSCRIPTION_DEPENDANT_LAYER_VERSION_2,
+                1, // number of dependencies for layer
+                SUBSCRIPTION_UNSUPPORTED_LAYER_ID,
+                SUBSCRIPTION_UNSUPPORTED_LAYER_VERSION
+            )
+        );
+
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        List<VmsLayer> expectedAvailableLayers =
+            new ArrayList<>(Arrays.asList(SUBSCRIPTION_LAYER,
+                SUBSCRIPTION_DEPENDANT_LAYER_1,
+                SUBSCRIPTION_DEPENDANT_LAYER_2));
+
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(expectedAvailableLayers, listener.getAvailableLayers());
+    }
+
     private class HalHandler implements VehicleHalPropertyHandler {
         private VehiclePropValue mValue;
 
@@ -165,6 +294,7 @@
     private class TestListener implements VmsSubscriberClientListener{
         private VmsLayer mLayer;
         private byte[] mPayload;
+        private List<VmsLayer> mAvailableLayers = new ArrayList<>();
 
         @Override
         public void onVmsMessageReceived(VmsLayer layer, byte[] payload) {
@@ -177,6 +307,8 @@
         @Override
         public void onLayersAvailabilityChange(List<VmsLayer> availableLayers) {
             Log.d(TAG, "onLayersAvailabilityChange: Layers: " + availableLayers);
+            mAvailableLayers.addAll(availableLayers);
+            mSubscriberSemaphore.release();
         }
 
         @Override
@@ -191,5 +323,9 @@
         public byte[] getPayload() {
             return mPayload;
         }
+
+        public List<VmsLayer> getAvailableLayers() {
+            return mAvailableLayers;
+        }
     }
 }
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 c092566..78f13e3 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -17,6 +17,7 @@
 package com.android.car;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 import com.android.car.CarPowerManagementService.PowerEventProcessingHandler;
@@ -27,6 +28,7 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+@SmallTest
 public class CarPowerManagementServiceTest extends AndroidTestCase {
     private static final String TAG = CarPowerManagementServiceTest.class.getSimpleName();
     private static final long WAIT_TIMEOUT_MS = 2000;
diff --git a/tests/carservice_unit_test/src/com/android/car/CrashTrackerTest.java b/tests/carservice_unit_test/src/com/android/car/CrashTrackerTest.java
index 1ac320f..0737b9b 100644
--- a/tests/carservice_unit_test/src/com/android/car/CrashTrackerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CrashTrackerTest.java
@@ -17,10 +17,12 @@
 
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.MutableBoolean;
 
 import com.android.car.CarService.CrashTracker;
 
+@SmallTest
 public class CrashTrackerTest extends AndroidTestCase {
 
     public void testCrashingTooManyTimes() throws InterruptedException {
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java b/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
index 07a0122..d6fd68d 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsLayersAvailabilityTest.java
@@ -20,11 +20,14 @@
 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, 2);
@@ -62,6 +65,15 @@
         super.setUp();
     }
 
+    public void testNoOffering() {
+        assertTrue(mLayersAvailability.getAvailableLayers().isEmpty());
+    }
+
+    public void testEmptyOffering() {
+        mLayersAvailability.setPublishersOffering(Collections.EMPTY_LIST);
+        assertTrue(mLayersAvailability.getAvailableLayers().isEmpty());
+    }
+
     public void testSingleLayerNoDeps() throws Exception {
         Set<VmsLayer> expectedAvailableLayers = new HashSet<>();
         expectedAvailableLayers.add(LAYER_X);
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java b/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
index 4ca7096..0ff1c85 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsRoutingTest.java
@@ -20,11 +20,13 @@
 import android.car.vms.VmsLayer;
 import android.car.vms.VmsSubscriptionState;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-
+@SmallTest
 public class VmsRoutingTest extends AndroidTestCase {
     private static VmsLayer LAYER_WITH_SUBSCRIPTION_1= new VmsLayer(1, 2);
     private static VmsLayer LAYER_WITH_SUBSCRIPTION_2= new VmsLayer(1, 3);
diff --git a/tests/obd2_app/Android.mk b/tests/obd2_app/Android.mk
new file mode 100644
index 0000000..7e1e22f
--- /dev/null
+++ b/tests/obd2_app/Android.mk
@@ -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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := Obd2App
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_STATIC_JAVA_LIBRARIES += \
+        com.android.car.obd2 \
+
+LOCAL_JAVA_VERSION := 1.8
+
+include $(BUILD_PACKAGE)
diff --git a/tests/obd2_app/AndroidManifest.xml b/tests/obd2_app/AndroidManifest.xml
new file mode 100644
index 0000000..184d939
--- /dev/null
+++ b/tests/obd2_app/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.car.obd2app">
+
+  <uses-permission android:name="android.permission.BLUETOOTH"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+  <application
+      android:allowBackup="true"
+      android:debuggable="true"
+      android:icon="@mipmap/ic_launcher"
+      android:label="@string/app_name"
+      android:supportsRtl="true"
+      android:theme="@style/AppTheme">
+    <activity android:name=".MainActivity">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+    <activity android:name=".SettingsActivity"/>
+  </application>
+
+</manifest>
diff --git a/tests/obd2_app/res/drawable/ic_info_black_24dp.xml b/tests/obd2_app/res/drawable/ic_info_black_24dp.xml
new file mode 100644
index 0000000..b9139d1
--- /dev/null
+++ b/tests/obd2_app/res/drawable/ic_info_black_24dp.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/>
+</vector>
diff --git a/tests/obd2_app/res/drawable/ic_notifications_black_24dp.xml b/tests/obd2_app/res/drawable/ic_notifications_black_24dp.xml
new file mode 100644
index 0000000..486956c
--- /dev/null
+++ b/tests/obd2_app/res/drawable/ic_notifications_black_24dp.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/>
+</vector>
diff --git a/tests/obd2_app/res/drawable/ic_sync_black_24dp.xml b/tests/obd2_app/res/drawable/ic_sync_black_24dp.xml
new file mode 100644
index 0000000..8511efa
--- /dev/null
+++ b/tests/obd2_app/res/drawable/ic_sync_black_24dp.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z"/>
+</vector>
\ No newline at end of file
diff --git a/tests/obd2_app/res/layout/activity_main.xml b/tests/obd2_app/res/layout/activity_main.xml
new file mode 100644
index 0000000..29d7bd2
--- /dev/null
+++ b/tests/obd2_app/res/layout/activity_main.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    tools:context="com.google.android.car.obd2app.MainActivity">
+
+  <TextView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignParentTop="true"
+      android:layout_alignParentStart="true"
+      android:id="@+id/statusBar"
+      android:layout_alignParentEnd="true"
+      android:text="Nothing to say"
+      android:minLines="10"/>
+  <Button
+      android:text="Connect"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignParentBottom="true"
+      android:layout_alignParentEnd="true"
+      android:id="@+id/connection"/>
+  <Button
+      android:text="Settings"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:id="@+id/settings"
+      android:layout_alignParentBottom="true"
+      android:layout_alignParentStart="true"
+      android:onClick="doSettings"/>
+</RelativeLayout>
diff --git a/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png b/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/obd2_app/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/obd2_app/res/values-w820dp/dimens.xml b/tests/obd2_app/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..1da9658
--- /dev/null
+++ b/tests/obd2_app/res/values-w820dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+  <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+  <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/obd2_app/res/values/arrays.xml b/tests/obd2_app/res/values/arrays.xml
new file mode 100644
index 0000000..2bd7ce3
--- /dev/null
+++ b/tests/obd2_app/res/values/arrays.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+  <string-array name="scan_delay_entries">
+    <item>2 seconds</item>
+    <item>5 seconds</item>
+    <item>10 seconds</item>
+  </string-array>
+  <string-array name="scan_delay_entryValues">
+    <item>2</item>
+    <item>5</item>
+    <item>10</item>
+  </string-array>
+</resources>
diff --git a/tests/obd2_app/res/values/colors.xml b/tests/obd2_app/res/values/colors.xml
new file mode 100644
index 0000000..49a370a
--- /dev/null
+++ b/tests/obd2_app/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+  <color name="colorPrimary">#3F51B5</color>
+  <color name="colorPrimaryDark">#303F9F</color>
+  <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/obd2_app/res/values/dimens.xml b/tests/obd2_app/res/values/dimens.xml
new file mode 100644
index 0000000..261477e
--- /dev/null
+++ b/tests/obd2_app/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+  <!-- Default screen margins, per the Android Design guidelines. -->
+  <dimen name="activity_horizontal_margin">16dp</dimen>
+  <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/tests/obd2_app/res/values/strings.xml b/tests/obd2_app/res/values/strings.xml
new file mode 100644
index 0000000..c615c4f
--- /dev/null
+++ b/tests/obd2_app/res/values/strings.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+  <string name="app_name">Obd2App</string>
+  <string name="title_activity_settings">Settings</string>
+
+  <!-- Strings related to Settings -->
+
+  <!-- Example General settings -->
+  <string name="pref_header_general">General</string>
+
+  <string name="pref_title_social_recommendations">Enable social recommendations</string>
+  <string name="pref_description_social_recommendations">Recommendations for people to contact based
+    on your message history
+  </string>
+
+  <string name="pref_title_display_name">Display name</string>
+  <string name="pref_default_display_name">John Smith</string>
+
+  <string name="pref_title_add_friends_to_messages">Add friends to messages</string>
+  <string-array name="pref_example_list_titles">
+    <item>Always</item>
+    <item>When possible</item>
+    <item>Never</item>
+  </string-array>
+  <string-array name="pref_example_list_values">
+    <item>1</item>
+    <item>0</item>
+    <item>-1</item>
+  </string-array>
+
+  <!-- Example settings for Data & Sync -->
+  <string name="pref_header_data_sync">Data &amp; sync</string>
+
+  <string name="pref_title_sync_frequency">Sync frequency</string>
+  <string-array name="pref_sync_frequency_titles">
+    <item>15 minutes</item>
+    <item>30 minutes</item>
+    <item>1 hour</item>
+    <item>3 hours</item>
+    <item>6 hours</item>
+    <item>Never</item>
+  </string-array>
+  <string-array name="pref_sync_frequency_values">
+    <item>15</item>
+    <item>30</item>
+    <item>60</item>
+    <item>180</item>
+    <item>360</item>
+    <item>-1</item>
+  </string-array>
+
+  <string-array name="list_preference_entries">
+    <item>Entry 1</item>
+    <item>Entry 2</item>
+    <item>Entry 3</item>
+  </string-array>
+
+  <string-array name="list_preference_entry_values">
+    <item>1</item>
+    <item>2</item>
+    <item>3</item>
+  </string-array>
+
+  <string-array name="multi_select_list_preference_default_value"/>
+
+  <string name="pref_title_system_sync_settings">System sync settings</string>
+
+  <!-- Example settings for Notifications -->
+  <string name="pref_header_notifications">Notifications</string>
+
+  <string name="pref_title_new_message_notifications">New message notifications</string>
+
+  <string name="pref_title_ringtone">Ringtone</string>
+  <string name="pref_ringtone_silent">Silent</string>
+
+  <string name="pref_title_vibrate">Vibrate</string>
+</resources>
diff --git a/tests/obd2_app/res/values/styles.xml b/tests/obd2_app/res/values/styles.xml
new file mode 100644
index 0000000..c21c0ac
--- /dev/null
+++ b/tests/obd2_app/res/values/styles.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+  <style name="AppTheme" parent="android:Theme.Material">
+  </style>
+
+</resources>
diff --git a/tests/obd2_app/res/xml/preferences.xml b/tests/obd2_app/res/xml/preferences.xml
new file mode 100644
index 0000000..0c6f534
--- /dev/null
+++ b/tests/obd2_app/res/xml/preferences.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+  <com.google.android.car.obd2app.BluetoothPreference
+      android:key="bluetooth_mac"
+      android:title="OBD2 Dongle"
+      android:dialogTitle="Select OBD2 Scanner" />
+  <com.google.android.car.obd2app.IntegerListPreference
+      android:key="scan_delay"
+      android:title="Time between queries"
+      android:dialogTitle="Select Delay"
+      android:entries="@array/scan_delay_entries"
+      android:entryValues="@array/scan_delay_entryValues"
+      android:defaultValue="2"/>
+</PreferenceScreen>
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/BluetoothPreference.java b/tests/obd2_app/src/com/google/android/car/obd2app/BluetoothPreference.java
new file mode 100644
index 0000000..59da4c0
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/BluetoothPreference.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.obd2app;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BluetoothPreference extends ListPreference {
+    private static final class DeviceEntry {
+        private final String mName;
+        private final String mAddress;
+
+        DeviceEntry(BluetoothDevice device) {
+            mAddress = device.getAddress();
+            if (device.getName() == null) {
+                mName = mAddress;
+            } else {
+                mName = device.getName();
+            }
+        }
+
+        String getName() {
+            return mName;
+        }
+
+        String getAddress() {
+            return mAddress;
+        }
+    }
+
+    public BluetoothPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
+        List<DeviceEntry> pairedDevices = new ArrayList<>();
+        defaultAdapter
+                .getBondedDevices()
+                .forEach((BluetoothDevice device) -> pairedDevices.add(new DeviceEntry(device)));
+        setEntries(pairedDevices.stream().map(DeviceEntry::getName).toArray(String[]::new));
+        setEntryValues(pairedDevices.stream().map(DeviceEntry::getAddress).toArray(String[]::new));
+    }
+
+    public BluetoothPreference(Context context) {
+        this(context, null);
+    }
+}
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/IntegerListPreference.java b/tests/obd2_app/src/com/google/android/car/obd2app/IntegerListPreference.java
new file mode 100644
index 0000000..6e9e9dc
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/IntegerListPreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.obd2app;
+
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class IntegerListPreference extends ListPreference {
+    public IntegerListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public IntegerListPreference(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        return value != null && persistInt(Integer.valueOf(value));
+    }
+
+    @Override
+    protected String getPersistedString(String defaultReturnValue) {
+        if (getSharedPreferences().contains(getKey())) {
+            return String.valueOf(getPersistedInt(2));
+        } else {
+            return defaultReturnValue;
+        }
+    }
+}
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/MainActivity.java b/tests/obd2_app/src/com/google/android/car/obd2app/MainActivity.java
new file mode 100644
index 0000000..dc38b5c
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/MainActivity.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.obd2app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+import java.util.Timer;
+
+public class MainActivity extends Activity implements StatusNotification {
+    public static final String TAG = MainActivity.class.getSimpleName();
+
+    private static final String BLUETOOTH_MAC_PREFERENCE_ID = "bluetooth_mac";
+    private static final String SCAN_DELAY_PREFERENCE_ID = "scan_delay";
+
+    private Obd2CollectionTask mCollectionTask = null;
+    private final Timer mTimer = new Timer("com.google.android.car.obd2app.collection");
+
+    private String getBluetoothDongleMacFromPreferences(String defaultValue) {
+        SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        return appPreferences.getString(BLUETOOTH_MAC_PREFERENCE_ID, defaultValue);
+    }
+
+    private int getScanDelayFromPreferences(int defaultValue) {
+        SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        return appPreferences.getInt(SCAN_DELAY_PREFERENCE_ID, defaultValue);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+        String bluetoothDongleMac = getBluetoothDongleMacFromPreferences("");
+        if (TextUtils.isEmpty(bluetoothDongleMac)) {
+            notifyNoDongle();
+        } else {
+            notifyPaired(bluetoothDongleMac);
+        }
+        findViewById(R.id.connection)
+                .setOnClickListener(
+                        new OnClickListener() {
+                            @Override
+                            public void onClick(View v) {
+                                handleConnection(v);
+                            }
+                        });
+        Log.i(TAG, "I did all the things");
+    }
+
+    private void stopConnection() {
+        mCollectionTask.cancel();
+        mTimer.purge();
+        mCollectionTask = null;
+    }
+
+    @Override
+    protected void onDestroy() {
+        stopConnection();
+    }
+
+    public void doSettings(View view) {
+        Intent launchSettings = new Intent(this, SettingsActivity.class);
+        startActivity(launchSettings);
+    }
+
+    @Override
+    public void notify(String status) {
+        Log.i(TAG, status);
+        runOnUiThread(() -> ((TextView) findViewById(R.id.statusBar)).setText(status));
+    }
+
+    public void handleConnection(View view) {
+        String deviceAddress = getBluetoothDongleMacFromPreferences("");
+        Log.i(TAG, "Considering a connection to " + deviceAddress);
+        if (TextUtils.isEmpty(deviceAddress)) {
+            notifyNoDongle();
+        }
+        if (mCollectionTask == null) {
+            mCollectionTask = Obd2CollectionTask.create(this, this, deviceAddress);
+            if (null == mCollectionTask) {
+                notifyConnectionFailed();
+                return;
+            }
+            final int delay = 1000 * getScanDelayFromPreferences(2);
+            mTimer.scheduleAtFixedRate(mCollectionTask, delay, delay);
+            ((Button) view).setText("Disconnect");
+        } else {
+            stopConnection();
+            ((Button) view).setText("Connect");
+        }
+    }
+}
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java b/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java
new file mode 100644
index 0000000..31c3db2
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.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.google.android.car.obd2app;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.car.obd2.Obd2Connection;
+import com.android.car.obd2.Obd2LiveFrameGenerator;
+import com.android.car.obd2.connections.BluetoothConnection;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Objects;
+import java.util.TimerTask;
+
+public class Obd2CollectionTask extends TimerTask {
+    private final Obd2Connection mConnection;
+    private final Obd2LiveFrameGenerator mLiveFrameGenerator;
+    private final StatusNotification mStatusNotification;
+    private final JsonWriter mJsonWriter;
+
+    public static @Nullable Obd2CollectionTask create(
+            Context context, StatusNotification statusNotification, String deviceAddress) {
+        try {
+            return new Obd2CollectionTask(
+                    Objects.requireNonNull(context),
+                    Objects.requireNonNull(statusNotification),
+                    Objects.requireNonNull(deviceAddress));
+        } catch (IOException | InterruptedException | IllegalStateException e) {
+            Log.i(MainActivity.TAG, "Connection failed due to exception", e);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean cancel() {
+        synchronized (mJsonWriter) {
+            try {
+                mJsonWriter.endArray();
+                mJsonWriter.flush();
+                mJsonWriter.close();
+            } catch (IOException e) {
+                Log.w(MainActivity.TAG, "IOException during close", e);
+            }
+            return super.cancel();
+        }
+    }
+
+    @Override
+    public void run() {
+        if (!mConnection.isConnected()) {
+            if (!mConnection.reconnect()) {
+                mStatusNotification.notifyDisconnected();
+                return;
+            }
+        }
+
+        try {
+            synchronized (mJsonWriter) {
+                mLiveFrameGenerator.generate(mJsonWriter);
+                mJsonWriter.flush();
+            }
+            mStatusNotification.notifyDataCapture();
+        } catch (Exception e) {
+            mStatusNotification.notifyException(e);
+        }
+    }
+
+    Obd2CollectionTask(Context context, StatusNotification statusNotification, String deviceAddress)
+        throws IOException, InterruptedException {
+        if (!isExternalStorageWriteable())
+            throw new IOException("Cannot write data to external storage");
+        mStatusNotification = statusNotification;
+        BluetoothConnection bluetoothConnection = new BluetoothConnection(deviceAddress);
+        if (!bluetoothConnection.isConnected()) {
+            statusNotification.notifyConnectionFailed();
+            throw new IllegalStateException("Unable to connect to remote end.");
+        }
+        mConnection = new Obd2Connection(bluetoothConnection);
+        mLiveFrameGenerator = new Obd2LiveFrameGenerator(mConnection);
+        mJsonWriter =
+            new JsonWriter(
+                new OutputStreamWriter(
+                    new FileOutputStream(getFilenameForStorage(context))));
+        mJsonWriter.beginArray();
+    }
+
+    private static boolean isExternalStorageWriteable() {
+        String state = Environment.getExternalStorageState();
+        return (Environment.MEDIA_MOUNTED.equals(state));
+    }
+
+    private static File getFilenameForStorage(Context context) {
+        String basename = String.format("obd2app.capture.%d", SystemClock.elapsedRealtimeNanos());
+        return new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), basename);
+    }
+}
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java b/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java
new file mode 100644
index 0000000..23f120e
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/SettingsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.obd2app;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/StatusNotification.java b/tests/obd2_app/src/com/google/android/car/obd2app/StatusNotification.java
new file mode 100644
index 0000000..185c384
--- /dev/null
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/StatusNotification.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.obd2app;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+interface StatusNotification {
+    void notify(String status);
+
+    default void notifyNoDongle() {
+        notify("No OBD2 dongle paired. Go to Settings.");
+    }
+
+    default void notifyPaired(String deviceAddress) {
+        notify("Paired to " + deviceAddress + ". Ready to capture data.");
+    }
+
+    default void notifyConnectionFailed() {
+        notify("Unable to connect.");
+    }
+
+    default void notifyConnected(String deviceAddress) {
+        notify("Connected to " + deviceAddress + ". Starting data capture.");
+    }
+
+    default void notifyDataCapture() {
+        LocalDateTime now = LocalDateTime.now();
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MMMM dd yyyy hh:mm:ssa");
+        notify("Successfully captured data at " + now.format(dateTimeFormatter));
+    }
+
+    default void notifyException(Exception e) {
+        StringWriter stringWriter = new StringWriter(1024);
+        e.printStackTrace(new PrintWriter(stringWriter));
+        notify("Exception occurred.\n" + stringWriter.toString());
+    }
+
+    default void notifyDisconnected() {
+        notify("Lost connection to remote end. Will try to reconnect.");
+    }
+}
diff --git a/tools/bootanalyze/Android.mk b/tools/bootanalyze/Android.mk
new file mode 100644
index 0000000..5df0dd8
--- /dev/null
+++ b/tools/bootanalyze/Android.mk
@@ -0,0 +1,20 @@
+# 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/emulator/VehicleHalProto_pb2.py b/tools/emulator/VehicleHalProto_pb2.py
index aaad547..f972126 100644
--- a/tools/emulator/VehicleHalProto_pb2.py
+++ b/tools/emulator/VehicleHalProto_pb2.py
@@ -1,20 +1,28 @@
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: VehicleHalProto.proto
 
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
 from google.protobuf.internal import enum_type_wrapper
 from google.protobuf import descriptor as _descriptor
 from google.protobuf import message as _message
 from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
 from google.protobuf import descriptor_pb2
 # @@protoc_insertion_point(imports)
 
+_sym_db = _symbol_database.Default()
+
 
 
 
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='VehicleHalProto.proto',
   package='emulator',
-  serialized_pb='\n\x15VehicleHalProto.proto\x12\x08\x65mulator\"\xba\x01\n\x11VehicleAreaConfig\x12\x0f\n\x07\x61rea_id\x18\x01 \x02(\x05\x12\x17\n\x0fmin_int32_value\x18\x02 \x01(\x11\x12\x17\n\x0fmax_int32_value\x18\x03 \x01(\x11\x12\x17\n\x0fmin_int64_value\x18\x04 \x01(\x12\x12\x17\n\x0fmax_int64_value\x18\x05 \x01(\x12\x12\x17\n\x0fmin_float_value\x18\x06 \x01(\x02\x12\x17\n\x0fmax_float_value\x18\x07 \x01(\x02\"\x9b\x02\n\x11VehiclePropConfig\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0e\n\x06\x61\x63\x63\x65ss\x18\x02 \x01(\x05\x12\x13\n\x0b\x63hange_mode\x18\x03 \x01(\x05\x12\x12\n\nvalue_type\x18\x04 \x01(\x05\x12\x17\n\x0fsupported_areas\x18\x05 \x01(\x05\x12\x31\n\x0c\x61rea_configs\x18\x06 \x03(\x0b\x32\x1b.emulator.VehicleAreaConfig\x12\x14\n\x0c\x63onfig_flags\x18\x07 \x01(\x05\x12\x14\n\x0c\x63onfig_array\x18\x08 \x03(\x05\x12\x15\n\rconfig_string\x18\t \x01(\t\x12\x17\n\x0fmin_sample_rate\x18\n \x01(\x02\x12\x17\n\x0fmax_sample_rate\x18\x0b \x01(\x02\"\xc5\x01\n\x10VehiclePropValue\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x12\n\nvalue_type\x18\x02 \x01(\x05\x12\x11\n\ttimestamp\x18\x03 \x01(\x03\x12\x0f\n\x07\x61rea_id\x18\x04 \x01(\x05\x12\x14\n\x0cint32_values\x18\x05 \x03(\x11\x12\x14\n\x0cint64_values\x18\x06 \x03(\x12\x12\x14\n\x0c\x66loat_values\x18\x07 \x03(\x02\x12\x14\n\x0cstring_value\x18\x08 \x01(\t\x12\x13\n\x0b\x62ytes_value\x18\t \x01(\x0c\"/\n\x0eVehiclePropGet\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0f\n\x07\x61rea_id\x18\x02 \x01(\x05\"\xd8\x01\n\x0f\x45mulatorMessage\x12#\n\x08msg_type\x18\x01 \x02(\x0e\x32\x11.emulator.MsgType\x12 \n\x06status\x18\x02 \x01(\x0e\x32\x10.emulator.Status\x12&\n\x04prop\x18\x03 \x03(\x0b\x32\x18.emulator.VehiclePropGet\x12+\n\x06\x63onfig\x18\x04 \x03(\x0b\x32\x1b.emulator.VehiclePropConfig\x12)\n\x05value\x18\x05 \x03(\x0b\x32\x1a.emulator.VehiclePropValue*\x8a\x02\n\x07MsgType\x12\x12\n\x0eGET_CONFIG_CMD\x10\x00\x12\x13\n\x0fGET_CONFIG_RESP\x10\x01\x12\x16\n\x12GET_CONFIG_ALL_CMD\x10\x02\x12\x17\n\x13GET_CONFIG_ALL_RESP\x10\x03\x12\x14\n\x10GET_PROPERTY_CMD\x10\x04\x12\x15\n\x11GET_PROPERTY_RESP\x10\x05\x12\x18\n\x14GET_PROPERTY_ALL_CMD\x10\x06\x12\x19\n\x15GET_PROPERTY_ALL_RESP\x10\x07\x12\x14\n\x10SET_PROPERTY_CMD\x10\x08\x12\x15\n\x11SET_PROPERTY_RESP\x10\t\x12\x16\n\x12SET_PROPERTY_ASYNC\x10\n*\xfb\x01\n\x06Status\x12\r\n\tRESULT_OK\x10\x00\x12\x11\n\rERROR_UNKNOWN\x10\x01\x12\x1b\n\x17\x45RROR_UNIMPLEMENTED_CMD\x10\x02\x12\x1a\n\x16\x45RROR_INVALID_PROPERTY\x10\x03\x12\x19\n\x15\x45RROR_INVALID_AREA_ID\x10\x04\x12 \n\x1c\x45RROR_PROPERTY_UNINITIALIZED\x10\x05\x12\x1d\n\x19\x45RROR_WRITE_ONLY_PROPERTY\x10\x06\x12\x1d\n\x19\x45RROR_MEMORY_ALLOC_FAILED\x10\x07\x12\x1b\n\x17\x45RROR_INVALID_OPERATION\x10\x08\x42\x02H\x03')
+  syntax='proto2',
+  serialized_pb=_b('\n\x15VehicleHalProto.proto\x12\x08\x65mulator\"\xba\x01\n\x11VehicleAreaConfig\x12\x0f\n\x07\x61rea_id\x18\x01 \x02(\x05\x12\x17\n\x0fmin_int32_value\x18\x02 \x01(\x11\x12\x17\n\x0fmax_int32_value\x18\x03 \x01(\x11\x12\x17\n\x0fmin_int64_value\x18\x04 \x01(\x12\x12\x17\n\x0fmax_int64_value\x18\x05 \x01(\x12\x12\x17\n\x0fmin_float_value\x18\x06 \x01(\x02\x12\x17\n\x0fmax_float_value\x18\x07 \x01(\x02\"\x9b\x02\n\x11VehiclePropConfig\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0e\n\x06\x61\x63\x63\x65ss\x18\x02 \x01(\x05\x12\x13\n\x0b\x63hange_mode\x18\x03 \x01(\x05\x12\x12\n\nvalue_type\x18\x04 \x01(\x05\x12\x17\n\x0fsupported_areas\x18\x05 \x01(\x05\x12\x31\n\x0c\x61rea_configs\x18\x06 \x03(\x0b\x32\x1b.emulator.VehicleAreaConfig\x12\x14\n\x0c\x63onfig_flags\x18\x07 \x01(\x05\x12\x14\n\x0c\x63onfig_array\x18\x08 \x03(\x05\x12\x15\n\rconfig_string\x18\t \x01(\t\x12\x17\n\x0fmin_sample_rate\x18\n \x01(\x02\x12\x17\n\x0fmax_sample_rate\x18\x0b \x01(\x02\"\xc5\x01\n\x10VehiclePropValue\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x12\n\nvalue_type\x18\x02 \x01(\x05\x12\x11\n\ttimestamp\x18\x03 \x01(\x03\x12\x0f\n\x07\x61rea_id\x18\x04 \x01(\x05\x12\x14\n\x0cint32_values\x18\x05 \x03(\x11\x12\x14\n\x0cint64_values\x18\x06 \x03(\x12\x12\x14\n\x0c\x66loat_values\x18\x07 \x03(\x02\x12\x14\n\x0cstring_value\x18\x08 \x01(\t\x12\x13\n\x0b\x62ytes_value\x18\t \x01(\x0c\"/\n\x0eVehiclePropGet\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0f\n\x07\x61rea_id\x18\x02 \x01(\x05\"\xd8\x01\n\x0f\x45mulatorMessage\x12#\n\x08msg_type\x18\x01 \x02(\x0e\x32\x11.emulator.MsgType\x12 \n\x06status\x18\x02 \x01(\x0e\x32\x10.emulator.Status\x12&\n\x04prop\x18\x03 \x03(\x0b\x32\x18.emulator.VehiclePropGet\x12+\n\x06\x63onfig\x18\x04 \x03(\x0b\x32\x1b.emulator.VehiclePropConfig\x12)\n\x05value\x18\x05 \x03(\x0b\x32\x1a.emulator.VehiclePropValue*\x8a\x02\n\x07MsgType\x12\x12\n\x0eGET_CONFIG_CMD\x10\x00\x12\x13\n\x0fGET_CONFIG_RESP\x10\x01\x12\x16\n\x12GET_CONFIG_ALL_CMD\x10\x02\x12\x17\n\x13GET_CONFIG_ALL_RESP\x10\x03\x12\x14\n\x10GET_PROPERTY_CMD\x10\x04\x12\x15\n\x11GET_PROPERTY_RESP\x10\x05\x12\x18\n\x14GET_PROPERTY_ALL_CMD\x10\x06\x12\x19\n\x15GET_PROPERTY_ALL_RESP\x10\x07\x12\x14\n\x10SET_PROPERTY_CMD\x10\x08\x12\x15\n\x11SET_PROPERTY_RESP\x10\t\x12\x16\n\x12SET_PROPERTY_ASYNC\x10\n*\xfb\x01\n\x06Status\x12\r\n\tRESULT_OK\x10\x00\x12\x11\n\rERROR_UNKNOWN\x10\x01\x12\x1b\n\x17\x45RROR_UNIMPLEMENTED_CMD\x10\x02\x12\x1a\n\x16\x45RROR_INVALID_PROPERTY\x10\x03\x12\x19\n\x15\x45RROR_INVALID_AREA_ID\x10\x04\x12 \n\x1c\x45RROR_PROPERTY_UNINITIALIZED\x10\x05\x12\x1d\n\x19\x45RROR_WRITE_ONLY_PROPERTY\x10\x06\x12\x1d\n\x19\x45RROR_MEMORY_ALLOC_FAILED\x10\x07\x12\x1b\n\x17\x45RROR_INVALID_OPERATION\x10\x08\x42\x02H\x03')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
 _MSGTYPE = _descriptor.EnumDescriptor(
   name='MsgType',
@@ -72,6 +80,7 @@
   serialized_start=979,
   serialized_end=1245,
 )
+_sym_db.RegisterEnumDescriptor(_MSGTYPE)
 
 MsgType = enum_type_wrapper.EnumTypeWrapper(_MSGTYPE)
 _STATUS = _descriptor.EnumDescriptor(
@@ -122,6 +131,7 @@
   serialized_start=1248,
   serialized_end=1499,
 )
+_sym_db.RegisterEnumDescriptor(_STATUS)
 
 Status = enum_type_wrapper.EnumTypeWrapper(_STATUS)
 GET_CONFIG_CMD = 0
@@ -192,14 +202,14 @@
     _descriptor.FieldDescriptor(
       name='min_float_value', full_name='emulator.VehicleAreaConfig.min_float_value', index=5,
       number=6, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
       name='max_float_value', full_name='emulator.VehicleAreaConfig.max_float_value', index=6,
       number=7, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -211,7 +221,10 @@
   ],
   options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
+  oneofs=[
+  ],
   serialized_start=36,
   serialized_end=222,
 )
@@ -283,21 +296,21 @@
     _descriptor.FieldDescriptor(
       name='config_string', full_name='emulator.VehiclePropConfig.config_string', index=8,
       number=9, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=unicode("", "utf-8"),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
       name='min_sample_rate', full_name='emulator.VehiclePropConfig.min_sample_rate', index=9,
       number=10, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
       name='max_sample_rate', full_name='emulator.VehiclePropConfig.max_sample_rate', index=10,
       number=11, type=2, cpp_type=6, label=1,
-      has_default_value=False, default_value=0,
+      has_default_value=False, default_value=float(0),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -309,7 +322,10 @@
   ],
   options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
+  oneofs=[
+  ],
   serialized_start=225,
   serialized_end=508,
 )
@@ -374,14 +390,14 @@
     _descriptor.FieldDescriptor(
       name='string_value', full_name='emulator.VehiclePropValue.string_value', index=7,
       number=8, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=unicode("", "utf-8"),
+      has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
       name='bytes_value', full_name='emulator.VehiclePropValue.bytes_value', index=8,
       number=9, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value="",
+      has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
@@ -393,7 +409,10 @@
   ],
   options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
+  oneofs=[
+  ],
   serialized_start=511,
   serialized_end=708,
 )
@@ -428,7 +447,10 @@
   ],
   options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
+  oneofs=[
+  ],
   serialized_start=710,
   serialized_end=757,
 )
@@ -484,7 +506,10 @@
   ],
   options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
+  oneofs=[
+  ],
   serialized_start=760,
   serialized_end=976,
 )
@@ -500,38 +525,45 @@
 DESCRIPTOR.message_types_by_name['VehiclePropValue'] = _VEHICLEPROPVALUE
 DESCRIPTOR.message_types_by_name['VehiclePropGet'] = _VEHICLEPROPGET
 DESCRIPTOR.message_types_by_name['EmulatorMessage'] = _EMULATORMESSAGE
+DESCRIPTOR.enum_types_by_name['MsgType'] = _MSGTYPE
+DESCRIPTOR.enum_types_by_name['Status'] = _STATUS
 
-class VehicleAreaConfig(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEAREACONFIG
-
+VehicleAreaConfig = _reflection.GeneratedProtocolMessageType('VehicleAreaConfig', (_message.Message,), dict(
+  DESCRIPTOR = _VEHICLEAREACONFIG,
+  __module__ = 'VehicleHalProto_pb2'
   # @@protoc_insertion_point(class_scope:emulator.VehicleAreaConfig)
+  ))
+_sym_db.RegisterMessage(VehicleAreaConfig)
 
-class VehiclePropConfig(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEPROPCONFIG
-
+VehiclePropConfig = _reflection.GeneratedProtocolMessageType('VehiclePropConfig', (_message.Message,), dict(
+  DESCRIPTOR = _VEHICLEPROPCONFIG,
+  __module__ = 'VehicleHalProto_pb2'
   # @@protoc_insertion_point(class_scope:emulator.VehiclePropConfig)
+  ))
+_sym_db.RegisterMessage(VehiclePropConfig)
 
-class VehiclePropValue(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEPROPVALUE
-
+VehiclePropValue = _reflection.GeneratedProtocolMessageType('VehiclePropValue', (_message.Message,), dict(
+  DESCRIPTOR = _VEHICLEPROPVALUE,
+  __module__ = 'VehicleHalProto_pb2'
   # @@protoc_insertion_point(class_scope:emulator.VehiclePropValue)
+  ))
+_sym_db.RegisterMessage(VehiclePropValue)
 
-class VehiclePropGet(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _VEHICLEPROPGET
-
+VehiclePropGet = _reflection.GeneratedProtocolMessageType('VehiclePropGet', (_message.Message,), dict(
+  DESCRIPTOR = _VEHICLEPROPGET,
+  __module__ = 'VehicleHalProto_pb2'
   # @@protoc_insertion_point(class_scope:emulator.VehiclePropGet)
+  ))
+_sym_db.RegisterMessage(VehiclePropGet)
 
-class EmulatorMessage(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _EMULATORMESSAGE
-
+EmulatorMessage = _reflection.GeneratedProtocolMessageType('EmulatorMessage', (_message.Message,), dict(
+  DESCRIPTOR = _EMULATORMESSAGE,
+  __module__ = 'VehicleHalProto_pb2'
   # @@protoc_insertion_point(class_scope:emulator.EmulatorMessage)
+  ))
+_sym_db.RegisterMessage(EmulatorMessage)
 
 
 DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('H\003'))
 # @@protoc_insertion_point(module_scope)
diff --git a/tools/emulator/vhal_emulator.py b/tools/emulator/vhal_emulator.py
index 514c0d5..5fbdd74 100644
--- a/tools/emulator/vhal_emulator.py
+++ b/tools/emulator/vhal_emulator.py
@@ -34,21 +34,21 @@
 
         # Get the response message to getConfig()
         reply = v.rxMsg()
-        print reply
+        print(reply)
 
         # Set left temperature to 70 degrees
         v.setProperty(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_ROW_1_LEFT, 70)
 
         # Get the response message to setProperty()
         reply = v.rxMsg()
-        print reply
+        print(reply)
 
         # Get the left temperature value
         v.getProperty(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_ROW_1_LEFT)
 
         # Get the response message to getProperty()
         reply = v.rxMsg()
-        print reply
+        print(reply)
 
     NOTE:  The rxMsg() is a blocking call, so it may be desirable to set up a separate RX thread
             to handle any asynchronous messages coming from the device.
@@ -72,6 +72,8 @@
             protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
 """
 
+from __future__ import print_function
+
 # Suppress .pyc files
 import sys
 sys.dont_write_bytecode = True
@@ -80,7 +82,9 @@
 import struct
 import subprocess
 
-# Generate the protobuf file from vendor/auto/embedded/lib/vehicle_hal:
+# Generate the protobuf file from hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0
+# It is recommended to use the protoc provided in: prebuilts/tools/common/m2/repository/com/google/protobuf/protoc/3.0.0
+# or a later version, in order to provide Python 3 compatibility
 #   protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
 import VehicleHalProto_pb2
 
@@ -123,7 +127,7 @@
         """
             For debugging, print the protobuf message string in hex.
         """
-        print "len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data)
+        print("len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data))
 
     def openSocket(self):
         """
diff --git a/tools/emulator/vhal_emulator_test.py b/tools/emulator/vhal_emulator_test.py
index 325e0a8..b8a8a39 100644
--- a/tools/emulator/vhal_emulator_test.py
+++ b/tools/emulator/vhal_emulator_test.py
@@ -27,6 +27,8 @@
             protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
 """
 
+from __future__ import print_function
+
 # Suppress .pyc files
 import sys
 sys.dont_write_bytecode = True
@@ -54,7 +56,7 @@
             testValue = "test string"
         elif valType in self._types.TYPE_BYTES:
             # Generate array of integers counting from 0
-            testValue = range(len(origValue))
+            testValue = list(range(len(origValue)))
         elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN:
             testValue = origValue ^ 1
         elif valType in self._types.TYPE_INT32:
@@ -216,7 +218,7 @@
                 newValue = self._getValueFromMsg(rxMsg)
                 if newValue != testValue:
                     self._log.error("testGetSet: set failed for propId=%d, area=%d", cfg.prop, area)
-                    print "testValue= ", testValue, "newValue= ", newValue
+                    print("testValue= ", testValue, "newValue= ", newValue)
                     continue
 
                 # Reset the value to what it was before
@@ -287,4 +289,3 @@
 if __name__ == '__main__':
     v = VhalTest(vhal_consts_2_0.vhal_types_2_0)
     v.runTests()
-
diff --git a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/Utils.java b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/Utils.java
index 34fd9cb..2a923bb 100644
--- a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/Utils.java
+++ b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/Utils.java
@@ -17,14 +17,14 @@
 package com.android.car.vehiclehal;
 
 import android.util.SparseArray;
-import com.android.car.vehiclehal.Utils.SparseArrayIterator.SparseArrayEntry;
 import java.util.Iterator;
 
 class Utils {
     private Utils() {}
 
     static class SparseArrayIterator<T>
-            implements Iterable<SparseArrayEntry<T>>, Iterator<SparseArrayEntry<T>> {
+            implements Iterable<SparseArrayIterator.SparseArrayEntry<T>>,
+                Iterator<SparseArrayIterator.SparseArrayEntry<T>> {
         static class SparseArrayEntry<U> {
             public final int key;
             public final U value;