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 android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothManager; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.res.Resources; 27 import android.os.Handler; 28 import android.util.IndentingPrintWriter; 29 import android.util.Slog; 30 31 import com.android.car.CarLog; 32 import com.android.car.CarServiceUtils; 33 import com.android.car.R; 34 35 /** 36 * An advertiser for the Bluetooth LE based Fast Pair service. FastPairProvider enables easy 37 * Bluetooth pairing between a peripheral and a phone participating in the Fast Pair Seeker role. 38 * When the seeker finds a compatible peripheral a notification prompts the user to begin pairing if 39 * desired. A peripheral should call startAdvertising when it is appropriate to pair, and 40 * stopAdvertising when pairing is complete or it is no longer appropriate to pair. 41 */ 42 public class FastPairProvider { 43 private static final String TAG = CarLog.tagFor(FastPairProvider.class); 44 private static final boolean DBG = FastPairUtils.DBG; 45 46 private final int mModelId; 47 private final String mAntiSpoofKey; 48 private final boolean mAutomaticAcceptance; 49 private final Context mContext; 50 private boolean mStarted; 51 private int mScanMode; 52 private BluetoothAdapter mBluetoothAdapter; 53 private FastPairAdvertiser mFastPairModelAdvertiser; 54 private FastPairAdvertiser mFastPairAccountAdvertiser; 55 private FastPairGattServer mFastPairGattServer; 56 private Handler mFastPairAdvertiserHandler; 57 58 FastPairAdvertiser.Callbacks mAdvertiserCallbacks = new FastPairAdvertiser.Callbacks() { 59 @Override 60 public void onRpaUpdated(BluetoothDevice device) { 61 mFastPairGattServer.updateLocalRpa(device); 62 } 63 }; 64 65 FastPairGattServer.Callbacks mGattServerCallbacks = new FastPairGattServer.Callbacks() { 66 @Override 67 public void onPairingCompleted(boolean successful) { 68 if (DBG) { 69 Slog.d(TAG, "onPairingCompleted " + successful); 70 } 71 if (successful || mScanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 72 advertiseAccountKeys(); 73 } 74 } 75 76 }; 77 78 /** 79 * listen for changes in the Bluetooth adapter specifically for the Bluetooth adapter turning 80 * on, turning off, and changes to discoverability 81 */ 82 BroadcastReceiver mDiscoveryModeChanged = new BroadcastReceiver() { 83 @Override 84 public void onReceive(Context context, Intent intent) { 85 String action = intent.getAction(); 86 if (DBG) { 87 Slog.d(TAG, "onReceive, " + action); 88 } 89 switch (action) { 90 case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED: 91 mScanMode = intent 92 .getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, 93 BluetoothAdapter.SCAN_MODE_NONE); 94 if (DBG) { 95 Slog.d(TAG, "NewScanMode = " + mScanMode); 96 } 97 if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 98 if (mBluetoothAdapter.isDiscovering()) { 99 advertiseModelId(); 100 } else { 101 stopAdvertising(); 102 } 103 } else if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE 104 && mFastPairGattServer != null 105 && !mFastPairGattServer.isConnected()) { 106 // The adapter is no longer discoverable, and the Fast Pair session is 107 // complete 108 advertiseAccountKeys(); 109 } 110 break; 111 112 case BluetoothAdapter.ACTION_STATE_CHANGED: 113 int state = intent 114 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); 115 if (state != BluetoothAdapter.STATE_ON) { 116 if (mFastPairGattServer != null) { 117 mFastPairGattServer.stop(); 118 mFastPairGattServer = null; 119 } 120 } 121 break; 122 } 123 } 124 }; 125 126 /** 127 * FastPairProvider constructor which loads Fast Pair variables from the device specific 128 * resource overlay. 129 * 130 * @param context user specific context on which all Bluetooth operations shall occur. 131 */ FastPairProvider(Context context)132 public FastPairProvider(Context context) { 133 mContext = context; 134 Resources res = mContext.getResources(); 135 136 mModelId = res.getInteger(R.integer.fastPairModelId); 137 mAntiSpoofKey = res.getString(R.string.fastPairAntiSpoofKey); 138 mAutomaticAcceptance = res.getBoolean(R.bool.fastPairAutomaticAcceptance); 139 mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); 140 } 141 142 /** 143 * Start the Fast Pair provider which will register for Bluetooth broadcasts. 144 */ start()145 public void start() { 146 if (mModelId == 0) { 147 Slog.w(TAG, "Model ID undefined, disabling"); 148 return; 149 } 150 Slog.d(TAG, "modelId == " + mModelId); 151 mFastPairAdvertiserHandler = new Handler( 152 CarServiceUtils.getHandlerThread(FastPairUtils.THREAD_NAME).getLooper()); 153 IntentFilter filter = new IntentFilter(); 154 filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 155 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 156 mContext.registerReceiver(mDiscoveryModeChanged, filter); 157 158 mStarted = true; 159 } 160 161 /** 162 * Stop the Fast Pair provider which will unregister the broadcast receiver. 163 */ stop()164 public void stop() { 165 if (mStarted) { 166 mContext.unregisterReceiver(mDiscoveryModeChanged); 167 mStarted = false; 168 } 169 } 170 stopAdvertising()171 void stopAdvertising() { 172 mFastPairAdvertiserHandler.post(new Runnable() { 173 @Override 174 public void run() { 175 if (mFastPairAccountAdvertiser != null) { 176 mFastPairAccountAdvertiser.stopAdvertising(); 177 } 178 if (mFastPairModelAdvertiser != null) { 179 mFastPairModelAdvertiser.stopAdvertising(); 180 } 181 } 182 }); 183 } 184 advertiseModelId()185 void advertiseModelId() { 186 mFastPairAdvertiserHandler.post(new Runnable() { 187 @Override 188 public void run() { 189 if (mFastPairAccountAdvertiser != null) { 190 mFastPairAccountAdvertiser.stopAdvertising(); 191 } 192 if (mFastPairModelAdvertiser == null) { 193 mFastPairModelAdvertiser = new FastPairAdvertiser(mContext, mModelId, 194 mAdvertiserCallbacks); 195 } 196 197 startGatt(); 198 mFastPairModelAdvertiser.advertiseModelId(); 199 } 200 }); 201 } 202 advertiseAccountKeys()203 void advertiseAccountKeys() { 204 mFastPairAdvertiserHandler.post(new Runnable() { 205 @Override 206 public void run() { 207 if (mFastPairModelAdvertiser != null) { 208 mFastPairModelAdvertiser.stopAdvertising(); 209 } 210 if (mFastPairAccountAdvertiser == null) { 211 mFastPairAccountAdvertiser = new FastPairAdvertiser(mContext, mModelId, 212 mAdvertiserCallbacks); 213 } 214 215 startGatt(); 216 mFastPairAccountAdvertiser.advertiseAccountKeys(); 217 } 218 }); 219 } 220 startGatt()221 void startGatt() { 222 if (mFastPairGattServer == null) { 223 mFastPairGattServer = new FastPairGattServer(mContext, mModelId, 224 mAntiSpoofKey, 225 mGattServerCallbacks, 226 mAutomaticAcceptance); 227 mFastPairGattServer.start(); 228 } 229 } 230 231 /** 232 * Dump current status of the Fast Pair provider 233 * 234 * @param writer 235 */ dump(IndentingPrintWriter writer)236 public void dump(IndentingPrintWriter writer) { 237 writer.println(TAG + " services"); 238 if (mModelId == 0) { 239 writer.increaseIndent(); 240 writer.println("Service Disabled"); 241 writer.decreaseIndent(); 242 return; 243 } 244 writer.increaseIndent(); 245 writer.println("Model ID : " + mModelId); 246 if (mFastPairModelAdvertiser != null) { 247 mFastPairModelAdvertiser.dump(writer); 248 } 249 if (mFastPairAccountAdvertiser != null) { 250 mFastPairAccountAdvertiser.dump(writer); 251 } 252 if (mFastPairGattServer != null) { 253 mFastPairGattServer.dump(writer); 254 } 255 writer.decreaseIndent(); 256 } 257 } 258