1 /* 2 * Copyright (C) 2018 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 static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothManager; 24 import android.car.builtin.util.Slogf; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.res.Resources; 30 import android.text.TextUtils; 31 import android.util.Log; 32 33 import com.android.car.CarLog; 34 import com.android.car.R; 35 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 36 import com.android.car.internal.util.IndentingPrintWriter; 37 38 /** 39 * An advertiser for the Bluetooth LE based Fast Pair service. FastPairProvider enables easy 40 * Bluetooth pairing between a peripheral and a phone participating in the Fast Pair Seeker role. 41 * When the seeker finds a compatible peripheral a notification prompts the user to begin pairing if 42 * desired. A peripheral should call startAdvertising when it is appropriate to pair, and 43 * stopAdvertising when pairing is complete or it is no longer appropriate to pair. 44 */ 45 public class FastPairProvider { 46 private static final String TAG = CarLog.tagFor(FastPairProvider.class); 47 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 48 static final String THREAD_NAME = "FastPairProvider"; 49 50 private final int mModelId; 51 private final String mAntiSpoofKey; 52 private final boolean mAutomaticAcceptance; 53 private final Context mContext; 54 private boolean mStarted; 55 private int mScanMode; 56 private final BluetoothAdapter mBluetoothAdapter; 57 private final FastPairAdvertiser mFastPairAdvertiser; 58 private FastPairGattServer mFastPairGattServer; 59 private final FastPairAccountKeyStorage mFastPairAccountKeyStorage; 60 61 FastPairAdvertiser.Callbacks mAdvertiserCallbacks = new FastPairAdvertiser.Callbacks() { 62 @Override 63 public void onRpaUpdated(BluetoothDevice device) { 64 mFastPairGattServer.updateLocalRpa(device); 65 } 66 }; 67 68 FastPairGattServer.Callbacks mGattServerCallbacks = new FastPairGattServer.Callbacks() { 69 @Override 70 public void onPairingCompleted(boolean successful) { 71 if (DBG) { 72 Slogf.d(TAG, "onPairingCompleted %s", successful); 73 } 74 // TODO (243171615): Reassess advertising transitions against specification 75 if (successful || mScanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 76 advertiseAccountKeys(); 77 } 78 } 79 }; 80 81 /** 82 * Listen for changes in the Bluetooth adapter state and scan mode. 83 * 84 * When the adapter is 85 * - ON: Ensure our GATT Server is up and that we are advertising either the model ID or account 86 * key filter, based on current scan mode. 87 * - OTHERWISE: Ensure our GATT server is off. 88 * 89 * When the scan mode is: 90 * - CONNECTABLE / DISCOVERABLE: Advertise the model ID if we are actively discovering as well. 91 * If we are not, then stop advertising temporarily. See below for why this is done. 92 * - CONNECTABLE: Advertise account key filter 93 * - NONE: Do not advertise anything. 94 */ 95 BroadcastReceiver mDiscoveryModeChanged = new BroadcastReceiver() { 96 @Override 97 public void onReceive(Context context, Intent intent) { 98 String action = intent.getAction(); 99 switch (action) { 100 case Intent.ACTION_USER_UNLOCKED: 101 if (DBG) { 102 Slogf.d(TAG, "User unlocked"); 103 } 104 mFastPairAccountKeyStorage.load(); 105 break; 106 107 // TODO (243171615): Reassess advertising transitions against specification 108 case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED: 109 int newScanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, 110 BluetoothAdapter.ERROR); 111 boolean isDiscovering = mBluetoothAdapter.isDiscovering(); 112 boolean isFastPairing = mFastPairGattServer.isConnected(); 113 if (DBG) { 114 Slogf.d(TAG, "Scan mode changed, old=%s, new=%s, discovering=%b," 115 + " fastpairing=%b", BluetoothUtils.getScanModeName(mScanMode), 116 BluetoothUtils.getScanModeName(newScanMode), isDiscovering, 117 isFastPairing); 118 } 119 mScanMode = newScanMode; 120 if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 121 // While the specification says we should always be advertising *something* 122 // it turns out the other applications implement other Fast Pair based 123 // features that also want to advertise (Smart Setup, for example, which is 124 // another Fast Pair based feature outside of BT Pairing facilitation). 125 // Seeker devices can only handle one 0xFE2C advertisement at a time. To 126 // reduce the chance of clashing, we only advertise our Model ID when we're 127 // sure we have the intent to pair. Otherwise, if we're in the discoverable 128 // state without intent to pair, then it may be another application. We stop 129 // advertising all together. 130 if (isDiscovering) { 131 advertiseModelId(); 132 } else { 133 stopAdvertising(); 134 } 135 } else if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 136 advertiseAccountKeys(); 137 } 138 break; 139 140 case BluetoothAdapter.ACTION_STATE_CHANGED: 141 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 142 BluetoothAdapter.ERROR); 143 int oldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 144 BluetoothAdapter.ERROR); 145 if (DBG) { 146 Slogf.d(TAG, "Adapter state changed, old=%s, new=%s", 147 BluetoothUtils.getAdapterStateName(oldState), 148 BluetoothUtils.getAdapterStateName(newState)); 149 } 150 if (newState == BluetoothAdapter.STATE_ON) { 151 startGatt(); 152 } else { 153 stopGatt(); 154 } 155 break; 156 default: 157 break; 158 } 159 } 160 }; 161 162 /** 163 * FastPairProvider constructor which loads Fast Pair variables from the device specific 164 * resource overlay. 165 * 166 * @param context user specific context on which all Bluetooth operations shall occur. 167 */ FastPairProvider(Context context)168 public FastPairProvider(Context context) { 169 mContext = context; 170 171 Resources res = mContext.getResources(); 172 mModelId = res.getInteger(R.integer.fastPairModelId); 173 mAntiSpoofKey = res.getString(R.string.fastPairAntiSpoofKey); 174 mAutomaticAcceptance = res.getBoolean(R.bool.fastPairAutomaticAcceptance); 175 176 mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); 177 mFastPairAccountKeyStorage = new FastPairAccountKeyStorage(mContext, 5); 178 mFastPairAdvertiser = new FastPairAdvertiser(mContext); 179 mFastPairGattServer = new FastPairGattServer(mContext, mModelId, mAntiSpoofKey, 180 mGattServerCallbacks, mAutomaticAcceptance, mFastPairAccountKeyStorage); 181 } 182 183 /** 184 * Determine if Fast Pair Provider is enabled based on the configuration parameters read in. 185 */ isEnabled()186 boolean isEnabled() { 187 return !(mModelId == 0 || TextUtils.isEmpty(mAntiSpoofKey)); 188 } 189 190 /** 191 * Is the Fast Pair Provider Started 192 * 193 * Being started means our advertiser exists and we are listening for events that would signal 194 * for us to create our GATT Server/Service. 195 */ isStarted()196 boolean isStarted() { 197 return mStarted; 198 } 199 200 /** 201 * Start the Fast Pair provider which will register for Bluetooth broadcasts. 202 */ start()203 public void start() { 204 if (mStarted) return; 205 if (!isEnabled()) { 206 Slogf.w(TAG, "Fast Pair Provider not configured, disabling, model=%d, key=%s", 207 mModelId, TextUtils.isEmpty(mAntiSpoofKey) ? "N/A" : "Set"); 208 return; 209 } 210 IntentFilter filter = new IntentFilter(); 211 filter.addAction(Intent.ACTION_USER_UNLOCKED); 212 filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 213 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 214 mContext.registerReceiver(mDiscoveryModeChanged, filter); 215 mStarted = true; 216 } 217 218 /** 219 * Stop the Fast Pair provider which will unregister the broadcast receiver. 220 */ stop()221 public void stop() { 222 if (!mStarted) return; 223 stopGatt(); 224 stopAdvertising(); 225 mContext.unregisterReceiver(mDiscoveryModeChanged); 226 mStarted = false; 227 } 228 advertiseModelId()229 void advertiseModelId() { 230 if (DBG) Slogf.i(TAG, "Advertise model ID"); 231 mFastPairAdvertiser.stopAdvertising(); 232 mFastPairAdvertiser.advertiseModelId(mModelId, mAdvertiserCallbacks); 233 } 234 advertiseAccountKeys()235 void advertiseAccountKeys() { 236 if (DBG) Slogf.i(TAG, "Advertise account key filter"); 237 mFastPairAdvertiser.stopAdvertising(); 238 mFastPairAdvertiser.advertiseAccountKeys(mFastPairAccountKeyStorage.getAllAccountKeys(), 239 mAdvertiserCallbacks); 240 } 241 stopAdvertising()242 void stopAdvertising() { 243 if (DBG) Slogf.i(TAG, "Stop all advertising"); 244 mFastPairAdvertiser.stopAdvertising(); 245 } 246 startGatt()247 void startGatt() { 248 if (DBG) Slogf.i(TAG, "Start Fast Pair GATT server"); 249 mFastPairGattServer.start(); 250 } 251 stopGatt()252 void stopGatt() { 253 if (DBG) Slogf.i(TAG, "Stop Fast Pair GATT server"); 254 mFastPairGattServer.stop(); 255 } 256 257 /** 258 * Dump current status of the Fast Pair provider 259 * 260 * This will get printed with the output of: 261 * adb shell dumpsys activity service com.android.car/.CarPerUserService 262 * 263 * @param writer 264 */ 265 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)266 public void dump(IndentingPrintWriter writer) { 267 writer.println("FastPairProvider:"); 268 writer.increaseIndent(); 269 writer.println("Status : " + (isEnabled() ? "Enabled" : "Disabled")); 270 writer.println("Model ID : " + mModelId); 271 writer.println("Anti-Spoof Key : " + (TextUtils.isEmpty(mAntiSpoofKey) ? "N/A" : "Set")); 272 writer.println("State : " + (isEnabled() ? "Started" : "Stopped")); 273 if (isEnabled()) { 274 mFastPairAdvertiser.dump(writer); 275 mFastPairGattServer.dump(writer); 276 mFastPairAccountKeyStorage.dump(writer); 277 } 278 writer.decreaseIndent(); 279 } 280 } 281