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