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 } 157 } 158 }; 159 160 /** 161 * FastPairProvider constructor which loads Fast Pair variables from the device specific 162 * resource overlay. 163 * 164 * @param context user specific context on which all Bluetooth operations shall occur. 165 */ FastPairProvider(Context context)166 public FastPairProvider(Context context) { 167 mContext = context; 168 169 Resources res = mContext.getResources(); 170 mModelId = res.getInteger(R.integer.fastPairModelId); 171 mAntiSpoofKey = res.getString(R.string.fastPairAntiSpoofKey); 172 mAutomaticAcceptance = res.getBoolean(R.bool.fastPairAutomaticAcceptance); 173 174 mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); 175 mFastPairAccountKeyStorage = new FastPairAccountKeyStorage(mContext, 5); 176 mFastPairAdvertiser = new FastPairAdvertiser(mContext); 177 mFastPairGattServer = new FastPairGattServer(mContext, mModelId, mAntiSpoofKey, 178 mGattServerCallbacks, mAutomaticAcceptance, mFastPairAccountKeyStorage); 179 } 180 181 /** 182 * Determine if Fast Pair Provider is enabled based on the configuration parameters read in. 183 */ isEnabled()184 boolean isEnabled() { 185 return !(mModelId == 0 || TextUtils.isEmpty(mAntiSpoofKey)); 186 } 187 188 /** 189 * Is the Fast Pair Provider Started 190 * 191 * Being started means our advertiser exists and we are listening for events that would signal 192 * for us to create our GATT Server/Service. 193 */ isStarted()194 boolean isStarted() { 195 return mStarted; 196 } 197 198 /** 199 * Start the Fast Pair provider which will register for Bluetooth broadcasts. 200 */ start()201 public void start() { 202 if (mStarted) return; 203 if (!isEnabled()) { 204 Slogf.w(TAG, "Fast Pair Provider not configured, disabling, model=%d, key=%s", 205 mModelId, TextUtils.isEmpty(mAntiSpoofKey) ? "N/A" : "Set"); 206 return; 207 } 208 IntentFilter filter = new IntentFilter(); 209 filter.addAction(Intent.ACTION_USER_UNLOCKED); 210 filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 211 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 212 mContext.registerReceiver(mDiscoveryModeChanged, filter); 213 mStarted = true; 214 } 215 216 /** 217 * Stop the Fast Pair provider which will unregister the broadcast receiver. 218 */ stop()219 public void stop() { 220 if (!mStarted) return; 221 mContext.unregisterReceiver(mDiscoveryModeChanged); 222 mStarted = false; 223 } 224 advertiseModelId()225 void advertiseModelId() { 226 if (DBG) Slogf.i(TAG, "Advertise model ID"); 227 mFastPairAdvertiser.stopAdvertising(); 228 mFastPairAdvertiser.advertiseModelId(mModelId, mAdvertiserCallbacks); 229 } 230 advertiseAccountKeys()231 void advertiseAccountKeys() { 232 if (DBG) Slogf.i(TAG, "Advertise account key filter"); 233 mFastPairAdvertiser.stopAdvertising(); 234 mFastPairAdvertiser.advertiseAccountKeys(mFastPairAccountKeyStorage.getAllAccountKeys(), 235 mAdvertiserCallbacks); 236 } 237 stopAdvertising()238 void stopAdvertising() { 239 if (DBG) Slogf.i(TAG, "Stop all advertising"); 240 mFastPairAdvertiser.stopAdvertising(); 241 } 242 startGatt()243 void startGatt() { 244 if (DBG) Slogf.i(TAG, "Start Fast Pair GATT server"); 245 mFastPairGattServer.start(); 246 } 247 stopGatt()248 void stopGatt() { 249 if (DBG) Slogf.i(TAG, "Stop Fast Pair GATT server"); 250 mFastPairGattServer.stop(); 251 } 252 253 /** 254 * Dump current status of the Fast Pair provider 255 * 256 * This will get printed with the output of: 257 * adb shell dumpsys activity service com.android.car/.PerUserCarService 258 * 259 * @param writer 260 */ 261 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)262 public void dump(IndentingPrintWriter writer) { 263 writer.println("FastPairProvider:"); 264 writer.increaseIndent(); 265 writer.println("Status : " + (isEnabled() ? "Enabled" : "Disabled")); 266 writer.println("Model ID : " + mModelId); 267 writer.println("Anti-Spoof Key : " + (TextUtils.isEmpty(mAntiSpoofKey) ? "N/A" : "Set")); 268 writer.println("State : " + (isEnabled() ? "Started" : "Stopped")); 269 if (isEnabled()) { 270 mFastPairAdvertiser.dump(writer); 271 mFastPairGattServer.dump(writer); 272 mFastPairAccountKeyStorage.dump(writer); 273 } 274 writer.decreaseIndent(); 275 } 276 } 277