blob: e2687cf877954f55bc8cfa138130c18b25ce4859 [file] [log] [blame]
nxpandroid64fd68c2015-09-23 16:45:15 +05301/*
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
17package com.android.nfc.handover;
18
19import android.app.Service;
20import android.bluetooth.BluetoothAdapter;
nxpandroid6fd9cdb2017-07-12 18:25:41 +053021import android.bluetooth.BluetoothClass;
nxpandroid64fd68c2015-09-23 16:45:15 +053022import android.bluetooth.BluetoothDevice;
nxpandroid281eb922016-08-25 20:27:46 +053023import android.bluetooth.OobData;
nxpandroid64fd68c2015-09-23 16:45:15 +053024import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
nxpandroid64fd68c2015-09-23 16:45:15 +053028import android.nfc.NfcAdapter;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.Message;
33import android.os.Messenger;
nxpandroid6fd9cdb2017-07-12 18:25:41 +053034import android.os.Parcelable;
35import android.os.ParcelUuid;
nxpandroid64fd68c2015-09-23 16:45:15 +053036import android.os.RemoteException;
37import android.util.Log;
38
Suhas Suresh999730c2018-04-25 12:15:51 +053039import java.util.Set;
40
nxpandroid64fd68c2015-09-23 16:45:15 +053041public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback {
42 static final String TAG = "PeripheralHandoverService";
43 static final boolean DBG = true;
44
45 static final int MSG_PAUSE_POLLING = 0;
46
47 public static final String BUNDLE_TRANSFER = "transfer";
48 public static final String EXTRA_PERIPHERAL_DEVICE = "device";
49 public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
50 public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
nxpandroid281eb922016-08-25 20:27:46 +053051 public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
nxpandroid6fd9cdb2017-07-12 18:25:41 +053052 public static final String EXTRA_PERIPHERAL_UUIDS = "uuids";
53 public static final String EXTRA_PERIPHERAL_CLASS = "class";
Suhas Suresh999730c2018-04-25 12:15:51 +053054 public static final String EXTRA_CLIENT = "client";
55 public static final String EXTRA_BT_ENABLED = "bt_enabled";
56
57 public static final int MSG_HEADSET_CONNECTED = 0;
58 public static final int MSG_HEADSET_NOT_CONNECTED = 1;
nxpandroid64fd68c2015-09-23 16:45:15 +053059
60 // Amount of time to pause polling when connecting to peripherals
61 private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
62 private static final int PAUSE_DELAY_MILLIS = 300;
63
nxpandroid1153eb32015-11-06 18:46:58 +053064 private static final Object sLock = new Object();
65
nxpandroid64fd68c2015-09-23 16:45:15 +053066 // Variables below only accessed on main thread
67 final Messenger mMessenger;
68
nxpandroid64fd68c2015-09-23 16:45:15 +053069 int mStartId;
70
71 BluetoothAdapter mBluetoothAdapter;
72 NfcAdapter mNfcAdapter;
73 Handler mHandler;
74 BluetoothPeripheralHandover mBluetoothPeripheralHandover;
Suhas Suresh999730c2018-04-25 12:15:51 +053075 BluetoothDevice mDevice;
76 Messenger mClient;
nxpandroid64fd68c2015-09-23 16:45:15 +053077 boolean mBluetoothHeadsetConnected;
78 boolean mBluetoothEnabledByNfc;
79
80 class MessageHandler extends Handler {
81 @Override
82 public void handleMessage(Message msg) {
83 switch (msg.what) {
84 case MSG_PAUSE_POLLING:
85 mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
86 break;
87 }
88 }
89 }
90
91 final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
92 @Override
93 public void onReceive(Context context, Intent intent) {
94 String action = intent.getAction();
95 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
96 handleBluetoothStateChanged(intent);
97 }
98 }
nxf500513a018e72019-04-23 17:11:41 +053099 };
nxpandroid64fd68c2015-09-23 16:45:15 +0530100
101 public PeripheralHandoverService() {
102 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
103 mHandler = new MessageHandler();
104 mMessenger = new Messenger(mHandler);
105 mBluetoothHeadsetConnected = false;
106 mBluetoothEnabledByNfc = false;
107 mStartId = 0;
108 }
109
110 @Override
111 public int onStartCommand(Intent intent, int flags, int startId) {
nxf500513a018e72019-04-23 17:11:41 +0530112
nxpandroid1153eb32015-11-06 18:46:58 +0530113 synchronized (sLock) {
114 if (mStartId != 0) {
115 mStartId = startId;
116 // already running
117 return START_STICKY;
118 }
119 mStartId = startId;
nxpandroid64fd68c2015-09-23 16:45:15 +0530120 }
121
nxpandroid64fd68c2015-09-23 16:45:15 +0530122 if (intent == null) {
123 if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
124 stopSelf(startId);
125 return START_NOT_STICKY;
126 }
127
128 if (doPeripheralHandover(intent.getExtras())) {
129 return START_STICKY;
130 } else {
131 stopSelf(startId);
132 return START_NOT_STICKY;
133 }
134 }
135
136 @Override
137 public void onCreate() {
138 super.onCreate();
nxpandroid64fd68c2015-09-23 16:45:15 +0530139 mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
140
141 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
142 registerReceiver(mBluetoothStatusReceiver, filter);
143 }
144
145 @Override
146 public void onDestroy() {
147 super.onDestroy();
nxpandroid64fd68c2015-09-23 16:45:15 +0530148 unregisterReceiver(mBluetoothStatusReceiver);
149 }
150
151 boolean doPeripheralHandover(Bundle msgData) {
152 if (mBluetoothPeripheralHandover != null) {
153 Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
154 return true;
155 }
156
157 if (msgData == null) {
158 return false;
159 }
160
Suhas Suresh999730c2018-04-25 12:15:51 +0530161 mDevice = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
nxpandroid64fd68c2015-09-23 16:45:15 +0530162 String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
163 int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
nxpandroid281eb922016-08-25 20:27:46 +0530164 OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530165 Parcelable[] parcelables = msgData.getParcelableArray(EXTRA_PERIPHERAL_UUIDS);
166 BluetoothClass btClass = msgData.getParcelable(EXTRA_PERIPHERAL_CLASS);
167
168 ParcelUuid[] uuids = null;
169 if (parcelables != null) {
170 uuids = new ParcelUuid[parcelables.length];
171 for (int i = 0; i < parcelables.length; i++) {
172 uuids[i] = (ParcelUuid)parcelables[i];
173 }
174 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530175
Suhas Suresh999730c2018-04-25 12:15:51 +0530176 mClient = msgData.getParcelable(EXTRA_CLIENT);
177 mBluetoothEnabledByNfc = msgData.getBoolean(EXTRA_BT_ENABLED);
178
nxpandroid64fd68c2015-09-23 16:45:15 +0530179 mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
Suhas Suresh999730c2018-04-25 12:15:51 +0530180 this, mDevice, name, transport, oobData, uuids, btClass, this);
nxpandroid64fd68c2015-09-23 16:45:15 +0530181
182 if (transport == BluetoothDevice.TRANSPORT_LE) {
183 mHandler.sendMessageDelayed(
184 mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
185 }
186 if (mBluetoothAdapter.isEnabled()) {
187 if (!mBluetoothPeripheralHandover.start()) {
188 mHandler.removeMessages(MSG_PAUSE_POLLING);
189 mNfcAdapter.resumePolling();
190 }
191 } else {
192 // Once BT is enabled, the headset pairing will be started
193 if (!enableBluetooth()) {
194 Log.e(TAG, "Error enabling Bluetooth.");
195 mBluetoothPeripheralHandover = null;
196 return false;
197 }
198 }
199
200 return true;
201 }
202
203 private void handleBluetoothStateChanged(Intent intent) {
204 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
205 BluetoothAdapter.ERROR);
206 if (state == BluetoothAdapter.STATE_ON) {
207 // If there is a pending device pairing, start it
208 if (mBluetoothPeripheralHandover != null &&
209 !mBluetoothPeripheralHandover.hasStarted()) {
210 if (!mBluetoothPeripheralHandover.start()) {
211 mNfcAdapter.resumePolling();
212 }
213 }
214 } else if (state == BluetoothAdapter.STATE_OFF) {
215 mBluetoothEnabledByNfc = false;
216 mBluetoothHeadsetConnected = false;
217 }
218 }
219
220 @Override
221 public void onBluetoothPeripheralHandoverComplete(boolean connected) {
222 // Called on the main thread
223 int transport = mBluetoothPeripheralHandover.mTransport;
224 mBluetoothPeripheralHandover = null;
225 mBluetoothHeadsetConnected = connected;
226
227 // <hack> resume polling immediately if the connection failed,
228 // otherwise just wait for polling to come back up after the timeout
229 // This ensures we don't disconnect if the user left the volantis
230 // on the tag after pairing completed, which results in automatic
231 // disconnection </hack>
232 if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
233 if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
234 mHandler.removeMessages(MSG_PAUSE_POLLING);
235 }
236
237 // do this unconditionally as the polling could have been paused as we were removing
238 // the message in the handler. It's a no-op if polling is already enabled.
239 mNfcAdapter.resumePolling();
240 }
241 disableBluetoothIfNeeded();
Suhas Suresh999730c2018-04-25 12:15:51 +0530242 replyToClient(connected);
nxpandroid1153eb32015-11-06 18:46:58 +0530243
244 synchronized (sLock) {
245 stopSelf(mStartId);
246 mStartId = 0;
247 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530248 }
249
250
251 boolean enableBluetooth() {
252 if (!mBluetoothAdapter.isEnabled()) {
253 mBluetoothEnabledByNfc = true;
254 return mBluetoothAdapter.enableNoAutoConnect();
255 }
256 return true;
257 }
258
259 void disableBluetoothIfNeeded() {
260 if (!mBluetoothEnabledByNfc) return;
Suhas Suresh999730c2018-04-25 12:15:51 +0530261 if (hasConnectedBluetoothDevices()) return;
nxpandroid64fd68c2015-09-23 16:45:15 +0530262
263 if (!mBluetoothHeadsetConnected) {
264 mBluetoothAdapter.disable();
265 mBluetoothEnabledByNfc = false;
266 }
267 }
268
Suhas Suresh999730c2018-04-25 12:15:51 +0530269 boolean hasConnectedBluetoothDevices() {
270 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
271
272 if (bondedDevices != null) {
273 for (BluetoothDevice device : bondedDevices) {
274 if (device.equals(mDevice)) {
275 // Not required to check the remote BT "target" device
276 // connection status, because sometimes the connection
277 // state is not yet been updated upon disconnection.
278 // It is enough to check the connection status for
279 // "other" remote BT device/s.
280 continue;
281 }
282 if (device.isConnected()) return true;
283 }
284 }
285 return false;
286 }
287
288 void replyToClient(boolean connected) {
289 if (mClient == null) {
290 return;
291 }
292
293 final int msgId = connected ? MSG_HEADSET_CONNECTED : MSG_HEADSET_NOT_CONNECTED;
294 final Message msg = Message.obtain(null, msgId);
295 msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0;
296 try {
297 mClient.send(msg);
298 } catch (RemoteException e) {
299 // Ignore
300 }
301 }
302
nxpandroid64fd68c2015-09-23 16:45:15 +0530303 @Override
nxf500513a018e72019-04-23 17:11:41 +0530304 public IBinder onBind(Intent intent) {
305 return null;
306 }
307
308 @Override
nxpandroid64fd68c2015-09-23 16:45:15 +0530309 public boolean onUnbind(Intent intent) {
310 // prevent any future callbacks to the client, no rebind call needed.
311 return false;
312 }
313}