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