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 android.app.Service;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothClass;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.OobData;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.nfc.NfcAdapter;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Message;
33 import android.os.Messenger;
34 import android.os.ParcelUuid;
35 import android.os.Parcelable;
36 import android.os.RemoteException;
37 import android.util.Log;
38 
39 import java.util.Set;
40 
41 public 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";
51     public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
52     public static final String EXTRA_PERIPHERAL_UUIDS = "uuids";
53     public static final String EXTRA_PERIPHERAL_CLASS = "class";
54     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;
59 
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 
64     private static final Object sLock = new Object();
65 
66     // Variables below only accessed on main thread
67     final Messenger mMessenger;
68 
69     int mStartId;
70 
71     BluetoothAdapter mBluetoothAdapter;
72     NfcAdapter mNfcAdapter;
73     Handler mHandler;
74     BluetoothPeripheralHandover mBluetoothPeripheralHandover;
75     BluetoothDevice mDevice;
76     Messenger mClient;
77     boolean mBluetoothHeadsetConnected;
78     boolean mBluetoothEnabledByNfc;
79 
80     class MessageHandler extends Handler {
81         @Override
handleMessage(Message msg)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         }
99     };
100 
PeripheralHandoverService()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
onStartCommand(Intent intent, int flags, int startId)111     public int onStartCommand(Intent intent, int flags, int startId) {
112 
113         synchronized (sLock) {
114             if (mStartId != 0) {
115                 mStartId = startId;
116                 // already running
117                 return START_STICKY;
118             }
119             mStartId = startId;
120         }
121 
122         if (intent == null) {
123             if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
124             synchronized (sLock) {
125                 stopSelf(startId);
126                 mStartId = 0;
127             }
128             return START_NOT_STICKY;
129         }
130 
131         if (doPeripheralHandover(intent.getExtras())) {
132             return START_STICKY;
133         } else {
134             onBluetoothPeripheralHandoverComplete(false);
135             return START_NOT_STICKY;
136         }
137     }
138 
139     @Override
onCreate()140     public void onCreate() {
141         super.onCreate();
142         mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
143 
144         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
145         registerReceiver(mBluetoothStatusReceiver, filter);
146     }
147 
148     @Override
onDestroy()149     public void onDestroy() {
150         super.onDestroy();
151         unregisterReceiver(mBluetoothStatusReceiver);
152     }
153 
doPeripheralHandover(Bundle msgData)154     boolean doPeripheralHandover(Bundle msgData) {
155         if (mBluetoothPeripheralHandover != null) {
156             Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
157             return true;
158         }
159 
160         if (msgData == null) {
161             return false;
162         }
163 
164         mDevice = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
165         String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
166         int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
167         OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
168         Parcelable[] parcelables = msgData.getParcelableArray(EXTRA_PERIPHERAL_UUIDS);
169         BluetoothClass btClass = msgData.getParcelable(EXTRA_PERIPHERAL_CLASS);
170 
171         ParcelUuid[] uuids = null;
172         if (parcelables != null) {
173             uuids = new ParcelUuid[parcelables.length];
174             for (int i = 0; i < parcelables.length; i++) {
175                 uuids[i] = (ParcelUuid)parcelables[i];
176             }
177         }
178 
179         mClient = msgData.getParcelable(EXTRA_CLIENT);
180         mBluetoothEnabledByNfc = msgData.getBoolean(EXTRA_BT_ENABLED);
181 
182         mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
183                 this, mDevice, name, transport, oobData, uuids, btClass, this);
184 
185         if (transport == BluetoothDevice.TRANSPORT_LE) {
186             mHandler.sendMessageDelayed(
187                     mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
188         }
189         if (mBluetoothAdapter.isEnabled()) {
190             if (!mBluetoothPeripheralHandover.start()) {
191                 return false;
192             }
193         } else {
194             // Once BT is enabled, the headset pairing will be started
195             if (!enableBluetooth()) {
196                 Log.e(TAG, "Error enabling Bluetooth.");
197                 return false;
198             }
199         }
200 
201         return true;
202     }
203 
handleBluetoothStateChanged(Intent intent)204     private void handleBluetoothStateChanged(Intent intent) {
205         int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
206                 BluetoothAdapter.ERROR);
207         if (state == BluetoothAdapter.STATE_ON) {
208             // If there is a pending device pairing, start it
209             if (mBluetoothPeripheralHandover != null &&
210                     !mBluetoothPeripheralHandover.hasStarted()) {
211                 if (!mBluetoothPeripheralHandover.start()) {
212                     onBluetoothPeripheralHandoverComplete(false);
213                 }
214             }
215         }
216     }
217 
218     @Override
onBluetoothPeripheralHandoverComplete(boolean connected)219     public void onBluetoothPeripheralHandoverComplete(boolean connected) {
220         // Called on the main thread
221         int transport = mBluetoothPeripheralHandover.mTransport;
222         mBluetoothPeripheralHandover = null;
223         mBluetoothHeadsetConnected = connected;
224 
225         // <hack> resume polling immediately if the connection failed,
226         // otherwise just wait for polling to come back up after the timeout
227         // This ensures we don't disconnect if the user left the volantis
228         // on the tag after pairing completed, which results in automatic
229         // disconnection </hack>
230         if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
231             if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
232                 mHandler.removeMessages(MSG_PAUSE_POLLING);
233             }
234 
235             // do this unconditionally as the polling could have been paused as we were removing
236             // the message in the handler. It's a no-op if polling is already enabled.
237             mNfcAdapter.resumePolling();
238         }
239         disableBluetoothIfNeeded();
240         replyToClient(connected);
241 
242         synchronized (sLock) {
243             stopSelf(mStartId);
244             mStartId = 0;
245         }
246     }
247 
248 
enableBluetooth()249     boolean enableBluetooth() {
250         if (!mBluetoothAdapter.isEnabled()) {
251             mBluetoothEnabledByNfc = true;
252             return mBluetoothAdapter.enableNoAutoConnect();
253         }
254         return true;
255     }
256 
disableBluetoothIfNeeded()257     void disableBluetoothIfNeeded() {
258         if (!mBluetoothEnabledByNfc) return;
259         if (hasConnectedBluetoothDevices()) return;
260 
261         if (!mBluetoothHeadsetConnected) {
262             mBluetoothAdapter.disable();
263             mBluetoothEnabledByNfc = false;
264         }
265     }
266 
hasConnectedBluetoothDevices()267     boolean hasConnectedBluetoothDevices() {
268         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
269 
270         if (bondedDevices != null) {
271             for (BluetoothDevice device : bondedDevices) {
272                 if (device.equals(mDevice)) {
273                     // Not required to check the remote BT "target" device
274                     // connection status, because sometimes the connection
275                     // state is not yet been updated upon disconnection.
276                     // It is enough to check the connection status for
277                     // "other" remote BT device/s.
278                     continue;
279                 }
280                 if (device.isConnected()) return true;
281             }
282         }
283         return false;
284     }
285 
replyToClient(boolean connected)286     void replyToClient(boolean connected) {
287         if (mClient == null) {
288             return;
289         }
290 
291         final int msgId = connected ? MSG_HEADSET_CONNECTED : MSG_HEADSET_NOT_CONNECTED;
292         final Message msg = Message.obtain(null, msgId);
293         msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0;
294         try {
295             mClient.send(msg);
296         } catch (RemoteException e) {
297             // Ignore
298         }
299     }
300 
301     @Override
onBind(Intent intent)302     public IBinder onBind(Intent intent) {
303         return null;
304     }
305 
306     @Override
onUnbind(Intent intent)307     public boolean onUnbind(Intent intent) {
308         // prevent any future callbacks to the client, no rebind call needed.
309         return false;
310     }
311 }
312