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