Merge "Adding KS example for observing changes in content"
diff --git a/car-usb-handler/AndroidManifest.xml b/car-usb-handler/AndroidManifest.xml
index 7803318..91730a6 100644
--- a/car-usb-handler/AndroidManifest.xml
+++ b/car-usb-handler/AndroidManifest.xml
@@ -15,12 +15,12 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.car.usb.handler" >
+ package="android.car.usb.handler">
<uses-sdk android:minSdkVersion="25" />
- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.MANAGE_USB" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher"
- android:directBootAware="true" >
+ android:directBootAware="true">
<activity android:name=".UsbHostManagementActivity"
android:theme="@android:style/Theme.Material.Light.Dialog"
android:launchMode="singleTop">
@@ -28,9 +28,10 @@
android:name="distractionOptimized"
android:value="true" />
</activity>
- <receiver android:name=".BootUsbScanner" >
+ <receiver android:name=".BootUsbScanner"
+ android:directBootAware="true">
<intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
+ <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
index ec9a89a..2762e6f 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -56,6 +56,8 @@
private static final boolean LOCAL_LOGD = true;
private static final boolean LOCAL_LOGV = true;
+ private static final int DISPATCH_RETRY_DELAY_MS = 1000;
+ private static final int DISPATCH_RETRY_ATTEMPTS = 5;
private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
private final Context mContext;
@@ -158,16 +160,16 @@
mCallback.processingStateChanged(true);
UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(device);
- if (settings != null && mUsbResolver.dispatch(
- device, settings.getHandler(), settings.getAoap())) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Usb Device: " + device + " was sent to component: "
- + settings.getHandler());
- }
- return;
+
+ if (settings == null) {
+ resolveDevice(device);
+ } else {
+ Object obj =
+ new UsbHostControllerHandlerDispatchData(
+ device, settings, DISPATCH_RETRY_ATTEMPTS, true);
+ Message.obtain(mHandler, UsbHostControllerHandler.MSG_DEVICE_DISPATCH, obj)
+ .sendToTarget();
}
- mCallback.titleChanged(generateTitle(mContext, device));
- mUsbResolver.resolve(device);
}
/**
@@ -175,7 +177,17 @@
*/
public void applyDeviceSettings(UsbDeviceSettings settings) {
mUsbSettingsStorage.saveSettings(settings);
- mUsbResolver.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
+ Message msg = mHandler.obtainMessage();
+ msg.obj =
+ new UsbHostControllerHandlerDispatchData(
+ getActiveDevice(), settings, DISPATCH_RETRY_ATTEMPTS, false);
+ msg.what = UsbHostControllerHandler.MSG_DEVICE_DISPATCH;
+ msg.sendToTarget();
+ }
+
+ private void resolveDevice(UsbDevice device) {
+ mCallback.titleChanged(generateTitle(mContext, device));
+ mUsbResolver.resolve(device);
}
/**
@@ -244,8 +256,34 @@
}
}
+ private class UsbHostControllerHandlerDispatchData {
+ private final UsbDevice mUsbDevice;
+ private final UsbDeviceSettings mUsbDeviceSettings;
+
+ public int mRetries = 0;
+ public boolean mCanResolve = true;
+
+ public UsbHostControllerHandlerDispatchData(
+ UsbDevice usbDevice, UsbDeviceSettings usbDeviceSettings,
+ int retries, boolean canResolve) {
+ mUsbDevice = usbDevice;
+ mUsbDeviceSettings = usbDeviceSettings;
+ mRetries = retries;
+ mCanResolve = canResolve;
+ }
+
+ public UsbDevice getUsbDevice() {
+ return mUsbDevice;
+ }
+
+ public UsbDeviceSettings getUsbDeviceSettings() {
+ return mUsbDeviceSettings;
+ }
+ }
+
private class UsbHostControllerHandler extends Handler {
private static final int MSG_DEVICE_REMOVED = 1;
+ private static final int MSG_DEVICE_DISPATCH = 2;
private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
@@ -263,6 +301,24 @@
case MSG_DEVICE_REMOVED:
doHandleDeviceRemoved();
break;
+ case MSG_DEVICE_DISPATCH:
+ UsbHostControllerHandlerDispatchData data =
+ (UsbHostControllerHandlerDispatchData) msg.obj;
+ UsbDevice device = data.getUsbDevice();
+ UsbDeviceSettings settings = data.getUsbDeviceSettings();
+ if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap())) {
+ if (data.mRetries > 0) {
+ --data.mRetries;
+ Message nextMessage = Message.obtain(msg);
+ mHandler.sendMessageDelayed(nextMessage, DISPATCH_RETRY_DELAY_MS);
+ } else if (data.mCanResolve) {
+ resolveDevice(device);
+ }
+ } else if (LOCAL_LOGV) {
+ Log.v(TAG, "Usb Device: " + data.getUsbDevice() + " was sent to component: "
+ + settings.getHandler());
+ }
+ break;
default:
Log.w(TAG, "Unhandled message: " + msg);
super.handleMessage(msg);
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
index 387ae62..d571d5d 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
@@ -15,17 +15,23 @@
*/
package android.car.usb.handler;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
+
import android.annotation.Nullable;
import android.app.Activity;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -55,6 +61,39 @@
private UsbHostController mController;
private PackageManager mPackageManager;
+ private final ResolveBroadcastReceiver mResolveBroadcastReceiver
+ = new ResolveBroadcastReceiver();
+ private boolean mReceiverRegistered = false;
+
+ private void unregisterResolveBroadcastReceiver() {
+ if (mReceiverRegistered) {
+ unregisterReceiver(mResolveBroadcastReceiver);
+ mReceiverRegistered = false;
+ }
+ }
+
+ private void processDevice() {
+ UsbDevice connectedDevice = getDevice();
+
+ if (connectedDevice != null) {
+ mController.processDevice(connectedDevice);
+ } else {
+ unregisterResolveBroadcastReceiver();
+ finish();
+ }
+ }
+
+ private class ResolveBroadcastReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ // We could have been unregistered after receiving the intent but before processing it,
+ // so make sure we are still registered.
+ if (mReceiverRegistered) {
+ processDevice();
+ unregisterResolveBroadcastReceiver();
+ }
+ }
+ }
+
private final AdapterView.OnItemClickListener mHandlerClickListener =
new AdapterView.OnItemClickListener() {
@Override
@@ -85,13 +124,25 @@
}
@Override
+ public void onPause() {
+ super.onPause();
+ unregisterResolveBroadcastReceiver();
+ }
+
+ @Override
public void onResume() {
super.onResume();
- UsbDevice connectedDevice = getDevice();
- if (connectedDevice != null) {
- mController.processDevice(connectedDevice);
+
+ UserManager userManager = getSystemService(UserManager.class);
+ if (userManager.isUserUnlocked() || getUserId() == UserHandle.USER_SYSTEM) {
+ processDevice();
} else {
- finish();
+ mReceiverRegistered = true;
+ registerReceiver(mResolveBroadcastReceiver, new IntentFilter(ACTION_USER_UNLOCKED));
+ // in case the car was unlocked while the receiver was being registered
+ if (userManager.isUserUnlocked()) {
+ mResolveBroadcastReceiver.onReceive(this, new Intent(ACTION_USER_UNLOCKED));
+ }
}
}
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
index 7e8704e..b578161 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbSettingsStorage.java
@@ -183,13 +183,18 @@
return contentValues;
}
-
private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 2;
private static final String DATABASE_NAME = "usb_devices.db";
+ // we are using device protected storage because we may need to access the db before the
+ // user has authenticated
UsbSettingsDbHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ super(
+ context.createDeviceProtectedStorageContext(),
+ DATABASE_NAME,
+ null,
+ DATABASE_VERSION);
}
@Override
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 2ce3d45..0775d82 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -289,6 +289,7 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.LOCATION_HARDWARE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
<application android:label="@string/app_title"
android:directBootAware="true"
@@ -303,6 +304,21 @@
</intent-filter>
</service>
<service android:name=".PerUserCarService" android:exported="false" />
+
+ <service
+ android:name="com.android.car.trust.CarBleTrustAgent"
+ android:permission="android.permission.BIND_TRUST_AGENT"
+ android:singleUser="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_trust_agent"/>
+ </service>
<activity android:name="com.android.car.pm.ActivityBlockingActivity"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 48f346e..200c4c3 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -158,4 +158,16 @@
<!-- UI mode for projection activity. See ProjectionOptions class for possible values. -->
<integer name="config_projectionUiMode" translatable="false">0</integer>
+ <!-- service/characteristics uuid for adding new escrow token -->
+ <string name="enrollment_service_uuid" translatable="false">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="enrollment_handle_uuid" translatable="false">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="enrollment_token_uuid" translatable="false">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
+
+ <!-- service/characteristics uuid for unlocking a device -->
+ <string name="unlock_service_uuid" translatable="false">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="unlock_escrow_token_uuid" translatable="false">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="unlock_handle_uuid" translatable="false">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
+
+ <string name="token_handle_shared_preferences" translatable="false">com.android.car.trust.TOKEN_HANDLE</string>
+
</resources>
diff --git a/service/res/xml/car_trust_agent.xml b/service/res/xml/car_trust_agent.xml
new file mode 100644
index 0000000..e53932d
--- /dev/null
+++ b/service/res/xml/car_trust_agent.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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/service/src/com/android/car/CarLocalServices.java b/service/src/com/android/car/CarLocalServices.java
index 2c619af..fe3dc07 100644
--- a/service/src/com/android/car/CarLocalServices.java
+++ b/service/src/com/android/car/CarLocalServices.java
@@ -17,6 +17,7 @@
package com.android.car;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +39,7 @@
*/
@SuppressWarnings("unchecked")
public static <T> T getService(Class<T> type) {
+ Log.d("CarLocalServices", " getService " + type.getSimpleName());
synchronized (sLocalServiceObjects) {
return (T) sLocalServiceObjects.get(type);
}
@@ -51,6 +53,7 @@
if (sLocalServiceObjects.containsKey(type)) {
throw new IllegalStateException("Overriding service registration");
}
+ Log.d("CarLocalServices", " Adding " + type.getSimpleName());
sLocalServiceObjects.put(type, service);
}
}
@@ -60,6 +63,7 @@
*/
@VisibleForTesting
public static <T> void removeServiceForTest(Class<T> type) {
+ Log.d("CarLocalServices", " Removing " + type.getSimpleName());
synchronized (sLocalServiceObjects) {
sLocalServiceObjects.remove(type);
}
@@ -69,6 +73,7 @@
* Remove all registered services. Should be called when car service restarts.
*/
public static void removeAllServices() {
+ Log.d("CarLocalServices", " removeAllServices");
synchronized (sLocalServiceObjects) {
sLocalServiceObjects.clear();
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index c43547b..35255ee 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -42,7 +42,7 @@
import com.android.car.internal.FeatureConfiguration;
import com.android.car.pm.CarPackageManagerService;
import com.android.car.systeminterface.SystemInterface;
-import com.android.car.trust.CarTrustAgentEnrollmentService;
+import com.android.car.trust.CarTrustedDeviceService;
import com.android.car.user.CarUserService;
import com.android.car.vms.VmsBrokerService;
import com.android.car.vms.VmsClientManager;
@@ -86,7 +86,7 @@
private final CarDiagnosticService mCarDiagnosticService;
private final CarStorageMonitoringService mCarStorageMonitoringService;
private final CarConfigurationService mCarConfigurationService;
- private final CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+ private final CarTrustedDeviceService mCarTrustedDeviceService;
private final CarMediaService mCarMediaService;
private final CarUserManagerHelper mUserManagerHelper;
private final CarUserService mCarUserService;
@@ -156,10 +156,13 @@
new CarConfigurationService(serviceContext, new JsonReaderImpl());
mCarLocationService = new CarLocationService(mContext, mCarPropertyService,
mUserManagerHelper);
- mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(serviceContext);
+ mCarTrustedDeviceService = new CarTrustedDeviceService(serviceContext);
mCarMediaService = new CarMediaService(serviceContext);
CarLocalServices.addService(CarUserService.class, mCarUserService);
+ Log.d(TAG, "Adding CarTrustedDeviceService");
+ CarLocalServices.addService(CarTrustedDeviceService.class,
+ mCarTrustedDeviceService);
CarLocalServices.addService(SystemInterface.class, mSystemInterface);
// Be careful with order. Service depending on other service should be inited later.
@@ -187,7 +190,7 @@
allServices.add(mVmsClientManager);
allServices.add(mVmsSubscriberService);
allServices.add(mVmsPublisherService);
- allServices.add(mCarTrustAgentEnrollmentService);
+ allServices.add(mCarTrustedDeviceService);
allServices.add(mCarMediaService);
allServices.add(mCarLocationService);
mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
@@ -301,7 +304,7 @@
return mCarConfigurationService;
case Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE:
assertTrustAgentEnrollmentPermission(mContext);
- return mCarTrustAgentEnrollmentService;
+ return mCarTrustedDeviceService.getCarTrustAgentEnrollmentService();
case Car.CAR_MEDIA_SERVICE:
return mCarMediaService;
default:
diff --git a/service/src/com/android/car/Utils.java b/service/src/com/android/car/Utils.java
index 284e610..70e1d7c 100644
--- a/service/src/com/android/car/Utils.java
+++ b/service/src/com/android/car/Utils.java
@@ -1,8 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.car;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import java.nio.ByteBuffer;
+
/**
* Some potentially useful static methods.
*/
@@ -17,7 +34,7 @@
}
static String getProfileName(int profile) {
- switch(profile) {
+ switch (profile) {
case BluetoothProfile.A2DP_SINK:
return "A2DP_SINK";
case BluetoothProfile.HEADSET_CLIENT:
@@ -46,7 +63,6 @@
* <p>
* A specific service in CarService can choose to use a circular buffer of N records to keep
* track of the last N transitions.
- *
*/
public static class TransitionLog {
private String mServiceName; // name of the service or tag
@@ -78,4 +94,43 @@
: "") + " changed from " + mFromState + " to " + mToState;
}
}
+
+ /**
+ * Returns a byte buffer corresponding to the passed long argument.
+ *
+ * @param primitive data to convert format.
+ */
+ public static byte[] longToBytes(long primitive) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.putLong(primitive);
+ return buffer.array();
+ }
+
+ /**
+ * Returns a byte buffer corresponding to the passed long argument.
+ *
+ * @param array data to convert format.
+ */
+ public static long bytesToLong(byte[] array) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.put(array);
+ buffer.flip();
+ long value = buffer.getLong();
+ return value;
+ }
+
+ /**
+ * Returns a String in Hex format that is formed from the bytes in the byte array
+ * Useful for debugging
+ *
+ * @param array the byte array
+ * @return the Hex string version of the input byte array
+ */
+ public static String byteArrayToHexString(byte[] array) {
+ StringBuilder sb = new StringBuilder(array.length * 2);
+ for (byte b : array) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
}
diff --git a/service/src/com/android/car/trust/BleManager.java b/service/src/com/android/car/trust/BleManager.java
new file mode 100644
index 0000000..26a9946
--- /dev/null
+++ b/service/src/com/android/car/trust/BleManager.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2019 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 static android.bluetooth.BluetoothProfile.GATT_SERVER;
+
+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.pm.PackageManager;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.car.Utils;
+
+/**
+ * A generic class that manages BLE operations like start/stop advertising, notifying connects/
+ * disconnects and reading/writing values to GATT characteristics.
+ *
+ * TODO(b/123248433) This could move to a separate comms library.
+ */
+public abstract class BleManager {
+ private static final String TAG = BleManager.class.getSimpleName();
+
+ private static final int BLE_RETRY_LIMIT = 5;
+ private static final int BLE_RETRY_INTERVAL_MS = 1000;
+
+ private final Handler mHandler = new Handler();
+
+ private final Context mContext;
+ private BluetoothManager mBluetoothManager;
+ private BluetoothLeAdvertiser mAdvertiser;
+ private BluetoothGattServer mGattServer;
+ private int mAdvertiserStartCount;
+
+ BleManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Starts the GATT server with the given {@link BluetoothGattService} and begins
+ * advertising.
+ *
+ * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
+ * Therefore, several retries will be made to ensure advertising is started.
+ *
+ * @param service {@link BluetoothGattService} that will be discovered by clients
+ */
+ protected void startAdvertising(BluetoothGattService service,
+ AdvertiseCallback advertiseCallback) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "startAdvertising: " + service.getUuid().toString());
+ }
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Log.e(TAG, "System does not support BLE");
+ return;
+ }
+
+ // Only open one Gatt server.
+ if (mGattServer == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Opening a new GATT Server");
+ }
+ mBluetoothManager = (BluetoothManager) mContext.getSystemService(
+ Context.BLUETOOTH_SERVICE);
+ mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
+
+ if (mGattServer == null) {
+ Log.e(TAG, "Gatt Server not created");
+ return;
+ }
+ }
+
+ mGattServer.clearServices();
+ 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(new ParcelUuid(service.getUuid()))
+ .build();
+
+ mAdvertiserStartCount = 0;
+ startAdvertisingInternally(settings, data, advertiseCallback);
+ }
+
+ private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
+ AdvertiseCallback advertiseCallback) {
+ mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+ if (mAdvertiser == null && mAdvertiserStartCount < BLE_RETRY_LIMIT) {
+ mHandler.postDelayed(
+ () -> startAdvertisingInternally(settings, data, advertiseCallback),
+ BLE_RETRY_INTERVAL_MS);
+ mAdvertiserStartCount += 1;
+ } else {
+ mHandler.removeCallbacks(null);
+ mAdvertiser.startAdvertising(settings, data, advertiseCallback);
+ mAdvertiserStartCount = 0;
+ }
+ }
+
+ protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
+ if (mAdvertiser != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "stopAdvertising: ");
+ }
+ mAdvertiser.stopAdvertising(advertiseCallback);
+ }
+ }
+
+ /**
+ * Notifies the characteristic change via {@link BluetoothGattServer}
+ */
+ protected void notifyCharacteristicChanged(BluetoothDevice device,
+ BluetoothGattCharacteristic characteristic, boolean confirm) {
+ if (mGattServer != null) {
+ mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
+ }
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Cleans up the BLE GATT server state.
+ */
+ void cleanup() {
+ // Stops the advertiser and GATT server. This needs to be done to avoid leaks
+ if (mAdvertiser != null) {
+ mAdvertiser.cleanup();
+ }
+
+ if (mGattServer != null) {
+ mGattServer.clearServices();
+ try {
+ for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
+ mGattServer.cancelConnection(d);
+ }
+ } catch (UnsupportedOperationException e) {
+ Log.e(TAG, "Error getting connected devices", e);
+ } finally {
+ stopGattServer();
+ }
+ }
+ }
+
+ /**
+ * Close the GATT Server
+ */
+ void stopGattServer() {
+ if (mGattServer == null) {
+ return;
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "stopGattServer");
+ }
+ mGattServer.close();
+ mGattServer = null;
+ }
+
+ /**
+ * Triggered when a device (GATT client) connected.
+ *
+ * @param device Remote device that connected on BLE.
+ */
+ protected void onRemoteDeviceConnected(BluetoothDevice device) {
+ }
+
+ /**
+ * Triggered when a device (GATT client) disconnected.
+ *
+ * @param device Remote device that disconnected on BLE.
+ */
+ protected void onRemoteDeviceDisconnected(BluetoothDevice device) {
+ }
+
+ /**
+ * Triggered when this BleManager receives a write request from a remote
+ * device. Sub-classes should implement how to handle requests.
+ * <p>
+ *
+ * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int,
+ * BluetoothGattCharacteristic, boolean, boolean, int, byte[])
+ */
+ protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId,
+ BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+ responseNeeded, int offset, byte[] value);
+
+ /**
+ * Triggered when this BleManager receives a read request from a remote device.
+ * <p>
+ *
+ * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int,
+ * BluetoothGattCharacteristic)
+ */
+ protected abstract void onCharacteristicRead(BluetoothDevice device,
+ int requestId, int offset, BluetoothGattCharacteristic characteristic);
+
+ private final BluetoothGattServerCallback mGattServerCallback =
+ new BluetoothGattServerCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothDevice device, int status,
+ int newState) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "BLE Connection State Change: " + newState);
+ }
+ switch (newState) {
+ case BluetoothProfile.STATE_CONNECTED:
+ onRemoteDeviceConnected(device);
+ break;
+ case BluetoothProfile.STATE_DISCONNECTED:
+ onRemoteDeviceDisconnected(device);
+ break;
+ default:
+ Log.w(TAG,
+ "Connection state not connecting or disconnecting; ignoring: "
+ + newState);
+ }
+ }
+
+ @Override
+ public void onServiceAdded(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, 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());
+ onCharacteristicRead(device, requestId, offset, characteristic);
+ }
+
+ @Override
+ public void onCharacteristicWriteRequest(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()
+ + "value: " + Utils.byteArrayToHexString(value));
+ }
+
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+ offset, value);
+ onCharacteristicWrite(device, requestId, characteristic,
+ preparedWrite, responseNeeded, offset, value);
+ }
+ };
+}
diff --git a/service/src/com/android/car/trust/BleService.java b/service/src/com/android/car/trust/BleService.java
deleted file mode 100644
index 3ec1dca..0000000
--- a/service/src/com/android/car/trust/BleService.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2019 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 static android.bluetooth.BluetoothProfile.GATT_SERVER;
-
-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.pm.PackageManager;
-import android.os.Handler;
-import android.os.ParcelUuid;
-import android.util.Log;
-
-/**
- * A generic service to start a BLE
- * TODO(b/123248433) This could move to a separate comms library.
- */
-public abstract class BleService extends Service {
- private static final String TAG = BleService.class.getSimpleName();
-
- private static final int BLE_RETRY_LIMIT = 5;
- private static final int BLE_RETRY_INTERVAL_MS = 1000;
-
- private final Handler mHandler = new Handler();
-
- private BluetoothManager mBluetoothManager;
- private BluetoothLeAdvertiser mAdvertiser;
- private BluetoothGattServer mGattServer;
- private int mAdvertiserStartCount;
-
- /**
- * Starts the GATT server with the given {@link BluetoothGattService} and begins
- * advertising.
- *
- * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
- * Therefore, several retries will be made to ensure advertising is started.
- *
- * @param service {@link BluetoothGattService} that will be discovered by clients
- */
- protected void startAdvertising(BluetoothGattService service,
- AdvertiseCallback advertiseCallback) {
- if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
- Log.e(TAG, "System does not support BLE");
- return;
- }
-
- // Only open one Gatt server.
- if (mGattServer == null) {
- mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
- mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
-
- if (mGattServer == null) {
- Log.e(TAG, "Gatt Server not created");
- return;
- }
- }
-
- mGattServer.clearServices();
- 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(new ParcelUuid(service.getUuid()))
- .build();
-
- mAdvertiserStartCount = 0;
- startAdvertisingInternally(settings, data, advertiseCallback);
- }
-
- private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
- AdvertiseCallback advertiseCallback) {
- mAdvertiserStartCount += 1;
- mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
- if (mAdvertiser == null && mAdvertiserStartCount < BLE_RETRY_LIMIT) {
- mHandler.postDelayed(
- () -> startAdvertisingInternally(settings, data, advertiseCallback),
- BLE_RETRY_INTERVAL_MS);
- } else {
- mHandler.removeCallbacks(null);
- mAdvertiser.startAdvertising(settings, data, advertiseCallback);
- mAdvertiserStartCount = 0;
- }
- }
-
- protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
- if (mAdvertiser != null) {
- mAdvertiser.stopAdvertising(advertiseCallback);
- }
- }
-
- /**
- * Notifies the characteristic change via {@link BluetoothGattServer}
- */
- protected void notifyCharacteristicChanged(BluetoothDevice device,
- BluetoothGattCharacteristic characteristic, boolean confirm) {
- if (mGattServer != null) {
- mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
- }
- }
-
- @Override
- public void onDestroy() {
- // Stops the advertiser and GATT server. This needs to be done to avoid leaks
- if (mAdvertiser != null) {
- mAdvertiser.cleanup();
- }
-
- if (mGattServer != null) {
- mGattServer.clearServices();
- try {
- for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
- mGattServer.cancelConnection(d);
- }
- } catch (UnsupportedOperationException e) {
- Log.e(TAG, "Error getting connected devices", e);
- } finally {
- mGattServer.close();
- }
- }
- super.onDestroy();
- }
-
- // Delegate to subclass
- protected void onAdvertiseStartSuccess() { }
- protected void onAdvertiseStartFailure(int errorCode) { }
- protected void onAdvertiseDeviceConnected(BluetoothDevice device) { }
- protected void onAdvertiseDeviceDisconnected(BluetoothDevice device) { }
-
- /**
- * Triggered when this BleService receives a write request from a remote
- * device. Sub-classes should implement how to handle requests.
- */
- protected abstract void onCharacteristicWrite(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.
- */
- protected abstract void onCharacteristicRead(BluetoothDevice device,
- int requestId, int offset, BluetoothGattCharacteristic characteristic);
-
- private final BluetoothGattServerCallback mGattServerCallback =
- new BluetoothGattServerCallback() {
- @Override
- public void onConnectionStateChange(BluetoothDevice device,
- final int status, final int newState) {
- switch (newState) {
- case BluetoothProfile.STATE_CONNECTED:
- onAdvertiseDeviceConnected(device);
- break;
- case BluetoothProfile.STATE_DISCONNECTED:
- onAdvertiseDeviceDisconnected(device);
- break;
- default:
- Log.w(TAG, "Connection state not connecting or disconnecting; ignoring: "
- + newState);
- }
- }
-
- @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());
- 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);
- onCharacteristicWrite(device, requestId, characteristic,
- preparedWrite, responseNeeded, offset, value);
- }
- };
-}
diff --git a/service/src/com/android/car/trust/CarBleTrustAgent.java b/service/src/com/android/car/trust/CarBleTrustAgent.java
new file mode 100644
index 0000000..ff360a3
--- /dev/null
+++ b/service/src/com/android/car/trust/CarBleTrustAgent.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 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.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.trust.TrustAgentService;
+import android.util.Log;
+
+import com.android.car.CarLocalServices;
+import com.android.car.Utils;
+import com.android.car.trust.CarTrustAgentEnrollmentService.CarTrustAgentEnrollmentRequestDelegate;
+import com.android.car.trust.CarTrustAgentUnlockService.CarTrustAgentUnlockDelegate;
+
+/**
+ * A BluetoothLE (BLE) based {@link TrustAgentService} that uses the escrow token unlock APIs.
+ * <p>
+ * This trust agent runs during direct boot and interacts with {@link CarTrustedDeviceService}
+ * to listen for remote devices to trigger an unlock.
+ * <p>
+ * The system {@link com.android.server.trust.TrustManagerService} binds to this agent and uses
+ * the data it receives from this agent to authorize a user in lieu of the PIN/Pattern/Password
+ * credentials.
+ */
+public class CarBleTrustAgent extends TrustAgentService {
+ private static final String TAG = CarBleTrustAgent.class.getSimpleName();
+ private boolean mIsDeviceLocked;
+ private CarTrustedDeviceService mCarTrustedDeviceService;
+ private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+ private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
+
+ @Override
+ public void onCreate() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate()");
+ }
+ super.onCreate();
+ // Registering for more granular BLE specific state changes as against Bluetooth state
+ // changes, helps with reducing latency in getting notified.
+ IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+ registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
+
+ // TODO(b/129144535) handle scenarios where CarService crashed. Maybe retrieve this
+ // every time we need instead of caching.
+ mCarTrustedDeviceService = CarLocalServices.getService(CarTrustedDeviceService.class);
+ if (mCarTrustedDeviceService == null) {
+ Log.e(TAG, "Cannot retrieve the Trusted device Service");
+ return;
+ }
+ mCarTrustAgentEnrollmentService =
+ mCarTrustedDeviceService.getCarTrustAgentEnrollmentService();
+ setEnrollmentRequestDelegate();
+ mCarTrustAgentUnlockService = mCarTrustedDeviceService.getCarTrustAgentUnlockService();
+ setUnlockRequestDelegate();
+ setManagingTrust(true);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Car Trust agent shutting down");
+ }
+ super.onDestroy();
+ mCarTrustAgentEnrollmentService = null;
+ if (mBluetoothBroadcastReceiver != null) {
+ unregisterReceiver(mBluetoothBroadcastReceiver);
+ }
+ }
+
+ // Overriding TrustAgentService methods
+ @Override
+ public void onDeviceLocked() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onDeviceLocked Current user: " + ActivityManager.getCurrentUser());
+ }
+ super.onDeviceLocked();
+ mIsDeviceLocked = true;
+ if (BluetoothAdapter.getDefaultAdapter().getState() == BluetoothAdapter.STATE_OFF) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Not starting Unlock Advertising yet, since Bluetooth Adapter is off");
+ }
+ return;
+ }
+ if (mCarTrustAgentUnlockService != null) {
+ mCarTrustAgentUnlockService.startUnlockAdvertising();
+ }
+ }
+
+ @Override
+ public void onDeviceUnlocked() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onDeviceUnlocked Current user: " + ActivityManager.getCurrentUser());
+ }
+ super.onDeviceUnlocked();
+ mIsDeviceLocked = false;
+ if (BluetoothAdapter.getDefaultAdapter().getState() == BluetoothAdapter.STATE_OFF) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Not stopping Unlock Advertising, since Bluetooth Adapter is off");
+ }
+ return;
+ }
+ if (mCarTrustAgentUnlockService != null) {
+ mCarTrustAgentUnlockService.stopUnlockAdvertising();
+
+ }
+ // TODO(b/128857992) - should we revoke trust right after to enable keyguard when
+ // switching user
+ }
+
+ @Override
+ public void onEscrowTokenRemoved(long handle, boolean successful) {
+ // TODO(b/128857992) To be implemented
+ }
+
+ @Override
+ public void onEscrowTokenStateReceived(long handle, int tokenState) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onEscrowTokenStateReceived: " + Long.toHexString(handle) + " state: "
+ + tokenState);
+ }
+ if (mCarTrustAgentEnrollmentService == null) {
+ return;
+ }
+ mCarTrustAgentEnrollmentService.onEscrowTokenActiveStateChanged(handle,
+ tokenState == TOKEN_STATE_ACTIVE);
+ }
+
+ @Override
+ public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onEscrowTokenAdded handle: " + Long.toHexString(handle) + "token: "
+ + Utils.byteArrayToHexString(token));
+ }
+ if (mCarTrustAgentEnrollmentService == null) {
+ return;
+ }
+ mCarTrustAgentEnrollmentService.onEscrowTokenAdded(token, handle, user.getIdentifier());
+ }
+
+ private void setEnrollmentRequestDelegate() {
+ if (mCarTrustAgentEnrollmentService == null) {
+ return;
+ }
+ mCarTrustAgentEnrollmentService.setEnrollmentRequestDelegate(mEnrollDelegate);
+ }
+
+ private void setUnlockRequestDelegate() {
+ if (mCarTrustAgentUnlockService == null) {
+ return;
+ }
+ mCarTrustAgentUnlockService.setUnlockRequestDelegate(mUnlockDelegate);
+ }
+
+ private void unlockUserInternally(int uid, byte[] token, long handle) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "About to unlock user: " + uid);
+ UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+ if (um.isUserUnlocked(UserHandle.of(uid))) {
+ Log.d(TAG, "User currently unlocked");
+ } else {
+ Log.d(TAG, "User currently locked");
+ }
+ }
+ unlockUserWithToken(handle, token, UserHandle.of(uid));
+ grantTrust("Granting trust from escrow token",
+ 0, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
+ }
+
+ private final BroadcastReceiver mBluetoothBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction() != null && BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
+ intent.getAction())) {
+ onBluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
+ }
+ }
+ };
+
+ private void onBluetoothStateChanged(int state) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onBluetoothStateChanged: " + state);
+ }
+ if (!mIsDeviceLocked) {
+ return;
+ }
+ switch (state) {
+ case BluetoothAdapter.STATE_BLE_ON:
+ if (mCarTrustAgentUnlockService != null) {
+ mCarTrustAgentUnlockService.startUnlockAdvertising();
+ }
+ break;
+ case BluetoothAdapter.STATE_OFF:
+ Log.e(TAG, "Bluetooth Adapter Off in lock screen");
+ if (mCarTrustedDeviceService != null) {
+ mCarTrustedDeviceService.cleanupBleService();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Implementing Delegates for Enrollment and Unlock. The CarBleTrustAgent acts as the interface
+ // between the Trust Agent framework and the Car Service. The Car service handles communicating
+ // with the peer device part and the framework handles the actual authentication. The
+ // CarBleTrustAgent abstracts these 2 pieces from each other.
+ /**
+ * Implementation of the {@link CarTrustAgentEnrollmentRequestDelegate}
+ */
+ private final CarTrustAgentEnrollmentRequestDelegate mEnrollDelegate =
+ new CarTrustAgentEnrollmentRequestDelegate() {
+ @Override
+ public void revokeTrust() {
+ // TODO(b/128857992) to be implemented
+ }
+
+ @Override
+ public void addEscrowToken(byte[] token, int uid) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "addEscrowToken. uid: " + uid + " token: "
+ + Utils.byteArrayToHexString(
+ token));
+ }
+ CarBleTrustAgent.this.addEscrowToken(token, UserHandle.of(uid));
+ }
+
+ @Override
+ public void removeEscrowToken(long handle, int uid) {
+ // TODO(b/128857992) to be implemented
+ }
+
+ @Override
+ public void isEscrowTokenActive(long handle, int uid) {
+ CarBleTrustAgent.this.isEscrowTokenActive(handle, UserHandle.of(uid));
+ }
+ };
+
+ /**
+ * Implementation of the {@link CarTrustAgentUnlockDelegate}
+ */
+ private final CarTrustAgentUnlockDelegate mUnlockDelegate = new CarTrustAgentUnlockDelegate() {
+ /**
+ * Pass the user and token credentials to authenticate with the LockSettingsService.
+ *
+ * @param user user being authorized
+ * @param token escrow token for the user
+ * @param handle the handle corresponding to the escrow token
+ */
+ @Override
+ public void onUnlockDataReceived(int user, byte[] token, long handle) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onUnlockDataReceived:" + user + " token: " + Long.toHexString(
+ Utils.bytesToLong(token)) + " handle: " + Long.toHexString(handle));
+ }
+ if (ActivityManager.getCurrentUser() != user) {
+ // Current behavior is to only authenticate the user we have booted into.
+ // TODO(b/129029418) Make identification & Auth vs Auth-only a
+ // configurable option
+ Log.e(TAG, "Expected User: " + ActivityManager.getCurrentUser()
+ + " Presented User: " + user);
+ return;
+ } else {
+ unlockUserInternally(user, token, handle);
+ }
+
+ }
+ };
+}
diff --git a/service/src/com/android/car/trust/CarTrustAgentBleManager.java b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
new file mode 100644
index 0000000..0b6cae1
--- /dev/null
+++ b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2019 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.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseSettings;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.car.CarLocalServices;
+import com.android.car.R;
+import com.android.car.Utils;
+
+import java.util.UUID;
+
+/**
+ * A BLE Service that is used for communicating with the trusted peer device. This extends from a
+ * more generic {@link BLEManager} and has more context on the BLE requirements for the Trusted
+ * device feature. It has knowledge on the GATT services and characteristics that are specific to
+ * the Trusted Device feature.
+ */
+class CarTrustAgentBleManager extends BleManager {
+ private static final String TAG = "CarTrustBLEManager";
+ private CarTrustedDeviceService mCarTrustedDeviceService;
+ private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+ private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
+
+ // Enrollment Service and Characteristic UUIDs
+ private UUID mEnrollmentServiceUuid;
+ private UUID mEnrollmentEscrowTokenUuid;
+ private UUID mEnrollmentTokenHandleUuid;
+ private BluetoothGattService mEnrollmentGattService;
+
+ // Unlock Service and Characteristic UUIDs
+ private UUID mUnlockServiceUuid;
+ private UUID mUnlockEscrowTokenUuid;
+ private UUID mUnlockTokenHandleUuid;
+ private BluetoothGattService mUnlockGattService;
+
+ CarTrustAgentBleManager(Context context) {
+ super(context);
+ }
+
+ // Overriding some of the {@link BLEManager} methods to be specific for Trusted Device feature.
+ @Override
+ public void onRemoteDeviceConnected(BluetoothDevice device) {
+ if (getTrustedDeviceService() != null) {
+ getTrustedDeviceService().onRemoteDeviceConnected(device);
+ }
+ }
+
+ @Override
+ public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+ if (getTrustedDeviceService() != null) {
+ getTrustedDeviceService().onRemoteDeviceDisconnected(device);
+ }
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothDevice device, int requestId,
+ BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+ responseNeeded, int offset, byte[] value) {
+ UUID uuid = characteristic.getUuid();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCharacteristicWrite received uuid: " + uuid);
+ }
+ if (uuid.equals(mEnrollmentEscrowTokenUuid)) {
+ if (getEnrollmentService() != null) {
+ getEnrollmentService().onEnrollmentDataReceived(value);
+ }
+ } else if (uuid.equals(mUnlockEscrowTokenUuid)) {
+ if (getUnlockService() != null) {
+ getUnlockService().onUnlockTokenReceived(value);
+ }
+ } else if (uuid.equals(mUnlockTokenHandleUuid)) {
+ if (getUnlockService() != null) {
+ getUnlockService().onUnlockHandleReceived(value);
+ }
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothDevice device,
+ int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
+ // Ignored read requests.
+ }
+
+ @Nullable
+ private CarTrustedDeviceService getTrustedDeviceService() {
+ if (mCarTrustedDeviceService == null) {
+ mCarTrustedDeviceService = CarLocalServices.getService(
+ CarTrustedDeviceService.class);
+ }
+ return mCarTrustedDeviceService;
+ }
+
+ @Nullable
+ private CarTrustAgentEnrollmentService getEnrollmentService() {
+ if (mCarTrustAgentEnrollmentService != null) {
+ return mCarTrustAgentEnrollmentService;
+ }
+
+ if (getTrustedDeviceService() != null) {
+ mCarTrustAgentEnrollmentService =
+ getTrustedDeviceService().getCarTrustAgentEnrollmentService();
+ }
+ return mCarTrustAgentEnrollmentService;
+ }
+
+ @Nullable
+ private CarTrustAgentUnlockService getUnlockService() {
+ if (mCarTrustAgentUnlockService != null) {
+ return mCarTrustAgentUnlockService;
+ }
+
+ if (getTrustedDeviceService() != null) {
+ mCarTrustAgentUnlockService =
+ getTrustedDeviceService().getCarTrustAgentUnlockService();
+ }
+ return mCarTrustAgentUnlockService;
+ }
+
+ /**
+ * Setup the BLE GATT server for Enrollment. The GATT server for Enrollment comprises of
+ * one GATT Service and 2 characteristics - one for the escrow token to be generated and sent
+ * from the phone and the other for the handle generated and sent by the Head unit.
+ */
+ void setupEnrollmentBleServer() {
+ mEnrollmentServiceUuid = UUID.fromString(
+ getContext().getString(R.string.enrollment_service_uuid));
+ mEnrollmentEscrowTokenUuid = UUID.fromString(
+ getContext().getString(R.string.enrollment_token_uuid));
+ mEnrollmentTokenHandleUuid = UUID.fromString(
+ getContext().getString(R.string.enrollment_handle_uuid));
+
+ mEnrollmentGattService = new BluetoothGattService(mEnrollmentServiceUuid,
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+ // Characteristic to describe the escrow token being used for unlock
+ BluetoothGattCharacteristic tokenCharacteristic = new BluetoothGattCharacteristic(
+ mEnrollmentEscrowTokenUuid,
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ // Characteristic to describe the handle being used for this escrow token
+ BluetoothGattCharacteristic handleCharacteristic = new BluetoothGattCharacteristic(
+ mEnrollmentTokenHandleUuid,
+ BluetoothGattCharacteristic.PROPERTY_NOTIFY,
+ BluetoothGattCharacteristic.PERMISSION_READ);
+
+ mEnrollmentGattService.addCharacteristic(tokenCharacteristic);
+ mEnrollmentGattService.addCharacteristic(handleCharacteristic);
+ }
+
+ /**
+ * Setup the BLE GATT server for Unlocking the Head unit. The GATT server for this phase also
+ * comprises of 1 Service and 2 characteristics. However both the token and the handle are
+ * sent ftrom the phone to the head unit.
+ */
+ void setupUnlockBleServer() {
+ mUnlockServiceUuid = UUID.fromString(
+ getContext().getString(R.string.unlock_service_uuid));
+ mUnlockEscrowTokenUuid = UUID.fromString(
+ getContext().getString(R.string.unlock_escrow_token_uuid));
+ mUnlockTokenHandleUuid = UUID.fromString(
+ getContext().getString(R.string.unlock_handle_uuid));
+
+ mUnlockGattService = new BluetoothGattService(mUnlockServiceUuid,
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+ // Characteristic to describe the escrow token being used for unlock
+ BluetoothGattCharacteristic tokenCharacteristic = new BluetoothGattCharacteristic(
+ mUnlockEscrowTokenUuid,
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ // Characteristic to describe the handle being used for this escrow token
+ BluetoothGattCharacteristic handleCharacteristic = new BluetoothGattCharacteristic(
+ mUnlockTokenHandleUuid,
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ mUnlockGattService.addCharacteristic(tokenCharacteristic);
+ mUnlockGattService.addCharacteristic(handleCharacteristic);
+ }
+
+ void startEnrollmentAdvertising() {
+ startAdvertising(mEnrollmentGattService, mEnrollmentAdvertisingCallback);
+ }
+
+ void stopEnrollmentAdvertising() {
+ stopAdvertising(mEnrollmentAdvertisingCallback);
+ }
+
+ void startUnlockAdvertising() {
+ startAdvertising(mUnlockGattService, mUnlockAdvertisingCallback);
+ }
+
+ void stopUnlockAdvertising() {
+ stopAdvertising(mUnlockAdvertisingCallback);
+ }
+
+ void disconnectRemoteDevice(BluetoothDevice device) {
+ // TODO(b/129029421) - is closing the GATT Server the right thing to do here?
+ }
+
+ /**
+ * Sends the handle corresponding to the escrow token that was generated by the phone to the
+ * phone.
+ *
+ * @param device the BLE peer device to send the handle to.
+ * @param handle the handle corresponding to the escrow token
+ */
+ void sendEnrollmentHandle(BluetoothDevice device, long handle) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "sendEnrollmentHandle: " + Long.toHexString(handle) + " "
+ + device.getAddress());
+ }
+ BluetoothGattCharacteristic enrollmentHandle = mEnrollmentGattService.getCharacteristic(
+ mEnrollmentTokenHandleUuid);
+ enrollmentHandle.setValue(Utils.longToBytes(handle));
+ notifyCharacteristicChanged(device, enrollmentHandle, false);
+ }
+
+ private final AdvertiseCallback mEnrollmentAdvertisingCallback = new AdvertiseCallback() {
+ @Override
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ super.onStartSuccess(settingsInEffect);
+ if (getEnrollmentService() != null) {
+ getEnrollmentService().onEnrollmentAdvertiseStartSuccess();
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Successfully started advertising service");
+ }
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
+
+ super.onStartFailure(errorCode);
+ if (getEnrollmentService() != null) {
+ getEnrollmentService().onEnrollmentAdvertiseStartFailure(errorCode);
+ }
+ }
+ };
+
+ private final AdvertiseCallback mUnlockAdvertisingCallback = new AdvertiseCallback() {
+ @Override
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ super.onStartSuccess(settingsInEffect);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock Advertising onStartSuccess");
+ }
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ Log.e(TAG, "Failed to advertise, errorCode: " + errorCode);
+ super.onStartFailure(errorCode);
+ }
+ };
+}
diff --git a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
index cd421e7..0054957 100644
--- a/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
+++ b/service/src/com/android/car/trust/CarTrustAgentEnrollmentService.java
@@ -17,86 +17,161 @@
package com.android.car.trust;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.bluetooth.BluetoothDevice;
import android.car.trust.ICarTrustAgentBleCallback;
import android.car.trust.ICarTrustAgentEnrollment;
import android.car.trust.ICarTrustAgentEnrollmentCallback;
-import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import com.android.car.CarServiceBase;
+import com.android.car.Utils;
+import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
- * A service that enables enrolling a phone as a trusted device for authenticating a user on the
- * IHU. This implements the APIs that an enrollment app can call to conduct an enrollment.
+ * A service that is part of the CarTrustedDeviceService that is responsible for allowing a
+ * phone to enroll as a trusted device. The enrolled phone can then be used for authenticating a
+ * user on the HU. This implements the {@link android.car.trust.CarTrustAgentEnrollmentManager}
+ * APIs that an app like Car Settings can call to conduct an enrollment.
*/
-public class CarTrustAgentEnrollmentService extends ICarTrustAgentEnrollment.Stub implements
- CarServiceBase {
+public class CarTrustAgentEnrollmentService extends ICarTrustAgentEnrollment.Stub {
private static final String TAG = "CarTrustAgentEnroll";
- private final Context mContext;
+ private static final String FAKE_AUTH_STRING = "000000";
+ private final CarTrustedDeviceService mTrustedDeviceService;
// List of clients listening to Enrollment state change events.
private final List<EnrollmentStateClient> mEnrollmentStateClients = new ArrayList<>();
- // List of clients listening to BLE state change events.
+ // List of clients listening to BLE state changes events during enrollment.
private final List<BleStateChangeClient> mBleStateChangeClients = new ArrayList<>();
+ private final CarTrustAgentBleManager mCarTrustAgentBleManager;
+ private CarTrustAgentEnrollmentRequestDelegate mEnrollmentDelegate;
+ private Object mRemoteDeviceLock = new Object();
+ @GuardedBy("mRemoteDeviceLock")
+ private BluetoothDevice mRemoteEnrollmentDevice;
+ @GuardedBy("this")
+ private boolean mEnrollmentHandshakeAccepted;
+ private final Map<Long, Boolean> mTokenActiveState = new HashMap<>();
- public CarTrustAgentEnrollmentService(Context context) {
- mContext = context;
+ public CarTrustAgentEnrollmentService(CarTrustedDeviceService service,
+ CarTrustAgentBleManager bleService) {
+ mTrustedDeviceService = service;
+ mCarTrustAgentBleManager = bleService;
}
- @Override
public synchronized void init() {
+ mCarTrustAgentBleManager.setupEnrollmentBleServer();
}
- @Override
public synchronized void release() {
for (EnrollmentStateClient client : mEnrollmentStateClients) {
client.mListenerBinder.unlinkToDeath(client, 0);
}
+ for (BleStateChangeClient client : mBleStateChangeClients) {
+ client.mListenerBinder.unlinkToDeath(client, 0);
+ }
mEnrollmentStateClients.clear();
+ setEnrollmentHandshakeAccepted(false);
}
+ // Implementing the ICarTrustAgentEnrollment interface
- // Binder methods
- // TODO(b/120911995) The methods don't do anything yet. The implementation will be checked in
- // a follow up CL.
+ /**
+ * Begin BLE advertisement for Enrollment. This should be called from an app that conducts
+ * the enrollment of the trusted device.
+ */
@Override
public void startEnrollmentAdvertising() {
+ // Stop any current broadcasts
+ mTrustedDeviceService.getCarTrustAgentUnlockService().stopUnlockAdvertising();
+ stopEnrollmentAdvertising();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "startEnrollmentAdvertising");
+ }
+ mCarTrustAgentBleManager.startEnrollmentAdvertising();
}
+ /**
+ * Stop BLE advertisement for Enrollment
+ */
@Override
public void stopEnrollmentAdvertising() {
+ mCarTrustAgentBleManager.stopEnrollmentAdvertising();
}
@Override
public void initiateEnrollmentHandshake(BluetoothDevice device) {
+ // TODO(b/129029320) - this is not needed since the IHU plays the server
+ // role and the secure handshake is initiated by the client.
}
+ /**
+ * Called by the client to notify that the user has accepted a pairing code or any out-of-band
+ * confirmation.
+ */
@Override
public void enrollmentHandshakeAccepted() {
+ setEnrollmentHandshakeAccepted(true);
}
+ /**
+ * Terminate the Enrollment process. To be called when an error is encountered during
+ * enrollment. For example - user pressed cancel on pairing code confirmation or user
+ * navigated away from the app before completing enrollment.
+ */
@Override
public void terminateEnrollmentHandshake() {
+ setEnrollmentHandshakeAccepted(false);
+ // Disconnect from BLE
+ mCarTrustAgentBleManager.disconnectRemoteDevice(mRemoteEnrollmentDevice);
}
+ /**
+ * Returns if there is an active token for the given user and handle.
+ *
+ * @param handle handle corresponding to the escrow token
+ * @param uid user id
+ * @return True if the escrow token is active, false if not
+ */
@Override
public boolean isEscrowTokenActive(long handle, int uid) {
+ if (mTokenActiveState.get(handle) != null) {
+ return mTokenActiveState.get(handle);
+ }
return false;
}
+ // TODO(b/128857992)- Implement this
@Override
public void revokeTrust(long handle) {
}
+ /**
+ * Get the Handles corresponding to the token for the current user. The client can use this
+ * to list the trusted devices for the user. This means that the client should maintain a map
+ * of the handles:Bluetooth device names.
+ *
+ * @param uid user id
+ * @return array of handles for the user.
+ */
@Override
public long[] getEnrollmentHandlesForUser(int uid) {
- return new long[0];
+ Set<String> handlesSet = mTrustedDeviceService.getSharedPrefs().getStringSet(
+ String.valueOf(uid),
+ new HashSet<>());
+ long[] handles = new long[handlesSet.size()];
+ int i = 0;
+ for (String handle : handlesSet) {
+ handles[i++] = Long.valueOf(handle);
+ }
+ return handles;
}
/**
@@ -125,6 +200,127 @@
}
}
+ void onEscrowTokenAdded(byte[] token, long handle, int uid) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onEscrowTokenAdded handle:" + handle + " uid:" + uid);
+ }
+ mTrustedDeviceService.getSharedPrefs().edit()
+ .putInt(String.valueOf(handle), uid)
+ .apply();
+ Set<String> handles = mTrustedDeviceService.getSharedPrefs().getStringSet(
+ String.valueOf(uid),
+ new HashSet<>());
+ handles.add(String.valueOf(handle));
+ mTrustedDeviceService.getSharedPrefs().edit().putStringSet(String.valueOf(uid),
+ handles).apply();
+
+ if (mRemoteEnrollmentDevice == null) {
+ Log.e(TAG, "onEscrowTokenAdded() but no remote device connected!");
+ //TODO(b/128857992) remove Escrow token now?
+ return;
+ }
+ mCarTrustAgentBleManager.sendEnrollmentHandle(mRemoteEnrollmentDevice, handle);
+ for (EnrollmentStateClient client : mEnrollmentStateClients) {
+ try {
+ client.mListener.onEscrowTokenAdded(handle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onEscrowTokenAdded dispatch failed", e);
+ }
+ }
+ }
+
+ void onEscrowTokenActiveStateChanged(long handle, boolean tokenState) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onEscrowTokenActiveStateChanged: " + Long.toHexString(handle));
+ }
+ mTokenActiveState.put(handle, tokenState);
+ dispatchEscrowTokenActiveStateChanged(handle, tokenState);
+ }
+
+ void onEnrollmentAdvertiseStartSuccess() {
+ for (BleStateChangeClient client : mBleStateChangeClients) {
+ try {
+ client.mListener.onEnrollmentAdvertisingStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+ }
+ }
+ }
+
+ void onEnrollmentAdvertiseStartFailure(int errorcode) {
+ for (BleStateChangeClient client : mBleStateChangeClients) {
+ try {
+ client.mListener.onEnrollmentAdvertisingFailed(errorcode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+ }
+ }
+ }
+
+ void onRemoteDeviceConnected(BluetoothDevice device) {
+ synchronized (mRemoteDeviceLock) {
+ mRemoteEnrollmentDevice = device;
+ }
+ for (BleStateChangeClient client : mBleStateChangeClients) {
+ try {
+ client.mListener.onBleEnrollmentDeviceConnected(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+ }
+ }
+ //TODO(b/11788064) Fake Authentication to enable clients to go through the enrollment flow.
+ fakeAuthentication();
+ }
+
+ void onRemoteDeviceDisconnected(BluetoothDevice device) {
+ synchronized (mRemoteDeviceLock) {
+ mRemoteEnrollmentDevice = null;
+ }
+ for (BleStateChangeClient client : mBleStateChangeClients) {
+ try {
+ client.mListener.onBleEnrollmentDeviceDisconnected(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+ }
+ }
+ }
+
+ void onEnrollmentDataReceived(byte[] value) {
+ if (mEnrollmentDelegate == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Enrollment Delegate not set");
+ }
+ return;
+ }
+ // The phone is not expected to send any data until the user has accepted the
+ // pairing.
+ if (!mEnrollmentHandshakeAccepted) {
+ Log.e(TAG, "User has not accepted the pairing code yet."
+ + Utils.byteArrayToHexString(value));
+ return;
+ }
+ mEnrollmentDelegate.addEscrowToken(value, ActivityManager.getCurrentUser());
+ }
+
+ // TODO(b/11788064) Fake Authentication until we hook up the crypto lib
+ private void fakeAuthentication() {
+ if (mRemoteEnrollmentDevice == null) {
+ Log.e(TAG, "Remote Device disconnected before Enrollment completed");
+ return;
+ }
+ for (EnrollmentStateClient client : mEnrollmentStateClients) {
+ try {
+ client.mListener.onAuthStringAvailable(mRemoteEnrollmentDevice, FAKE_AUTH_STRING);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onAdvertiseSuccess dispatch failed", e);
+ }
+ }
+ }
+
+ private synchronized void setEnrollmentHandshakeAccepted(boolean accepted) {
+ mEnrollmentHandshakeAccepted = accepted;
+ }
+
/**
* Iterates through the list of registered Enrollment State Change clients -
* {@link EnrollmentStateClient} and finds if the given client is already registered.
@@ -235,6 +431,59 @@
}
/**
+ * The interface that an enrollment delegate has to implement to add/remove escrow tokens.
+ */
+ interface CarTrustAgentEnrollmentRequestDelegate {
+ /**
+ * Add the given escrow token that was generated by the peer device that is being enrolled.
+ *
+ * @param token the 64 bit token
+ * @param uid user id
+ */
+ void addEscrowToken(byte[] token, int uid);
+
+ /**
+ * Remove the given escrow token. This should be called when removing a trusted device.
+ *
+ * @param handle the 64 bit token
+ * @param uid user id
+ */
+ void removeEscrowToken(long handle, int uid);
+
+ /**
+ * Query if the token is active. The result is asynchronously delivered through a callback
+ * {@link CarTrustAgentEnrollmentService#onEscrowTokenActiveStateChanged(long, boolean)}
+ *
+ * @param handle the 64 bit token
+ * @param uid user id
+ */
+ void isEscrowTokenActive(long handle, int uid);
+
+ /**
+ * Calls the Trust framwework's revoke trust to revoke the trust that was granted for the
+ * current user.
+ */
+ void revokeTrust();
+ }
+
+ void setEnrollmentRequestDelegate(CarTrustAgentEnrollmentRequestDelegate delegate) {
+ mEnrollmentDelegate = delegate;
+ }
+
+ void dump(PrintWriter writer) {
+ }
+
+ private void dispatchEscrowTokenActiveStateChanged(long handle, boolean active) {
+ for (EnrollmentStateClient client : mEnrollmentStateClients) {
+ try {
+ client.mListener.onEscrowTokenActiveStateChanged(handle, active);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot notify client of a Token Activation change: " + active);
+ }
+ }
+ }
+
+ /**
* Class that holds onto client related information - listener interface, process that hosts the
* binder object etc.
* <p>
@@ -301,9 +550,12 @@
return mListenerBinder == binder;
}
- }
-
- @Override
- public void dump(PrintWriter writer) {
+ public void onEnrollmentAdvertisementStarted() {
+ try {
+ mListener.onEnrollmentAdvertisingStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "onEnrollmentAdvertisementStarted() failed", e);
+ }
+ }
}
}
diff --git a/service/src/com/android/car/trust/CarTrustAgentUnlockService.java b/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
new file mode 100644
index 0000000..0f5e031
--- /dev/null
+++ b/service/src/com/android/car/trust/CarTrustAgentUnlockService.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 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.util.Log;
+
+import com.android.car.Utils;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * A service that interacts with the Trust Agent {@link CarBleTrustAgent} and a comms (BLE) service
+ * {@link CarTrustAgentBleManager} to receive the necessary credentials to authenticate
+ * an Android user.
+ */
+public class CarTrustAgentUnlockService {
+ private static final String TAG = "CarTrustAgentUnlock";
+ private final CarTrustedDeviceService mTrustedDeviceService;
+ private final CarTrustAgentBleManager mCarTrustAgentBleManager;
+ private CarTrustAgentUnlockDelegate mUnlockDelegate;
+ // Locks
+ private final Object mTokenLock = new Object();
+ private final Object mHandleLock = new Object();
+ private final Object mDeviceLock = new Object();
+
+ @GuardedBy("mTokenLock")
+ private byte[] mUnlockToken;
+ @GuardedBy("mHandleLock")
+ private byte[] mUnlockHandle;
+ @GuardedBy("mDeviceLock")
+ private BluetoothDevice mRemoteUnlockDevice;
+
+ CarTrustAgentUnlockService(CarTrustedDeviceService service,
+ CarTrustAgentBleManager bleService) {
+ mTrustedDeviceService = service;
+ mCarTrustAgentBleManager = bleService;
+ }
+
+ /**
+ * The interface that an unlock delegate has to implement to get the auth credentials from
+ * the unlock service.
+ */
+ interface CarTrustAgentUnlockDelegate {
+ /**
+ * Called when the Unlock service has the auth credentials to pass.
+ *
+ * @param user user being authorized
+ * @param token escrow token for the user
+ * @param handle the handle corresponding to the escrow token
+ */
+ void onUnlockDataReceived(int user, byte[] token, long handle);
+ }
+
+ /**
+ * Set a delegate that implements {@link CarTrustAgentUnlockDelegate}. The delegate will be
+ * handed the auth related data (token and handle) when it is received from the remote
+ * trusted device. The delegate is expected to use that to authorize the user.
+ */
+ void setUnlockRequestDelegate(CarTrustAgentUnlockDelegate delegate) {
+ mUnlockDelegate = delegate;
+ }
+
+ /**
+ * Start Unlock Advertising
+ */
+ void startUnlockAdvertising() {
+ mTrustedDeviceService.getCarTrustAgentEnrollmentService().stopEnrollmentAdvertising();
+ stopUnlockAdvertising();
+ mCarTrustAgentBleManager.startUnlockAdvertising();
+ }
+
+ /**
+ * Stop unlock advertising
+ */
+ void stopUnlockAdvertising() {
+ mCarTrustAgentBleManager.stopUnlockAdvertising();
+ // Also disconnect from the peer.
+ if (mRemoteUnlockDevice != null) {
+ mCarTrustAgentBleManager.disconnectRemoteDevice(mRemoteUnlockDevice);
+ }
+ }
+
+ void init() {
+ mCarTrustAgentBleManager.setupUnlockBleServer();
+ }
+
+ void release() {
+ synchronized (mDeviceLock) {
+ mRemoteUnlockDevice = null;
+ }
+ }
+
+ void onRemoteDeviceConnected(BluetoothDevice device) {
+ synchronized (mDeviceLock) {
+ if (mRemoteUnlockDevice != null) {
+ // TBD, return when this is encountered?
+ Log.e(TAG, "Unexpected: Cannot connect to another device when already connected");
+ }
+ mRemoteUnlockDevice = device;
+ }
+ }
+
+ void onRemoteDeviceDisconnected(BluetoothDevice device) {
+ // sanity checking
+ if (!device.equals(mRemoteUnlockDevice) && device.getAddress() != null) {
+ Log.e(TAG, "Disconnected from an unknown device:" + device.getAddress());
+ }
+ synchronized (mDeviceLock) {
+ mRemoteUnlockDevice = null;
+ }
+ }
+
+ void onUnlockTokenReceived(byte[] value) {
+ synchronized (mTokenLock) {
+ mUnlockToken = value;
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock Token: " + mUnlockToken);
+ }
+ if (mUnlockToken == null || mUnlockHandle == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock Handle not available yet");
+ }
+ return;
+ }
+ if (mUnlockDelegate == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "No Unlock delegate");
+ }
+ return;
+ }
+ mUnlockDelegate.onUnlockDataReceived(
+ mTrustedDeviceService.getUserHandleByTokenHandle(Utils.bytesToLong(mUnlockHandle)),
+ mUnlockToken,
+ Utils.bytesToLong(mUnlockHandle));
+
+ synchronized (mTokenLock) {
+ mUnlockToken = null;
+ }
+ synchronized (mHandleLock) {
+ mUnlockHandle = null;
+ }
+ }
+
+ void onUnlockHandleReceived(byte[] value) {
+ synchronized (mHandleLock) {
+ mUnlockHandle = value;
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock Handle: " + mUnlockHandle);
+ }
+ if (mUnlockToken == null || mUnlockHandle == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock Token not available yet");
+ }
+ return;
+ }
+
+ if (mUnlockDelegate == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "No Unlock delegate");
+ }
+ return;
+ }
+ mUnlockDelegate.onUnlockDataReceived(
+ mTrustedDeviceService.getUserHandleByTokenHandle(Utils.bytesToLong(mUnlockHandle)),
+ mUnlockToken,
+ Utils.bytesToLong(mUnlockHandle));
+
+ synchronized (mUnlockToken) {
+ mUnlockToken = null;
+ }
+ synchronized (mHandleLock) {
+ mUnlockHandle = null;
+ }
+ }
+
+ void dump(PrintWriter writer) {
+ }
+}
diff --git a/service/src/com/android/car/trust/CarTrustedDeviceService.java b/service/src/com/android/car/trust/CarTrustedDeviceService.java
new file mode 100644
index 0000000..538f31f
--- /dev/null
+++ b/service/src/com/android/car/trust/CarTrustedDeviceService.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 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.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.android.car.CarServiceBase;
+import com.android.car.R;
+
+import java.io.PrintWriter;
+
+/**
+ * The part of the Car service that enables the Trusted device feature. Trusted Device is a feature
+ * where a remote device is enrolled as a trusted device that can authorize an Android user in lieu
+ * of the user entering a password or PIN.
+ * <p>
+ * It is comprised of the {@link CarTrustAgentEnrollmentService} for handling enrollment and
+ * {@link CarTrustAgentUnlockService} for handling unlock/auth.
+ *
+ */
+public class CarTrustedDeviceService implements CarServiceBase {
+ private static final String TAG = CarTrustedDeviceService.class.getSimpleName();
+ private final Context mContext;
+ private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
+ private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
+ private CarTrustAgentBleManager mCarTrustAgentBleManager;
+ private SharedPreferences mTrustAgentTokenPreferences;
+
+
+ public CarTrustedDeviceService(Context context) {
+ mContext = context;
+ mCarTrustAgentBleManager = new CarTrustAgentBleManager(context);
+ mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(this,
+ mCarTrustAgentBleManager);
+ mCarTrustAgentUnlockService = new CarTrustAgentUnlockService(this,
+ mCarTrustAgentBleManager);
+ }
+
+ @Override
+ public synchronized void init() {
+ mCarTrustAgentEnrollmentService.init();
+ mCarTrustAgentUnlockService.init();
+ }
+
+ @Override
+ public synchronized void release() {
+ mCarTrustAgentBleManager.cleanup();
+ mCarTrustAgentEnrollmentService.release();
+ mCarTrustAgentUnlockService.release();
+ }
+
+ /**
+ * Returns the internal {@link CarTrustAgentEnrollmentService} instance.
+ */
+ public CarTrustAgentEnrollmentService getCarTrustAgentEnrollmentService() {
+ return mCarTrustAgentEnrollmentService;
+ }
+
+ /**
+ * Returns the internal {@link CarTrustAgentUnlockService} instance.
+ */
+ public CarTrustAgentUnlockService getCarTrustAgentUnlockService() {
+ return mCarTrustAgentUnlockService;
+ }
+
+ /**
+ * Returns User Id for the given token handle
+ *
+ * @param handle The handle corresponding to the escrow token
+ * @return User id corresponding to the handle
+ */
+ int getUserHandleByTokenHandle(long handle) {
+ return getSharedPrefs().getInt(String.valueOf(handle), -1);
+ }
+
+ void onRemoteDeviceConnected(BluetoothDevice device) {
+ mCarTrustAgentEnrollmentService.onRemoteDeviceConnected(device);
+ mCarTrustAgentUnlockService.onRemoteDeviceConnected(device);
+ }
+
+ void onRemoteDeviceDisconnected(BluetoothDevice device) {
+ mCarTrustAgentEnrollmentService.onRemoteDeviceDisconnected(device);
+ mCarTrustAgentUnlockService.onRemoteDeviceDisconnected(device);
+ }
+
+ void cleanupBleService() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "cleanupBleService");
+ }
+ mCarTrustAgentBleManager.stopGattServer();
+ mCarTrustAgentBleManager.stopEnrollmentAdvertising();
+ mCarTrustAgentBleManager.stopUnlockAdvertising();
+ }
+
+ SharedPreferences getSharedPrefs() {
+ if (mTrustAgentTokenPreferences != null) {
+ return mTrustAgentTokenPreferences;
+ }
+ mTrustAgentTokenPreferences = mContext.getSharedPreferences(
+ mContext.getString(R.string.token_handle_shared_preferences), Context.MODE_PRIVATE);
+ return mTrustAgentTokenPreferences;
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ }
+}