blob: 8517c98ccd7c8b56b1583e931e5e2a040b680826 [file] [log] [blame]
/*
* 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.IntDef;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.content.Context;
import android.os.ParcelUuid;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.car.BLEStreamProtos.BLEMessageProto.BLEMessage;
import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
import com.android.car.CarLocalServices;
import com.android.car.R;
import com.android.car.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
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";
/**
* The UUID of the Client Characteristic Configuration Descriptor. This descriptor is
* responsible for specifying if a characteristic can be subscribed to for notifications.
*
* @see <a href="https://www.bluetooth.com/specifications/gatt/descriptors/">
* GATT Descriptors</a>
*/
private static final UUID CLIENT_CHARACTERISTIC_CONFIG =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
/** @hide */
@IntDef(prefix = {"TRUSTED_DEVICE_OPERATION_"}, value = {
TRUSTED_DEVICE_OPERATION_NONE,
TRUSTED_DEVICE_OPERATION_ENROLLMENT,
TRUSTED_DEVICE_OPERATION_UNLOCK
})
@Retention(RetentionPolicy.SOURCE)
public @interface TrustedDeviceOperation {
}
private static final int TRUSTED_DEVICE_OPERATION_NONE = 0;
private static final int TRUSTED_DEVICE_OPERATION_ENROLLMENT = 1;
private static final int TRUSTED_DEVICE_OPERATION_UNLOCK = 2;
@TrustedDeviceOperation
private int mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_NONE;
private CarTrustedDeviceService mCarTrustedDeviceService;
private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
private String mOriginalBluetoothName;
private byte[] mUniqueId;
private String mRandomName;
private int mMtuSize = 20;
// Enrollment Service and Characteristic UUIDs
private UUID mEnrollmentServiceUuid;
private UUID mEnrollmentClientWriteUuid;
private UUID mEnrollmentServerWriteUuid;
private BluetoothGattService mEnrollmentGattService;
// Unlock Service and Characteristic UUIDs
private UUID mUnlockServiceUuid;
private UUID mUnlockClientWriteUuid;
private UUID mUnlockServerWriteUuid;
private BluetoothGattService mUnlockGattService;
private BLEMessagePayloadStream mBleMessagePayloadStream = new BLEMessagePayloadStream();
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) {
return;
}
// Retrieving device name only happens in enrollment, the retrieved device name will be
// stored in sharedPreference for further use.
if (mCurrentTrustedDeviceOperation == TRUSTED_DEVICE_OPERATION_ENROLLMENT
&& device.getName() == null) {
retrieveDeviceName(device);
}
getTrustedDeviceService().onRemoteDeviceConnected(device);
}
@Override
public void onRemoteDeviceDisconnected(BluetoothDevice device) {
if (getTrustedDeviceService() != null) {
getTrustedDeviceService().onRemoteDeviceDisconnected(device);
}
}
@Override
protected void onDeviceNameRetrieved(@Nullable String deviceName) {
if (getTrustedDeviceService() != null) {
getTrustedDeviceService().onDeviceNameRetrieved(deviceName);
}
}
@Override
protected void onMtuSizeChanged(int size) {
mMtuSize = size;
}
@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);
}
// This write operation is not thread safe individually, but is guarded by the callback
// here.
mBleMessagePayloadStream.write(value);
if (!mBleMessagePayloadStream.isComplete()) {
return;
}
if (uuid.equals(mEnrollmentClientWriteUuid)) {
if (getEnrollmentService() != null) {
getEnrollmentService().onEnrollmentDataReceived(
mBleMessagePayloadStream.toByteArray());
}
} else if (uuid.equals(mUnlockClientWriteUuid)) {
if (getUnlockService() != null) {
getUnlockService().onUnlockDataReceived(mBleMessagePayloadStream.toByteArray());
}
}
mBleMessagePayloadStream.reset();
}
@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;
}
@Nullable
private byte[] getUniqueId() {
if (mUniqueId != null) {
return mUniqueId;
}
if (getTrustedDeviceService() != null && getTrustedDeviceService().getUniqueId() != null) {
mUniqueId = Utils.uuidToBytes(getTrustedDeviceService().getUniqueId());
}
return mUniqueId;
}
@Nullable
private String getRandomName() {
if (mRandomName != null) {
return mRandomName;
}
if (getTrustedDeviceService() != null) {
mRandomName = getTrustedDeviceService().getRandomName();
}
return mRandomName;
}
/**
* Setup the BLE GATT server for Enrollment. The GATT server for Enrollment comprises of one
* GATT Service and 2 characteristics - one for the phone to write to and one for the head unit
* to write to.
*/
void setupEnrollmentBleServer() {
mEnrollmentServiceUuid = UUID.fromString(
getContext().getString(R.string.enrollment_service_uuid));
mEnrollmentClientWriteUuid = UUID.fromString(
getContext().getString(R.string.enrollment_client_write_uuid));
mEnrollmentServerWriteUuid = UUID.fromString(
getContext().getString(R.string.enrollment_server_write_uuid));
mEnrollmentGattService = new BluetoothGattService(mEnrollmentServiceUuid,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
// Characteristic the connected bluetooth device will write to.
BluetoothGattCharacteristic clientCharacteristic =
new BluetoothGattCharacteristic(mEnrollmentClientWriteUuid,
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
// Characteristic that this manager will write to.
BluetoothGattCharacteristic serverCharacteristic =
new BluetoothGattCharacteristic(mEnrollmentServerWriteUuid,
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ);
addDescriptorToCharacteristic(serverCharacteristic);
mEnrollmentGattService.addCharacteristic(clientCharacteristic);
mEnrollmentGattService.addCharacteristic(serverCharacteristic);
}
/**
* 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
* from the phone to the head unit.
*/
void setupUnlockBleServer() {
mUnlockServiceUuid = UUID.fromString(getContext().getString(R.string.unlock_service_uuid));
mUnlockClientWriteUuid = UUID
.fromString(getContext().getString(R.string.unlock_client_write_uuid));
mUnlockServerWriteUuid = UUID
.fromString(getContext().getString(R.string.unlock_server_write_uuid));
mUnlockGattService = new BluetoothGattService(mUnlockServiceUuid,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
// Characteristic the connected bluetooth device will write to.
BluetoothGattCharacteristic clientCharacteristic = new BluetoothGattCharacteristic(
mUnlockClientWriteUuid,
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
// Characteristic that this manager will write to.
BluetoothGattCharacteristic serverCharacteristic = new BluetoothGattCharacteristic(
mUnlockServerWriteUuid,
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ);
addDescriptorToCharacteristic(serverCharacteristic);
mUnlockGattService.addCharacteristic(clientCharacteristic);
mUnlockGattService.addCharacteristic(serverCharacteristic);
}
private void addDescriptorToCharacteristic(BluetoothGattCharacteristic characteristic) {
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(
CLIENT_CHARACTERISTIC_CONFIG,
BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
characteristic.addDescriptor(descriptor);
}
void startEnrollmentAdvertising() {
mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_ENROLLMENT;
// Replace name to ensure it is small enough to be advertised
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
String name = getRandomName();
if (mOriginalBluetoothName == null) {
mOriginalBluetoothName = adapter.getName();
}
if (name != null) {
adapter.setName(name);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Changing bluetooth adapter name from "
+ mOriginalBluetoothName + " to " + name);
}
startAdvertising(mEnrollmentGattService,
new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(new ParcelUuid(mEnrollmentServiceUuid))
.build(),
mEnrollmentAdvertisingCallback);
}
void stopEnrollmentAdvertising() {
if (mOriginalBluetoothName != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Changing bluetooth adapter name back to "
+ mOriginalBluetoothName);
}
BluetoothAdapter.getDefaultAdapter().setName(mOriginalBluetoothName);
}
stopAdvertising(mEnrollmentAdvertisingCallback);
}
void startUnlockAdvertising() {
mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_UNLOCK;
startAdvertising(mUnlockGattService,
new AdvertiseData.Builder()
.setIncludeDeviceName(false)
.addServiceData(new ParcelUuid(mUnlockServiceUuid), getUniqueId())
.addServiceUuid(new ParcelUuid(mUnlockServiceUuid))
.build(),
mUnlockAdvertisingCallback);
}
void stopUnlockAdvertising() {
mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_NONE;
stopAdvertising(mUnlockAdvertisingCallback);
}
void disconnectRemoteDevice() {
mBleMessagePayloadStream.reset();
stopGattServer();
}
void sendUnlockMessage(BluetoothDevice device, byte[] message, OperationType operation,
boolean isPayloadEncrypted) {
BluetoothGattCharacteristic writeCharacteristic = mUnlockGattService
.getCharacteristic(mUnlockServerWriteUuid);
sendMessage(device, writeCharacteristic, message, operation, isPayloadEncrypted);
}
void sendEnrollmentMessage(BluetoothDevice device, byte[] message, OperationType operation,
boolean isPayloadEncrypted) {
BluetoothGattCharacteristic writeCharacteristic = mEnrollmentGattService
.getCharacteristic(mEnrollmentServerWriteUuid);
sendMessage(device, writeCharacteristic, message, operation, isPayloadEncrypted);
}
/**
* Sends the given message to the specified device and characteristic.
*
* @param device The device to send the message to.
* @param characteristic The characteristic to write to.
* @param message A message to send.
* @param operation The type of operation this message represents.
* @param isPayloadEncrypted {@code true} if the message is encrypted.
*/
private void sendMessage(BluetoothDevice device, BluetoothGattCharacteristic characteristic,
byte[] message, OperationType operation, boolean isPayloadEncrypted) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "sendMessage to: " + device.getAddress() + "; and characteristic UUID: "
+ characteristic.getUuid());
}
List<BLEMessage> bleMessages = BLEMessageV1Factory.makeBLEMessages(message, operation,
mMtuSize, isPayloadEncrypted);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "sending " + bleMessages.size() + " messages to device");
}
for (BLEMessage bleMessage : bleMessages) {
// TODO(b/131719066) get acknowledgement from the phone then continue to send packets
characteristic.setValue(bleMessage.toByteArray());
notifyCharacteristicChanged(device, characteristic, 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();
}
}
};
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);
if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED) {
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Start unlock advertising fail, retry to advertising..");
}
setupUnlockBleServer();
startUnlockAdvertising();
}
};
}