• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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