| /* |
| * Copyright (C) 2021 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.bluetooth; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothManager; |
| import android.bluetooth.le.AdvertiseData; |
| import android.bluetooth.le.AdvertisingSet; |
| import android.bluetooth.le.AdvertisingSetCallback; |
| import android.bluetooth.le.AdvertisingSetParameters; |
| import android.bluetooth.le.BluetoothLeAdvertiser; |
| import android.content.Context; |
| import android.os.ParcelUuid; |
| import android.util.IndentingPrintWriter; |
| import android.util.Log; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.util.Arrays; |
| |
| /** |
| * The FastPairAdvertiser is responsible for the BLE advertisement of either the model ID while |
| * in pairing mode or the stored account keys while not in pairing mode. |
| * |
| * Note that two different advertisers should be created and only one should be advertising at a |
| * time. |
| */ |
| class FastPairAdvertiser { |
| // Service ID assigned for FastPair. |
| public static final ParcelUuid FastPairServiceUuid = ParcelUuid |
| .fromString("0000FE2C-0000-1000-8000-00805f9b34fb"); |
| private static final String TAG = FastPairAdvertiser.class.getSimpleName(); |
| private static final boolean DBG = FastPairUtils.DBG; |
| |
| private final Callbacks mCallbacks; |
| private final Context mContext; |
| private final byte[] mFastPairModelData; |
| |
| private BluetoothAdapter mBluetoothAdapter; |
| private BluetoothLeAdvertiser mBluetoothLeAdvertiser; |
| private AdvertisingSetParameters mAdvertisingSetParameters; |
| private AdvertiseData mData; |
| private boolean mAdvertising = false; |
| |
| interface Callbacks { |
| /** |
| * Notify the Resolvable Private Address of the BLE advertiser. |
| * |
| * @param device The current LE address |
| */ |
| void onRpaUpdated(BluetoothDevice device); |
| } |
| |
| FastPairAdvertiser(Context context, int modelId, Callbacks callbacks) { |
| mContext = context; |
| mCallbacks = callbacks; |
| ByteBuffer modelIdBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt( |
| modelId); |
| mFastPairModelData = Arrays.copyOfRange(modelIdBytes.array(), 1, 4); |
| initializeBluetoothLeAdvertiser(); |
| } |
| |
| /** |
| * Advertise the model id when in pairing mode. |
| */ |
| void advertiseModelId() { |
| if (DBG) { |
| Log.d(TAG, "AdvertiseModelId"); |
| } |
| mAdvertisingSetParameters = new AdvertisingSetParameters.Builder() |
| .setLegacyMode(true) |
| .setInterval(AdvertisingSetParameters.INTERVAL_LOW) |
| .setScannable(true) |
| .setConnectable(true) |
| .build(); |
| mData = new AdvertiseData.Builder() |
| .addServiceUuid(FastPairServiceUuid) |
| .addServiceData(FastPairServiceUuid, mFastPairModelData) |
| .setIncludeTxPowerLevel(true) |
| .build(); |
| startAdvertising(); |
| } |
| |
| /** |
| * Advertise the stored account keys while not in pairing mode |
| */ |
| void advertiseAccountKeys() { |
| if (DBG) { |
| Log.d(TAG, "AdvertiseAccountKeys"); |
| } |
| mAdvertisingSetParameters = new AdvertisingSetParameters.Builder() |
| .setLegacyMode(true) |
| .setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM) |
| .setScannable(true) |
| .setConnectable(true) |
| .build(); |
| mData = new AdvertiseData.Builder() |
| .addServiceUuid(FastPairServiceUuid) |
| .addServiceData(FastPairServiceUuid, |
| FastPairUtils.getAccountKeyAdvertisement(mContext)) |
| .setIncludeTxPowerLevel(true) |
| .build(); |
| startAdvertising(); |
| } |
| |
| /** |
| * Stop advertising when it is time to shut down. |
| */ |
| void stopAdvertising() { |
| if (DBG) { |
| Log.d(TAG, "stoppingAdvertising"); |
| } |
| if (mBluetoothLeAdvertiser == null) return; |
| mBluetoothLeAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback); |
| } |
| |
| /** |
| * Attempt to set mBluetoothLeAdvertiser from the BluetoothAdapter |
| * |
| * Returns |
| * true if mBluetoothLeAdvertiser is set |
| * false if mBluetoothLeAdvertiser is still null |
| */ |
| private boolean initializeBluetoothLeAdvertiser() { |
| if (mBluetoothLeAdvertiser != null) return true; |
| |
| BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); |
| if (bluetoothManager == null) return false; |
| |
| mBluetoothAdapter = bluetoothManager.getAdapter(); |
| if (mBluetoothAdapter == null) return false; |
| |
| mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); |
| return (mBluetoothLeAdvertiser != null); |
| } |
| |
| /** |
| * Acquire the LE advertiser from the Bluetooth adapter, and if available start the configured |
| * advertiser. |
| */ |
| private void startAdvertising() { |
| if (!initializeBluetoothLeAdvertiser()) return; |
| if (!mAdvertising) { |
| if (DBG) Log.d(TAG, "startingAdvertising"); |
| mBluetoothLeAdvertiser.startAdvertisingSet(mAdvertisingSetParameters, mData, null, null, |
| null, mAdvertisingSetCallback); |
| } |
| } |
| |
| /* Callback to handle changes in advertising. */ |
| private AdvertisingSetCallback mAdvertisingSetCallback = new AdvertisingSetCallback() { |
| @Override |
| public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, |
| int status) { |
| if (DBG) { |
| Log.d(TAG, "onAdvertisingSetStarted(): txPower:" + txPower + " , status: " |
| + status); |
| } |
| mAdvertising = true; |
| if (advertisingSet == null) return; |
| advertisingSet.getOwnAddress(); |
| } |
| |
| @Override |
| public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { |
| if (DBG) Log.d(TAG, "onAdvertisingSetStopped():"); |
| mAdvertising = false; |
| } |
| |
| @Override |
| public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, |
| String address) { |
| if (DBG) Log.d(TAG, "onOwnAddressRead Type= " + addressType + " Address= " + address); |
| mCallbacks.onRpaUpdated(mBluetoothAdapter.getRemoteDevice(address)); |
| } |
| }; |
| |
| public void dump(IndentingPrintWriter writer) { |
| if (mAdvertising) { |
| writer.println("Currently advertising : " + mData); |
| } |
| } |
| } |