1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.bluetooth; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothManager; 22 import android.bluetooth.le.AdvertiseData; 23 import android.bluetooth.le.AdvertisingSet; 24 import android.bluetooth.le.AdvertisingSetCallback; 25 import android.bluetooth.le.AdvertisingSetParameters; 26 import android.bluetooth.le.BluetoothLeAdvertiser; 27 import android.content.Context; 28 import android.os.ParcelUuid; 29 import android.util.IndentingPrintWriter; 30 import android.util.Log; 31 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.util.Arrays; 35 36 /** 37 * The FastPairAdvertiser is responsible for the BLE advertisement of either the model ID while 38 * in pairing mode or the stored account keys while not in pairing mode. 39 * 40 * Note that two different advertisers should be created and only one should be advertising at a 41 * time. 42 */ 43 class FastPairAdvertiser { 44 // Service ID assigned for FastPair. 45 public static final ParcelUuid FastPairServiceUuid = ParcelUuid 46 .fromString("0000FE2C-0000-1000-8000-00805f9b34fb"); 47 private static final String TAG = FastPairAdvertiser.class.getSimpleName(); 48 private static final boolean DBG = FastPairUtils.DBG; 49 50 private final Callbacks mCallbacks; 51 private final Context mContext; 52 private final byte[] mFastPairModelData; 53 54 private BluetoothAdapter mBluetoothAdapter; 55 private BluetoothLeAdvertiser mBluetoothLeAdvertiser; 56 private AdvertisingSetParameters mAdvertisingSetParameters; 57 private AdvertiseData mData; 58 private boolean mAdvertising = false; 59 60 interface Callbacks { 61 /** 62 * Notify the Resolvable Private Address of the BLE advertiser. 63 * 64 * @param device The current LE address 65 */ onRpaUpdated(BluetoothDevice device)66 void onRpaUpdated(BluetoothDevice device); 67 } 68 FastPairAdvertiser(Context context, int modelId, Callbacks callbacks)69 FastPairAdvertiser(Context context, int modelId, Callbacks callbacks) { 70 mContext = context; 71 mCallbacks = callbacks; 72 ByteBuffer modelIdBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt( 73 modelId); 74 mFastPairModelData = Arrays.copyOfRange(modelIdBytes.array(), 1, 4); 75 initializeBluetoothLeAdvertiser(); 76 } 77 78 /** 79 * Advertise the model id when in pairing mode. 80 */ advertiseModelId()81 void advertiseModelId() { 82 if (DBG) { 83 Log.d(TAG, "AdvertiseModelId"); 84 } 85 mAdvertisingSetParameters = new AdvertisingSetParameters.Builder() 86 .setLegacyMode(true) 87 .setInterval(AdvertisingSetParameters.INTERVAL_LOW) 88 .setScannable(true) 89 .setConnectable(true) 90 .build(); 91 mData = new AdvertiseData.Builder() 92 .addServiceUuid(FastPairServiceUuid) 93 .addServiceData(FastPairServiceUuid, mFastPairModelData) 94 .setIncludeTxPowerLevel(true) 95 .build(); 96 startAdvertising(); 97 } 98 99 /** 100 * Advertise the stored account keys while not in pairing mode 101 */ advertiseAccountKeys()102 void advertiseAccountKeys() { 103 if (DBG) { 104 Log.d(TAG, "AdvertiseAccountKeys"); 105 } 106 mAdvertisingSetParameters = new AdvertisingSetParameters.Builder() 107 .setLegacyMode(true) 108 .setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM) 109 .setScannable(true) 110 .setConnectable(true) 111 .build(); 112 mData = new AdvertiseData.Builder() 113 .addServiceUuid(FastPairServiceUuid) 114 .addServiceData(FastPairServiceUuid, 115 FastPairUtils.getAccountKeyAdvertisement(mContext)) 116 .setIncludeTxPowerLevel(true) 117 .build(); 118 startAdvertising(); 119 } 120 121 /** 122 * Stop advertising when it is time to shut down. 123 */ stopAdvertising()124 void stopAdvertising() { 125 if (DBG) { 126 Log.d(TAG, "stoppingAdvertising"); 127 } 128 if (mBluetoothLeAdvertiser == null) return; 129 mBluetoothLeAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback); 130 } 131 132 /** 133 * Attempt to set mBluetoothLeAdvertiser from the BluetoothAdapter 134 * 135 * Returns 136 * true if mBluetoothLeAdvertiser is set 137 * false if mBluetoothLeAdvertiser is still null 138 */ initializeBluetoothLeAdvertiser()139 private boolean initializeBluetoothLeAdvertiser() { 140 if (mBluetoothLeAdvertiser != null) return true; 141 142 BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); 143 if (bluetoothManager == null) return false; 144 145 mBluetoothAdapter = bluetoothManager.getAdapter(); 146 if (mBluetoothAdapter == null) return false; 147 148 mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); 149 return (mBluetoothLeAdvertiser != null); 150 } 151 152 /** 153 * Acquire the LE advertiser from the Bluetooth adapter, and if available start the configured 154 * advertiser. 155 */ startAdvertising()156 private void startAdvertising() { 157 if (!initializeBluetoothLeAdvertiser()) return; 158 if (!mAdvertising) { 159 if (DBG) Log.d(TAG, "startingAdvertising"); 160 mBluetoothLeAdvertiser.startAdvertisingSet(mAdvertisingSetParameters, mData, null, null, 161 null, mAdvertisingSetCallback); 162 } 163 } 164 165 /* Callback to handle changes in advertising. */ 166 private AdvertisingSetCallback mAdvertisingSetCallback = new AdvertisingSetCallback() { 167 @Override 168 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, 169 int status) { 170 if (DBG) { 171 Log.d(TAG, "onAdvertisingSetStarted(): txPower:" + txPower + " , status: " 172 + status); 173 } 174 mAdvertising = true; 175 if (advertisingSet == null) return; 176 advertisingSet.getOwnAddress(); 177 } 178 179 @Override 180 public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { 181 if (DBG) Log.d(TAG, "onAdvertisingSetStopped():"); 182 mAdvertising = false; 183 } 184 185 @Override 186 public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, 187 String address) { 188 if (DBG) Log.d(TAG, "onOwnAddressRead Type= " + addressType + " Address= " + address); 189 mCallbacks.onRpaUpdated(mBluetoothAdapter.getRemoteDevice(address)); 190 } 191 }; 192 dump(IndentingPrintWriter writer)193 public void dump(IndentingPrintWriter writer) { 194 if (mAdvertising) { 195 writer.println("Currently advertising : " + mData); 196 } 197 } 198 } 199