| /* |
| * 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.Handler; |
| import android.os.Looper; |
| 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.BLEStreamProtos.VersionExchangeProto.BLEVersionExchange; |
| import com.android.car.CarLocalServices; |
| import com.android.car.R; |
| import com.android.car.Utils; |
| import com.android.car.protobuf.InvalidProtocolBufferException; |
| |
| import java.io.IOException; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Queue; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * 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"); |
| |
| /** |
| * Reserved bytes for an ATT write request payload. |
| * |
| * <p>The attribute protocol uses 3 bytes to encode the command type and attribute ID. These |
| * bytes need to be subtracted from the reported MTU size and the resulting value will |
| * represent the total amount of bytes that can be sent in a write. |
| */ |
| private static final int ATT_PAYLOAD_RESERVED_BYTES = 3; |
| |
| /** @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; |
| private static final long BLE_MESSAGE_RETRY_DELAY_MS = TimeUnit.SECONDS.toMillis(2); |
| private static final int BLE_MESSAGE_RETRY_LIMIT = 20; |
| |
| @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 mEnrollmentDeviceName; |
| |
| /** |
| * The maximum amount of bytes that can be written over BLE. |
| * |
| * <p>This initial value is 20 because BLE has a default write of 23 bytes. However, 3 bytes |
| * are subtracted due to bytes being reserved for the command type and attribute ID. |
| * |
| * @see #ATT_PAYLOAD_RESERVED_BYTES |
| */ |
| private int mMaxWriteSize = 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 Queue<BLEMessage> mMessageQueue = new LinkedList<>(); |
| private BLEMessagePayloadStream mBleMessagePayloadStream = new BLEMessagePayloadStream(); |
| |
| // This is a boolean because there's only one supported version. |
| private boolean mIsVersionExchanged; |
| private int mBleMessageRetryStartCount; |
| private Handler mHandler = new Handler(Looper.getMainLooper()); |
| private Runnable mSendRepeatedBleMessage; |
| |
| 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); |
| } |
| |
| mMessageQueue.clear(); |
| mIsVersionExchanged = false; |
| getTrustedDeviceService().onRemoteDeviceConnected(device); |
| if (mSendRepeatedBleMessage != null) { |
| mHandler.removeCallbacks(mSendRepeatedBleMessage); |
| mSendRepeatedBleMessage = null; |
| } |
| } |
| |
| @Override |
| public void onRemoteDeviceDisconnected(BluetoothDevice device) { |
| if (getTrustedDeviceService() != null) { |
| getTrustedDeviceService().onRemoteDeviceDisconnected(device); |
| } |
| |
| mMessageQueue.clear(); |
| mIsVersionExchanged = false; |
| mBleMessagePayloadStream.reset(); |
| |
| if (mSendRepeatedBleMessage != null) { |
| mHandler.removeCallbacks(mSendRepeatedBleMessage); |
| } |
| mSendRepeatedBleMessage = null; |
| } |
| |
| @Override |
| protected void onDeviceNameRetrieved(@Nullable String deviceName) { |
| if (getTrustedDeviceService() != null) { |
| getTrustedDeviceService().onDeviceNameRetrieved(deviceName); |
| } |
| } |
| |
| @Override |
| protected void onMtuSizeChanged(int size) { |
| mMaxWriteSize = size - ATT_PAYLOAD_RESERVED_BYTES; |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "MTU size changed to: " + size |
| + "; setting max payload size to: " + mMaxWriteSize); |
| } |
| } |
| |
| @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 (!mIsVersionExchanged) { |
| resolveBLEVersion(device, value, uuid); |
| return; |
| } |
| |
| BLEMessage message; |
| try { |
| message = BLEMessage.parseFrom(value); |
| } catch (InvalidProtocolBufferException e) { |
| Log.e(TAG, "Can not parse BLE message", e); |
| return; |
| } |
| |
| if (message.getOperation() == OperationType.ACK) { |
| handleClientAckMessage(device, uuid); |
| return; |
| } |
| |
| // This write operation is not thread safe individually, but is guarded by the callback |
| // here. |
| try { |
| mBleMessagePayloadStream.write(message); |
| } catch (IOException e) { |
| Log.e(TAG, "Can write the BLE message's payload", e); |
| return; |
| } |
| |
| if (!mBleMessagePayloadStream.isComplete()) { |
| // If it's not complete, make sure the client knows that this message was received. |
| sendAcknowledgmentMessage(device, uuid); |
| 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 getEnrollmentDeviceName() { |
| if (mEnrollmentDeviceName != null) { |
| return mEnrollmentDeviceName; |
| } |
| |
| if (getTrustedDeviceService() != null) { |
| mEnrollmentDeviceName = getTrustedDeviceService().getEnrollmentDeviceName(); |
| } |
| return mEnrollmentDeviceName; |
| } |
| |
| private void resolveBLEVersion(BluetoothDevice device, byte[] value, |
| UUID clientCharacteristicUUID) { |
| BluetoothGattCharacteristic characteristic = |
| getCharacteristicForWrite(clientCharacteristicUUID); |
| |
| if (characteristic == null) { |
| Log.e(TAG, "Invalid UUID (" + clientCharacteristicUUID |
| + ") during version exchange; disconnecting from remote device."); |
| disconnectRemoteDevice(); |
| return; |
| } |
| |
| BLEVersionExchange deviceVersion; |
| try { |
| deviceVersion = BLEVersionExchange.parseFrom(value); |
| } catch (InvalidProtocolBufferException e) { |
| disconnectRemoteDevice(); |
| Log.e(TAG, "Could not parse version exchange message", e); |
| return; |
| } |
| |
| if (!BLEVersionExchangeResolver.hasSupportedVersion(deviceVersion)) { |
| Log.e(TAG, "No supported version found during version exchange."); |
| disconnectRemoteDevice(); |
| return; |
| } |
| |
| BLEVersionExchange headunitVersion = BLEVersionExchangeResolver.makeVersionExchange(); |
| setValueOnCharacteristicAndNotify(device, headunitVersion.toByteArray(), characteristic); |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Sent supported version to the phone."); |
| } |
| |
| mIsVersionExchanged = true; |
| } |
| |
| /** |
| * 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.PROPERTY_WRITE_NO_RESPONSE, |
| 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.PROPERTY_WRITE_NO_RESPONSE, |
| 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 |
| String name = getEnrollmentDeviceName(); |
| if (name != null) { |
| BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| if (mOriginalBluetoothName == null) { |
| mOriginalBluetoothName = adapter.getName(); |
| } |
| 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() { |
| 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); |
| } |
| |
| /** |
| * Handles an ACK from the client. |
| * |
| * <p>An ACK means that the client has successfully received a partial BLEMessage, meaning the |
| * next part of the message can be sent. |
| * |
| * @param device The client device. |
| * @param clientCharacteristicUUID The UUID of the characteristic on the device that the ACK |
| * was written to. |
| */ |
| private void handleClientAckMessage(BluetoothDevice device, UUID clientCharacteristicUUID) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Received ACK from client. Attempting to write next message in queue. " |
| + "UUID: " + clientCharacteristicUUID); |
| } |
| |
| BluetoothGattCharacteristic writeCharacteristic = |
| getCharacteristicForWrite(clientCharacteristicUUID); |
| |
| if (writeCharacteristic == null) { |
| Log.e(TAG, "No corresponding write characteristic found for writing next message in" |
| + " queue. UUID: " + clientCharacteristicUUID); |
| return; |
| } |
| if (mSendRepeatedBleMessage != null) { |
| mHandler.removeCallbacks(mSendRepeatedBleMessage); |
| mSendRepeatedBleMessage = null; |
| } |
| // Previous message has been sent successfully so we can start the next message. |
| mMessageQueue.remove(); |
| writeNextMessageInQueue(device, writeCharacteristic); |
| } |
| |
| /** |
| * Sends the given message to the specified device and characteristic. |
| * The message will be splited into multiple messages wrapped in BLEMessage proto. |
| * |
| * @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, |
| mMaxWriteSize, isPayloadEncrypted); |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "sending " + bleMessages.size() + " messages to device"); |
| } |
| |
| mMessageQueue.addAll(bleMessages); |
| writeNextMessageInQueue(device, characteristic); |
| } |
| |
| /** |
| * Writes the next message in {@link #mMessageQueue} to the given characteristic. |
| * |
| * <p>If the message queue is empty, then this method will do nothing. |
| */ |
| private void writeNextMessageInQueue(BluetoothDevice device, |
| BluetoothGattCharacteristic characteristic) { |
| if (mMessageQueue.isEmpty()) { |
| Log.e(TAG, "Call to write next message in queue, but the message queue is empty"); |
| return; |
| } |
| // When there is only one message, no ACKs are sent, so we no need to retry based on ACKs. |
| if (mMessageQueue.size() == 1) { |
| setValueOnCharacteristicAndNotify(device, mMessageQueue.remove().toByteArray(), |
| characteristic); |
| return; |
| } |
| mBleMessageRetryStartCount = 0; |
| mSendRepeatedBleMessage = new Runnable() { |
| @Override |
| public void run() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "BLE message sending... " + "retry count: " |
| + mBleMessageRetryStartCount); |
| } |
| if (mBleMessageRetryStartCount < BLE_MESSAGE_RETRY_LIMIT) { |
| setValueOnCharacteristicAndNotify(device, mMessageQueue.peek().toByteArray(), |
| characteristic); |
| mBleMessageRetryStartCount++; |
| mHandler.postDelayed(this, BLE_MESSAGE_RETRY_DELAY_MS); |
| } else { |
| Log.e(TAG, "Error during BLE message sending - exceeded retry limit."); |
| mHandler.removeCallbacks(this); |
| mCarTrustAgentEnrollmentService.terminateEnrollmentHandshake(); |
| mSendRepeatedBleMessage = null; |
| } |
| } |
| }; |
| mHandler.post(mSendRepeatedBleMessage); |
| } |
| |
| private void sendAcknowledgmentMessage(BluetoothDevice device, UUID clientCharacteristicUUID) { |
| BluetoothGattCharacteristic writeCharacteristic = |
| getCharacteristicForWrite(clientCharacteristicUUID); |
| |
| if (writeCharacteristic == null) { |
| Log.e(TAG, "No corresponding write characteristic found for sending ACK. UUID: " |
| + clientCharacteristicUUID); |
| return; |
| } |
| |
| setValueOnCharacteristicAndNotify(device, |
| BLEMessageV1Factory.makeAcknowledgementMessage().toByteArray(), |
| writeCharacteristic); |
| } |
| |
| /** |
| * Sets the given message on the specified characteristic. |
| * |
| * <p>Upon successfully setting of the value, any listeners on the characteristic will be |
| * notified that its value has changed. |
| * |
| * @param device The device has own the given characteristic. |
| * @param message The message to set as the characteristic's value. |
| * @param characteristic The characteristic to set the value on. |
| */ |
| private void setValueOnCharacteristicAndNotify(BluetoothDevice device, byte[] message, |
| BluetoothGattCharacteristic characteristic) { |
| characteristic.setValue(message); |
| notifyCharacteristicChanged(device, characteristic, false); |
| } |
| |
| /** |
| * Returns the characteristic that can be written to based on the given UUID. |
| * |
| * <p>The UUID will be one that corresponds to either enrollment or unlock. This method will |
| * return the write characteristic for enrollment or unlock respectively. |
| * |
| * @return The write characteristic or {@code null} if the UUID is invalid. |
| */ |
| @Nullable |
| private BluetoothGattCharacteristic getCharacteristicForWrite(UUID uuid) { |
| if (uuid.equals(mEnrollmentClientWriteUuid)) { |
| return mEnrollmentGattService.getCharacteristic(mEnrollmentServerWriteUuid); |
| } |
| |
| if (uuid.equals(mUnlockClientWriteUuid)) { |
| return mUnlockGattService.getCharacteristic(mUnlockServerWriteUuid); |
| } |
| |
| return null; |
| } |
| |
| 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(); |
| } |
| }; |
| } |