Merge "Add CarLatinIME to the base car make file."
diff --git a/TrustAgent/Android.mk b/TrustAgent/Android.mk
index f672e57..dc70ef3 100644
--- a/TrustAgent/Android.mk
+++ b/TrustAgent/Android.mk
@@ -16,6 +16,8 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_USE_AAPT2 := true
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CarTrustAgentService
diff --git a/TrustAgent/AndroidManifest.xml b/TrustAgent/AndroidManifest.xml
index b499a73..1f5e6e6 100644
--- a/TrustAgent/AndroidManifest.xml
+++ b/TrustAgent/AndroidManifest.xml
@@ -16,26 +16,72 @@
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.car.trust">
+    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+
+    <!-- Need Bluetooth LE -->
+    <uses-feature android:name="android.hardware.bluetooth_le"  android:required="true" />
+
     <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+    <!-- Needed to unlock user -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
     <application android:label="@string/app_name">
         <service
-                android:name=".CarBluetoothTrustAgent"
+                android:name=".CarBleTrustAgent"
                 android:label="@string/app_name"
                 android:permission="android.permission.BIND_TRUST_AGENT"
+                android:directBootAware="true"
                 android:exported="true">
             <intent-filter>
                 <action android:name="android.service.trust.TrustAgentService" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            <!-- Warning: the meta data must be included if the service is direct boot aware.
+                 If not included, the device will crash before boot completes. Rendering the
+                 device unusable. -->
+            <meta-data android:name="android.service.trust.trustagent"
+                       android:resource="@xml/car_sample_trust_agent"/>
         </service>
-        <receiver android:name=".BootupReceiver">
+
+        <!-- CarUnlockService needs to be direct boot aware, since the trust agent
+             binds to it during direct boot.-->
+        <service android:name=".CarUnlockService"
+                 android:directBootAware="true">
+            <!-- Warning: the meta data must be included if the service is direct boot aware.
+                 If not included, the device will crash before boot completes. Rendering the device
+                 unusable. -->
+            <meta-data android:name="android.service.trust.trustagent"
+                       android:resource="@xml/car_sample_trust_agent"/>
+        </service>
+
+        <service android:name=".CarEnrolmentService"/>
+
+        <activity
+                android:name=".MainActivity"
+                android:label="@string/app_name"
+                android:exported="true"
+                android:launchMode="singleInstance">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
-        </receiver>
+        </activity>
+
+        <activity android:name=".CarEnrolmentActivity"
+            android:exported="false" />
+
+        <activity android:name=".PhoneEnrolmentActivity"
+            android:exported="false" />
+
+        <activity android:name=".PhoneUnlockActivity"
+            android:exported="false" />
     </application>
 </manifest>
diff --git a/TrustAgent/res/layout/car_client.xml b/TrustAgent/res/layout/car_client.xml
new file mode 100644
index 0000000..c5a27f0
--- /dev/null
+++ b/TrustAgent/res/layout/car_client.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:weightSum="1">
+
+    <ScrollView
+        android:id="@+id/scroll"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:scrollbars="vertical"
+        android:layout_weight="0.70"
+        android:fillViewport="true">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/textfield"/>
+    </ScrollView>
+    <Button
+        android:id="@+id/start_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/start_advertising"
+        android:layout_weight="0.15"/>
+    <Button
+        android:id="@+id/revoke_trust_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/revoke_trust"
+        android:layout_weight="0.15"/>
+</LinearLayout>
diff --git a/TrustAgent/res/layout/main_app.xml b/TrustAgent/res/layout/main_app.xml
new file mode 100644
index 0000000..5153ca8
--- /dev/null
+++ b/TrustAgent/res/layout/main_app.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:weightSum="1">
+
+    <Button android:id="@+id/car_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0.20"
+        android:text="@string/car"/>
+    <Button android:id="@+id/phone_enrolment_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0.20"
+        android:text="@string/enrollment_client"/>
+    <Button android:id="@+id/phone_unlock_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0.20"
+        android:text="@string/unlock_client"/>
+</LinearLayout>
diff --git a/TrustAgent/res/layout/phone_client.xml b/TrustAgent/res/layout/phone_client.xml
new file mode 100644
index 0000000..41ead63
--- /dev/null
+++ b/TrustAgent/res/layout/phone_client.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:weightSum="1">
+    <ScrollView
+        android:id="@+id/scroll"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:scrollbars="vertical"
+        android:layout_weight="0.60"
+        android:fillViewport="true">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/output"/>
+    </ScrollView>
+    <Button
+        android:id="@+id/ble_scan_btn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/start_scanning"
+        android:layout_weight="0.20"/>
+    <Button
+        android:id="@+id/action_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0.20"/>
+</LinearLayout>
diff --git a/TrustAgent/res/values/strings.xml b/TrustAgent/res/values/strings.xml
index 3184184..d23a4b3 100644
--- a/TrustAgent/res/values/strings.xml
+++ b/TrustAgent/res/values/strings.xml
@@ -1,21 +1,43 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2016 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+<!-- Copyright (C) 2017 The Android Open Source Project
 
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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">CarBluetoothTrustAgent</string>
-    <string name="trust_granted_explanation">Paired Device was found</string>
-</resources>
\ No newline at end of file
+    <string name="app_name">CarBleTrustAgent</string>
+    <string name="trust_granted_explanation">Unlock via escrow token, now granting trust</string>
+
+    <!-- service/characteristics uuid for unlocking a device -->
+    <string name="unlock_service_uuid">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="unlock_escrow_token_uiid">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="unlock_handle_uiid">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
+
+    <!-- service/characteristics uuid for adding new escrow token -->
+    <string name="enrollment_service_uuid">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="enrollment_handle_uuid">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
+    <string name="enrollment_token_uuid">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
+
+    <string name="pref_key_token_handle">token-handle-key</string>
+    <string name="pref_key_escrow_token">escrow-token-key</string>
+
+    <string name="unlock_button">Unlock Car</string>
+    <string name="enroll_button">Enroll New Token</string>
+
+    <string name="start_advertising">Start Advertising</string>
+    <string name="revoke_trust">Revoke Trust</string>
+
+    <string name="car">Car</string>
+    <string name="enrollment_client">Phone Enrolment Client</string>
+    <string name="unlock_client">Phone Unlock Client</string>
+    <string name="start_scanning">Start Scanning</string>
+</resources>
diff --git a/TrustAgent/res/xml/car_sample_trust_agent.xml b/TrustAgent/res/xml/car_sample_trust_agent.xml
new file mode 100644
index 0000000..0c7d91d
--- /dev/null
+++ b/TrustAgent/res/xml/car_sample_trust_agent.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
+  -->
+<trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:priv-android="http://schemas.android.com/apk/prv/res/android"
+             android:settingsActivity=".MainActivity"
+             priv-android:unlockProfile="true" />
diff --git a/TrustAgent/src/com/android/car/trust/BootupReceiver.java b/TrustAgent/src/com/android/car/trust/BootupReceiver.java
deleted file mode 100644
index 352003f..0000000
--- a/TrustAgent/src/com/android/car/trust/BootupReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.trust;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class BootupReceiver extends BroadcastReceiver {
-    private static final String TAG = "CarBTTrustAgent";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "ACTION_BOOT_COMPLETED");
-            }
-            context.startService(new Intent(context, CarBluetoothTrustAgent.class));
-        }
-    }
-}
diff --git a/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
new file mode 100644
index 0000000..99bbf22
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.trust.TrustAgentService;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import com.android.car.trust.comms.SimpleBleServer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A sample trust agent that demonstrates how to use the escrow token unlock APIs. </p>
+ *
+ * This trust agent runs during direct boot and binds to a BLE service that listens for remote
+ * devices to trigger an unlock.
+ */
+public class CarBleTrustAgent extends TrustAgentService {
+    public static final String ACTION_REVOKE_TRUST = "revoke-trust-action";
+    public static final String ACTION_ADD_TOKEN = "add-token-action";
+    public static final String ACTION_IS_TOKEN_ACTIVE = "is-token-active-action";
+    public static final String ACTION_REMOVE_TOKEN = "remove-token-action";
+    public static final String ACTION_UNLOCK_DEVICE = "unlock-device-action";
+
+    public static final String ACTION_TOKEN_STATUS_RESULT = "token-status-result-action";
+    public static final String ACTION_ADD_TOKEN_RESULT = "add-token-result-action";
+
+    public static final String INTENT_EXTRA_ESCROW_TOKEN = "extra-escrow-token";
+    public static final String INTENT_EXTRA_TOKEN_HANDLE = "extra-token-handle";
+    public static final String INTENT_EXTRA_TOKEN_STATUS = "extra-token-status";
+
+
+    private static final String TAG = "CarBleTrustAgent";
+
+    private static final long TRUST_DURATION_MS = TimeUnit.MINUTES.toMicros(5);
+    private static final long BLE_RETRY_MS = TimeUnit.SECONDS.toMillis(1);
+
+    private CarUnlockService mCarUnlockService;
+    private LocalBroadcastManager mLocalBroadcastManager;
+
+    private boolean mBleServiceBound;
+
+    // We cannot directly bind to TrustAgentService since the onBind method is final.
+    // As a result, we communicate with the various UI components using a LocalBroadcastManager.
+    private final BroadcastReceiver mTrustEventReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Received broadcast: " + action);
+            }
+            if (ACTION_REVOKE_TRUST.equals(action)) {
+                revokeTrust();
+            } else if (ACTION_ADD_TOKEN.equals(action)) {
+                byte[] token = intent.getByteArrayExtra(INTENT_EXTRA_ESCROW_TOKEN);
+                addEscrowToken(token, getCurrentUserHandle());
+            } else if (ACTION_IS_TOKEN_ACTIVE.equals(action)) {
+                long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1);
+                isEscrowTokenActive(handle, getCurrentUserHandle());
+            } else if (ACTION_REMOVE_TOKEN.equals(action)) {
+                long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1);
+                removeEscrowToken(handle, getCurrentUserHandle());
+            }
+        }
+    };
+
+    @Override
+    public void onTrustTimeout() {
+        super.onTrustTimeout();
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onTrustTimeout(): timeout expired");
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Bluetooth trust agent starting up");
+        }
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_REVOKE_TRUST);
+        filter.addAction(ACTION_ADD_TOKEN);
+        filter.addAction(ACTION_IS_TOKEN_ACTIVE);
+        filter.addAction(ACTION_REMOVE_TOKEN);
+
+        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this /* context */);
+        mLocalBroadcastManager.registerReceiver(mTrustEventReceiver, filter);
+
+        // If the user is already unlocked, don't bother starting the BLE service.
+        UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+        if (!um.isUserUnlocked()) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "User locked, will now bind CarUnlockService");
+            }
+            Intent intent = new Intent(this, CarUnlockService.class);
+
+            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        } else {
+            setManagingTrust(true);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Car Trust agent shutting down");
+        }
+        mLocalBroadcastManager.unregisterReceiver(mTrustEventReceiver);
+
+        // Unbind the service to avoid leaks from BLE stack.
+        if (mBleServiceBound) {
+            unbindService(mServiceConnection);
+        }
+        super.onDestroy();
+    }
+
+    private SimpleBleServer.ConnectionListener mConnectionListener
+            = new SimpleBleServer.ConnectionListener() {
+        @Override
+        public void onServerStarted() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "BLE server started");
+            }
+        }
+
+        @Override
+        public void onServerStartFailed(int errorCode) {
+            Log.w(TAG, "BLE server failed to start. Error Code: " + errorCode);
+        }
+
+        @Override
+        public void onDeviceConnected(BluetoothDevice device) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "BLE device connected. Name: " + device.getName()
+                        + " Address: " + device.getAddress());
+            }
+        }
+    };
+
+    private CarUnlockService.UnlockServiceCallback mUnlockCallback
+            = new CarUnlockService.UnlockServiceCallback() {
+        @Override
+        public void unlockDevice(byte[] token, long handle) {
+            unlock(token, handle);
+        }
+    };
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "CarUnlockService connected");
+            }
+
+            mBleServiceBound = true;
+            CarUnlockService.UnlockServiceBinder binder
+                    = (CarUnlockService.UnlockServiceBinder) service;
+            mCarUnlockService = binder.getService();
+            mCarUnlockService.addUnlockServiceCallback(mUnlockCallback);
+            mCarUnlockService.addConnectionListener(mConnectionListener);
+            maybeStartBleUnlockService();
+        }
+
+        public void onServiceDisconnected(ComponentName arg0) {
+            mCarUnlockService = null;
+            mBleServiceBound = false;
+        }
+
+    };
+
+    private void maybeStartBleUnlockService() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Trying to open a Ble GATT server");
+        }
+
+        BluetoothManager btManager =
+                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+        BluetoothGattServer mGattServer
+                = btManager.openGattServer(this, new BluetoothGattServerCallback() {
+            @Override
+            public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+                super.onConnectionStateChange(device, status, newState);
+            }
+        });
+
+        // The BLE stack is started up before the trust agent service, however Gatt capabilities
+        // might not be ready just yet. Keep trying until a GattServer can open up before proceeding
+        // to start the rest of the BLE services.
+        if (mGattServer == null) {
+            Log.e(TAG, "Gatt not available, will try again...in " + BLE_RETRY_MS + "ms");
+
+            Handler handler = new Handler();
+            handler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    maybeStartBleUnlockService();
+                }
+            }, BLE_RETRY_MS);
+        } else {
+            mGattServer.close();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "GATT available, starting up UnlockService");
+            }
+            mCarUnlockService.start();
+        }
+    }
+
+    private void unlock(byte[] token, long handle) {
+        UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "About to unlock user. Current handle: " + handle
+                    + " Time: " + System.currentTimeMillis());
+        }
+        unlockUserWithToken(handle, token, getCurrentUserHandle());
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Attempted to unlock user, is user unlocked? " + um.isUserUnlocked()
+                    + " Time: " + System.currentTimeMillis());
+        }
+        setManagingTrust(true);
+
+        if (um.isUserUnlocked()) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, getString(R.string.trust_granted_explanation));
+            }
+            grantTrust("Granting trust from escrow token",
+                    TRUST_DURATION_MS, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
+            // Trust has been granted, disable the BLE server. This trust agent service does
+            // not need to receive additional BLE data.
+            unbindService(mServiceConnection);
+        }
+    }
+
+    @Override
+    public void onEscrowTokenRemoved(long handle, boolean successful) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenRemoved. Handle: " + handle + " successful? " + successful);
+        }
+    }
+
+    @Override
+    public void onEscrowTokenStateReceived(long handle, int tokenState) {
+        boolean isActive = tokenState == TOKEN_STATE_ACTIVE;
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Token handle: " + handle + " isActive: " + isActive);
+        }
+
+        Intent intent = new Intent();
+        intent.setAction(ACTION_TOKEN_STATUS_RESULT);
+        intent.putExtra(INTENT_EXTRA_TOKEN_STATUS, isActive);
+
+        mLocalBroadcastManager.sendBroadcast(intent);
+    }
+
+    @Override
+    public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onEscrowTokenAdded, handle: " + handle);
+        }
+
+        Intent intent = new Intent();
+        intent.setAction(ACTION_ADD_TOKEN_RESULT);
+        intent.putExtra(INTENT_EXTRA_TOKEN_HANDLE, handle);
+
+        mLocalBroadcastManager.sendBroadcast(intent);
+    }
+
+    private UserHandle getCurrentUserHandle() {
+        return UserHandle.of(UserHandle.myUserId());
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java b/TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java
deleted file mode 100644
index fa52e1b..0000000
--- a/TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.car.trust;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.service.trust.TrustAgentService;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A simple trust agent that grants trust when a paired bluetooth device is connected.
- */
-public class CarBluetoothTrustAgent extends TrustAgentService {
-    private static final String TAG = "CarBTTrustAgent";
-    private static final long TRUST_DURATION_MS = TimeUnit.MINUTES.toMicros(5);
-
-    private String mTrustGrantedMessage;
-
-    private BluetoothAdapter mBluetoothAdapter;
-
-    // List of paired devices
-    private HashMap<String, BluetoothDevice> mBondedDeviceMap = new HashMap<>();
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        mTrustGrantedMessage = getString(R.string.trust_granted_explanation);
-
-        registerReceiver(mBtReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
-        registerReceiver(mBtReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
-        registerReceiver(mBtReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
-        registerReceiver(mBtReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-
-        registerReceiver(mSreenOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
-        registerReceiver(mSreenOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
-
-        setManagingTrust(true);
-        mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
-                .getAdapter();
-
-        // Bonded/paired devices are only returned if bluetooth is enabled.
-        // If not enabled, wait for ACTION_STATE_CHANGED to get bonded/paired devices.
-        if (mBluetoothAdapter.isEnabled()) {
-            updateBondedDevices();
-        }
-    }
-
-    @Override
-    public void onTrustTimeout() {
-        super.onTrustTimeout();
-        // If there is still a connected device, we can continue granting trust.
-        for (BluetoothDevice device : mBondedDeviceMap.values()) {
-            if (device.isConnected()) {
-                grantTrust();
-                break;
-            }
-        }
-    }
-
-    private final BroadcastReceiver mSreenOnOffReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "ACTION_SCREEN_ON");
-                }
-                updateTrustStatus();
-            } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "ACTION_SCREEN_OFF: revoking trust");
-                }
-                revokeTrust();
-            }
-        }
-    };
-
-    /**
-     * The BroadcastReceiver that listens for bluetooth broadcasts.
-     */
-    private final BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-            if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "ACTION_ACL_CONNECTED device: " + device.getName());
-                }
-                int state = device.getBondState();
-                if (state == BluetoothDevice.BOND_BONDED) {
-                    mBondedDeviceMap.put(device.getAddress(), device);
-                    updateTrustStatus();
-                }
-
-            } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "ACTION_ACL_DISCONNECTED device: " + device.getName());
-                }
-
-                updateTrustStatus();
-            } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "ACTION_BOND_STATE_CHANGED device: " + device.getName());
-                }
-
-                int state = device.getBondState();
-                if (state == BluetoothDevice.BOND_BONDED) {
-                    mBondedDeviceMap.put(device.getAddress(), device);
-                    grantTrust();
-                } else if (state == BluetoothDevice.BOND_NONE) {
-                    mBondedDeviceMap.remove(device.getAddress());
-                    updateTrustStatus();
-                }
-            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
-                // Bluetooth was just turned on.
-                if (state == BluetoothAdapter.STATE_ON) {
-                    updateBondedDevices();
-                }
-            }
-        }
-    };
-
-    private void updateBondedDevices() {
-        for (BluetoothDevice d : mBluetoothAdapter.getBondedDevices()) {
-            mBondedDeviceMap.put(d.getAddress(), d);
-
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "ACTION_STATE_CHANGED device: " + d.getName());
-            }
-        }
-        updateTrustStatus();
-    }
-
-    private void updateTrustStatus() {
-        // Nothing is paired, this trust agent should not grant trust
-        if (mBondedDeviceMap.size() == 0) {
-            revokeTrust();
-        }
-
-        boolean deviceConnected = false;
-        for (BluetoothDevice device : mBondedDeviceMap.values()) {
-            if (device.isConnected()) {
-                deviceConnected = true;
-                break;
-            }
-        }
-        if (!deviceConnected) {
-            revokeTrust();
-        } else {
-            grantTrust();
-        }
-    }
-
-    private void grantTrust() {
-        try {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Granting Trust");
-            }
-            grantTrust(mTrustGrantedMessage, TRUST_DURATION_MS,
-                    TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
-        } catch (IllegalStateException e) {
-            Log.d(TAG, "IllegalStateException: " + e.getMessage());
-        }
-    }
-}
diff --git a/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
new file mode 100644
index 0000000..89e68fe
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.car.trust.CarEnrolmentService.EnrolmentCallback;
+import com.android.car.trust.comms.SimpleBleServer.ConnectionListener;
+
+import static com.android.car.trust.CarBleTrustAgent.ACTION_ADD_TOKEN_RESULT;
+import static com.android.car.trust.CarBleTrustAgent.ACTION_TOKEN_STATUS_RESULT;
+import static com.android.car.trust.CarBleTrustAgent.INTENT_EXTRA_TOKEN_HANDLE;
+import static com.android.car.trust.CarBleTrustAgent.INTENT_EXTRA_TOKEN_STATUS;
+
+/**
+ * Setup activity that binds {@link CarEnrolmentService} and starts the enrolment process.
+ */
+public class CarEnrolmentActivity extends Activity {
+    private static String TAG = "CarEnrolment";
+    private static String SP_HANDLE_KEY = "sp-test";
+
+    private TextView mOutputText;
+    private TextView mStartButton;
+
+    private long mHandle;
+
+    private CarEnrolmentService mEnrolmentService;
+
+    private BluetoothDevice mDevice;
+
+    private boolean mServiceBound;
+
+    private LocalBroadcastManager mLocalBroadcastManager;
+
+    private SharedPreferences mPrefs;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            intent.getPackage();
+
+            String action = intent.getAction();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Received broadcast: " + action);
+            }
+
+            if (ACTION_TOKEN_STATUS_RESULT.equals(action)) {
+                boolean tokenActive = intent.getBooleanExtra(INTENT_EXTRA_TOKEN_STATUS, false);
+                appendOutputText("Is token active? " + tokenActive + " handle: " + mHandle);
+            } else if (ACTION_ADD_TOKEN_RESULT.equals(action)) {
+                final long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1);
+
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mPrefs.edit().putLong(SP_HANDLE_KEY, handle).apply();
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "stored new handle");
+                        }
+                    }
+                });
+
+                mEnrolmentService.sendHandle(handle, mDevice);
+                appendOutputText("Escrow Token Added. Handle: " + handle
+                        + "\nLock and unlock the device to activate token");
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.car_client);
+        mOutputText = (TextView) findViewById(R.id.textfield);
+
+        final Intent intent = new Intent(this, CarEnrolmentService.class);
+
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this /* context */);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_TOKEN_STATUS_RESULT);
+        filter.addAction(ACTION_ADD_TOKEN_RESULT);
+
+        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this /* context */);
+        mLocalBroadcastManager.registerReceiver(mReceiver, filter);
+
+        mStartButton = (Button) findViewById(R.id.start_button);
+        mStartButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // binding the service will start it if not started.
+                bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+            }
+        });
+
+        Button revokeButton = (Button) findViewById(R.id.revoke_trust_button);
+        revokeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(CarBleTrustAgent.ACTION_REVOKE_TRUST);
+                intent.setPackage(getPackageName());
+                sendBroadcast(intent);
+            }
+        });
+    }
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            mServiceBound = true;
+            CarEnrolmentService.EnrolmentServiceBinder binder
+                    = (CarEnrolmentService.EnrolmentServiceBinder) service;
+            mEnrolmentService = binder.getService();
+            mEnrolmentService.addEnrolmentCallback(mEnrolmentCallback);
+            mEnrolmentService.addConnectionListener(mConnectionListener);
+            mEnrolmentService.start();
+        }
+
+        public void onServiceDisconnected(ComponentName arg0) {
+            mEnrolmentService = null;
+            mServiceBound = false;
+        }
+    };
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (!mPrefs.contains(SP_HANDLE_KEY)) {
+            appendOutputText("No handles found.");
+            return;
+        }
+
+        try {
+            mHandle = mPrefs.getLong(SP_HANDLE_KEY, -1);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onResume, checking handle active: " + mHandle);
+            }
+            isTokenActive(mHandle);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error checking if token is valid");
+            appendOutputText("Error checking if token is valid");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mServiceBound) {
+            unbindService(mServiceConnection);
+        }
+        super.onDestroy();
+    }
+
+    private void appendOutputText(final String text) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mOutputText.append("\n" + text);
+            }
+        });
+    }
+
+    private ConnectionListener mConnectionListener = new ConnectionListener() {
+        @Override
+        public void onServerStarted() {
+            appendOutputText("Server started");
+        }
+
+        @Override
+        public void onServerStartFailed(int errorCode) {
+            appendOutputText("Server failed to start, error code: " + errorCode);
+        }
+
+        @Override
+        public void onDeviceConnected(BluetoothDevice device) {
+            mDevice = device;
+            appendOutputText("Device connected: " + device.getName()
+                    + " addr: " + device.getAddress());
+        }
+    };
+
+    private EnrolmentCallback mEnrolmentCallback = new EnrolmentCallback() {
+        @Override
+        public void onEnrolmentDataReceived(byte[] token) {
+            appendOutputText("Enrolment data received ");
+            addEscrowToken(token);
+        }
+    };
+
+    private void isTokenActive(long handle) throws RemoteException {
+        Intent intent = new Intent();
+        intent.setAction(CarBleTrustAgent.ACTION_IS_TOKEN_ACTIVE);
+        intent.putExtra(CarBleTrustAgent.INTENT_EXTRA_TOKEN_HANDLE, handle);
+
+        mLocalBroadcastManager.sendBroadcast(intent);
+    }
+
+    private void addEscrowToken(byte[] token) {
+        long handle;
+
+        if (mPrefs.contains(SP_HANDLE_KEY)) {
+            handle = mPrefs.getLong(SP_HANDLE_KEY, -1);
+            appendOutputText("Removing old token, handle value: " + handle);
+            Intent intent = new Intent();
+            intent.setAction(CarBleTrustAgent.ACTION_REMOVE_TOKEN);
+            intent.putExtra(CarBleTrustAgent.INTENT_EXTRA_TOKEN_HANDLE, handle);
+            mLocalBroadcastManager.sendBroadcast(intent);
+        }
+
+        Intent intent = new Intent();
+        intent.setAction(CarBleTrustAgent.ACTION_ADD_TOKEN);
+        intent.putExtra(CarBleTrustAgent.INTENT_EXTRA_ESCROW_TOKEN, token);
+
+        mLocalBroadcastManager.sendBroadcast(intent);
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java b/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java
new file mode 100644
index 0000000..e6bbee2
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.car.trust.comms.SimpleBleServer;
+
+import java.util.HashSet;
+import java.util.UUID;
+
+/**
+ * A service that receives escrow token enrollment requests from remote devices.
+ */
+public class CarEnrolmentService extends SimpleBleServer {
+    private static final String TAG = "CarEnrolmentService";
+
+    public interface EnrolmentCallback {
+        void onEnrolmentDataReceived(byte[] token);
+    }
+
+    private BluetoothGattService mEnrolmentService;
+    private BluetoothGattCharacteristic mEnrolmentEscrowToken;
+    private BluetoothGattCharacteristic mEnrolmentTokenHandle;
+
+    private HashSet<EnrolmentCallback> mCallbacks;
+
+    private final IBinder mBinder = new EnrolmentServiceBinder();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mCallbacks = new HashSet<>();
+        setupEnrolmentService();
+    }
+
+    public void start() {
+        ParcelUuid uuid = new ParcelUuid(
+                UUID.fromString(getString(R.string.enrollment_service_uuid)));
+        start(uuid, mEnrolmentService);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onCharacteristicWrite(BluetoothDevice device,
+            int requestId, BluetoothGattCharacteristic characteristic,
+            boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        if (characteristic.getUuid().equals(mEnrolmentEscrowToken.getUuid())) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Enrolment token received, value: " + Utils.getLong(value));
+            }
+
+            for (EnrolmentCallback callback : mCallbacks) {
+                callback.onEnrolmentDataReceived(value);
+            }
+        }
+    }
+
+    @Override
+    public void onCharacteristicRead(BluetoothDevice device,
+            int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+        //Enrolment service should not have any read requests.
+    }
+
+    public void addEnrolmentCallback(EnrolmentCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    public void sendHandle(long handle, BluetoothDevice device) {
+        mEnrolmentTokenHandle.setValue(Utils.getBytes(handle));
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Sending notification for EscrowToken Handle");
+        }
+        mGattServer.notifyCharacteristicChanged(device,
+                mEnrolmentTokenHandle, false /* confirm */);
+    }
+
+    public class EnrolmentServiceBinder extends Binder {
+        public CarEnrolmentService getService() {
+            return CarEnrolmentService.this;
+        }
+    }
+
+    // Create services and characteristics for enrolling new unlocking escrow tokens
+    private void setupEnrolmentService() {
+        mEnrolmentService = new BluetoothGattService(
+                UUID.fromString(getString(R.string.enrollment_service_uuid)),
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        // Characteristic to describe the escrow token being used for unlock
+        mEnrolmentEscrowToken = new BluetoothGattCharacteristic(
+                UUID.fromString(getString(R.string.enrollment_token_uuid)),
+                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        // Characteristic to describe the handle being used for this escrow token
+        mEnrolmentTokenHandle = new BluetoothGattCharacteristic(
+                UUID.fromString(getString(R.string.enrollment_handle_uuid)),
+                BluetoothGattCharacteristic.PROPERTY_NOTIFY,
+                BluetoothGattCharacteristic.PERMISSION_READ);
+
+        mEnrolmentService.addCharacteristic(mEnrolmentEscrowToken);
+        mEnrolmentService.addCharacteristic(mEnrolmentTokenHandle);
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/CarUnlockService.java b/TrustAgent/src/com/android/car/trust/CarUnlockService.java
new file mode 100644
index 0000000..0c1bc89
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarUnlockService.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.car.trust.comms.SimpleBleServer;
+
+import java.util.UUID;
+
+/**
+ * A service that receives unlock requests from remote devices.
+ */
+public class CarUnlockService extends SimpleBleServer {
+    /**
+     * A callback to receives callback
+     */
+    public interface UnlockServiceCallback {
+        void unlockDevice(byte[] token, long handle);
+    }
+
+    private static final String TAG = "CarUnlockService";
+
+    private BluetoothGattService mUnlockService;
+    private BluetoothGattCharacteristic mUnlockEscrowToken;
+    private BluetoothGattCharacteristic mUnlockTokenHandle;
+
+    private UnlockServiceCallback mCallback;
+
+    private byte[] mCurrentToken;
+    private Long mCurrentHandle;
+
+    private final IBinder mBinder = new UnlockServiceBinder();
+
+    public class UnlockServiceBinder extends Binder {
+        public CarUnlockService getService() {
+            return CarUnlockService.this;
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "CarUnlockService starting up, creating BLE service");
+        }
+        setupUnlockService();
+    }
+
+    /**
+     * Start advertising the BLE unlock service
+     */
+    public void start() {
+        ParcelUuid uuid = new ParcelUuid(
+                UUID.fromString(getString(R.string.unlock_service_uuid)));
+        start(uuid, mUnlockService);
+    }
+
+    public void addUnlockServiceCallback(UnlockServiceCallback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onCharacteristicWrite(BluetoothDevice device,
+            int requestId, BluetoothGattCharacteristic characteristic,
+            boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+        UUID uuid = characteristic.getUuid();
+
+        if (uuid.equals(mUnlockTokenHandle.getUuid())) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unlock handle received, value: " + Utils.getLong(value));
+            }
+            mCurrentHandle = Utils.getLong(value);
+            unlockDataReceived();
+        } else if (uuid.equals(mUnlockEscrowToken.getUuid())) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unlock escrow token received, value: " + Utils.getLong(value));
+            }
+            mCurrentToken = value;
+            unlockDataReceived();
+        }
+    }
+
+    @Override
+    public void onCharacteristicRead(BluetoothDevice device,
+            int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+        // The BLE unlock service should not receive any read requests.
+    }
+
+    private synchronized void unlockDataReceived() {
+        // If any piece of the unlocking data is not received, then do not unlock.
+        if (mCurrentHandle == null || mCurrentToken == null) {
+            return;
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Handle and token both received, requesting unlock. Time: "
+                    + System.currentTimeMillis());
+        }
+        // Both the handle and token has been received, try to unlock the device.
+
+
+        mCallback.unlockDevice(mCurrentToken, mCurrentHandle);
+
+        // Once we've notified the client of the unlocking data, clear it out.
+        mCurrentToken = null;
+        mCurrentHandle = null;
+    }
+
+
+    // Create services and characteristics to receive tokens and handles for unlocking the device.
+    private void setupUnlockService() {
+        mUnlockService = new BluetoothGattService(
+                UUID.fromString(getString(R.string.unlock_service_uuid)),
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        // Characteristic to describe the escrow token being used for unlock
+        mUnlockEscrowToken = new BluetoothGattCharacteristic(
+                UUID.fromString(getString(R.string.unlock_escrow_token_uiid)),
+                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        // Characteristic to describe the handle being used for this escrow token
+        mUnlockTokenHandle = new BluetoothGattCharacteristic(
+                UUID.fromString(getString(R.string.unlock_handle_uiid)),
+                BluetoothGattCharacteristic.PROPERTY_WRITE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+        mUnlockService.addCharacteristic(mUnlockEscrowToken);
+        mUnlockService.addCharacteristic(mUnlockTokenHandle);
+    }
+
+}
diff --git a/TrustAgent/src/com/android/car/trust/MainActivity.java b/TrustAgent/src/com/android/car/trust/MainActivity.java
new file mode 100644
index 0000000..8c1348b
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/MainActivity.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * Selects whether the device is started as the car or remote device.
+ */
+public class MainActivity extends Activity {
+    private static final int FINE_LOCATION_REQUEST_CODE = 13;
+
+    private Button mCarEnrolmentButton;
+    private Button mPhoneEnrolmentButton;
+    private Button mPhoneUnlockButton;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_app);
+        mCarEnrolmentButton = (Button) findViewById(R.id.car_button);
+        mPhoneEnrolmentButton = (Button) findViewById(R.id.phone_enrolment_button);
+        mPhoneUnlockButton = (Button) findViewById(R.id.phone_unlock_button);
+
+        mCarEnrolmentButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(MainActivity.this /* context */,
+                        CarEnrolmentActivity.class);
+                startActivity(intent);
+            }
+        });
+
+        mPhoneEnrolmentButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(MainActivity.this /* context */,
+                        PhoneEnrolmentActivity.class);
+                startActivity(intent);
+            }
+        });
+
+        mPhoneUnlockButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(MainActivity.this /* context */,
+                        PhoneUnlockActivity.class);
+                startActivity(intent);
+            }
+        });
+
+        if (!checkPermissionGranted()) {
+            requestPermissions(new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
+                    FINE_LOCATION_REQUEST_CODE);
+            // If location access isn't granted, BLE scanning will fail.
+            mCarEnrolmentButton.setEnabled(false);
+            mPhoneEnrolmentButton.setEnabled(false);
+            mPhoneUnlockButton.setEnabled(false);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode,
+            String permissions[], int[] grantResults) {
+        if (requestCode == FINE_LOCATION_REQUEST_CODE && checkPermissionGranted()) {
+            mCarEnrolmentButton.setEnabled(true);
+            mPhoneEnrolmentButton.setEnabled(true);
+            mPhoneUnlockButton.setEnabled(true);
+        }
+    }
+
+    private boolean checkPermissionGranted() {
+        return checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+}
\ No newline at end of file
diff --git a/TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java
new file mode 100644
index 0000000..dac4b79
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Activity to allow the user to add an escrow token to a remote device.
+ */
+public class PhoneEnrolmentActivity extends Activity {
+    private Button mScanButton;
+    private Button mEnrollButton;
+
+    private TextView mTextOutput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.phone_client);
+
+        mScanButton = (Button) findViewById(R.id.ble_scan_btn);
+        mEnrollButton = (Button) findViewById(R.id.action_button);
+        mEnrollButton.setText(getString(R.string.enroll_button));
+
+        mTextOutput = (TextView) findViewById(R.id.output);
+
+        PhoneEnrolmentController controller = new PhoneEnrolmentController(this /* context */);
+        controller.bind(mTextOutput, mScanButton, mEnrollButton);
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java
new file mode 100644
index 0000000..b887dc4
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.car.trust.comms.SimpleBleClient;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE enrollment service.
+ * It also binds the UI components to control the enrollment process.
+ */
+public class PhoneEnrolmentController {
+    private static final String TAG = "PhoneEnrolmentCtlr";
+    private String mTokenHandleKey;
+    private String mEscrowTokenKey;
+
+    // BLE characteristics associated with the enrollment/add escrow token service.
+    private BluetoothGattCharacteristic mEnrolmentTokenHandle;
+    private BluetoothGattCharacteristic mEnrolmentEscrowToken;
+
+    private ParcelUuid mEnrolmentServiceUuid;
+
+    private SimpleBleClient mClient;
+    private Context mContext;
+
+    private TextView mTextView;
+    private Handler mHandler;
+
+    private Button mScanButton;
+    private Button mEnrolButton;
+
+    public PhoneEnrolmentController(Context context) {
+        mContext = context;
+
+        mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
+        mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
+
+        mClient = new SimpleBleClient(context);
+        mEnrolmentServiceUuid = new ParcelUuid(
+                UUID.fromString(mContext.getString(R.string.enrollment_service_uuid)));
+        mClient.addCallback(mCallback /* callback */);
+
+        mHandler = new Handler(mContext.getMainLooper());
+    }
+
+    /**
+     * Binds the views to the actions that can be performed by this controller.
+     *
+     * @param textView    A text view used to display results from various BLE actions
+     * @param scanButton  Button used to start scanning for available BLE devices.
+     * @param enrolButton Button used to send new escrow token to remote device.
+     */
+    public void bind(TextView textView, Button scanButton, Button enrolButton) {
+        mTextView = textView;
+        mScanButton = scanButton;
+        mEnrolButton = enrolButton;
+
+        mScanButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mClient.start(mEnrolmentServiceUuid);
+            }
+        });
+
+        mEnrolButton.setEnabled(false);
+        mEnrolButton.setAlpha(0.3f);
+        mEnrolButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                appendOutputText("Sending new escrow token to remote device");
+
+                byte[] token = generateEscrowToken();
+                sendEnrolmentRequest(token);
+
+                // WARNING: Store the token so it can be used later for unlocking. This token
+                // should NEVER be stored on the device that is being unlocked. It should
+                // always be securely stored on a remote device that will trigger the unlock.
+                storeToken(token);
+            }
+        });
+    }
+
+    /**
+     * @return A random byte array that is used as the escrow token for remote device unlock.
+     */
+    private byte[] generateEscrowToken() {
+        Random random = new Random();
+        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+        buffer.putLong(0, random.nextLong());
+        return buffer.array();
+    }
+
+    private void sendEnrolmentRequest(byte[] token) {
+        mEnrolmentEscrowToken.setValue(token);
+        mClient.writeCharacteristic(mEnrolmentEscrowToken);
+        storeToken(token);
+    }
+
+    private SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
+        @Override
+        public void onDeviceConnected(BluetoothDevice device) {
+            appendOutputText("Device connected: " + device.getName()
+                    + " addr: " + device.getAddress());
+        }
+
+        @Override
+        public void onDeviceDisconnected() {
+            appendOutputText("Device disconnected");
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic) {
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCharacteristicChanged: " + Utils.getLong(characteristic.getValue()));
+            }
+
+            if (characteristic.getUuid().equals(mEnrolmentTokenHandle.getUuid())) {
+                // Store the new token handle that the BLE server is sending us. This required
+                // to unlock the device.
+                long handle = Utils.getLong(characteristic.getValue());
+                storeHandle(handle);
+                appendOutputText("Token handle received: " + handle);
+            }
+        }
+
+        @Override
+        public void onServiceDiscovered(BluetoothGattService service) {
+            if (!service.getUuid().equals(mEnrolmentServiceUuid.getUuid())) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Service UUID: " + service.getUuid()
+                            + " does not match Enrolment UUID " + mEnrolmentServiceUuid.getUuid());
+                }
+                return;
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Enrolment Service # characteristics: "
+                        + service.getCharacteristics().size());
+            }
+            mEnrolmentEscrowToken
+                    = Utils.getCharacteristic(R.string.enrollment_token_uuid, service, mContext);
+            mEnrolmentTokenHandle
+                    = Utils.getCharacteristic(R.string.enrollment_handle_uuid, service, mContext);
+            mClient.setCharacteristicNotification(mEnrolmentTokenHandle, true /* enable */);
+            appendOutputText("Enrolment BLE client successfully connected");
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    // Services are now set up, allow users to enrol new escrow tokens.
+                    mEnrolButton.setEnabled(true);
+                    mEnrolButton.setAlpha(1.0f);
+                }
+            });
+        }
+    };
+
+    private void storeHandle(long handle) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+        prefs.edit().putLong(mTokenHandleKey, handle).apply();
+    }
+
+    private void storeToken(byte[] token) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+        String byteArray = Base64.encodeToString(token, Base64.DEFAULT);
+        prefs.edit().putString(mEscrowTokenKey, byteArray).apply();
+    }
+
+    private void appendOutputText(final String text) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mTextView.append("\n" + text);
+            }
+        });
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java b/TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java
new file mode 100644
index 0000000..767e1c5
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Activity to allow the user to unlock a remote devices with the stored escrow token.
+ */
+public class PhoneUnlockActivity extends Activity {
+    private Button mScannButton;
+    private Button mUnlockButton;
+
+    private TextView mTextOutput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.phone_client);
+
+        mScannButton = (Button) findViewById(R.id.ble_scan_btn);
+        mUnlockButton = (Button) findViewById(R.id.action_button);
+        mUnlockButton.setText(getString(R.string.unlock_button));
+
+        mTextOutput = (TextView) findViewById(R.id.output);
+
+        PhoneUnlockController controller = new PhoneUnlockController(this /* context */);
+        controller.bind(mTextOutput, mScannButton, mUnlockButton);
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java b/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java
new file mode 100644
index 0000000..c956333
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.car.trust.comms.SimpleBleClient;
+
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service.
+ */
+public class PhoneUnlockController {
+    private static final String TAG = "PhoneUnlockController";
+
+    private String mTokenHandleKey;
+    private String mEscrowTokenKey;
+
+    // BLE characteristics associated with the enrolment/add escrow token service.
+    private BluetoothGattCharacteristic mUnlockTokenHandle;
+    private BluetoothGattCharacteristic mUnlockEscrowToken;
+
+    private ParcelUuid mUnlockServiceUuid;
+
+    private SimpleBleClient mClient;
+    private Context mContext;
+
+    private TextView mTextView;
+    private Handler mHandler;
+
+    private Button mScanButton;
+    private Button mUnlockButton;
+
+    public PhoneUnlockController(Context context) {
+        mContext = context;
+
+        mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
+        mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
+
+        mClient = new SimpleBleClient(context);
+        mUnlockServiceUuid = new ParcelUuid(
+                UUID.fromString(mContext.getString(R.string.unlock_service_uuid)));
+        mClient.addCallback(mCallback /* callback */);
+
+        mHandler = new Handler(mContext.getMainLooper());
+    }
+
+    /**
+     * Binds the views to the actions that can be performed by this controller.
+     *
+     * @param textView    A text view used to display results from various BLE actions
+     * @param scanButton  Button used to start scanning for available BLE devices.
+     * @param enrolButton Button used to send new escrow token to remote device.
+     */
+    public void bind(TextView textView, Button scanButton, Button enrolButton) {
+        mTextView = textView;
+        mScanButton = scanButton;
+        mUnlockButton = enrolButton;
+
+        mScanButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mClient.start(mUnlockServiceUuid);
+            }
+        });
+
+        mUnlockButton.setEnabled(false);
+        mUnlockButton.setAlpha(0.3f);
+        mUnlockButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                appendOutputText("Sending unlock token and handle to remote device");
+                sendUnlockRequest();
+            }
+        });
+    }
+
+    private void sendUnlockRequest() {
+        // Retrieve stored token and handle and write to remote device.
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+        long handle = prefs.getLong(mTokenHandleKey, -1);
+        byte[] token = Base64.decode(prefs.getString(mEscrowTokenKey, null), Base64.DEFAULT);
+
+        mUnlockEscrowToken.setValue(token);
+        mUnlockTokenHandle.setValue(Utils.getBytes(handle));
+
+        mClient.writeCharacteristic(mUnlockEscrowToken);
+        mClient.writeCharacteristic(mUnlockTokenHandle);
+    }
+
+    private SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
+        @Override
+        public void onDeviceConnected(BluetoothDevice device) {
+            appendOutputText("Device connected: " + device.getName()
+                    + " addr: " + device.getAddress());
+        }
+
+        @Override
+        public void onDeviceDisconnected() {
+            appendOutputText("Device disconnected");
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic) {
+            // Not expecting any characteristics changes for the unlocking client.
+        }
+
+        @Override
+        public void onServiceDiscovered(BluetoothGattService service) {
+            if (!service.getUuid().equals(mUnlockServiceUuid.getUuid())) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Service UUID: " + service.getUuid()
+                        + " does not match Enrolment UUID " + mUnlockServiceUuid.getUuid());
+                }
+                return;
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unlock Service # characteristics: "
+                        + service.getCharacteristics().size());
+            }
+            mUnlockEscrowToken
+                    = Utils.getCharacteristic(R.string.unlock_escrow_token_uiid, service, mContext);
+            mUnlockTokenHandle
+                    = Utils.getCharacteristic(R.string.unlock_handle_uiid, service, mContext);
+            appendOutputText("Unlock BLE client successfully connected");
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    // Services are now set up, allow users to enrol new escrow tokens.
+                    mUnlockButton.setEnabled(true);
+                    mUnlockButton.setAlpha(1.0f);
+                }
+            });
+        }
+    };
+
+    private void appendOutputText(final String text) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mTextView.append("\n" + text);
+            }
+        });
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/Utils.java b/TrustAgent/src/com/android/car/trust/Utils.java
new file mode 100644
index 0000000..3e9181f
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+public class Utils {
+
+    public static byte[] getBytes(long l) {
+        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+        buffer.putLong(0, l);
+        return buffer.array();
+    }
+
+    public static long getLong(byte[] bytes) {
+        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+        buffer.put(bytes);
+        buffer.flip();
+        return buffer.getLong();
+    }
+
+    public static BluetoothGattCharacteristic getCharacteristic(int uuidRes,
+            BluetoothGattService service, Context context) {
+        return service.getCharacteristic(UUID.fromString(context.getString(uuidRes)));
+    }
+}
diff --git a/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java b/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java
new file mode 100644
index 0000000..77bf2d3
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust.comms;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A simple client that supports the scanning and connecting to available BLE devices. Should be
+ * used along with {@link SimpleBleServer}.
+ */
+public class SimpleBleClient {
+    public interface ClientCallback {
+        /**
+         * Called when a device that has a matching service UUID is found.
+         **/
+        void onDeviceConnected(BluetoothDevice device);
+
+        void onDeviceDisconnected();
+
+        void onCharacteristicChanged(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic);
+
+        /**
+         * Called for each {@link BluetoothGattService} that is discovered on the
+         * {@link BluetoothDevice} after a matching scan result and connection.
+         *
+         * @param service {@link BluetoothGattService} that has been discovered.
+         */
+        void onServiceDiscovered(BluetoothGattService service);
+    }
+
+    /**
+     * Wrapper class to allow queuing of BLE actions. The BLE stack allows only one action to be
+     * executed at a time.
+     */
+    public static class BleAction {
+        public static final int ACTION_WRITE = 0;
+        public static final int ACTION_READ = 1;
+
+        private int mAction;
+        private BluetoothGattCharacteristic mCharacteristic;
+
+        public BleAction(BluetoothGattCharacteristic characteristic, int action) {
+            mAction = action;
+            mCharacteristic = characteristic;
+        }
+
+        public int getAction() {
+            return mAction;
+        }
+
+        public BluetoothGattCharacteristic getCharacteristic() {
+            return mCharacteristic;
+        }
+    }
+
+    private static final String TAG = "SimpleBleClient";
+    private static final long SCAN_TIME_MS = 10000;
+
+    private Queue<BleAction> mBleActionQueue = new ConcurrentLinkedQueue<BleAction>();
+
+    private BluetoothManager mBtManager;
+    private BluetoothLeScanner mScanner;
+
+    protected BluetoothGatt mBtGatt;
+
+    private List<ClientCallback> mCallbacks;
+    private ParcelUuid mServiceUuid;
+    private Context mContext;
+
+    public SimpleBleClient(@NonNull Context context) {
+        mContext = context;
+        mBtManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+        mScanner = mBtManager.getAdapter().getBluetoothLeScanner();
+        mCallbacks = new ArrayList<>();
+    }
+
+    /**
+     * Start scanning for a BLE devices with the specified service uuid.
+     *
+     * @param parcelUuid {@link ParcelUuid} used to identify the device that should be used for
+     *                   this client. This uuid should be the same as the one that is set in the
+     *                   {@link android.bluetooth.le.AdvertiseData.Builder} by the advertising
+     *                   device.
+     */
+    public void start(ParcelUuid parcelUuid) {
+        mServiceUuid = parcelUuid;
+
+        // We only want to scan for devices that have the correct uuid set in its advertise data.
+        List<ScanFilter> filters = new ArrayList<ScanFilter>();
+        ScanFilter.Builder serviceFilter = new ScanFilter.Builder();
+        serviceFilter.setServiceUuid(mServiceUuid);
+        filters.add(serviceFilter.build());
+
+        ScanSettings.Builder settings = new ScanSettings.Builder();
+        settings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Start scanning for uuid: " + mServiceUuid.getUuid());
+        }
+        mScanner.startScan(filters, settings.build(), mScanCallback);
+
+        Handler handler = new Handler();
+        handler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                mScanner.stopScan(mScanCallback);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Stopping Scanner");
+                }
+            }
+        }, SCAN_TIME_MS);
+    }
+
+    private boolean hasServiceUuid(ScanResult result) {
+        if (result.getScanRecord() == null
+                || result.getScanRecord().getServiceUuids() == null
+                || result.getScanRecord().getServiceUuids().size() == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Writes to a {@link BluetoothGattCharacteristic} if possible, or queues the action until
+     * other actions are complete.
+     *
+     * @param characteristic {@link BluetoothGattCharacteristic} to be written
+     */
+    public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+        processAction(new BleAction(characteristic, BleAction.ACTION_WRITE));
+    }
+
+    /**
+     * Reads a {@link BluetoothGattCharacteristic} if possible, or queues the read action until
+     * other actions are complete.
+     *
+     * @param characteristic {@link BluetoothGattCharacteristic} to be read.
+     */
+    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
+        processAction(new BleAction(characteristic, BleAction.ACTION_READ));
+    }
+
+    /**
+     * Enable or disable notification for specified {@link BluetoothGattCharacteristic}.
+     *
+     * @param characteristic The {@link BluetoothGattCharacteristic} for which to enable
+     *                       notifications.
+     * @param enabled        True if notifications should be enabled, false otherwise.
+     */
+    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+            boolean enabled) {
+        mBtGatt.setCharacteristicNotification(characteristic, enabled);
+    }
+
+    /**
+     * Add a {@link ClientCallback} to listen for updates from BLE components
+     */
+    public void addCallback(ClientCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    public void removeCallback(ClientCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void processAction(BleAction action) {
+        // Only execute actions if the queue is empty.
+        if (mBleActionQueue.size() > 0) {
+            mBleActionQueue.add(action);
+            return;
+        }
+
+        mBleActionQueue.add(action);
+        executeAction(mBleActionQueue.peek());
+    }
+
+    private void processNextAction() {
+        mBleActionQueue.poll();
+        executeAction(mBleActionQueue.peek());
+    }
+
+    private void executeAction(BleAction action) {
+        if (action == null) {
+            return;
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Executing BLE Action type: " + action.getAction());
+        }
+
+        int actionType = action.getAction();
+        switch (actionType) {
+            case BleAction.ACTION_WRITE:
+                mBtGatt.writeCharacteristic(action.getCharacteristic());
+                break;
+            case BleAction.ACTION_READ:
+                mBtGatt.readCharacteristic(action.getCharacteristic());
+                break;
+            default:
+        }
+    }
+
+    private String getStatus(int status) {
+        switch (status) {
+            case BluetoothGatt.GATT_FAILURE:
+                return "Failure";
+            case BluetoothGatt.GATT_SUCCESS:
+                return "GATT_SUCCESS";
+            case BluetoothGatt.GATT_READ_NOT_PERMITTED:
+                return "GATT_READ_NOT_PERMITTED";
+            case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
+                return "GATT_WRITE_NOT_PERMITTED";
+            case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
+                return "GATT_INSUFFICIENT_AUTHENTICATION";
+            case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
+                return "GATT_REQUEST_NOT_SUPPORTED";
+            case BluetoothGatt.GATT_INVALID_OFFSET:
+                return "GATT_INVALID_OFFSET";
+            case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH:
+                return "GATT_INVALID_ATTRIBUTE_LENGTH";
+            case BluetoothGatt.GATT_CONNECTION_CONGESTED:
+                return "GATT_CONNECTION_CONGESTED";
+            default:
+                return "unknown";
+        }
+    }
+
+    private ScanCallback mScanCallback = new ScanCallback() {
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            BluetoothDevice device = result.getDevice();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Scan result found: " + result.getScanRecord().getServiceUuids());
+            }
+
+            if (!hasServiceUuid(result)) {
+                return;
+            }
+
+            for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Scan result UUID: " + uuid);
+                }
+                if (uuid.equals(mServiceUuid)) {
+                    // This client only supports connecting to one service.
+                    // Once we find one, stop scanning and open a GATT connection to the device.
+                    mScanner.stopScan(mScanCallback);
+                    mBtGatt = device.connectGatt(mContext, false /* autoConnect */, mGattCallback);
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public void onBatchScanResults(List<ScanResult> results) {
+            for (ScanResult r : results) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Batch scanResult: " + r.getDevice().getName()
+                            + " " + r.getDevice().getAddress());
+                    }
+            }
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            Log.w(TAG, "Scan failed: " + errorCode);
+        }
+    };
+
+    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+        @Override
+        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+            super.onConnectionStateChange(gatt, status, newState);
+
+            String state = "";
+
+            if (newState == BluetoothProfile.STATE_CONNECTED) {
+                state = "Connected";
+                mBtGatt.discoverServices();
+                for (ClientCallback callback : mCallbacks) {
+                    callback.onDeviceConnected(gatt.getDevice());
+                }
+
+            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+                state = "Disconnected";
+                for (ClientCallback callback : mCallbacks) {
+                    callback.onDeviceDisconnected();
+                }
+            }
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, " Gatt connection status: " + getStatus(status) + " newState: " + state);
+            }
+        }
+
+        @Override
+        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            super.onServicesDiscovered(gatt, status);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onServicesDiscovered: " + status);
+            }
+
+            List<BluetoothGattService> services = gatt.getServices();
+            if (services == null || services.size() <= 0) {
+                return;
+            }
+
+            // Notify clients of newly discovered services.
+            for (BluetoothGattService service : mBtGatt.getServices()) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Found service: " + service.getUuid() + " notifying clients");
+                }
+                for (ClientCallback callback : mCallbacks) {
+                    callback.onServiceDiscovered(service);
+                }
+            }
+        }
+
+        @Override
+        public void onCharacteristicWrite(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCharacteristicWrite: " + status);
+            }
+            processNextAction();
+        }
+
+        @Override
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCharacteristicRead:" + new String(characteristic.getValue()));
+            }
+            processNextAction();
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic) {
+            for (ClientCallback callback : mCallbacks) {
+                callback.onCharacteristicChanged(gatt, characteristic);
+            }
+            processNextAction();
+        }
+    };
+}
diff --git a/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java b/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java
new file mode 100644
index 0000000..da3f7ac
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.car.trust.comms;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.HashSet;
+
+/**
+ * A generic service to start a BLE
+ */
+public abstract class SimpleBleServer extends Service {
+
+    /**
+     * Listener that is notified when the status of the BLE server changes.
+     */
+    public interface ConnectionListener {
+        /**
+         * Called when the GATT server is started and BLE is successfully advertising.
+         */
+        void onServerStarted();
+
+        /**
+         * Called when the BLE advertisement fails to start.
+         *
+         * @param errorCode Error code (see {@link AdvertiseCallback}#ADVERTISE_FAILED_* constants)
+         */
+        void onServerStartFailed(int errorCode);
+
+        /**
+         * Called when a device is connected.
+         * @param device
+         */
+        void onDeviceConnected(BluetoothDevice device);
+    }
+
+    private static final String TAG = "SimpleBleServer";
+
+    private BluetoothLeAdvertiser mAdvertiser;
+    protected BluetoothGattServer mGattServer;
+
+    private HashSet<ConnectionListener> mListeners = new HashSet<>();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // Override in child classes.
+        return null;
+    }
+
+    /**
+     * Starts the GATT server with the given {@link BluetoothGattService} and begins
+     * advertising with the {@link ParcelUuid}.
+     * @param advertiseUuid Service Uuid used in the {@link AdvertiseData}
+     * @param service {@link BluetoothGattService} that will be discovered by clients
+     */
+    protected void start(ParcelUuid advertiseUuid, BluetoothGattService service) {
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+            Log.e(TAG, "System does not support BLE");
+            return;
+        }
+
+        BluetoothManager btManager =
+                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+
+        mGattServer = btManager.openGattServer(this, mGattServerCallback);
+        if (mGattServer == null) {
+            Log.e(TAG, "Gatt Server not created");
+            return;
+        }
+
+        // We only allow adding one service in this implementation. If multiple services need
+        // to be added, then they need to be queued up and added only after
+        // BluetoothGattServerCallback.onServiceAdded is called.
+        mGattServer.addService(service);
+
+        AdvertiseSettings settings = new AdvertiseSettings.Builder()
+                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
+                .setConnectable(true)
+                .build();
+
+        AdvertiseData data = new AdvertiseData.Builder()
+                .setIncludeDeviceName(true)
+                .addServiceUuid(advertiseUuid)
+                .build();
+
+        mAdvertiser
+                = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+
+        mAdvertiser.startAdvertising(settings, data, mAdvertisingCallback);
+    }
+
+    /**
+     * Stops the advertiser and GATT server. This needs to be done to avoid leaks
+     */
+    protected void stop() {
+        if (mAdvertiser != null) {
+            mAdvertiser.stopAdvertising(mAdvertisingCallback);
+            mAdvertiser.cleanup();
+        }
+
+        if (mGattServer != null) {
+            mGattServer.clearServices();
+            try {
+                for (BluetoothDevice d : mGattServer.getConnectedDevices()) {
+                    mGattServer.cancelConnection(d);
+                }
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "Error getting connected devices", e);
+            } finally {
+                mGattServer.close();
+            }
+        }
+
+        mListeners.clear();
+    }
+
+    @Override
+    public void onDestroy() {
+        stop();
+        super.onDestroy();
+    }
+
+    public void addConnectionListener(ConnectionListener listener) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Adding connection listener");
+        }
+        mListeners.add(listener);
+    }
+
+    /**
+     * Triggered when this BleService receives a write request from a remote
+     * device. Sub-classes should implement how to handle requests.
+     */
+    public abstract void onCharacteristicWrite(final BluetoothDevice device, int requestId,
+            BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+            responseNeeded, int offset, byte[] value);
+
+    /**
+     * Triggered when this BleService receives a read request from a remote device.
+     */
+    public abstract void onCharacteristicRead(BluetoothDevice device,
+            int requestId, int offset, final BluetoothGattCharacteristic characteristic);
+
+    private AdvertiseCallback mAdvertisingCallback = new AdvertiseCallback() {
+        @Override
+        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+            super.onStartSuccess(settingsInEffect);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Successfully started advertising service");
+            }
+            for (ConnectionListener listener : mListeners) {
+                listener.onServerStarted();
+            }
+        }
+
+        @Override
+        public void onStartFailure(int errorCode) {
+            super.onStartFailure(errorCode);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Failed to advertise, errorCode: " + errorCode);
+            }
+            for (ConnectionListener listener : mListeners) {
+                listener.onServerStartFailed(errorCode);
+            }
+        }
+    };
+
+    private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
+        @Override
+        public void onConnectionStateChange(BluetoothDevice device,
+                final int status, final int newState) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "GattServer connection change status: "
+                        + newState + " newState: "
+                        + newState + " device name: " + device.getName());
+            }
+            if (newState == BluetoothProfile.STATE_CONNECTED) {
+                for (ConnectionListener listener : mListeners) {
+                    listener.onDeviceConnected(device);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceAdded(final int status, BluetoothGattService service) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Service added status: " + status + " uuid: " + service.getUuid());
+            }
+        }
+
+        @Override
+        public void onCharacteristicReadRequest(BluetoothDevice device,
+                int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
+            }
+            mGattServer.sendResponse(device, requestId,
+                    BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
+            SimpleBleServer.
+                    this.onCharacteristicRead(device, requestId, offset, characteristic);
+        }
+
+        @Override
+        public void onCharacteristicWriteRequest(final BluetoothDevice device, int requestId,
+                BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+                responseNeeded, int offset, byte[] value) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid());
+            }
+            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+                    offset, value);
+
+            SimpleBleServer.
+                    this.onCharacteristicWrite(device, requestId, characteristic,
+                    preparedWrite, responseNeeded, offset, value);
+        }
+    };
+}
diff --git a/car-cluster-logging-renderer/AndroidManifest.xml b/car-cluster-logging-renderer/AndroidManifest.xml
index 7802988..5fc8d57 100644
--- a/car-cluster-logging-renderer/AndroidManifest.xml
+++ b/car-cluster-logging-renderer/AndroidManifest.xml
@@ -17,7 +17,10 @@
       package="android.car.cluster.loggingrenderer"
       android:versionCode="1"
       android:versionName="1.0">
-    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+    <application android:label="@string/app_name"
+                 android:icon="@drawable/ic_launcher"
+                 android:directBootAware="true"
+                 android:persistent="true">
         <service android:name=".LoggingClusterRenderingService"
                  android:exported="false"
                  android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
diff --git a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
index fc205ec..0c37537 100644
--- a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
+++ b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
@@ -636,12 +636,12 @@
      */
     public static final class IgnitionMonitors {
         public static final class IgnitionMonitor {
-            public final boolean mAvailable;
-            public final boolean mIncomplete;
+            public final boolean available;
+            public final boolean incomplete;
 
             IgnitionMonitor(boolean available, boolean incomplete) {
-                mAvailable = available;
-                mIncomplete = incomplete;
+                this.available = available;
+                this.incomplete = incomplete;
             }
 
             public static final class Builder {
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
index 21af78c..9cb0e79 100644
--- a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
+++ b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
@@ -24,23 +24,32 @@
  */
 interface IVmsSubscriberService {
     /**
-     * Subscribes the listener to receive messages from layer/version. If silent is true, the
-     * service will not send subscription notifications to publishers (i.e. this is a passive
-     * subscriber). Note that removeOnVmsMessageReceivedListener does not require the parameter
-     * silent because the service keeps track of silent subscribes and it will not generate
-     * unsubscribe events in this scenario.
+     * Subscribes the listener to receive messages from layer/version.
      */
     void addOnVmsMessageReceivedListener(
-            int layer,
-            int version,
             in IOnVmsMessageReceivedListener listener,
-            boolean silent) = 0;
+            int layer,
+            int version) = 0;
+
+    /**
+     * Subscribes the listener to receive messages from all published layer/version. The
+     * service will not send any subscription notifications to publishers (i.e. this is a passive
+     * subscriber).
+     */
+    void addOnVmsMessageReceivedPassiveListener(in IOnVmsMessageReceivedListener listener) = 1;
 
     /**
      * Tells the VmsSubscriberService a client unsubscribes to layer messages.
      */
     void removeOnVmsMessageReceivedListener(
+            in IOnVmsMessageReceivedListener listener,
             int layer,
-            int version,
-            in IOnVmsMessageReceivedListener listener) = 1;
+            int version) = 2;
+
+    /**
+     * Tells the VmsSubscriberService a passive client unsubscribes. This will not unsubscribe
+     * the listener from any specific layer it has subscribed to.
+     */
+    void removeOnVmsMessageReceivedPassiveListener(
+            in IOnVmsMessageReceivedListener listener) = 3;
 }
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index 092b397..4dc4997 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -94,6 +94,9 @@
      * @return if the call to the method VmsPublisherService.publish was successful.
      */
     public final boolean publish(int layerId, int layerVersion, byte[] payload) {
+        if (DBG) {
+            Log.d(TAG, "Publishing for layer ID: " + layerId + " Version: " + layerVersion);
+        }
         if (mVmsPublisherService == null) {
             throw new IllegalStateException("VmsPublisherService not set.");
         }
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
index 0fb32d6..87486a4 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -151,7 +151,7 @@
      *                        it only listens passively.
      * @throws IllegalStateException if the listener was not set via {@link #setListener}.
      */
-    public void subscribe(int layer, int version, boolean silentSubscribe)
+    public void subscribe(int layer, int version)
             throws CarNotConnectedException {
         if (DBG) {
             Log.d(TAG, "Subscribing to layer: " + layer + ", version: " + version);
@@ -166,8 +166,31 @@
             throw new IllegalStateException("Listener was not set.");
         }
         try {
-            mVmsSubscriberService.addOnVmsMessageReceivedListener(layer, version, mIListener,
-                    silentSubscribe);
+            mVmsSubscriberService.addOnVmsMessageReceivedListener(mIListener, layer, version);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        } catch (IllegalStateException ex) {
+            Car.checkCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    public void subscribeAll()
+        throws CarNotConnectedException {
+        if (DBG) {
+            Log.d(TAG, "Subscribing passively to all data messages");
+        }
+        OnVmsMessageReceivedListener listener;
+        synchronized (mListenerLock) {
+            listener = mListener;
+        }
+        if (listener == null) {
+            Log.w(TAG, "subscribe: listener was not set, " +
+                "setListener must be called first.");
+            throw new IllegalStateException("Listener was not set.");
+        }
+        try {
+            mVmsSubscriberService.addOnVmsMessageReceivedPassiveListener(mIListener);
         } catch (RemoteException e) {
             Log.e(TAG, "Could not connect: ", e);
             throw new CarNotConnectedException(e);
@@ -197,7 +220,7 @@
             throw new IllegalStateException("Listener was not set.");
         }
         try {
-            mVmsSubscriberService.removeOnVmsMessageReceivedListener(layer, version, mIListener);
+            mVmsSubscriberService.removeOnVmsMessageReceivedListener(mIListener, layer, version);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to unregister subscriber", e);
             // ignore
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index 520b8b4..1a21ea4 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -24,6 +24,9 @@
 import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
 import android.car.hardware.CarPropertyValue;
+import android.car.hardware.CarSensorEvent;
+import android.car.hardware.CarSensorManager;
+import android.car.hardware.ICarSensorEventListener;
 import android.car.hardware.cabin.CarCabinManager;
 import android.car.hardware.property.CarPropertyEvent;
 import android.car.hardware.property.ICarPropertyEventListener;
@@ -69,7 +72,6 @@
  * connected.  The device that successfully connects on a profile is moved to the top of the list
  * of devices for that profile, so the next time a connection attempt is made, the policy starts
  * with the last connected device first.
- *
  */
 
 public class BluetoothDeviceConnectionPolicy {
@@ -90,8 +92,11 @@
 
     // Events that are listened to for triggering an auto-connect:
     // Cabin events like Door unlock coming from the Cabin Service.
-    private CarCabinService mCarCabinService;
-    private ICarPropertyEventListener mCabinEventListener;
+    private final CarCabinService mCarCabinService;
+    private final CarPropertyListener mCabinEventListener;
+    // Sensor events like Ignition switch ON from the Car Sensor Service
+    private final CarSensorService mCarSensorService;
+    private final CarSensorEventListener mCarSensorEventListener;
 
     // Profile Proxies.
     private BluetoothHeadsetClient mBluetoothHeadsetClient;
@@ -111,17 +116,21 @@
     private ConnectionParams mConnectionInFlight;
 
     public static BluetoothDeviceConnectionPolicy create(Context context,
-            CarCabinService carCabinService) {
-        return new BluetoothDeviceConnectionPolicy(context, carCabinService);
+            CarCabinService carCabinService, CarSensorService carSensorService) {
+        return new BluetoothDeviceConnectionPolicy(context, carCabinService, carSensorService);
     }
 
-    private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService) {
+    private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService,
+            CarSensorService carSensorService) {
         mContext = context;
         mCarCabinService = carCabinService;
+        mCarSensorService = carSensorService;
         mProfilesToConnect = Arrays.asList(new Integer[]
                 {BluetoothProfile.HEADSET_CLIENT,
                         BluetoothProfile.PBAP_CLIENT, BluetoothProfile.A2DP_SINK,
                         BluetoothProfile.MAP_CLIENT,});
+        mCabinEventListener = new CarPropertyListener();
+        mCarSensorEventListener = new CarSensorEventListener();
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mBluetoothAdapter == null) {
             Log.w(TAG, "No Bluetooth Adapter Available");
@@ -319,44 +328,66 @@
      */
     private void setupEventListeners() {
         // Setting up a listener for events from CarCabinService
-        // For now, we only listen to door unlock signal coming from {@link CarCabinService},
-        // but more events will be added here soon -  b/34723490
-        mCabinEventListener = new ICarPropertyEventListener.Stub() {
-            @Override
-            public void onEvent(CarPropertyEvent event) throws RemoteException {
-                handleCabinChangeEvent(event);
-            }
-        };
+        // For now, we listen to door unlock signal coming from {@link CarCabinService},
+        // and Ignition state START from {@link CarSensorService}
         mCarCabinService.registerListener(mCabinEventListener);
+        mCarSensorService.registerOrUpdateSensorListener(
+                CarSensorManager.SENSOR_TYPE_IGNITION_STATE, 0, mCarSensorEventListener);
     }
 
     /**
-     * handleCabinChangeEvent handles events coming in from the {@link CarCabinService}
+     * Handles events coming in from the {@link CarCabinService}
      * The events that can trigger Bluetooth Scanning from CarCabinService is Door Unlock.
      * Upon receiving the event that is of interest, initiate a connection attempt by calling
      * the policy {@link BluetoothDeviceConnectionPolicy}
      */
-    private void handleCabinChangeEvent(CarPropertyEvent event) {
-        if (DBG) {
-            Log.d(TAG, "Cabin change Event : " + event.getEventType());
-        }
-        Boolean locked;
-        CarPropertyValue value = event.getCarPropertyValue();
-        Object o = value.getValue();
+    private class CarPropertyListener extends ICarPropertyEventListener.Stub {
+        @Override
+        public void onEvent(CarPropertyEvent event) throws RemoteException {
+            if (DBG) {
+                Log.d(TAG, "Cabin change Event : " + event.getEventType());
+            }
+            Boolean locked;
+            CarPropertyValue value = event.getCarPropertyValue();
+            Object o = value.getValue();
 
-        if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
-            if (o instanceof Boolean) {
-                locked = (Boolean) o;
-                // Attempting a connection only on a door unlock
-                if (locked) {
+            if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
+                if (o instanceof Boolean) {
+                    locked = (Boolean) o;
                     if (DBG) {
-                        Log.d(TAG, "Door locked");
+                        Log.d(TAG, "Door Lock: " + locked);
                     }
-                } else {
+                    // Attempting a connection only on a door unlock
+                    if (!locked) {
+                        initiateConnection();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles events coming in from the {@link CarSensorService}
+     * The events that can trigger Bluetooth Scanning from CarSensorService is Ignition START.
+     * Upon receiving the event that is of interest, initiate a connection attempt by calling
+     * the policy {@link BluetoothDeviceConnectionPolicy}
+     */
+
+    private class CarSensorEventListener extends ICarSensorEventListener.Stub {
+        @Override
+        public void onSensorChanged(List<CarSensorEvent> events) throws RemoteException {
+            if (events != null & events.size() > 0) {
+                CarSensorEvent event = events.get(0);
+                if (DBG) {
+                    Log.d(TAG, "Sensor event Type : " + event.sensorType);
+                }
+                if (event.sensorType == CarSensorManager.SENSOR_TYPE_IGNITION_STATE) {
                     if (DBG) {
-                        Log.d(TAG, "Door Unlocked");
+                        Log.d(TAG, "Sensor value : " + event.intValues[0]);
                     }
-                    initiateConnection();
+                    if (event.intValues[0] == CarSensorEvent.IGNITION_STATE_START) {
+                        initiateConnection();
+                    }
                 }
             }
         }
@@ -671,7 +702,6 @@
         BluetoothDevice devToConnect = devInfo.getNextDeviceInQueueLocked();
         if (devToConnect != null) {
             switch (profile) {
-                // b/34723437 - Add MAP_CLIENT
                 case BluetoothProfile.A2DP_SINK:
                     if (mBluetoothA2dpSink != null) {
                         if (DBG) {
@@ -1095,11 +1125,11 @@
             return false;
         }
 
-        if (mBluetoothAdapter != null ) {
+        if (mBluetoothAdapter != null) {
             if (DBG) {
                 Log.d(TAG, "Bonded devices size:" + mBluetoothAdapter.getBondedDevices().size());
             }
-            if(mBluetoothAdapter.getBondedDevices().isEmpty()) {
+            if (mBluetoothAdapter.getBondedDevices().isEmpty()) {
                 if (DBG) {
                     Log.d(TAG, "No Bonded Devices available. Quit rebuilding");
                 }
@@ -1109,7 +1139,7 @@
 
         boolean rebuildSuccess = true;
         // Iterate through the Map's entries and build the {@link #mProfileToConnectableDevicesMap}
-        if (mProfileToConnectableDevicesMap == null ) {
+        if (mProfileToConnectableDevicesMap == null) {
             mProfileToConnectableDevicesMap = new HashMap<Integer, BluetoothDevicesInfo>();
         }
 
diff --git a/service/src/com/android/car/CarBluetoothService.java b/service/src/com/android/car/CarBluetoothService.java
index 0d23b77..3efcda8 100644
--- a/service/src/com/android/car/CarBluetoothService.java
+++ b/service/src/com/android/car/CarBluetoothService.java
@@ -32,12 +32,13 @@
 
     private static final String TAG = "CarBluetoothService";
     private final Context mContext;
-    private BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy = null;
+    private final BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy;
 
-    public CarBluetoothService(Context context, CarCabinService carCabinService) {
+    public CarBluetoothService(Context context, CarCabinService carCabinService,
+            CarSensorService carSensorService) {
         mContext = context;
         mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext,
-                carCabinService);
+                carCabinService, carSensorService);
     }
 
     @Override
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
index 5b4b675..c14a385 100644
--- a/service/src/com/android/car/CarDiagnosticService.java
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -25,12 +25,8 @@
 import android.car.hardware.ICarDiagnostic;
 import android.car.hardware.ICarDiagnosticEventListener;
 import android.content.Context;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Log;
 import com.android.car.internal.CarPermission;
@@ -53,9 +49,6 @@
 /** @hide */
 public class CarDiagnosticService extends ICarDiagnostic.Stub
         implements CarServiceBase, DiagnosticHalService.DiagnosticListener {
-    /** {@link #mDiagnosticLock} is not waited forever for handling disconnection */
-    private static final long MAX_DIAGNOSTIC_LOCK_WAIT_MS = 1000;
-
     /** lock to access diagnostic structures */
     private final ReentrantLock mDiagnosticLock = new ReentrantLock();
     /** hold clients callback */
@@ -487,82 +480,6 @@
         }
     }
 
-    private class DiagnosticDispatchHandler extends Handler {
-        private static final long DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS = 16; // over 60Hz
-
-        private static final int MSG_DIAGNOSTIC_DATA = 0;
-
-        private long mLastDiagnosticDispatchTime = -1;
-        private int mFreeListIndex = 0;
-        private final LinkedList<CarDiagnosticEvent>[] mDiagnosticDataList = new LinkedList[2];
-
-        private DiagnosticDispatchHandler(Looper looper) {
-            super(looper);
-            for (int i = 0; i < mDiagnosticDataList.length; i++) {
-                mDiagnosticDataList[i] = new LinkedList<CarDiagnosticEvent>();
-            }
-        }
-
-        private synchronized void handleDiagnosticEvents(List<CarDiagnosticEvent> data) {
-            LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
-            list.addAll(data);
-            requestDispatchLocked();
-        }
-
-        private synchronized void handleDiagnosticEvent(CarDiagnosticEvent event) {
-            LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
-            list.add(event);
-            requestDispatchLocked();
-        }
-
-        private void requestDispatchLocked() {
-            Message msg = obtainMessage(MSG_DIAGNOSTIC_DATA);
-            long now = SystemClock.uptimeMillis();
-            long delta = now - mLastDiagnosticDispatchTime;
-            if (delta > DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS) {
-                sendMessage(msg);
-            } else {
-                sendMessageDelayed(msg, DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS - delta);
-            }
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DIAGNOSTIC_DATA:
-                    doHandleDiagnosticData();
-                    break;
-                default:
-                    break;
-            }
-        }
-
-        private void doHandleDiagnosticData() {
-            List<CarDiagnosticEvent> listToDispatch = null;
-            synchronized (this) {
-                mLastDiagnosticDispatchTime = SystemClock.uptimeMillis();
-                int nonFreeListIndex = mFreeListIndex ^ 0x1;
-                List<CarDiagnosticEvent> nonFreeList = mDiagnosticDataList[nonFreeListIndex];
-                List<CarDiagnosticEvent> freeList = mDiagnosticDataList[mFreeListIndex];
-                if (nonFreeList.size() > 0) {
-                    // copy again, but this should not be normal case
-                    nonFreeList.addAll(freeList);
-                    listToDispatch = nonFreeList;
-                    freeList.clear();
-                } else if (freeList.size() > 0) {
-                    listToDispatch = freeList;
-                    mFreeListIndex = nonFreeListIndex;
-                }
-            }
-            // leave this part outside lock so that time-taking dispatching can be done without
-            // blocking diagnostic event notification.
-            if (listToDispatch != null) {
-                processDiagnosticData(listToDispatch);
-                listToDispatch.clear();
-            }
-        }
-    }
-
     /** internal instance for pending client request */
     private class DiagnosticClient implements Listeners.IListener {
         /** callback for diagnostic events */
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 5ed9d49..b4846b7 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -396,8 +396,6 @@
         extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback);
         intent.putExtras(extras);
         intent.setComponent(ComponentName.unflattenFromString(carInputService));
-        // Explicitly start service as we do not use BIND_AUTO_CREATE flag to handle service crash.
-        mContext.startService(intent);
-        return mContext.bindService(intent, mInputServiceConnection, Context.BIND_IMPORTANT);
+        return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE);
     }
 }
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 52fd93f..89ee8b0 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -121,8 +121,8 @@
             mRegisteredService = serviceIntent;
         }
         UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
-        mContext.startServiceAsUser(serviceIntent, userHandle);
-        mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_IMPORTANT, userHandle);
+        mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
+                userHandle);
     }
 
     private void unbindServiceIfBound() {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 61aae91..e671ec7 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -43,7 +43,7 @@
 
 public class ICarImpl extends ICar.Stub {
 
-    public static final String INTERNAL_INPUT_SERVICE =  "internal_input";
+    public static final String INTERNAL_INPUT_SERVICE = "internal_input";
     public static final String INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE =
             "system_activity_monitoring";
 
@@ -120,14 +120,15 @@
                 mCarPowerManagementService, mCarAudioService, this);
         mCarVendorExtensionService = new CarVendorExtensionService(serviceContext,
                 mHal.getVendorExtensionHal());
-        mCarBluetoothService = new CarBluetoothService(serviceContext, mCarCabinService);
+        mCarBluetoothService = new CarBluetoothService(serviceContext, mCarCabinService,
+                mCarSensorService);
         if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
             mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
             mVmsPublisherService = new VmsPublisherService(serviceContext, mHal.getVmsHal());
         }
         if (FeatureConfiguration.ENABLE_DIAGNOSTIC) {
             mCarDiagnosticService = new CarDiagnosticService(serviceContext,
-                mHal.getDiagnosticHal());
+                    mHal.getDiagnosticHal());
         }
 
         // Be careful with order. Service depending on other service should be inited later.
@@ -357,6 +358,7 @@
         private static final String PARAM_NIGHT_MODE = "night";
         private static final String PARAM_SENSOR_MODE = "sensor";
         private static final String PARAM_ZONED_BOOLEAN = "zoned-boolean";
+        private static final String PARAM_GLOBAL_INT = "global-integer";
 
         private void dumpHelp(PrintWriter pw) {
             pw.println("Car service commands:");
@@ -380,17 +382,32 @@
                     break;
                 case COMMAND_INJECT_EVENT:
                     String eventType;
-                    if(args.length > 1) {
-                         eventType = args[1];
-                         // Zoned boolean event
-                         if(eventType.equalsIgnoreCase(PARAM_ZONED_BOOLEAN)) {
-                             if(args.length < 5) {
-                                 writer.println("Incorrect number of arguments.");
-                                 dumpHelp(writer);
-                                 break;
-                             }
-                             inject_zoned_boolean_event(args[2], args[3], args[4], writer);
-                         }
+                    if (args.length > 1) {
+                        eventType = args[1].toLowerCase();
+                        switch (eventType) {
+                            case PARAM_ZONED_BOOLEAN:
+                                if (args.length < 5) {
+                                    writer.println("Incorrect number of arguments.");
+                                    dumpHelp(writer);
+                                    break;
+                                }
+                                inject_zoned_boolean_event(args[2], args[3], args[4], writer);
+                                break;
+
+                            case PARAM_GLOBAL_INT:
+                                if (args.length < 4) {
+                                    writer.println("Incorrect number of Arguments");
+                                    dumpHelp(writer);
+                                    break;
+                                }
+                                inject_global_integer_event(args[2], args[3], writer);
+                                break;
+
+                            default:
+                                writer.println("Unsupported event type");
+                                dumpHelp(writer);
+                                break;
+                        }
                     }
                     break;
                 default:
@@ -434,12 +451,13 @@
 
         /**
          * Inject a fake boolean HAL event to help testing.
-         * Currently supporting Door Unlock/Lock
+         *
          * @param property - Vehicle Property
-         * @param value - boolean value for the property
-         * @param writer - Printwriter
+         * @param value    - boolean value for the property
+         * @param writer   - Printwriter
          */
-        private void inject_zoned_boolean_event(String property, String zone, String value, PrintWriter writer) {
+        private void inject_zoned_boolean_event(String property, String zone, String value,
+                PrintWriter writer) {
             Log.d(CarLog.TAG_SERVICE, "Injecting Boolean event");
             boolean event;
             int propId;
@@ -459,5 +477,27 @@
             mHal.injectBooleanEvent(propId, zoneId, event);
         }
 
+        /**
+         * Inject a fake Integer HAL event to help testing.
+         *
+         * @param property - Vehicle Property
+         * @param value    - Integer value to inject
+         * @param writer   - PrintWriter
+         */
+        private void inject_global_integer_event(String property, String value,
+                PrintWriter writer) {
+            Log.d(CarLog.TAG_SERVICE, "Injecting integer event");
+            int propId;
+            int eventValue;
+            try {
+                propId = Integer.decode(property);
+                eventValue = Integer.decode(value);
+            } catch (NumberFormatException e) {
+                writer.println("Invalid property Id or event value.  Prefix hex values with 0x");
+                return;
+            }
+            mHal.injectIntegerEvent(propId, eventValue);
+        }
+
     }
 }
\ No newline at end of file
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 2d8f309..2c7215c 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -17,6 +17,8 @@
 package com.android.car;
 
 import android.car.annotation.FutureFeature;
+import android.car.vms.IOnVmsMessageReceivedListener;
+
 import android.car.vms.IVmsPublisherClient;
 import android.car.vms.IVmsPublisherService;
 import android.content.ComponentName;
@@ -37,6 +39,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * + Receives HAL updates by implementing VmsHalService.VmsHalListener.
@@ -92,21 +95,50 @@
     // Implements IVmsPublisherService interface.
     @Override
     public void publish(int layerId, int layerVersion, byte[] payload) {
+        if (DBG) {
+            Log.d(TAG, "Publishing for layer ID: " + layerId + " Version: " + layerVersion);
+        }
         ICarImpl.assertVmsPublisherPermission(mContext);
-        mHal.setDataMessage(layerId, layerVersion, payload);
+        VmsLayer layer = new VmsLayer(layerId, layerVersion);
+
+        // Sned the message to application listeners.
+        Set<IOnVmsMessageReceivedListener> listeners = mHal.getListeners(layer);
+
+        if (DBG) {
+            Log.d(TAG, "Number of subscribed apps: " + listeners.size());
+        }
+        for (IOnVmsMessageReceivedListener listener : listeners) {
+            try {
+                listener.onVmsMessageReceived(layerId, layerVersion, payload);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to publish to listener: " + listener);
+            }
+        }
+
+        // Send the message to HAL
+        if (mHal.isHalSubscribed(layer)) {
+            Log.d(TAG, "HAL is subscribed");
+            mHal.setDataMessage(layerId, layerVersion, payload);
+        } else {
+            Log.d(TAG, "HAL is NOT subscribed");
+        }
     }
 
     @Override
-    public boolean hasSubscribers(int layer, int version) {
+    public boolean hasSubscribers(int layerId, int layerVersion) {
         ICarImpl.assertVmsPublisherPermission(mContext);
-        // TODO(antoniocortes): implement this logic.
-        return true;
+        VmsLayer layer = new VmsLayer(layerId, layerVersion);
+        return mHal.isHalSubscribed(layer) || mHal.hasLayerSubscriptions(layer);
     }
 
     // Implements VmsHalListener interface
     @Override
     public void onChange(int layerId, int layerVersion, boolean hasSubscribers) {
-        // TODO(antoniocortes): notify publishers if this message causes a subscription change.
+        if (hasSubscribers) {
+            mHal.addHalSubscription(new VmsLayer(layerId, layerVersion));
+        } else {
+            mHal.removeHalSubscription(new VmsLayer(layerId, layerVersion));
+        }
     }
 
     /**
diff --git a/service/src/com/android/car/VmsRouting.java b/service/src/com/android/car/VmsRouting.java
index 6f93783..333634d 100644
--- a/service/src/com/android/car/VmsRouting.java
+++ b/service/src/com/android/car/VmsRouting.java
@@ -150,6 +150,24 @@
     }
 
     /**
+     * Checks if a listener is subscribed to any messages.
+     * @param listener that may have subscription.
+     * @return true if the listener uis subscribed to messages.
+     */
+    public boolean containsListener(IOnVmsMessageReceivedListener listener) {
+        synchronized (mLock) {
+            // Check if listener is subscribed to a layer.
+            for (Set<IOnVmsMessageReceivedListener> layerListeners: mLayerSubscriptions.values()) {
+                if (layerListeners.contains(listener)) {
+                    return true;
+                }
+            }
+            // Check is listener is subscribed to all data messages.
+            return mPromiscuousSubscribers.contains(listener);
+        }
+    }
+
+    /**
      * Add a layer and version to the HAL subscriptions.
      * @param layer the HAL subscribes to.
      */
@@ -170,6 +188,28 @@
     }
 
     /**
+     * checks if the HAL is subscribed to a layer.
+     * @param layer
+     * @return true if the HAL is subscribed to layer.
+     */
+    public boolean isHalSubscribed(VmsLayer layer) {
+        synchronized (mLock) {
+            return mHalSubscriptions.contains(layer);
+        }
+    }
+
+    /**
+     * checks if there are subscribers to a layer.
+     * @param layer
+     * @return true if there are subscribers to layer.
+     */
+    public boolean hasLayerSubscriptions(VmsLayer layer) {
+        synchronized (mLock) {
+            return mLayerSubscriptions.containsKey(layer);
+        }
+    }
+
+    /**
      * @return a Set of layers and versions which VMS clients are subscribed to.
      */
     public Collection<VmsLayer> getSubscribedLayers() {
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
index 2a0d45a..679b07d 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -33,11 +33,11 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * + Receives HAL updates by implementing VmsHalService.VmsHalListener.
  * + Offers subscriber/publisher services by implementing IVmsService.Stub.
- * TODO(antoniocortes): implement layer subscription logic (i.e. routing).
  */
 @FutureFeature
 public class VmsSubscriberService extends IVmsSubscriberService.Stub
@@ -48,7 +48,10 @@
 
     private final Context mContext;
     private final VmsHalService mHal;
+
+    @GuardedBy("mSubscriberServiceLock")
     private final VmsListenerManager mMessageReceivedManager = new VmsListenerManager();
+    private final Object mSubscriberServiceLock = new Object();
 
     /**
      * Keeps track of listeners of this service.
@@ -57,11 +60,11 @@
         /**
          * Allows to modify mListenerMap and mListenerDeathRecipientMap as a single unit.
          */
-        private final Object mLock = new Object();
-        @GuardedBy("mLock")
+        private final Object mListenerManagerLock = new Object();
+        @GuardedBy("mListenerManagerLock")
         private final Map<IBinder, ListenerDeathRecipient> mListenerDeathRecipientMap =
                 new HashMap<>();
-        @GuardedBy("mLock")
+        @GuardedBy("mListenerManagerLock")
         private final Map<IBinder, IOnVmsMessageReceivedListener> mListenerMap = new HashMap<>();
 
         class ListenerDeathRecipient implements IBinder.DeathRecipient {
@@ -79,6 +82,20 @@
                 if (DBG) {
                     Log.d(TAG, "binderDied " + mListenerBinder);
                 }
+
+                // Get the Listener from the Binder
+                IOnVmsMessageReceivedListener listener = mListenerMap.get(mListenerBinder);
+
+                // Remove the listener subscriptions.
+                if (listener != null) {
+                    Log.d(TAG, "Removing subscriptions for dead listener: " + listener);
+                    mHal.removeDeadListener(listener);
+                } else {
+                    Log.d(TAG, "Handling dead binder with no matching listener");
+
+                }
+
+                // Remove binder
                 VmsListenerManager.this.removeListener(mListenerBinder);
             }
 
@@ -113,7 +130,7 @@
                 Log.d(TAG, "register: " + listener);
             }
             IBinder listenerBinder = listener.asBinder();
-            synchronized (mLock) {
+            synchronized (mListenerManagerLock) {
                 if (mListenerMap.containsKey(listenerBinder)) {
                     // Already registered, nothing to do.
                     return;
@@ -152,8 +169,7 @@
         // Removes the listenerBinder from the current state.
         // The function assumes that binder will exist both in listeners and death recipients list.
         private void removeListener(IBinder listenerBinder) {
-            synchronized (mLock) {
-                //TODO(asafro): mHal.vms.routing.removeDeadListener(mListenerMap(listenerBinder))
+            synchronized (mListenerManagerLock) {
                 boolean found = mListenerMap.remove(listenerBinder) != null;
                 if (found) {
                     mListenerDeathRecipientMap.get(listenerBinder).release();
@@ -170,7 +186,7 @@
          * @return list of listeners.
          */
         public List<IOnVmsMessageReceivedListener> getListeners() {
-            synchronized (mLock) {
+            synchronized (mListenerManagerLock) {
                 return new ArrayList<>(mListenerMap.values());
             }
         }
@@ -199,21 +215,70 @@
 
     // Implements IVmsService interface.
     @Override
-    public void addOnVmsMessageReceivedListener(int layer, int version,
-            IOnVmsMessageReceivedListener listener, boolean silent) {
-        mMessageReceivedManager.add(listener);
+    public void addOnVmsMessageReceivedListener(IOnVmsMessageReceivedListener listener,
+        int layerId, int layerVersion) {
+        synchronized (mSubscriberServiceLock) {
+            // Add the listener so it can subscribe.
+            mMessageReceivedManager.add(listener);
+
+            // Add the subscription for the layer.
+            VmsLayer layer = new VmsLayer(layerId, layerVersion);
+            mHal.addSubscription(listener, layer);
+        }
     }
 
     @Override
-    public void removeOnVmsMessageReceivedListener(int layer, int version,
-            IOnVmsMessageReceivedListener listener) {
-        mMessageReceivedManager.remove(listener);
+    public void removeOnVmsMessageReceivedListener(IOnVmsMessageReceivedListener listener,
+        int layerId, int layerVersion) {
+        synchronized (mSubscriberServiceLock) {
+            // Remove the subscription.
+            VmsLayer layer = new VmsLayer(layerId, layerVersion);
+            mHal.removeSubscription(listener, layer);
+
+            // Remove the listener if it has no more subscriptions.
+            if (!mHal.containsListener(listener)) {
+                mMessageReceivedManager.remove(listener);
+            }
+        }
+    }
+
+    @Override
+    public void addOnVmsMessageReceivedPassiveListener(IOnVmsMessageReceivedListener listener) {
+        synchronized (mSubscriberServiceLock) {
+            mMessageReceivedManager.add(listener);
+            mHal.addSubscription(listener);
+        }
+    }
+
+    @Override
+    public void removeOnVmsMessageReceivedPassiveListener(IOnVmsMessageReceivedListener listener) {
+        synchronized (mSubscriberServiceLock) {
+            // Remove the subscription.
+            mHal.removeSubscription(listener);
+
+            // Remove the listener if it has no more subscriptions.
+            if (!mHal.containsListener(listener)) {
+                mMessageReceivedManager.remove(listener);
+            }
+        }
     }
 
     // Implements VmsHalSubscriberListener interface
     @Override
     public void onChange(int layerId, int layerVersion, byte[] payload) {
-        for (IOnVmsMessageReceivedListener subscriber : mMessageReceivedManager.getListeners()) {
+        VmsLayer layer = new VmsLayer(layerId, layerVersion);
+        if(DBG) {
+            Log.d(TAG, "Publishing a message for layer: " + layer);
+        }
+
+        Set<IOnVmsMessageReceivedListener> listeners = mHal.getListeners(layer);
+
+        // If there are no listeners we're done.
+        if ((listeners == null)) {
+            return;
+        }
+
+        for (IOnVmsMessageReceivedListener subscriber : listeners) {
             try {
                 subscriber.onVmsMessageReceived(layerId, layerVersion, payload);
             } catch (RemoteException e) {
@@ -222,5 +287,6 @@
                 Log.e(TAG, "onVmsMessageReceived calling failed: ", e);
             }
         }
+
     }
 }
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index f64e49c..84e3678 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -33,7 +33,10 @@
 import com.android.car.CarServiceUtils;
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArraySet;
@@ -120,15 +123,36 @@
         return mVehiclePropertyToConfig.get(halPropId, null);
     }
 
+    private List<Integer> getPropConfigArray(int halPropId) {
+        VehiclePropConfig propConfig = getPropConfig(halPropId);
+        return propConfig.configArray;
+    }
+
     private int getNumIntegerSensors(int halPropId) {
         int count = Obd2IntegerSensorIndex.LAST_SYSTEM_INDEX + 1;
-        count = count + getPropConfig(halPropId).configArray.get(0);
+        List<Integer> configArray = getPropConfigArray(halPropId);
+        if(configArray.size() < 2) {
+            Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
+                    "property 0x%x does not specify the number of vendor-specific properties." +
+                            "assuming 0.", halPropId));
+        }
+        else {
+            count += configArray.get(0);
+        }
         return count;
     }
 
     private int getNumFloatSensors(int halPropId) {
         int count = Obd2FloatSensorIndex.LAST_SYSTEM_INDEX + 1;
-        count = count + getPropConfig(halPropId).configArray.get(1);
+        List<Integer> configArray = getPropConfigArray(halPropId);
+        if(configArray.size() < 2) {
+            Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
+                "property 0x%x does not specify the number of vendor-specific properties." +
+                    "assuming 0.", halPropId));
+        }
+        else {
+            count += configArray.get(1);
+        }
         return count;
     }
 
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 052f1da..fba375c 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -461,6 +461,12 @@
         synchronized (this) {
             for (VehiclePropValue v : propValues) {
                 HalServiceBase service = mPropertyHandlers.get(v.prop);
+                if(service == null) {
+                    if (DBG) {
+                        Log.d(CarLog.TAG_HAL, "HalService is null for " + v.prop);
+                    }
+                    return;
+                }
                 service.getDispatchList().add(v);
                 mServicesToDispatch.add(service);
                 VehiclePropertyEventInfo info = mEventLog.get(v.prop);
@@ -555,6 +561,18 @@
         onPropertyEvent(Lists.newArrayList(v));
     }
 
+    /**
+     * Inject a fake Integer HAL event - for testing purposes.
+     * @param propId - VehicleProperty ID
+     * @param value - Integer value to inject
+     */
+    public void injectIntegerEvent(int propId, int value) {
+        VehiclePropValue v = createPropValue(propId, 0);
+        v.value.int32Values.add(value);
+        v.timestamp = SystemClock.elapsedRealtimeNanos();
+        onPropertyEvent(Lists.newArrayList(v));
+    }
+
     private static class VehiclePropertyEventInfo {
         private int eventCount;
         private VehiclePropValue lastEvent;
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index b69824b..c23bb6a 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -18,19 +18,19 @@
 import static com.android.car.CarServiceUtils.toByteArray;
 import static java.lang.Integer.toHexString;
 
-import android.annotation.Nullable;
 import android.car.VehicleAreaType;
 import android.car.annotation.FutureFeature;
+import android.car.vms.IOnVmsMessageReceivedListener;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageIntegerValuesIndex;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
 import android.util.Log;
-
 import com.android.car.CarLog;
+import com.android.car.VmsLayer;
+import com.android.car.VmsRouting;
 import com.android.internal.annotations.GuardedBy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -39,7 +39,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -64,6 +63,9 @@
     private CopyOnWriteArrayList<VmsHalSubscriberListener> mSubscriberListeners =
         new CopyOnWriteArrayList<>();
     private final VehicleHal mVehicleHal;
+    @GuardedBy("mLock")
+    private VmsRouting mRouting = new VmsRouting();
+    private final Object mLock = new Object();
 
     /**
      * The VmsPublisherService implements this interface to receive data from the HAL.
@@ -105,6 +107,128 @@
         mSubscriberListeners.remove(listener);
     }
 
+    public void addSubscription(IOnVmsMessageReceivedListener listener, VmsLayer layer) {
+        synchronized (mLock) {
+            // Check if publishers need to be notified about this change in subscriptions.
+            boolean firstSubscriptionForLayer = !mRouting.getSubscribedLayers().contains(layer);
+
+            // Add the listeners subscription to the layer
+            mRouting.addSubscription(listener, layer);
+
+            // Notify the publishers
+            if (firstSubscriptionForLayer) {
+                notifyPublishers(layer, true);
+            }
+        }
+    }
+
+    public void removeSubscription(IOnVmsMessageReceivedListener listener, VmsLayer layer) {
+        synchronized (mLock) {
+            if (!mRouting.hasLayerSubscriptions(layer)) {
+                Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
+                return;
+            }
+
+            // Remove the listeners subscription to the layer
+            mRouting.removeSubscription(listener, layer);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            boolean layerHasSubscribers = mRouting.getSubscribedLayers().contains(layer);
+
+            // Notify the publishers
+            if (!layerHasSubscribers) {
+                notifyPublishers(layer, false);
+            }
+        }
+    }
+
+    public void addSubscription(IOnVmsMessageReceivedListener listener) {
+        synchronized (mLock) {
+            mRouting.addSubscription(listener);
+        }
+    }
+
+    public void removeSubscription(IOnVmsMessageReceivedListener listener) {
+        synchronized (mLock) {
+            mRouting.removeSubscription(listener);
+        }
+    }
+
+    public void removeDeadListener(IOnVmsMessageReceivedListener listener) {
+        synchronized (mLock) {
+            mRouting.removeDeadListener(listener);
+        }
+    }
+
+    public Set<IOnVmsMessageReceivedListener> getListeners(VmsLayer layer) {
+        return mRouting.getListeners(layer);
+    }
+
+    public boolean isHalSubscribed(VmsLayer layer) {
+        return mRouting.isHalSubscribed(layer);
+    }
+
+    public boolean hasLayerSubscriptions(VmsLayer layer) {
+        return mRouting.hasLayerSubscriptions(layer);
+    }
+
+    public void addHalSubscription(VmsLayer layer) {
+        synchronized (mLock) {
+            // Check if publishers need to be notified about this change in subscriptions.
+            boolean firstSubscriptionForLayer = !mRouting.getSubscribedLayers().contains(layer);
+
+            // Add the listeners subscription to the layer
+            mRouting.addHalSubscription(layer);
+
+            if (firstSubscriptionForLayer) {
+                notifyPublishers(layer, true);
+            }
+        }
+    }
+
+    public void removeHalSubscription(VmsLayer layer) {
+        synchronized (mLock) {
+            if (!mRouting.hasLayerSubscriptions(layer)) {
+                Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
+                return;
+            }
+
+            // Remove the listeners subscription to the layer
+            mRouting.removeHalSubscription(layer);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            boolean layerHasSubscribers = mRouting.getSubscribedLayers().contains(layer);
+
+            // Notify the publishers
+            if (!layerHasSubscribers) {
+                notifyPublishers(layer, false);
+            }
+        }
+    }
+
+    public boolean containsListener(IOnVmsMessageReceivedListener listener) {
+        return mRouting.containsListener(listener);
+    }
+
+    /**
+     * Notify all the publishers and the HAL on subscription changes regardless of who triggered
+     * the change.
+     *
+     * @param layer which is being subscribed to or unsubscribed from.
+     * @param hasListeners indicates if the notification is for subscription or unsubscription.
+     */
+    public void notifyPublishers(VmsLayer layer, boolean hasSubscribers) {
+        synchronized (mLock) {
+            // notify the HAL
+            setSubscriptionRequest(layer.getId(), layer.getVersion(), hasSubscribers);
+
+            // Notify the App publishers
+            for (VmsHalPublisherListener listener : mPublisherListeners) {
+                listener.onChange(layer.getId(), layer.getVersion(), hasSubscribers);
+            }
+        }
+    }
+
     @Override
     public void init() {
         if (DBG) {
@@ -146,6 +270,9 @@
 
     @Override
     public void handleHalEvents(List<VehiclePropValue> values) {
+        if (DBG) {
+            Log.d(TAG, "Handling a VMS property change");
+        }
         for (VehiclePropValue v : values) {
             ArrayList<Integer> vec = v.value.int32Values;
             int messageType = vec.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
@@ -160,6 +287,12 @@
 
             }
 
+            if (DBG) {
+                Log.d(TAG,
+                    "Received message for Type: " + messageType +
+                        " Layer Id: " + layerId +
+                        "Version: " + layerVersion);
+            }
             // This is a data message intended for subscribers.
             if (messageType == VmsMessageType.DATA) {
                 // Get the payload.
@@ -169,14 +302,14 @@
                 for (VmsHalSubscriberListener listener : mSubscriberListeners) {
                     listener.onChange(layerId, layerVersion, payload);
                 }
-            } else {
-                //TODO(b/35386660): This is placeholder until implementing subscription manager.
-                // Get subscribe or unsubscribe.
-                boolean hasSubscribers = (messageType == VmsMessageType.SUBSCRIBE) ? true : false;
-
-                // Send the message.
+            } else if (messageType == VmsMessageType.SUBSCRIBE) {
                 for (VmsHalPublisherListener listener : mPublisherListeners) {
-                    listener.onChange(layerId, layerVersion, hasSubscribers);
+                    listener.onChange(layerId, layerVersion, true);
+                }
+            } else {
+                // messageType == VmsMessageType.UNSUBSCRIBE
+                for (VmsHalPublisherListener listener : mPublisherListeners) {
+                    listener.onChange(layerId, layerVersion, false);
                 }
             }
         }
@@ -194,15 +327,9 @@
      * @param property the value used to update the HAL property.
      * @return         true if the call to the HAL to update the property was successful.
      */
-    public boolean setSubscribeRequest(int layerId, int layerVersion) {
-        VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.SUBSCRIBE,
-            layerId,
-            layerVersion);
-        return setPropertyValue(vehiclePropertyValue);
-    }
-
-    public boolean setUnsubscribeRequest(int layerId, int layerVersion) {
-        VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.UNSUBSCRIBE,
+    public boolean setSubscriptionRequest(int layerId, int layerVersion, boolean hasSubscribers) {
+        VehiclePropValue vehiclePropertyValue = toVehiclePropValue(
+            hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE,
             layerId,
             layerVersion);
         return setPropertyValue(vehiclePropertyValue);
@@ -213,11 +340,6 @@
             layerId,
             layerVersion,
             payload);
-        // TODO(b/34977500): remove call to handleHalEvents once the routing is implemented.
-        // This temporal code forwards messages from publishers to subscribers.
-        List<VehiclePropValue> list = new ArrayList<>();
-        list.add(vehiclePropertyValue);
-        handleHalEvents(list);
         return setPropertyValue(vehiclePropertyValue);
     }
 
@@ -259,4 +381,4 @@
         }
         return vehicleProp;
     }
-}
+}
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
index c531e82..b4915cc 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
@@ -243,8 +243,8 @@
                     mDelivered.setChecked(true);
                 } else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_RECEIVED)) {
 
-                    String[] recipients = intent.getStringArrayExtra(android.provider
-                            .ContactsContract.Intents.EXTRA_RECIPIENT_CONTACT_URI);
+                    String[] recipients = intent.getStringArrayExtra(
+                            BluetoothMapClient.EXTRA_SENDER_CONTACT_URI);
                     StringBuilder stringBuilder = new StringBuilder();
                     if (recipients != null) {
                         for (String s : recipients) {
@@ -252,8 +252,8 @@
                         }
                     }
 
-                    String[] recipientsName = intent.getStringArrayExtra(android.provider
-                            .ContactsContract.Intents.EXTRA_RECIPIENT_CONTACT_NAME);
+                    String[] recipientsName = intent.getStringArrayExtra(
+                            BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME);
                     StringBuilder stringBuilderName = new StringBuilder();
                     if (recipientsName != null) {
                         for (String s : recipientsName) {
diff --git a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
index 71f957e..39c0519 100644
--- a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
+++ b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
@@ -89,7 +89,7 @@
         private void configureSubscriptions(VmsSubscriberManager vmsSubscriberManager) {
             try {
                 vmsSubscriberManager.setListener(mListener);
-                vmsSubscriberManager.subscribe(TEST_LAYER_ID, TEST_LAYER_VERSION, false);
+                vmsSubscriberManager.subscribe(TEST_LAYER_ID, TEST_LAYER_VERSION);
             } catch (android.car.CarNotConnectedException e) {
                 Log.e(TAG, "Car is not connected!", e);
             }
diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
new file mode 100644
index 0000000..bc18c13
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.test;
+
+import static java.lang.Integer.toHexString;
+
+import android.car.Car;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticEvent.FuelSystemStatus;
+import android.car.hardware.CarDiagnosticEvent.FuelType;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors.CommonIgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors.CompressionIgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors.SparkIgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.Obd2FloatSensorIndex;
+import android.car.hardware.CarDiagnosticEvent.Obd2IntegerSensorIndex;
+import android.car.hardware.CarDiagnosticEvent.SecondaryAirStatus;
+import android.car.hardware.CarDiagnosticManager;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Test the public entry points for the CarDiagnosticManager */
+@MediumTest
+public class CarDiagnosticManagerTest extends MockedCarTestBase {
+    private static final String TAG = CarDiagnosticManagerTest.class.getSimpleName();
+
+    private final DiagnosticEventBuilder mLiveFrameEventBuilder =
+            new DiagnosticEventBuilder(VehicleProperty.OBD2_LIVE_FRAME);
+    private final DiagnosticEventBuilder mFreezeFrameEventBuilder =
+            new DiagnosticEventBuilder(VehicleProperty.OBD2_FREEZE_FRAME);
+    private final FreezeFrameProperties mFreezeFrameProperties = new FreezeFrameProperties();
+
+    private CarDiagnosticManager mCarDiagnosticManager;
+
+    private static final String DTC = "P1010";
+
+    /**
+     * This class is a central repository for freeze frame data. It ensures that timestamps and
+     * events are kept in sync and provides a consistent access model for diagnostic properties.
+     */
+    class FreezeFrameProperties {
+        private final HashMap<Long, VehiclePropValue> mEvents = new HashMap<>();
+
+        public final VehicleHalPropertyHandler mFreezeFrameInfoHandler =
+                new FreezeFrameInfoHandler();
+        public final VehicleHalPropertyHandler mFreezeFrameHandler = new FreezeFrameHandler();
+        public final VehicleHalPropertyHandler mFreezeFrameClearHandler =
+                new FreezeFrameClearHandler();
+
+        synchronized VehiclePropValue addNewEvent(DiagnosticEventBuilder builder) {
+            long timestamp = SystemClock.elapsedRealtimeNanos();
+            return addNewEvent(builder, timestamp);
+        }
+
+        synchronized VehiclePropValue addNewEvent(DiagnosticEventBuilder builder, long timestamp) {
+            VehiclePropValue newEvent = builder.build(timestamp);
+            mEvents.put(timestamp, newEvent);
+            return newEvent;
+        }
+
+        synchronized VehiclePropValue removeEvent(long timestamp) {
+            return mEvents.remove(timestamp);
+        }
+
+        synchronized void removeEvents() {
+            mEvents.clear();
+        }
+
+        synchronized long[] getTimestamps() {
+            return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
+        }
+
+        synchronized VehiclePropValue getEvent(long timestamp) {
+            return mEvents.get(timestamp);
+        }
+
+        class FreezeFramePropertyHandler implements VehicleHalPropertyHandler {
+            private boolean mSubscribed = false;
+
+            protected final int VEHICLE_PROPERTY;
+
+            protected FreezeFramePropertyHandler(int propertyId) {
+                VEHICLE_PROPERTY = propertyId;
+            }
+
+            @Override
+            public synchronized void onPropertySet(VehiclePropValue value) {
+                assertEquals(VEHICLE_PROPERTY, value.prop);
+            }
+
+            @Override
+            public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+                assertEquals(VEHICLE_PROPERTY, value.prop);
+                return null;
+            }
+
+            @Override
+            public synchronized void onPropertySubscribe(
+                    int property, int zones, float sampleRate) {
+                assertEquals(VEHICLE_PROPERTY, property);
+                mSubscribed = true;
+            }
+
+            @Override
+            public synchronized void onPropertyUnsubscribe(int property) {
+                assertEquals(VEHICLE_PROPERTY, property);
+                if (!mSubscribed) {
+                    throw new IllegalArgumentException(
+                            "Property was not subscribed 0x" + toHexString(property));
+                }
+                mSubscribed = false;
+            }
+        }
+
+        class FreezeFrameInfoHandler extends FreezeFramePropertyHandler {
+            FreezeFrameInfoHandler() {
+                super(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
+            }
+
+            @Override
+            public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+                super.onPropertyGet(value);
+                VehiclePropValueBuilder builder =
+                        VehiclePropValueBuilder.newBuilder(VEHICLE_PROPERTY);
+                builder.setInt64Value(getTimestamps());
+                return builder.build();
+            }
+        }
+
+        class FreezeFrameHandler extends FreezeFramePropertyHandler {
+            FreezeFrameHandler() {
+                super(VehicleProperty.OBD2_FREEZE_FRAME);
+            }
+
+            @Override
+            public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+                super.onPropertyGet(value);
+                long timestamp = value.value.int64Values.get(0);
+                return getEvent(timestamp);
+            }
+        }
+
+        class FreezeFrameClearHandler extends FreezeFramePropertyHandler {
+            FreezeFrameClearHandler() {
+                super(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
+            }
+
+            @Override
+            public synchronized void onPropertySet(VehiclePropValue value) {
+                super.onPropertySet(value);
+                if (0 == value.value.int64Values.size()) {
+                    removeEvents();
+                } else {
+                    for (long timestamp : value.value.int64Values) {
+                        removeEvent(timestamp);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void configureMockedHal() {
+        java.util.Collection<Integer> numVendorSensors = Arrays.asList(0, 0);
+        addProperty(VehicleProperty.OBD2_LIVE_FRAME, mLiveFrameEventBuilder.build())
+                .setConfigArray(numVendorSensors);
+        addProperty(
+                VehicleProperty.OBD2_FREEZE_FRAME_INFO,
+                mFreezeFrameProperties.mFreezeFrameInfoHandler);
+        addProperty(VehicleProperty.OBD2_FREEZE_FRAME, mFreezeFrameProperties.mFreezeFrameHandler)
+                .setConfigArray(numVendorSensors);
+        addProperty(
+                VehicleProperty.OBD2_FREEZE_FRAME_CLEAR,
+                mFreezeFrameProperties.mFreezeFrameClearHandler);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE, 30);
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS,
+            FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION);
+        mLiveFrameEventBuilder.addIntSensor(
+                Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START, 5000);
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE, 2);
+        mLiveFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD, 0.125f);
+        mLiveFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.VEHICLE_SPEED, 12.5f);
+
+        mFreezeFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE, 30);
+        mFreezeFrameEventBuilder.addIntSensor(
+                Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START, 5000);
+        mFreezeFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE, 2);
+        mFreezeFrameEventBuilder.addFloatSensor(
+                Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD, 0.125f);
+        mFreezeFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.VEHICLE_SPEED, 12.5f);
+        mFreezeFrameEventBuilder.setDTC(DTC);
+
+        super.setUp();
+
+        mCarDiagnosticManager =
+                (CarDiagnosticManager) getCar().getCarManager(Car.DIAGNOSTIC_SERVICE);
+    }
+
+    public void testLiveFrameRead() throws Exception {
+        CarDiagnosticEvent liveFrame = mCarDiagnosticManager.getLatestLiveFrame();
+
+        assertNotNull(liveFrame);
+        assertTrue(liveFrame.isLiveFrame());
+        assertFalse(liveFrame.isFreezeFrame());
+        assertFalse(liveFrame.isEmptyFrame());
+
+        assertEquals(
+                5000,
+                liveFrame
+                        .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+                        .intValue());
+        assertEquals(
+                30,
+                liveFrame
+                        .getSystemIntegerSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE)
+                        .intValue());
+        assertEquals(
+                2,
+                liveFrame
+                        .getSystemIntegerSensor(Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE)
+                        .intValue());
+        assertEquals(
+                0.125f,
+                liveFrame
+                        .getSystemFloatSensor(Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD)
+                        .floatValue());
+        assertEquals(12.5f,
+                liveFrame
+                        .getSystemFloatSensor(Obd2FloatSensorIndex.VEHICLE_SPEED)
+                        .floatValue());
+    }
+
+    public void testLiveFrameEvent() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+                listener,
+                CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+                android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        listener.reset();
+        long time = SystemClock.elapsedRealtimeNanos();
+        mLiveFrameEventBuilder.addIntSensor(
+                Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START, 5100);
+
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time));
+        assertTrue(listener.waitForEvent(time));
+
+        CarDiagnosticEvent liveFrame = listener.getLastEvent();
+
+        assertEquals(
+                5100,
+                liveFrame
+                        .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+                        .intValue());
+    }
+
+    public void testMissingSensorRead() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+            listener,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build());
+        assertTrue(listener.waitForEvent());
+
+        CarDiagnosticEvent liveFrame = listener.getLastEvent();
+        assertNotNull(liveFrame);
+
+        assertNull(liveFrame.getSystemIntegerSensor(
+            Obd2IntegerSensorIndex.DRIVER_DEMAND_PERCENT_TORQUE));
+        assertEquals(-1, liveFrame.getSystemIntegerSensor(
+                Obd2IntegerSensorIndex.DRIVER_DEMAND_PERCENT_TORQUE,
+                -1));
+
+        assertNull(liveFrame.getSystemFloatSensor(
+                Obd2FloatSensorIndex.OXYGEN_SENSOR6_VOLTAGE));
+        assertEquals(0.25f, liveFrame.getSystemFloatSensor(
+                Obd2FloatSensorIndex.OXYGEN_SENSOR5_VOLTAGE,
+                0.25f));
+
+        assertNull(liveFrame.getVendorIntegerSensor(Obd2IntegerSensorIndex.VENDOR_START));
+        assertEquals(-1, liveFrame.getVendorIntegerSensor(
+                Obd2IntegerSensorIndex.VENDOR_START, -1));
+
+        assertNull(liveFrame.getVendorFloatSensor(Obd2FloatSensorIndex.VENDOR_START));
+        assertEquals(0.25f, liveFrame.getVendorFloatSensor(
+                Obd2FloatSensorIndex.VENDOR_START, 0.25f));
+    }
+
+    public void testFuelSystemStatus() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+            listener,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build());
+        assertTrue(listener.waitForEvent());
+
+        CarDiagnosticEvent liveFrame = listener.getLastEvent();
+        assertNotNull(liveFrame);
+
+        assertEquals(FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION,
+            liveFrame.getSystemIntegerSensor(Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS).intValue());
+        assertEquals(FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION,
+                liveFrame.getFuelSystemStatus().intValue());
+    }
+
+    public void testSecondaryAirStatus() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+            listener,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS,
+            SecondaryAirStatus.FROM_OUTSIDE_OR_OFF);
+        long timestamp = SystemClock.elapsedRealtimeNanos();
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+        assertTrue(listener.waitForEvent(timestamp));
+
+        CarDiagnosticEvent liveFrame = listener.getLastEvent();
+        assertNotNull(liveFrame);
+
+        assertEquals(SecondaryAirStatus.FROM_OUTSIDE_OR_OFF,
+            liveFrame.getSystemIntegerSensor(
+                    Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS).intValue());
+        assertEquals(SecondaryAirStatus.FROM_OUTSIDE_OR_OFF,
+            liveFrame.getSecondaryAirStatus().intValue());
+    }
+
+    public void testIgnitionMonitors() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+            listener,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        // cfr. CarDiagnosticEvent for the meaning of the several bits
+        final int sparkMonitorsValue = 0x1 | (0x1 << 2) | (0x1 << 3) | (0x1 << 6)
+                | (0x1 << 10) | (0x1 << 11);
+
+        final int compressionMonitorsValue = (0x1 << 2) | (0x1 << 3) | (0x1 << 6)
+                | (0x1 << 12) | (0x1 << 13);
+
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED,0);
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS,
+            sparkMonitorsValue);
+
+        long timestamp = SystemClock.elapsedRealtimeNanos();
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+        assertTrue(listener.waitForEvent(timestamp));
+
+        CarDiagnosticEvent liveFrame = listener.getLastEvent();
+        assertNotNull(liveFrame);
+
+        CommonIgnitionMonitors commonIgnitionMonitors = liveFrame.getIgnitionMonitors();
+        assertNotNull(commonIgnitionMonitors);
+        assertTrue(commonIgnitionMonitors.components.available);
+        assertFalse(commonIgnitionMonitors.components.incomplete);
+        assertTrue(commonIgnitionMonitors.fuelSystem.available);
+        assertTrue(commonIgnitionMonitors.fuelSystem.incomplete);
+        assertFalse(commonIgnitionMonitors.misfire.available);
+        assertFalse(commonIgnitionMonitors.misfire.incomplete);
+
+        SparkIgnitionMonitors sparkIgnitionMonitors =
+                commonIgnitionMonitors.asSparkIgnitionMonitors();
+        assertNotNull(sparkIgnitionMonitors);
+        assertNull(commonIgnitionMonitors.asCompressionIgnitionMonitors());
+
+        assertTrue(sparkIgnitionMonitors.EGR.available);
+        assertFalse(sparkIgnitionMonitors.EGR.incomplete);
+        assertFalse(sparkIgnitionMonitors.oxygenSensorHeater.available);
+        assertFalse(sparkIgnitionMonitors.oxygenSensorHeater.incomplete);
+        assertTrue(sparkIgnitionMonitors.oxygenSensor.available);
+        assertTrue(sparkIgnitionMonitors.oxygenSensor.incomplete);
+        assertFalse(sparkIgnitionMonitors.ACRefrigerant.available);
+        assertFalse(sparkIgnitionMonitors.ACRefrigerant.incomplete);
+        assertFalse(sparkIgnitionMonitors.secondaryAirSystem.available);
+        assertFalse(sparkIgnitionMonitors.secondaryAirSystem.incomplete);
+        assertFalse(sparkIgnitionMonitors.evaporativeSystem.available);
+        assertFalse(sparkIgnitionMonitors.evaporativeSystem.incomplete);
+        assertFalse(sparkIgnitionMonitors.heatedCatalyst.available);
+        assertFalse(sparkIgnitionMonitors.heatedCatalyst.incomplete);
+        assertFalse(sparkIgnitionMonitors.catalyst.available);
+        assertFalse(sparkIgnitionMonitors.catalyst.incomplete);
+
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED,1);
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS,
+            compressionMonitorsValue);
+
+        timestamp += 1000;
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+        assertTrue(listener.waitForEvent(timestamp));
+
+        liveFrame = listener.getLastEvent();
+        assertNotNull(liveFrame);
+        assertEquals(timestamp, liveFrame.timestamp);
+
+        commonIgnitionMonitors = liveFrame.getIgnitionMonitors();
+        assertNotNull(commonIgnitionMonitors);
+        assertFalse(commonIgnitionMonitors.components.available);
+        assertFalse(commonIgnitionMonitors.components.incomplete);
+        assertTrue(commonIgnitionMonitors.fuelSystem.available);
+        assertTrue(commonIgnitionMonitors.fuelSystem.incomplete);
+        assertFalse(commonIgnitionMonitors.misfire.available);
+        assertFalse(commonIgnitionMonitors.misfire.incomplete);
+        CompressionIgnitionMonitors compressionIgnitionMonitors =
+            commonIgnitionMonitors.asCompressionIgnitionMonitors();
+        assertNull(commonIgnitionMonitors.asSparkIgnitionMonitors());
+        assertNotNull(compressionIgnitionMonitors);
+
+        assertTrue(compressionIgnitionMonitors.EGROrVVT.available);
+        assertFalse(compressionIgnitionMonitors.EGROrVVT.incomplete);
+        assertFalse(compressionIgnitionMonitors.PMFilter.available);
+        assertFalse(compressionIgnitionMonitors.PMFilter.incomplete);
+        assertFalse(compressionIgnitionMonitors.exhaustGasSensor.available);
+        assertFalse(compressionIgnitionMonitors.exhaustGasSensor.incomplete);
+        assertTrue(compressionIgnitionMonitors.boostPressure.available);
+        assertTrue(compressionIgnitionMonitors.boostPressure.incomplete);
+        assertFalse(compressionIgnitionMonitors.NOxSCR.available);
+        assertFalse(compressionIgnitionMonitors.NOxSCR.incomplete);
+        assertFalse(compressionIgnitionMonitors.NMHCCatalyst.available);
+        assertFalse(compressionIgnitionMonitors.NMHCCatalyst.incomplete);
+    }
+
+    public void testFuelType() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+            listener,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.FUEL_TYPE,
+            FuelType.BIFUEL_RUNNING_LPG);
+        long timestamp = SystemClock.elapsedRealtimeNanos();
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+        assertTrue(listener.waitForEvent(timestamp));
+
+        CarDiagnosticEvent liveFrame = listener.getLastEvent();
+        assertNotNull(liveFrame);
+
+        assertEquals(FuelType.BIFUEL_RUNNING_LPG,
+            liveFrame.getSystemIntegerSensor(
+                Obd2IntegerSensorIndex.FUEL_TYPE).intValue());
+        assertEquals(FuelType.BIFUEL_RUNNING_LPG,
+            liveFrame.getFuelType().intValue());
+    }
+
+    public void testMultipleListeners() throws Exception {
+        Listener listener1 = new Listener();
+        Listener listener2 = new Listener();
+
+        mCarDiagnosticManager.registerListener(
+            listener1,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+        mCarDiagnosticManager.registerListener(
+            listener2,
+            CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+            android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        listener1.reset();
+        listener2.reset();
+
+        long time = SystemClock.elapsedRealtimeNanos();
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time));
+        assertTrue(listener1.waitForEvent(time));
+        assertTrue(listener2.waitForEvent(time));
+
+        CarDiagnosticEvent event1 = listener1.getLastEvent();
+        CarDiagnosticEvent event2 = listener2.getLastEvent();
+        assertEquals(
+            5000,
+            event1
+                .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+                .intValue());
+        assertEquals(
+            5000,
+            event2
+                .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+                .intValue());
+
+        listener1.reset();
+        listener2.reset();
+
+        mCarDiagnosticManager.unregisterListener(listener1);
+
+        time += 1000;
+        getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time));
+        assertFalse(listener1.waitForEvent(time));
+        assertTrue(listener2.waitForEvent(time));
+
+        assertNull(listener1.getLastEvent());
+        event2 = listener2.getLastEvent();
+
+        assertTrue(event1.isEarlierThan(event2));
+
+        assertEquals(
+            5000,
+            event2
+                .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+                .intValue());
+    }
+
+    public void testFreezeFrameEvent() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+                listener,
+                CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+                android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        listener.reset();
+        VehiclePropValue injectedEvent =
+                mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+        getMockedVehicleHal().injectEvent(injectedEvent);
+        assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+        CarDiagnosticEvent freezeFrame = listener.getLastEvent();
+
+        assertEquals(DTC, freezeFrame.dtc);
+
+        mFreezeFrameEventBuilder.addIntSensor(
+                Obd2IntegerSensorIndex.ABSOLUTE_BAROMETRIC_PRESSURE, 22);
+        injectedEvent = mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+        getMockedVehicleHal().injectEvent(injectedEvent);
+        assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+        freezeFrame = listener.getLastEvent();
+
+        assertNotNull(freezeFrame);
+        assertFalse(freezeFrame.isLiveFrame());
+        assertTrue(freezeFrame.isFreezeFrame());
+        assertFalse(freezeFrame.isEmptyFrame());
+
+        assertEquals(DTC, freezeFrame.dtc);
+        assertEquals(
+                22,
+                freezeFrame
+                        .getSystemIntegerSensor(Obd2IntegerSensorIndex.ABSOLUTE_BAROMETRIC_PRESSURE)
+                        .intValue());
+    }
+
+    public void testFreezeFrameTimestamps() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+                listener,
+                CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+                android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        Set<Long> generatedTimestamps = new HashSet<>();
+
+        VehiclePropValue injectedEvent =
+                mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+        getMockedVehicleHal().injectEvent(injectedEvent);
+        generatedTimestamps.add(injectedEvent.timestamp);
+        assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+        injectedEvent =
+                mFreezeFrameProperties.addNewEvent(
+                        mFreezeFrameEventBuilder, injectedEvent.timestamp + 1000);
+        getMockedVehicleHal().injectEvent(injectedEvent);
+        generatedTimestamps.add(injectedEvent.timestamp);
+        assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+        long[] acquiredTimestamps = mCarDiagnosticManager.getFreezeFrameTimestamps();
+        assertEquals(generatedTimestamps.size(), acquiredTimestamps.length);
+        for (long acquiredTimestamp : acquiredTimestamps) {
+            assertTrue(generatedTimestamps.contains(acquiredTimestamp));
+        }
+    }
+
+    public void testClearFreezeFrameTimestamps() throws Exception {
+        Listener listener = new Listener();
+        mCarDiagnosticManager.registerListener(
+                listener,
+                CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+                android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+        VehiclePropValue injectedEvent =
+                mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+        getMockedVehicleHal().injectEvent(injectedEvent);
+        assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+        assertNotNull(mCarDiagnosticManager.getFreezeFrame(injectedEvent.timestamp));
+        mCarDiagnosticManager.clearFreezeFrames(injectedEvent.timestamp);
+        assertNull(mCarDiagnosticManager.getFreezeFrame(injectedEvent.timestamp));
+    }
+
+    class Listener implements CarDiagnosticManager.OnDiagnosticEventListener {
+        private final Object mSync = new Object();
+
+        private CarDiagnosticEvent mLastEvent = null;
+
+        CarDiagnosticEvent getLastEvent() {
+            return mLastEvent;
+        }
+
+        void reset() {
+            synchronized (mSync) {
+                mLastEvent = null;
+            }
+        }
+
+        boolean waitForEvent() throws InterruptedException {
+            return waitForEvent(0);
+        }
+
+        boolean waitForEvent(long eventTimeStamp) throws InterruptedException {
+            long start = SystemClock.elapsedRealtime();
+            boolean matchTimeStamp = eventTimeStamp != 0;
+            synchronized (mSync) {
+                while ((mLastEvent == null
+                                || (matchTimeStamp && mLastEvent.timestamp != eventTimeStamp))
+                        && (start + SHORT_WAIT_TIMEOUT_MS > SystemClock.elapsedRealtime())) {
+                    mSync.wait(10L);
+                }
+                return mLastEvent != null
+                        && (!matchTimeStamp || mLastEvent.timestamp == eventTimeStamp);
+            }
+        }
+
+        @Override
+        public void onDiagnosticEvent(CarDiagnosticEvent event) {
+            synchronized (mSync) {
+                // We're going to hold a reference to this object
+                mLastEvent = event;
+                mSync.notify();
+            }
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/car/test/DiagnosticEventBuilder.java b/tests/carservice_test/src/com/android/car/test/DiagnosticEventBuilder.java
new file mode 100644
index 0000000..ba511c8
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/test/DiagnosticEventBuilder.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.test;
+
+import android.car.hardware.CarDiagnosticEvent.Obd2FloatSensorIndex;
+import android.car.hardware.CarDiagnosticEvent.Obd2IntegerSensorIndex;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.util.SparseArray;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import java.util.BitSet;
+import java.util.Iterator;
+
+/**
+ * A builder class for a VehiclePropValue that encapsulates a diagnostic event. This is the Java
+ * equivalent of Obd2SensorStore.cpp in the native layer.
+ *
+ * @hide
+ */
+public class DiagnosticEventBuilder {
+    /**
+     * An array-like container that knows to return a default value for any unwritten-to index.
+     *
+     * @param <T> the element type
+     */
+    class DefaultedArray<T> implements Iterable<T> {
+        private final SparseArray<T> mElements = new SparseArray<>();
+        private final int mSize;
+        private final T mDefaultValue;
+
+        DefaultedArray(int size, T defaultValue) {
+            mSize = size;
+            mDefaultValue = defaultValue;
+        }
+
+        private int checkIndex(int index) {
+            if (index < 0 || index >= mSize)
+                throw new IndexOutOfBoundsException(
+                    String.format("Index: %d, Size: %d", index, mSize));
+            return index;
+        }
+
+        DefaultedArray<T> set(int index, T element) {
+            checkIndex(index);
+            mElements.put(index, element);
+            return this;
+        }
+
+        T get(int index) {
+            checkIndex(index);
+            return mElements.get(index, mDefaultValue);
+        }
+
+        int size() {
+            return mSize;
+        }
+
+        @Override
+        public Iterator<T> iterator() {
+            return new Iterator<T>() {
+                private int mIndex = 0;
+
+                @Override
+                public boolean hasNext() {
+                    return (mIndex >= 0) && (mIndex < mSize);
+                }
+
+                @Override
+                public T next() {
+                    int index = mIndex++;
+                    return get(index);
+                }
+            };
+        }
+    }
+
+    private final int mPropertyId;
+    private final int mNumIntSensors;
+    private final DefaultedArray<Integer> mIntValues;
+    private final DefaultedArray<Float> mFloatValues;
+    private final BitSet mBitmask;
+    private String mDtc = null;
+
+    public DiagnosticEventBuilder(VehiclePropConfig propConfig) {
+        this(propConfig.prop, propConfig.configArray.get(0), propConfig.configArray.get(1));
+    }
+
+    public DiagnosticEventBuilder(int propertyId) {
+        this(propertyId, 0, 0);
+    }
+
+    public DiagnosticEventBuilder(
+            int propertyId, int numVendorIntSensors, int numVendorFloatSensors) {
+        mPropertyId = propertyId;
+        mNumIntSensors = Obd2IntegerSensorIndex.LAST_SYSTEM + 1 + numVendorIntSensors;
+        final int numFloatSensors = Obd2FloatSensorIndex.LAST_SYSTEM + 1 + numVendorFloatSensors;
+        mBitmask = new BitSet(mNumIntSensors + numFloatSensors);
+        mIntValues = new DefaultedArray<>(mNumIntSensors, 0);
+        mFloatValues = new DefaultedArray<>(numFloatSensors, 0.0f);
+    }
+
+    public DiagnosticEventBuilder addIntSensor(int index, int value) {
+        mIntValues.set(index, value);
+        mBitmask.set(index);
+        return this;
+    }
+
+    public DiagnosticEventBuilder addFloatSensor(int index, float value) {
+        mFloatValues.set(index, value);
+        mBitmask.set(mNumIntSensors + index);
+        return this;
+    }
+
+    public DiagnosticEventBuilder setDTC(String dtc) {
+        mDtc = dtc;
+        return this;
+    }
+
+    public VehiclePropValue build() {
+        return build(0);
+    }
+
+    public VehiclePropValue build(long timestamp) {
+        VehiclePropValueBuilder propValueBuilder = VehiclePropValueBuilder.newBuilder(mPropertyId);
+        if (0 == timestamp) {
+            propValueBuilder.setTimestamp();
+        } else {
+            propValueBuilder.setTimestamp(timestamp);
+        }
+        mIntValues.forEach(propValueBuilder::addIntValue);
+        mFloatValues.forEach(propValueBuilder::addFloatValue);
+        return propValueBuilder.addByteValue(mBitmask.toByteArray()).setStringValue(mDtc).build();
+    }
+}
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 a146e73..84f564d 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
@@ -24,17 +24,20 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
 import com.android.car.R;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import com.android.car.vehiclehal.test.MockedVehicleHal;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -43,6 +46,8 @@
 @MediumTest
 public class VmsPublisherClientServiceTest extends MockedCarTestBase {
     private static final String TAG = "VmsPublisherTest";
+    private static final int MOCK_PUBLISHER_LAYER_ID = 12;
+    private static final int MOCK_PUBLISHER_LAYER_VERSION = 34;
 
     private HalHandler mHalHandler;
     // Used to block until the HAL property is updated in HalHandler.onPropertySet.
@@ -85,6 +90,14 @@
         return wrapper;
     }
 
+    private VehiclePropValue getHalSubscriptionRequest() {
+        return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+            .addIntValue(VmsMessageType.SUBSCRIBE)
+            .addIntValue(MOCK_PUBLISHER_LAYER_ID)
+            .addIntValue(MOCK_PUBLISHER_LAYER_VERSION)
+            .build();
+    }
+
     @Override
     protected void setUp() throws Exception {
         /**
@@ -93,6 +106,10 @@
          */
         mHalHandlerSemaphore = new Semaphore(0);
         super.setUp();
+
+        // Inject a subscribe event which simulates the HAL is subscribed to the Mock Publisher.
+        MockedVehicleHal mHal = getMockedVehicleHal();
+        mHal.injectEvent(getHalSubscriptionRequest());
     }
 
     /**
@@ -104,6 +121,10 @@
      * this test.
      */
     public void testPublish() throws Exception {
+        //TODO: This test is using minial synchronisation between clients.
+        //      If more complexity is added this may result in publisher
+        //      publishing before the subscriber subscribed, in which case
+        //      the semaphore will not be released.
         assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
         VehiclePropValue.RawValue rawValue = mHalHandler.getValue().value;
         int messageType = rawValue.int32Values.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
@@ -125,7 +146,13 @@
         @Override
         public synchronized void onPropertySet(VehiclePropValue value) {
             mValue = value;
-            mHalHandlerSemaphore.release();
+
+            // 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) ==
+                    VmsMessageType.DATA) {
+                mHalHandlerSemaphore.release();
+            }
         }
 
         @Override
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 97215e2..d0b3ac8 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
@@ -22,10 +22,10 @@
 import android.car.vms.VmsSubscriberManager.OnVmsMessageReceivedListener;
 import android.car.vms.VmsSubscriberManager;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
 import android.os.SystemClock;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
@@ -41,6 +41,8 @@
 @MediumTest
 public class VmsSubscriberManagerTest extends MockedCarTestBase {
     private static final String TAG = "VmsSubscriberManagerTest";
+    private static final int SUBSCRIPTION_LAYER_ID = 2;
+    private static final int SUBSCRIPTION_LAYER_VERSION = 3;
 
     private HalHandler mHalHandler;
     // Used to block until the HAL property is updated in HalHandler.onPropertySet.
@@ -70,7 +72,7 @@
                 Car.VMS_SUBSCRIBER_SERVICE);
         TestListener listener = new TestListener();
         vmsSubscriberManager.setListener(listener);
-        vmsSubscriberManager.subscribe(0, 0, false);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER_ID, SUBSCRIPTION_LAYER_VERSION);
 
         // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
         VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
@@ -78,16 +80,45 @@
                 .setTimestamp(SystemClock.elapsedRealtimeNanos())
                 .build();
         v.value.int32Values.add(VmsMessageType.DATA); // MessageType
-        v.value.int32Values.add(2); // Layer ID
-        v.value.int32Values.add(3); // Layer Version
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
         v.value.bytes.add((byte) 0xa);
         v.value.bytes.add((byte) 0xb);
         assertEquals(0, mSubscriberSemaphore.availablePermits());
 
         getMockedVehicleHal().injectEvent(v);
         assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
-        assertEquals(2, listener.getLayerId());
-        assertEquals(3, listener.getLayerVersion());
+        assertEquals(SUBSCRIPTION_LAYER_ID, listener.getLayerId());
+        assertEquals(SUBSCRIPTION_LAYER_VERSION, listener.getLayerVersion());
+        byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
+        assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
+    }
+
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribeAll() throws Exception {
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+            Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribeAll();
+
+        // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+            .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+            .setTimestamp(SystemClock.elapsedRealtimeNanos())
+            .build();
+        v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(SUBSCRIPTION_LAYER_ID, listener.getLayerId());
+        assertEquals(SUBSCRIPTION_LAYER_VERSION, listener.getLayerVersion());
         byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
         assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
     }
diff --git a/tools/bootanalyze/bootanalyze.py b/tools/bootanalyze/bootanalyze.py
index d05aa4d..d685209 100755
--- a/tools/bootanalyze/bootanalyze.py
+++ b/tools/bootanalyze/bootanalyze.py
@@ -39,6 +39,7 @@
 BOOT_ANIM_END_TIME_KEY = "BootAnimEnd"
 KERNEL_BOOT_COMPLETE = "BootComplete_kernel"
 LOGCAT_BOOT_COMPLETE = "BootComplete"
+LAUNCHER_START = "LauncherStart"
 BOOT_TIME_TOO_BIG = 200.0
 MAX_RETRIES = 5
 DEBUG = False
@@ -170,13 +171,13 @@
   if args.reboot:
     reboot()
 
-  logcat_events, logcat_timing_events = collect_events(
-    search_events, ADB_CMD + ' logcat -b all -v epoch', timings, [ LOGCAT_BOOT_COMPLETE,\
-                                                                   KERNEL_BOOT_COMPLETE ])
-
   dmesg_events, e = collect_events(search_events, ADB_CMD + ' shell su root dmesg -w', {},\
                                    [ KERNEL_BOOT_COMPLETE ])
 
+  logcat_events, logcat_timing_events = collect_events(
+    search_events, ADB_CMD + ' logcat -b all -v epoch', timings, [ LOGCAT_BOOT_COMPLETE,\
+                                                                   KERNEL_BOOT_COMPLETE, \
+                                                                   LAUNCHER_START ])
   logcat_event_time = extract_time(
     logcat_events, TIME_LOGCAT, float);
   logcat_original_time = extract_time(
diff --git a/tools/bootanalyze/config.yaml b/tools/bootanalyze/config.yaml
index 1afcd60..85f648e 100644
--- a/tools/bootanalyze/config.yaml
+++ b/tools/bootanalyze/config.yaml
@@ -36,9 +36,10 @@
   PackageManagerInit_ready: StartPackageManagerService took to complete
   BluetoothService_start: Starting com.android.server.BluetoothService
   SystemUi_start: for service com.android.systemui/.
-  LauncherReady: Em.Overview:\s*onResume
+  CarLauncherReady: Em.Overview:\s*onResume
   CarService_start: for service com.android.car/.CarService
   BootAnimStart: starting service 'bootanim'
   BootAnimEnd: Service 'bootanim'
   BootComplete: Starting phase 1000
   BootComplete_kernel: processing action \(sys\.boot_completed=1\)
+  LauncherStart: START.*HOME.*NexusLauncherActivity
diff --git a/tools/emulator/VehicleHalProto_pb2.py b/tools/emulator/VehicleHalProto_pb2.py
new file mode 100644
index 0000000..aaad547
--- /dev/null
+++ b/tools/emulator/VehicleHalProto_pb2.py
@@ -0,0 +1,537 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: VehicleHalProto.proto
+
+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 descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+
+
+
+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')
+
+_MSGTYPE = _descriptor.EnumDescriptor(
+  name='MsgType',
+  full_name='emulator.MsgType',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='GET_CONFIG_CMD', index=0, number=0,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_CONFIG_RESP', index=1, number=1,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_CONFIG_ALL_CMD', index=2, number=2,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_CONFIG_ALL_RESP', index=3, number=3,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_PROPERTY_CMD', index=4, number=4,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_PROPERTY_RESP', index=5, number=5,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_PROPERTY_ALL_CMD', index=6, number=6,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GET_PROPERTY_ALL_RESP', index=7, number=7,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SET_PROPERTY_CMD', index=8, number=8,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SET_PROPERTY_RESP', index=9, number=9,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SET_PROPERTY_ASYNC', index=10, number=10,
+      options=None,
+      type=None),
+  ],
+  containing_type=None,
+  options=None,
+  serialized_start=979,
+  serialized_end=1245,
+)
+
+MsgType = enum_type_wrapper.EnumTypeWrapper(_MSGTYPE)
+_STATUS = _descriptor.EnumDescriptor(
+  name='Status',
+  full_name='emulator.Status',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='RESULT_OK', index=0, number=0,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_UNKNOWN', index=1, number=1,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_UNIMPLEMENTED_CMD', index=2, number=2,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_INVALID_PROPERTY', index=3, number=3,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_INVALID_AREA_ID', index=4, number=4,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_PROPERTY_UNINITIALIZED', index=5, number=5,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_WRITE_ONLY_PROPERTY', index=6, number=6,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_MEMORY_ALLOC_FAILED', index=7, number=7,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR_INVALID_OPERATION', index=8, number=8,
+      options=None,
+      type=None),
+  ],
+  containing_type=None,
+  options=None,
+  serialized_start=1248,
+  serialized_end=1499,
+)
+
+Status = enum_type_wrapper.EnumTypeWrapper(_STATUS)
+GET_CONFIG_CMD = 0
+GET_CONFIG_RESP = 1
+GET_CONFIG_ALL_CMD = 2
+GET_CONFIG_ALL_RESP = 3
+GET_PROPERTY_CMD = 4
+GET_PROPERTY_RESP = 5
+GET_PROPERTY_ALL_CMD = 6
+GET_PROPERTY_ALL_RESP = 7
+SET_PROPERTY_CMD = 8
+SET_PROPERTY_RESP = 9
+SET_PROPERTY_ASYNC = 10
+RESULT_OK = 0
+ERROR_UNKNOWN = 1
+ERROR_UNIMPLEMENTED_CMD = 2
+ERROR_INVALID_PROPERTY = 3
+ERROR_INVALID_AREA_ID = 4
+ERROR_PROPERTY_UNINITIALIZED = 5
+ERROR_WRITE_ONLY_PROPERTY = 6
+ERROR_MEMORY_ALLOC_FAILED = 7
+ERROR_INVALID_OPERATION = 8
+
+
+
+_VEHICLEAREACONFIG = _descriptor.Descriptor(
+  name='VehicleAreaConfig',
+  full_name='emulator.VehicleAreaConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='area_id', full_name='emulator.VehicleAreaConfig.area_id', index=0,
+      number=1, type=5, cpp_type=1, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='min_int32_value', full_name='emulator.VehicleAreaConfig.min_int32_value', index=1,
+      number=2, type=17, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='max_int32_value', full_name='emulator.VehicleAreaConfig.max_int32_value', index=2,
+      number=3, type=17, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='min_int64_value', full_name='emulator.VehicleAreaConfig.min_int64_value', index=3,
+      number=4, type=18, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='max_int64_value', full_name='emulator.VehicleAreaConfig.max_int64_value', index=4,
+      number=5, type=18, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _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,
+      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,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=36,
+  serialized_end=222,
+)
+
+
+_VEHICLEPROPCONFIG = _descriptor.Descriptor(
+  name='VehiclePropConfig',
+  full_name='emulator.VehiclePropConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='prop', full_name='emulator.VehiclePropConfig.prop', index=0,
+      number=1, type=5, cpp_type=1, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='access', full_name='emulator.VehiclePropConfig.access', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='change_mode', full_name='emulator.VehiclePropConfig.change_mode', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='value_type', full_name='emulator.VehiclePropConfig.value_type', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='supported_areas', full_name='emulator.VehiclePropConfig.supported_areas', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='area_configs', full_name='emulator.VehiclePropConfig.area_configs', index=5,
+      number=6, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='config_flags', full_name='emulator.VehiclePropConfig.config_flags', index=6,
+      number=7, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='config_array', full_name='emulator.VehiclePropConfig.config_array', index=7,
+      number=8, type=5, cpp_type=1, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _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"),
+      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,
+      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,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=225,
+  serialized_end=508,
+)
+
+
+_VEHICLEPROPVALUE = _descriptor.Descriptor(
+  name='VehiclePropValue',
+  full_name='emulator.VehiclePropValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='prop', full_name='emulator.VehiclePropValue.prop', index=0,
+      number=1, type=5, cpp_type=1, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='value_type', full_name='emulator.VehiclePropValue.value_type', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='timestamp', full_name='emulator.VehiclePropValue.timestamp', index=2,
+      number=3, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='area_id', full_name='emulator.VehiclePropValue.area_id', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='int32_values', full_name='emulator.VehiclePropValue.int32_values', index=4,
+      number=5, type=17, cpp_type=1, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='int64_values', full_name='emulator.VehiclePropValue.int64_values', index=5,
+      number=6, type=18, cpp_type=2, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='float_values', full_name='emulator.VehiclePropValue.float_values', index=6,
+      number=7, type=2, cpp_type=6, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _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"),
+      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="",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=511,
+  serialized_end=708,
+)
+
+
+_VEHICLEPROPGET = _descriptor.Descriptor(
+  name='VehiclePropGet',
+  full_name='emulator.VehiclePropGet',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='prop', full_name='emulator.VehiclePropGet.prop', index=0,
+      number=1, type=5, cpp_type=1, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='area_id', full_name='emulator.VehiclePropGet.area_id', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=710,
+  serialized_end=757,
+)
+
+
+_EMULATORMESSAGE = _descriptor.Descriptor(
+  name='EmulatorMessage',
+  full_name='emulator.EmulatorMessage',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='msg_type', full_name='emulator.EmulatorMessage.msg_type', index=0,
+      number=1, type=14, cpp_type=8, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='status', full_name='emulator.EmulatorMessage.status', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='prop', full_name='emulator.EmulatorMessage.prop', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='config', full_name='emulator.EmulatorMessage.config', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='emulator.EmulatorMessage.value', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=760,
+  serialized_end=976,
+)
+
+_VEHICLEPROPCONFIG.fields_by_name['area_configs'].message_type = _VEHICLEAREACONFIG
+_EMULATORMESSAGE.fields_by_name['msg_type'].enum_type = _MSGTYPE
+_EMULATORMESSAGE.fields_by_name['status'].enum_type = _STATUS
+_EMULATORMESSAGE.fields_by_name['prop'].message_type = _VEHICLEPROPGET
+_EMULATORMESSAGE.fields_by_name['config'].message_type = _VEHICLEPROPCONFIG
+_EMULATORMESSAGE.fields_by_name['value'].message_type = _VEHICLEPROPVALUE
+DESCRIPTOR.message_types_by_name['VehicleAreaConfig'] = _VEHICLEAREACONFIG
+DESCRIPTOR.message_types_by_name['VehiclePropConfig'] = _VEHICLEPROPCONFIG
+DESCRIPTOR.message_types_by_name['VehiclePropValue'] = _VEHICLEPROPVALUE
+DESCRIPTOR.message_types_by_name['VehiclePropGet'] = _VEHICLEPROPGET
+DESCRIPTOR.message_types_by_name['EmulatorMessage'] = _EMULATORMESSAGE
+
+class VehicleAreaConfig(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _VEHICLEAREACONFIG
+
+  # @@protoc_insertion_point(class_scope:emulator.VehicleAreaConfig)
+
+class VehiclePropConfig(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _VEHICLEPROPCONFIG
+
+  # @@protoc_insertion_point(class_scope:emulator.VehiclePropConfig)
+
+class VehiclePropValue(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _VEHICLEPROPVALUE
+
+  # @@protoc_insertion_point(class_scope:emulator.VehiclePropValue)
+
+class VehiclePropGet(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _VEHICLEPROPGET
+
+  # @@protoc_insertion_point(class_scope:emulator.VehiclePropGet)
+
+class EmulatorMessage(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _EMULATORMESSAGE
+
+  # @@protoc_insertion_point(class_scope:emulator.EmulatorMessage)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
+# @@protoc_insertion_point(module_scope)
diff --git a/tools/emulator/vhal_consts_2_0.py b/tools/emulator/vhal_consts_2_0.py
new file mode 100644
index 0000000..f2433f6
--- /dev/null
+++ b/tools/emulator/vhal_consts_2_0.py
@@ -0,0 +1,175 @@
+# Copyright 2017 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+    This file contains constants defined in hardware/interfaces/vehicle/2.0/types.hal
+
+    Constants in this file are parsed from:
+        out/soong/.intermediates/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle@2.0_genc++_headers/gen/android/hardware/automotive/vehicle/2.0/types.h
+
+    Currently, there is no script to auto-generate this constants file.  The file is generated by
+    copying enum fields into an editor and running a macro to format it.  The elements being used
+    are shown in the following table:
+
+        type.h file:                 this file:
+        VehiclePropertyType enum --> VEHICLE_VALUE_TYPE_*
+        VehicleProperty enum     --> VEHICLE_PROPERTY_*
+        VehicleAreaZone enum     --> VEHICLE_ZONE_*
+        VehiclePropertyType enum --> class vhal_types_2_0
+"""
+
+# Vehicle Property ID
+VEHICLE_PROPERTY_INFO_VIN = 286261504
+VEHICLE_PROPERTY_INFO_MAKE = 286261505
+VEHICLE_PROPERTY_INFO_MODEL = 286261506
+VEHICLE_PROPERTY_INFO_MODEL_YEAR = 289407235
+VEHICLE_PROPERTY_INFO_FUEL_CAPACITY = 291504388
+VEHICLE_PROPERTY_PERF_ODOMETER = 291504644
+VEHICLE_PROPERTY_PERF_VEHICLE_SPEED = 291504647
+VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP = 291504897
+VEHICLE_PROPERTY_ENGINE_OIL_TEMP = 291504900
+VEHICLE_PROPERTY_ENGINE_RPM = 291504901
+VEHICLE_PROPERTY_GEAR_SELECTION = 289408000
+VEHICLE_PROPERTY_CURRENT_GEAR = 289408001
+VEHICLE_PROPERTY_PARKING_BRAKE_ON = 287310850
+VEHICLE_PROPERTY_DRIVING_STATUS = 289408004
+VEHICLE_PROPERTY_FUEL_LEVEL_LOW = 287310853
+VEHICLE_PROPERTY_NIGHT_MODE = 287310855
+VEHICLE_PROPERTY_TURN_SIGNAL_STATE = 289408008
+VEHICLE_PROPERTY_IGNITION_STATE = 289408009
+VEHICLE_PROPERTY_HVAC_FAN_SPEED = 306185472
+VEHICLE_PROPERTY_HVAC_FAN_DIRECTION = 306185473
+VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT = 308282626
+VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET = 308282627
+VEHICLE_PROPERTY_HVAC_DEFROSTER = 320865540
+VEHICLE_PROPERTY_HVAC_AC_ON = 304088325
+VEHICLE_PROPERTY_HVAC_MAX_AC_ON = 304088326
+VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON = 304088327
+VEHICLE_PROPERTY_HVAC_RECIRC_ON = 304088328
+VEHICLE_PROPERTY_HVAC_DUAL_ON = 304088329
+VEHICLE_PROPERTY_HVAC_AUTO_ON = 304088330
+VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE = 356517131
+VEHICLE_PROPERTY_HVAC_SIDE_MIRROR_HEAT = 339739916
+VEHICLE_PROPERTY_HVAC_STEERING_WHEEL_TEMP = 289408269
+VEHICLE_PROPERTY_HVAC_TEMPERATURE_UNITS = 306185486
+VEHICLE_PROPERTY_HVAC_ACTUAL_FAN_SPEED_RPM = 306185487
+VEHICLE_PROPERTY_HVAC_FAN_DIRECTION_AVAILABLE = 306185489
+VEHICLE_PROPERTY_HVAC_POWER_ON = 304088336
+VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE = 291505923
+VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE = 291505924
+VEHICLE_PROPERTY_RADIO_PRESET = 289474561
+VEHICLE_PROPERTY_AUDIO_FOCUS = 289474816
+VEHICLE_PROPERTY_AUDIO_FOCUS_EXT_SYNC = 289474832
+VEHICLE_PROPERTY_AUDIO_VOLUME = 289474817
+VEHICLE_PROPERTY_AUDIO_VOLUME_EXT_SYNC = 289474833
+VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT = 289474818
+VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY = 289474819
+VEHICLE_PROPERTY_AUDIO_HW_VARIANT = 289409284
+VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT = 289474821
+VEHICLE_PROPERTY_AUDIO_STREAM_STATE = 289474822
+VEHICLE_PROPERTY_AUDIO_PARAMETERS = 286263559
+VEHICLE_PROPERTY_AP_POWER_STATE = 2560
+VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS = 289409537
+VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON = 289409538
+VEHICLE_PROPERTY_HW_KEY_INPUT = 289475088
+VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO = 289475104
+VEHICLE_PROPERTY_UNIX_TIME = 290458160
+VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS = 289409585
+VEHICLE_PROPERTY_DOOR_POS = 373295872
+VEHICLE_PROPERTY_DOOR_MOVE = 373295873
+VEHICLE_PROPERTY_DOOR_LOCK = 371198722
+VEHICLE_PROPERTY_MIRROR_Z_POS = 339741504
+VEHICLE_PROPERTY_MIRROR_Z_MOVE = 339741505
+VEHICLE_PROPERTY_MIRROR_Y_POS = 339741506
+VEHICLE_PROPERTY_MIRROR_Y_MOVE = 339741507
+VEHICLE_PROPERTY_MIRROR_LOCK = 287312708
+VEHICLE_PROPERTY_MIRROR_FOLD = 287312709
+VEHICLE_PROPERTY_SEAT_MEMORY_SELECT = 356518784
+VEHICLE_PROPERTY_SEAT_MEMORY_SET = 356518785
+VEHICLE_PROPERTY_SEAT_BELT_BUCKLED = 354421634
+VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS = 356518787
+VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE = 356518788
+VEHICLE_PROPERTY_SEAT_FORE_AFT_POS = 356518789
+VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE = 356518790
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS = 356518791
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE = 356518792
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS = 356518793
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE = 356518794
+VEHICLE_PROPERTY_SEAT_HEIGHT_POS = 356518795
+VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE = 356518796
+VEHICLE_PROPERTY_SEAT_DEPTH_POS = 356518797
+VEHICLE_PROPERTY_SEAT_DEPTH_MOVE = 356518798
+VEHICLE_PROPERTY_SEAT_TILT_POS = 356518799
+VEHICLE_PROPERTY_SEAT_TILT_MOVE = 356518800
+VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS = 356518801
+VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE = 356518802
+VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803
+VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804
+VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS = 289409941
+VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE = 356518806
+VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS = 356518807
+VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE = 356518808
+VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS = 356518809
+VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE = 356518810
+VEHICLE_PROPERTY_WINDOW_POS = 289409984
+VEHICLE_PROPERTY_WINDOW_MOVE = 289409985
+VEHICLE_PROPERTY_WINDOW_VENT_POS = 289409986
+VEHICLE_PROPERTY_WINDOW_VENT_MOVE = 289409987
+VEHICLE_PROPERTY_WINDOW_LOCK = 287312836
+VEHICLE_PROPERTY_VEHICLE_MAPS_DATA_SERVICE = 299895808
+VEHICLE_PROPERTY_OBD2_LIVE_FRAME = 299896064
+VEHICLE_PROPERTY_OBD2_FREEZE_FRAME = 299896065
+
+# Vehicle Value Type
+VEHICLE_VALUE_TYPE_STRING       = 0x00100000
+VEHICLE_VALUE_TYPE_BOOLEAN      = 0x00200000
+VEHICLE_VALUE_TYPE_INT32        = 0x00400000
+VEHICLE_VALUE_TYPE_INT32_VEC    = 0x00410000
+VEHICLE_VALUE_TYPE_INT64        = 0x00500000
+VEHICLE_VALUE_TYPE_FLOAT        = 0x00600000
+VEHICLE_VALUE_TYPE_FLOAT_VEC    = 0x00610000
+VEHICLE_VALUE_TYPE_BYTES        = 0x00700000
+VEHICLE_VALUE_TYPE_COMPLEX      = 0x00E00000
+
+# Vehicle zone / area definitions
+VEHICLE_ZONE_ROW_1_LEFT    = 0x00000001
+VEHICLE_ZONE_ROW_1_CENTER  = 0x00000002
+VEHICLE_ZONE_ROW_1_RIGHT   = 0x00000004
+VEHICLE_ZONE_ROW_1_ALL     = 0x00000008
+VEHICLE_ZONE_ROW_2_LEFT    = 0x00000010
+VEHICLE_ZONE_ROW_2_CENTER  = 0x00000020
+VEHICLE_ZONE_ROW_2_RIGHT   = 0x00000040
+VEHICLE_ZONE_ROW_2_ALL     = 0x00000080
+VEHICLE_ZONE_ROW_3_LEFT    = 0x00000100
+VEHICLE_ZONE_ROW_3_CENTER  = 0x00000200
+VEHICLE_ZONE_ROW_3_RIGHT   = 0x00000400
+VEHICLE_ZONE_ROW_3_ALL     = 0x00000800
+VEHICLE_ZONE_ROW_4_LEFT    = 0x00001000
+VEHICLE_ZONE_ROW_4_CENTER  = 0x00002000
+VEHICLE_ZONE_ROW_4_RIGHT   = 0x00004000
+VEHICLE_ZONE_ROW_4_ALL     = 0x00008000
+VEHICLE_ZONE_ALL           = 0x80000000
+
+# Create a container of value_type constants to be used by vhal_emulator
+class vhal_types_2_0:
+    TYPE_STRING = [VEHICLE_VALUE_TYPE_STRING]
+    TYPE_BYTES  = [VEHICLE_VALUE_TYPE_BYTES]
+    TYPE_INT32  = [VEHICLE_VALUE_TYPE_BOOLEAN,
+                   VEHICLE_VALUE_TYPE_INT32]
+    TYPE_INT64  = [VEHICLE_VALUE_TYPE_INT64]
+    TYPE_FLOAT  = [VEHICLE_VALUE_TYPE_FLOAT]
+    TYPE_INT32S = [VEHICLE_VALUE_TYPE_INT32_VEC]
+    TYPE_FLOATS = [VEHICLE_VALUE_TYPE_FLOAT_VEC]
+
diff --git a/tools/emulator/vhal_emulator.py b/tools/emulator/vhal_emulator.py
new file mode 100644
index 0000000..44e5566
--- /dev/null
+++ b/tools/emulator/vhal_emulator.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+    This module provides a vhal class which sends and receives messages to the vehicle HAL module
+    on an Android Auto device.  It uses port forwarding via ADB to communicted with the Android
+    device.
+
+    Example Usage:
+
+        import vhal_consts_1_0 as c
+        from vhal_emulator import Vhal
+
+        # Create an instance of vhal class.  Need to pass the vhal_types constants.
+        v = Vhal(c.vhal_types_1_0)
+
+        # Get the property config (if desired)
+        v.getConfig(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET)
+
+        # Get the response message to getConfig()
+        reply = v.rxMsg()
+        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
+
+        # 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
+
+    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.
+
+    Example for creating RX thread (assumes vhal has already been instantiated):
+
+        from threading import Thread
+
+        # Define a simple thread that receives messags from a vhal object (v) and prints them
+        def rxThread(v):
+            while(1):
+                print v.rxMsg()
+
+        rx = Thread(target=rxThread, args=(v,))
+        rx.start()
+
+    Protocol Buffer:
+        This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
+        If the VehicleHalProto.proto file has changed, re-generate the python version using:
+
+            protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
+"""
+
+# Suppress .pyc files
+import sys
+sys.dont_write_bytecode = True
+
+import socket
+import struct
+import subprocess
+
+# Generate the protobuf file from vendor/auto/embedded/lib/vehicle_hal:
+#   protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
+import VehicleHalProto_pb2
+
+
+class Vhal:
+    """
+        Dictionary of prop_id to value_type.  Used by setProperty() to properly format data.
+    """
+    _propToType = {}
+
+    ### Private Functions
+    def _txCmd(self, cmd):
+        """
+            Transmits a protobuf to Android Auto device.  Should not be called externally.
+        """
+        # Serialize the protobuf into a string
+        msgStr = cmd.SerializeToString()
+        msgLen = len(msgStr)
+        # Convert the message length into int32 byte array
+        msgHdr = struct.pack('!I', msgLen)
+        # Send the message length first
+        self.sock.send(msgHdr)
+        # Then send the protobuf
+        self.sock.send(msgStr)
+
+    ### Public Functions
+    def printHex(self, data):
+        """
+            For debugging, print the protobuf message string in hex.
+        """
+        print "len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data)
+
+    def openSocket(self):
+        """
+            Connects to an Android Auto device running a Vehicle HAL with simulator.
+        """
+        # Hard-coded socket port needs to match the one in DefaultVehicleHal
+        portNumber = 33452
+        # Setup ADB port forwarding
+        subprocess.call("adb forward tcp:%d tcp:%d" % (portNumber, portNumber), shell=True)
+        # Open the socket and connect
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect(('localhost', portNumber))
+
+    def rxMsg(self):
+        """
+            Receive a message over the socket.  This function blocks if a message is not available.
+              May want to wrap this function inside of an rx thread to also collect asynchronous
+              messages generated by the device.
+        """
+        # Receive the message length (int32) first
+        b = self.sock.recv(4)
+        if (len(b) == 4):
+            msgLen, = struct.unpack('!I', b)
+            if (msgLen > 0):
+                # Receive the actual message
+                b = self.sock.recv(msgLen)
+                if (len(b) == msgLen):
+                    # Unpack the protobuf
+                    msg = VehicleHalProto_pb2.EmulatorMessage()
+                    msg.ParseFromString(b)
+                    return msg
+
+    def getConfig(self, prop):
+        """
+            Sends a getConfig message for the specified property.
+        """
+        cmd = VehicleHalProto_pb2.EmulatorMessage()
+        cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_CMD
+        propGet = cmd.prop.add()
+        propGet.prop = prop
+        self._txCmd(cmd)
+
+    def getConfigAll(self):
+        """
+            Sends a getConfigAll message to the host.  This will return all configs avaialable.
+        """
+        cmd = VehicleHalProto_pb2.EmulatorMessage()
+        cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_ALL_CMD
+        self._txCmd(cmd)
+
+    def getProperty(self, prop, area_id):
+        """
+            Sends a getProperty command for the specified property ID and area ID.
+        """
+        cmd = VehicleHalProto_pb2.EmulatorMessage()
+        cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_CMD
+        propGet = cmd.prop.add()
+        propGet.prop = prop
+        propGet.area_id = area_id
+        self._txCmd(cmd)
+
+    def getPropertyAll(self):
+        """
+            Sends a getPropertyAll message to the host.  This will return all properties avaialable.
+        """
+        cmd = VehicleHalProto_pb2.EmulatorMessage()
+        cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_ALL_CMD
+        self._txCmd(cmd)
+
+    def setProperty(self, prop, area_id, value):
+        """
+            Sends a setProperty command for the specified property ID, area ID, and value.
+              This function chooses the proper value field to populate based on the config for the
+              property.  It is the caller's responsibility to ensure the value data is the proper
+              type.
+        """
+        cmd = VehicleHalProto_pb2.EmulatorMessage()
+        cmd.msg_type = VehicleHalProto_pb2.SET_PROPERTY_CMD
+        propValue = cmd.value.add()
+        propValue.prop = prop
+        # Insert value into the proper area
+        propValue.area_id = area_id
+        # Determine the value_type and populate the correct value field in protoBuf
+        try:
+            valType = self._propToType[prop]
+        except KeyError:
+            raise ValueError('propId is invalid:', prop)
+            return
+        propValue.value_type = valType
+        if valType in self._types.TYPE_STRING:
+            propValue.string_value = value
+        elif valType in self._types.TYPE_BYTES:
+            propValue.bytes_value = value
+        elif valType in self._types.TYPE_INT32:
+            propValue.int32_values.append(value)
+        elif valType in self._types.TYPE_INT64:
+            propValue.int64_values.append(value)
+        elif valType in self._types.TYPE_FLOAT:
+            propValue.float_values.append(value)
+        elif valType in self._types.TYPE_INT32S:
+            propValue.int32_values.extend(value)
+        elif valType in self._types.TYPE_FLOATS:
+            propValue.float_values.extend(value)
+        else:
+            raise ValueError('value type not recognized:', valType)
+            return
+        self._txCmd(cmd)
+
+    def __init__(self, types):
+        # Save the list of types constants
+        self._types = types
+        # Open the socket
+        self.openSocket()
+        # Get the list of configs
+        self.getConfigAll()
+        msg = self.rxMsg()
+        # Parse the list of configs to generate a dictionary of prop_id to type
+        for cfg in msg.config:
+            self._propToType[cfg.prop] = cfg.value_type
+
diff --git a/tools/emulator/vhal_emulator_test.py b/tools/emulator/vhal_emulator_test.py
new file mode 100644
index 0000000..325e0a8
--- /dev/null
+++ b/tools/emulator/vhal_emulator_test.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2017 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+    This module tests the Vehicle HAL using adb socket.
+
+    Protocol Buffer:
+        This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
+        If the VehicleHalProto.proto file has changed, re-generate the python version using:
+
+            protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
+            protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
+"""
+
+# Suppress .pyc files
+import sys
+sys.dont_write_bytecode = True
+
+import VehicleHalProto_pb2
+import vhal_consts_2_0
+import vhal_emulator
+import logging
+
+class VhalTest:
+    # Global vars
+    _badProps = [0, 0x3FFFFFFF]     # List of bad properties to try for negative tests
+    _configs = 0                    # List of configs from DUT
+    _log = 0                        # Logger module
+    _vhal = 0                       # Handle to VHAL object that communicates over socket to DUT
+
+    def _getMidpoint(self, minVal, maxVal):
+        retVal =  minVal + (maxVal - minVal)/2
+        return retVal
+
+    # Generates a test value based on the config
+    def _generateTestValue(self, cfg, idx, origValue):
+        valType = cfg.value_type
+        if valType in self._types.TYPE_STRING:
+            testValue = "test string"
+        elif valType in self._types.TYPE_BYTES:
+            # Generate array of integers counting from 0
+            testValue = range(len(origValue))
+        elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN:
+            testValue = origValue ^ 1
+        elif valType in self._types.TYPE_INT32:
+            try:
+                testValue = self._getMidpoint(cfg.area_configs[idx].min_int32_value,
+                                              cfg.area_configs[idx].max_int32_value)
+            except:
+                # min/max values aren't set.  Set a hard-coded value
+                testValue = 123
+        elif valType in self._types.TYPE_INT64:
+            try:
+                testValue = self._getMidpoint(cfg.area_configs[idx].min_int64_value,
+                                              cfg.area_configs[idx].max_int64_value)
+            except:
+                # min/max values aren't set.  Set a large hard-coded value
+                testValue = 1 << 50
+        elif valType in self._types.TYPE_FLOAT:
+            try:
+                testValue = self._getMidpoint(cfg.area_configs[idx].min_float_value,
+                                              cfg.area_configs[idx].max_float_value)
+            except:
+                # min/max values aren't set.  Set a hard-coded value
+                testValue = 123.456
+            # Truncate float to 5 decimal places
+            testValue = "%.5f" % testValue
+            testValue = float(testValue)
+        else:
+            self._log.error("generateTestValue:  valType=%d is not handled", valType)
+            testValue = None
+        return testValue
+
+    # Helper function to extract values array from rxMsg
+    def _getValueFromMsg(self, rxMsg):
+        # Check to see only one property value is returned
+        if len(rxMsg.value) != 1:
+            self._log.error("getValueFromMsg:  Received invalid value")
+            value = None
+        else:
+            valType = rxMsg.value[0].value_type
+            if valType in self._types.TYPE_STRING:
+                value = rxMsg.value[0].string_value
+            elif valType in self._types.TYPE_BYTES:
+                value = rxMsg.value[0].bytes_value
+            elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN:
+                value = rxMsg.value[0].int32_values[0]
+            elif valType in self._types.TYPE_INT32:
+                value = rxMsg.value[0].int32_values[0]
+            elif valType in self._types.TYPE_INT64:
+                value = rxMsg.value[0].int64_values[0]
+            elif valType in self._types.TYPE_FLOAT:
+                value = rxMsg.value[0].float_values[0]
+                # Truncate float to 5 decimal places
+                value = "%.5f" % value
+                value = float(value)
+            else:
+                self._log.error("getValuesFromMsg:  valType=%d is not handled", valType)
+                value = None
+        return value
+
+    # Helper function to receive a message and validate the type and status
+    #   retVal = 1 if no errors
+    #   retVal = 0 if errors detected
+    def _rxMsgAndValidate(self, expectedType, expectedStatus):
+        retVal = 1
+        rxMsg = self._vhal.rxMsg()
+        if rxMsg.msg_type != expectedType:
+            self._log.error("rxMsg Type expected: %d, received: %d", expectedType, rxMsg.msg_type)
+            retVal = 0
+        if rxMsg.status != expectedStatus:
+            self._log.error("rxMsg Status expected: %d, received: %d", expectedStatus, rxMsg.status)
+            retVal = 0
+        return rxMsg, retVal
+
+    # Calls getConfig() on each individual property ID and verifies it matches with the config
+    #   received in getConfigAll()
+    def testGetConfig(self):
+        self._log.info("Starting testGetConfig...")
+        for cfg in self._configs:
+            self._log.debug("  Getting config for propId=%d", cfg.prop)
+            self._vhal.getConfig(cfg.prop)
+            rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
+                                                   VehicleHalProto_pb2.RESULT_OK)
+            if retVal:
+                if rxMsg.config[0] != cfg:
+                    self._log.error("testGetConfig failed.  prop=%d, expected:\n%s\nreceived:\n%s",
+                               cfg.prop, str(cfg), str(rxMsg.config))
+        self._log.info("  Finished testGetConfig!")
+
+    # Calls getConfig() on invalid property ID and verifies it generates an error
+    def testGetBadConfig(self):
+        self._log.info("Starting testGetBadConfig...")
+        for prop in self._badProps:
+            self._log.debug("  Testing bad propId=%d", prop)
+            self._vhal.getConfig(prop)
+            rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
+                                                   VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
+            if retVal:
+                for cfg in rxMsg.config:
+                    self._log.error("testGetBadConfig  prop=%d, expected:None, received:\n%s",
+                                    cfg.prop, str(rxMsg.config))
+        self._log.info("  Finished testGetBadConfig!")
+
+    def testGetPropertyAll(self):
+        self._log.info("Starting testGetPropertyAll...")
+        self._vhal.getPropertyAll()
+        rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_ALL_RESP,
+                                               VehicleHalProto_pb2.RESULT_OK)
+        if retVal == 0:
+            self._log.error("testGetPropertyAll:  Failed to receive proper rxMsg")
+
+        # TODO: Finish writing this test.  What should we be testing, anyway?
+
+        self._log.info("  Finished testGetPropertyAll!")
+
+    def testGetSet(self):
+        self._log.info("Starting testGetSet()...")
+        for cfg in self._configs:
+            areas = cfg.supported_areas
+            idx = -1
+            while (idx == -1) | (areas != 0):
+                idx += 1
+                # Get the area to test
+                area = areas & (areas -1)
+                area ^= areas
+
+                # Remove the area from areas
+                areas ^= area
+
+                self._log.debug("  Testing propId=%d, area=%d", cfg.prop, area)
+
+                # Get the current value
+                self._vhal.getProperty(cfg.prop, area)
+                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
+                                                       VehicleHalProto_pb2.RESULT_OK)
+
+                # Save the original value
+                origValue = self._getValueFromMsg(rxMsg)
+                if origValue == None:
+                    self._log.error("testGetSet:  Could not get value for prop=%d, area=%d",
+                                    cfg.prop, area)
+                    continue
+
+                # Generate the test value
+                testValue = self._generateTestValue(cfg, idx, origValue)
+                if testValue == None:
+                    self._log.error("testGetSet:  Cannot generate test value for prop=%d, area=%d",
+                                    cfg.prop, area)
+                    continue
+
+                # Send the new value
+                self._vhal.setProperty(cfg.prop, area, testValue)
+                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
+                                                       VehicleHalProto_pb2.RESULT_OK)
+
+                # Get the new value and verify it
+                self._vhal.getProperty(cfg.prop, area)
+                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
+                                                       VehicleHalProto_pb2.RESULT_OK)
+                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
+                    continue
+
+                # Reset the value to what it was before
+                self._vhal.setProperty(cfg.prop, area, origValue)
+                rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
+                                                       VehicleHalProto_pb2.RESULT_OK)
+        self._log.info("  Finished testGetSet()!")
+
+    def testGetBadProperty(self):
+        self._log.info("Starting testGetBadProperty()...")
+        for prop in self._badProps:
+            self._log.debug("  Testing bad propId=%d", prop)
+            self._vhal.getProperty(prop, 0)
+            rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
+                                                   VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
+            if retVal:
+                for value in rxMsg.value:
+                    self._log.error("testGetBadProperty  prop=%d, expected:None, received:\n%s",
+                                    prop, str(rxMsg))
+        self._log.info("  Finished testGetBadProperty()!")
+
+    def testSetBadProperty(self):
+        self._log.info("Starting testSetBadProperty()...")
+        area = 1
+        value = 100
+        for prop in self._badProps:
+            self._log.debug("  Testing bad propId=%d", prop)
+            area = area + 1
+            value = value + 1
+            try:
+                self._vhal.setProperty(prop, area, value)
+                self._log.error("testGetBadProperty failed.  prop=%d, area=%d, value=%d",
+                                prop, area, value)
+            except ValueError as e:
+                # Received expected error
+                pass
+        self._log.info("  Finished testSetBadProperty()!")
+
+    def runTests(self):
+        self.testGetConfig()
+        self.testGetBadConfig()
+        self.testGetPropertyAll()
+        self.testGetSet()
+        self.testGetBadProperty()
+        self.testSetBadProperty()
+        # Add new tests here to be run
+
+
+    # Valid logLevels:
+    #   CRITICAL    50
+    #   ERRROR      40
+    #   WARNING     30
+    #   INFO        20
+    #   DEBUG       10
+    #   NOTSET      0
+    def __init__(self, types, logLevel=20):
+        self._types = types
+        # Configure the logger
+        logging.basicConfig()
+        self._log = logging.getLogger('vhal_emulator_test')
+        self._log.setLevel(logLevel)
+        # Start the VHAL Emulator
+        self._vhal = vhal_emulator.Vhal(types)
+        # Get the list of configs
+        self._vhal.getConfigAll()
+        self._configs = self._vhal.rxMsg().config
+
+if __name__ == '__main__':
+    v = VhalTest(vhal_consts_2_0.vhal_types_2_0)
+    v.runTests()
+