Pass car API stubs to non-SDK enforcement am: 43231d977b
am: 068566892d
Change-Id: Id63dce0443ab0da5f0457eb1d97e940fd2f6dccb
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;
}