• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.nfc.handover;
18 
19 import static com.android.nfc.handover.BluetoothPeripheralHandover.ACTION_CANCEL_CONNECT;
20 
21 import android.app.Service;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothClass;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.OobData;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.nfc.NfcAdapter;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.Message;
35 import android.os.Messenger;
36 import android.os.ParcelUuid;
37 import android.os.Parcelable;
38 import android.os.RemoteException;
39 import android.text.TextUtils;
40 import android.util.Log;
41 
42 import androidx.annotation.VisibleForTesting;
43 
44 import java.util.Objects;
45 import java.util.Set;
46 
47 public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback {
48     static final String TAG = "PeripheralHandoverService";
49     static final boolean DBG = true;
50 
51     static final int MSG_PAUSE_POLLING = 0;
52 
53     public static final String BUNDLE_TRANSFER = "transfer";
54     public static final String EXTRA_PERIPHERAL_DEVICE = "device";
55     public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
56     public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
57     public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
58     public static final String EXTRA_PERIPHERAL_UUIDS = "uuids";
59     public static final String EXTRA_PERIPHERAL_CLASS = "class";
60     public static final String EXTRA_CLIENT = "client";
61     public static final String EXTRA_BT_ENABLED = "bt_enabled";
62 
63     public static final int MSG_HEADSET_CONNECTED = 0;
64     public static final int MSG_HEADSET_NOT_CONNECTED = 1;
65 
66     // Amount of time to pause polling when connecting to peripherals
67     private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
68     private static final int PAUSE_DELAY_MILLIS = 300;
69 
70     private static final Object sLock = new Object();
71 
72     // Variables below only accessed on main thread
73     final Messenger mMessenger;
74 
75     int mStartId;
76 
77     BluetoothAdapter mBluetoothAdapter;
78     NfcAdapter mNfcAdapter;
79     Handler mHandler;
80     BluetoothPeripheralHandover mBluetoothPeripheralHandover;
81     BluetoothDevice mDevice;
82     String mName;
83     Messenger mClient;
84     boolean mBluetoothHeadsetConnected;
85     boolean mBluetoothEnabledByNfc;
86     Bundle mPendingMsgData = null;
87 
88     class MessageHandler extends Handler {
89         @Override
handleMessage(Message msg)90         public void handleMessage(Message msg) {
91             switch (msg.what) {
92                 case MSG_PAUSE_POLLING:
93                     mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
94                     break;
95             }
96         }
97     }
98 
99     final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
100         @Override
101         public void onReceive(Context context, Intent intent) {
102             String action = intent.getAction();
103             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
104                 handleBluetoothStateChanged(intent);
105             }
106         }
107     };
108 
PeripheralHandoverService()109     public PeripheralHandoverService() {
110         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
111         mHandler = new MessageHandler();
112         mMessenger = new Messenger(mHandler);
113         mBluetoothHeadsetConnected = false;
114         mBluetoothEnabledByNfc = false;
115         mStartId = 0;
116     }
117 
118     @Override
onStartCommand(Intent intent, int flags, int startId)119     public int onStartCommand(Intent intent, int flags, int startId) {
120         if (intent == null) {
121             if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
122             synchronized (sLock) {
123                 stopSelf(startId);
124                 mStartId = 0;
125             }
126             return START_NOT_STICKY;
127         }
128 
129         Bundle msgData = intent.getExtras();
130         BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
131         String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
132 
133         synchronized (sLock) {
134             if (mStartId != 0) {
135                 Log.d(TAG, "Ongoing handover to " + mDevice);
136                 if (!Objects.equals(mDevice, device) || !TextUtils.equals(mName, name)) {
137                     Log.w(TAG, "Cancel ongoing handover");
138                     sendBroadcast(new Intent(ACTION_CANCEL_CONNECT));
139                     // Wait for the previous attempt to be fully cancelled. Store the new pairing
140                     // data to start the pairing after cancellation.
141                     mPendingMsgData = new Bundle(msgData);
142                 }
143                 mStartId = startId;
144                 // already running
145                 return START_STICKY;
146             }
147             mStartId = startId;
148         }
149 
150         if (doPeripheralHandover(intent.getExtras())) {
151             return START_STICKY;
152         } else {
153             onBluetoothPeripheralHandoverComplete(false);
154             return START_NOT_STICKY;
155         }
156     }
157 
158     @Override
onCreate()159     public void onCreate() {
160         super.onCreate();
161         mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
162 
163         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
164         registerReceiver(mBluetoothStatusReceiver, filter);
165     }
166 
167     @Override
onDestroy()168     public void onDestroy() {
169         super.onDestroy();
170         unregisterReceiver(mBluetoothStatusReceiver);
171     }
172 
doPeripheralHandover(Bundle msgData)173     boolean doPeripheralHandover(Bundle msgData) {
174         Log.d(TAG, "doPeripheralHandover: " + msgData);
175         if (mBluetoothPeripheralHandover != null) {
176             Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
177             return true;
178         }
179 
180         if (msgData == null) {
181             return false;
182         }
183 
184         mDevice = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
185         mName = msgData.getString(EXTRA_PERIPHERAL_NAME);
186         int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
187         OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
188         Parcelable[] parcelables = msgData.getParcelableArray(EXTRA_PERIPHERAL_UUIDS);
189         BluetoothClass btClass = msgData.getParcelable(EXTRA_PERIPHERAL_CLASS);
190 
191         ParcelUuid[] uuids = null;
192         if (parcelables != null) {
193             uuids = new ParcelUuid[parcelables.length];
194             for (int i = 0; i < parcelables.length; i++) {
195                 uuids[i] = (ParcelUuid)parcelables[i];
196             }
197         }
198 
199         mClient = msgData.getParcelable(EXTRA_CLIENT);
200         mBluetoothEnabledByNfc = msgData.getBoolean(EXTRA_BT_ENABLED);
201 
202         mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
203                 this, mDevice, mName, transport, oobData, uuids, btClass, this);
204 
205         if (transport == BluetoothDevice.TRANSPORT_LE) {
206             mHandler.sendMessageDelayed(
207                     mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
208         }
209         if (mBluetoothAdapter.isEnabled()) {
210             if (!mBluetoothPeripheralHandover.start()) {
211                 return false;
212             }
213         } else {
214             // Once BT is enabled, the headset pairing will be started
215             if (!enableBluetooth()) {
216                 Log.e(TAG, "Error enabling Bluetooth.");
217                 return false;
218             }
219         }
220 
221         return true;
222     }
223 
handleBluetoothStateChanged(Intent intent)224     private void handleBluetoothStateChanged(Intent intent) {
225         int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
226                 BluetoothAdapter.ERROR);
227         if (state == BluetoothAdapter.STATE_OFF) {
228             sendBroadcast(new Intent(ACTION_CANCEL_CONNECT));
229         } else if (state == BluetoothAdapter.STATE_ON) {
230             // If there is a pending device pairing, start it
231             if (mBluetoothPeripheralHandover != null &&
232                     !mBluetoothPeripheralHandover.hasStarted()) {
233                 if (!mBluetoothPeripheralHandover.start()) {
234                     onBluetoothPeripheralHandoverComplete(false);
235                 }
236             }
237         }
238     }
239 
240     @Override
onBluetoothPeripheralHandoverComplete(boolean connected)241     public void onBluetoothPeripheralHandoverComplete(boolean connected) {
242         // Called on the main thread
243         int transport = mBluetoothPeripheralHandover.getTransport();
244         mBluetoothPeripheralHandover = null;
245         mBluetoothHeadsetConnected = connected;
246 
247         // <hack> resume polling immediately if the connection failed,
248         // otherwise just wait for polling to come back up after the timeout
249         // This ensures we don't disconnect if the user left the volantis
250         // on the tag after pairing completed, which results in automatic
251         // disconnection </hack>
252         if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
253             if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
254                 mHandler.removeMessages(MSG_PAUSE_POLLING);
255             }
256 
257             // do this unconditionally as the polling could have been paused as we were removing
258             // the message in the handler. It's a no-op if polling is already enabled.
259             mNfcAdapter.resumePolling();
260         }
261         disableBluetoothIfNeeded();
262         replyToClient(connected);
263 
264         if (mPendingMsgData != null) {
265             Log.d(TAG, "Resume next handover after cancellation of previous handover");
266             doPeripheralHandover(mPendingMsgData);
267             mPendingMsgData = null;
268         } else {
269             synchronized (sLock) {
270                 stopSelf(mStartId);
271                 mStartId = 0;
272             }
273         }
274     }
275 
276 
enableBluetooth()277     boolean enableBluetooth() {
278         if (!mBluetoothAdapter.isEnabled()) {
279             mBluetoothEnabledByNfc = true;
280             return mBluetoothAdapter.enableNoAutoConnect();
281         }
282         return true;
283     }
284 
disableBluetoothIfNeeded()285     void disableBluetoothIfNeeded() {
286         if (!mBluetoothEnabledByNfc) return;
287         if (hasConnectedBluetoothDevices()) return;
288 
289         if (!mBluetoothHeadsetConnected) {
290             mBluetoothAdapter.disable();
291             mBluetoothEnabledByNfc = false;
292         }
293     }
294 
hasConnectedBluetoothDevices()295     boolean hasConnectedBluetoothDevices() {
296         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
297 
298         if (bondedDevices != null) {
299             for (BluetoothDevice device : bondedDevices) {
300                 if (device.equals(mDevice)) {
301                     // Not required to check the remote BT "target" device
302                     // connection status, because sometimes the connection
303                     // state is not yet been updated upon disconnection.
304                     // It is enough to check the connection status for
305                     // "other" remote BT device/s.
306                     continue;
307                 }
308                 if (device.isConnected()) return true;
309             }
310         }
311         return false;
312     }
313 
replyToClient(boolean connected)314     void replyToClient(boolean connected) {
315         if (mClient == null) {
316             return;
317         }
318 
319         final int msgId = connected ? MSG_HEADSET_CONNECTED : MSG_HEADSET_NOT_CONNECTED;
320         final Message msg = Message.obtain(null, msgId);
321         msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0;
322         try {
323             mClient.send(msg);
324         } catch (RemoteException e) {
325             // Ignore
326         }
327     }
328 
329     @Override
onBind(Intent intent)330     public IBinder onBind(Intent intent) {
331         return null;
332     }
333 
334     @Override
onUnbind(Intent intent)335     public boolean onUnbind(Intent intent) {
336         // prevent any future callbacks to the client, no rebind call needed.
337         return false;
338     }
339 
340     @VisibleForTesting
PeripheralHandoverService(BluetoothAdapter bluetoothAdapter, NfcAdapter nfcAdapter, BluetoothPeripheralHandover bluetoothPeripheralHandover, MessageHandler handler, Messenger messenger, BluetoothDevice device, boolean bluetoothEnabledByNfc)341     public PeripheralHandoverService(BluetoothAdapter bluetoothAdapter, NfcAdapter nfcAdapter,
342             BluetoothPeripheralHandover bluetoothPeripheralHandover, MessageHandler handler,
343             Messenger messenger, BluetoothDevice device, boolean bluetoothEnabledByNfc) {
344         mBluetoothAdapter = bluetoothAdapter;
345         mNfcAdapter = nfcAdapter;
346         mBluetoothPeripheralHandover = bluetoothPeripheralHandover;
347         mHandler = handler;
348         mClient = messenger;
349         mMessenger = messenger;
350         mDevice = device;
351         mBluetoothEnabledByNfc = bluetoothEnabledByNfc;
352         mBluetoothHeadsetConnected = false;
353         mStartId = 0;
354         mPendingMsgData = null;
355         mName = "Test name";
356     }
357 }
358