Merge "DO NOT MERGE - Merge pie-platform-release (PPRL.181205.001) into stage-aosp-master" into stage-aosp-master
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 725967d..df6b85e 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -5,7 +5,6 @@
     field public static final java.lang.String CAR_DRIVING_STATE_SERVICE = "drivingstate";
     field public static final java.lang.String DIAGNOSTIC_SERVICE = "diagnostic";
     field public static final java.lang.String HVAC_SERVICE = "hvac";
-    field public static final java.lang.String PERMISSION_ADJUST_CAR_CABIN = "android.car.permission.ADJUST_CAR_CABIN";
     field public static final java.lang.String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.CLEAR_CAR_DIAGNOSTICS";
     field public static final java.lang.String PERMISSION_CAR_DIAGNOSTIC_READ_ALL = "android.car.permission.CAR_DIAGNOSTICS";
     field public static final java.lang.String PERMISSION_CAR_DRIVING_STATE = "android.car.permission.CAR_DRIVING_STATE";
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 738a423..c1bf502 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -281,14 +281,6 @@
             "android.car.permission.CONTROL_APP_BLOCKING";
 
     /**
-     * Permission necessary to access Car Cabin APIs.
-     * @hide
-     */
-    @SystemApi
-    public static final String PERMISSION_ADJUST_CAR_CABIN =
-            "android.car.permission.ADJUST_CAR_CABIN";
-
-    /**
      * Permission necessary to access car's engine information.
      * @hide
      */
diff --git a/car-lib/src/android/car/PortLocationType.java b/car-lib/src/android/car/PortLocationType.java
new file mode 100644
index 0000000..3006a29
--- /dev/null
+++ b/car-lib/src/android/car/PortLocationType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car;
+
+/**
+ * Used by INFO_FUEL_DOOR_LOCATION/INFO_CHARGE_PORT_LOCATION to enumerate fuel door or
+ * ev port location.
+ * Use getProperty and setProperty in {@link android.car.hardware.property.CarPropertyManager} to
+ * set and get this VHAL property.
+ * @hide
+ */
+public final class PortLocationType {
+    public static final int UNKNOWN = 0;
+    public static final int FRONT_LEFT = 1;
+    public static final int FRONT_RIGHT = 2;
+    public static final int REAR_RIGHT = 3;
+    public static final int REAR_LEFT = 4;
+    public static final int FRONT = 5;
+    public static final int REAR = 6;
+
+    private PortLocationType() {}
+}
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 7cef341..24c9cd7 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -27,11 +27,6 @@
         android:description="@string/car_permission_desc"
         android:label="@string/car_permission_label" />
     <permission
-        android:name="android.car.permission.ADJUST_CAR_CABIN"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_cabin"
-        android:description="@string/car_permission_desc_cabin" />
-    <permission
         android:name="android.car.permission.CAR_ENERGY"
         android:permissionGroup="android.car.permission-group.CAR_MONITORING"
         android:protectionLevel="dangerous"
diff --git a/service/src/com/android/car/CarPropertyService.java b/service/src/com/android/car/CarPropertyService.java
index 785e598..a01d4b8 100644
--- a/service/src/com/android/car/CarPropertyService.java
+++ b/service/src/com/android/car/CarPropertyService.java
@@ -137,6 +137,13 @@
 
     @Override
     public void init() {
+        if (mConfigs == null) {
+            // Cache the configs list to avoid subsequent binder calls
+            mConfigs = mHal.getPropertyList();
+            if (DBG) {
+                Log.d(TAG, "cache CarPropertyConfigs " + mConfigs.size());
+            }
+        }
     }
 
     @Override
@@ -159,9 +166,6 @@
         if (DBG) {
             Log.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate);
         }
-        if (mConfigs == null) {
-            mConfigs = mHal.getPropertyList();
-        }
         if (mConfigs.get(propId) == null) {
             // Do not attempt to register an invalid propId
             Log.e(TAG, "registerListener:  propId is not in config list:  " + propId);
@@ -285,15 +289,10 @@
 
     /**
      * Return the list of properties that the caller may access.
-     * Should be called before get/setProperty().
      */
     @Override
     public List<CarPropertyConfig> getPropertyList() {
         List<CarPropertyConfig> returnList = new ArrayList<CarPropertyConfig>();
-        if (mConfigs == null) {
-            // Cache the configs list to avoid subsequent binder calls
-            mConfigs = mHal.getPropertyList();
-        }
         for (CarPropertyConfig c : mConfigs.values()) {
             if (ICarImpl.hasPermission(mContext, mHal.getReadPermission(c.getPropertyId()))) {
                 // Only add properties the list if the process has permissions to read it
diff --git a/tests/CarTrustAgentClientApp/Android.mk b/tests/CarTrustAgentClientApp/Android.mk
new file mode 100644
index 0000000..7945ee5
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CarTrustAgentClient
+
+LOCAL_USE_AAPT2 := true
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4
+
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := optional
+LOCAL_MIN_SDK_VERSION := 23
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/CarTrustAgentClientApp/AndroidManifest.xml b/tests/CarTrustAgentClientApp/AndroidManifest.xml
new file mode 100644
index 0000000..35a9a6d
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.trust.client">
+
+    <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-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">
+        <activity
+                android:name=".PhoneEnrolmentActivity"
+                android:label="@string/app_name"
+                android:exported="true"
+                android:launchMode="singleInstance">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/CarTrustAgentClientApp/README.txt b/tests/CarTrustAgentClientApp/README.txt
new file mode 100644
index 0000000..bf6c444
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/README.txt
@@ -0,0 +1,2 @@
+IMPORTANT NOTE: This is a reference app to smart unlock paired HU during development.
+Consider moving the functionality to a more proper place.
diff --git a/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml b/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml
new file mode 100644
index 0000000..620e04e
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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="match_parent"
+        android:layout_height="0dp"
+        android:scrollbars="vertical"
+        android:layout_weight="0.80">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/output"/>
+    </ScrollView>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="0.10"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/enroll_scan"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="2"
+            android:text="@string/enroll_scan"/>
+        <Button
+            android:id="@+id/enroll_button"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="3"
+            android:text="@string/enroll_button"/>
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="0.10"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/unlock_scan"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="2"
+            android:text="@string/unlock_scan"/>
+        <Button
+            android:id="@+id/unlock_button"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="3"
+            android:text="@string/unlock_button"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/CarTrustAgentClientApp/res/values/strings.xml b/tests/CarTrustAgentClientApp/res/values/strings.xml
new file mode 100644
index 0000000..5c9b4db
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">CarTrustAgentClient</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="enroll_button">Enroll new token</string>
+    <string name="enroll_scan">Scan to enroll</string>
+    <string name="unlock_button">Unlock</string>
+    <string name="unlock_scan">Scan to unlock</string>
+</resources>
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java
new file mode 100644
index 0000000..c1d30c1
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.client;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+/**
+ * Activity to allow the user to add an escrow token to a remote device. <p/>
+ *
+ * For this to work properly, the correct permissions must be set in the system config.  In AOSP,
+ * this config is in frameworks/base/core/res/res/values/config.xml <p/>
+ *
+ * The config must set config_allowEscrowTokenForTrustAgent to true.  For the desired car
+ * experience, the config should also set config_strongAuthRequiredOnBoot to false.
+ */
+public class PhoneEnrolmentActivity extends Activity {
+
+    private static final int FINE_LOCATION_REQUEST_CODE = 42;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.phone_enrolment_activity);
+
+        PhoneEnrolmentController enrolmentController = new PhoneEnrolmentController(this);
+        enrolmentController.bind(findViewById(R.id.output), findViewById(R.id.enroll_scan),
+                findViewById(R.id.enroll_button));
+
+        PhoneUnlockController unlockController = new PhoneUnlockController(this);
+        unlockController.bind(findViewById(R.id.output), findViewById(R.id.unlock_scan),
+                findViewById(R.id.unlock_button));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(
+                    new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
+                    FINE_LOCATION_REQUEST_CODE);
+        }
+    }
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java
new file mode 100644
index 0000000..030e3d2
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.client;
+
+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.widget.Button;
+import android.widget.TextView;
+
+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 final 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) {
+
+            Log.d(Utils.LOG_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())) {
+                Log.d(Utils.LOG_TAG, "Service UUID: " + service.getUuid()
+                        + " does not match Enrolment UUID " + mEnrolmentServiceUuid.getUuid());
+                return;
+            }
+
+            Log.d(Utils.LOG_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(() -> {
+                // Services are now set up, allow users to enrol new escrow tokens.
+                mEnrolButton.setEnabled(true);
+                mEnrolButton.setAlpha(1.0f);
+            });
+        }
+    };
+
+    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 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;
+        mEnrolButton = enrolButton;
+
+        scanButton.setOnClickListener((view) -> mClient.start(mEnrolmentServiceUuid));
+
+        mEnrolButton.setEnabled(false);
+        mEnrolButton.setAlpha(0.3f);
+        mEnrolButton.setOnClickListener((view) -> {
+            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 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(() -> mTextView.append("\n" + text));
+    }
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java
new file mode 100644
index 0000000..78e50b4
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.client;
+
+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.widget.Button;
+import android.widget.TextView;
+
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service.
+ */
+public class PhoneUnlockController {
+
+    private final 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())) {
+                Log.d(Utils.LOG_TAG, "Service UUID: " + service.getUuid()
+                        + " does not match Enrolment UUID " + mUnlockServiceUuid.getUuid());
+                return;
+            }
+
+            Log.d(Utils.LOG_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(() -> {
+                // Services are now set up, allow users to enrol new escrow tokens.
+                mUnlockButton.setEnabled(true);
+                mUnlockButton.setAlpha(1.0f);
+            });
+        }
+    };
+
+    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 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;
+        mUnlockButton = enrolButton;
+
+        scanButton.setOnClickListener((view) -> mClient.start(mUnlockServiceUuid));
+
+        mUnlockButton.setEnabled(false);
+        mUnlockButton.setAlpha(0.3f);
+        mUnlockButton.setOnClickListener((view) -> {
+            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 void appendOutputText(final String text) {
+        mHandler.post(() -> mTextView.append("\n" + text));
+    }
+}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java
new file mode 100644
index 0000000..c0fecb3
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.client;
+
+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.util.Log;
+
+import androidx.annotation.NonNull;
+
+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 long SCAN_TIME_MS = 10000;
+
+    private final Queue<BleAction> mBleActionQueue = new ConcurrentLinkedQueue<BleAction>();
+    private final List<ClientCallback> mCallbacks = new ArrayList<>();
+    private final Context mContext;
+    private final BluetoothLeScanner mScanner;
+
+    private BluetoothGatt mBtGatt;
+    private ParcelUuid mServiceUuid;
+
+    public SimpleBleClient(@NonNull Context context) {
+        mContext = context;
+        BluetoothManager btManager = (BluetoothManager) mContext.getSystemService(
+                Context.BLUETOOTH_SERVICE);
+        mScanner = btManager.getAdapter().getBluetoothLeScanner();
+    }
+
+    /**
+     * 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);
+
+        Log.d(Utils.LOG_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);
+                Log.d(Utils.LOG_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;
+        }
+
+        Log.d(Utils.LOG_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();
+            Log.d(Utils.LOG_TAG, "Scan result found: " + result.getScanRecord().getServiceUuids());
+
+            if (!hasServiceUuid(result)) {
+                return;
+            }
+
+            for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) {
+                Log.d(Utils.LOG_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) {
+                Log.d(Utils.LOG_TAG, "Batch scanResult: " + r.getDevice().getName()
+                        + " " + r.getDevice().getAddress());
+            }
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            Log.e(Utils.LOG_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();
+                }
+            }
+            Log.d(Utils.LOG_TAG, "Gatt connection status: " + getStatus(status)
+                    + " newState: " + state);
+        }
+
+        @Override
+        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            super.onServicesDiscovered(gatt, status);
+            Log.d(Utils.LOG_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()) {
+                Log.d(Utils.LOG_TAG, "Found service: " + service.getUuid() + " notifying clients");
+                for (ClientCallback callback : mCallbacks) {
+                    callback.onServiceDiscovered(service);
+                }
+            }
+        }
+
+        @Override
+        public void onCharacteristicWrite(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            Log.d(Utils.LOG_TAG, "onCharacteristicWrite: " + status);
+            processNextAction();
+        }
+
+        @Override
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            Log.d(Utils.LOG_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/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java
new file mode 100644
index 0000000..003a86c
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.client;
+
+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 final String LOG_TAG = "CarTrustAgentClient";
+
+    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/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 2be5a83..19a5552 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -19,7 +19,10 @@
 
     <uses-permission android:name="android.Manifest.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
-    <uses-permission android:name="android.car.permission.ADJUST_CAR_CABIN" />
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_DOORS" />
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_WINDOWS" />
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_MIRRORS" />
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_SEATS" />
     <uses-permission android:name="android.car.permission.CAR_ENERGY" />
     <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 3a50b45..c63c980 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -173,7 +173,7 @@
     protected MockContext getCarServiceContext() throws NameNotFoundException {
         if (mMockContext == null) {
             mMockContext = new MockContext(getContext()
-                .createPackageContext("com.android.car", Context.CONTEXT_IGNORE_SECURITY));
+                .createPackageContext("com.android.car.test", Context.CONTEXT_IGNORE_SECURITY));
         }
         return mMockContext;
     }