• 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 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.Parcelable;
35 import android.os.ParcelUuid;
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             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
onCreate()137     public void onCreate() {
138         super.onCreate();
139         mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
140 
141         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
142         registerReceiver(mBluetoothStatusReceiver, filter);
143     }
144 
145     @Override
onDestroy()146     public void onDestroy() {
147         super.onDestroy();
148         unregisterReceiver(mBluetoothStatusReceiver);
149     }
150 
doPeripheralHandover(Bundle msgData)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 
161         mDevice = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
162         String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
163         int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
164         OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
165         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         }
175 
176         mClient = msgData.getParcelable(EXTRA_CLIENT);
177         mBluetoothEnabledByNfc = msgData.getBoolean(EXTRA_BT_ENABLED);
178 
179         mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
180                 this, mDevice, name, transport, oobData, uuids, btClass, this);
181 
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 
handleBluetoothStateChanged(Intent intent)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         }
215     }
216 
217     @Override
onBluetoothPeripheralHandoverComplete(boolean connected)218     public void onBluetoothPeripheralHandoverComplete(boolean connected) {
219         // Called on the main thread
220         int transport = mBluetoothPeripheralHandover.mTransport;
221         mBluetoothPeripheralHandover = null;
222         mBluetoothHeadsetConnected = connected;
223 
224         // <hack> resume polling immediately if the connection failed,
225         // otherwise just wait for polling to come back up after the timeout
226         // This ensures we don't disconnect if the user left the volantis
227         // on the tag after pairing completed, which results in automatic
228         // disconnection </hack>
229         if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
230             if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
231                 mHandler.removeMessages(MSG_PAUSE_POLLING);
232             }
233 
234             // do this unconditionally as the polling could have been paused as we were removing
235             // the message in the handler. It's a no-op if polling is already enabled.
236             mNfcAdapter.resumePolling();
237         }
238         disableBluetoothIfNeeded();
239         replyToClient(connected);
240 
241         synchronized (sLock) {
242             stopSelf(mStartId);
243             mStartId = 0;
244         }
245     }
246 
247 
enableBluetooth()248     boolean enableBluetooth() {
249         if (!mBluetoothAdapter.isEnabled()) {
250             mBluetoothEnabledByNfc = true;
251             return mBluetoothAdapter.enableNoAutoConnect();
252         }
253         return true;
254     }
255 
disableBluetoothIfNeeded()256     void disableBluetoothIfNeeded() {
257         if (!mBluetoothEnabledByNfc) return;
258         if (hasConnectedBluetoothDevices()) return;
259 
260         if (!mBluetoothHeadsetConnected) {
261             mBluetoothAdapter.disable();
262             mBluetoothEnabledByNfc = false;
263         }
264     }
265 
hasConnectedBluetoothDevices()266     boolean hasConnectedBluetoothDevices() {
267         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
268 
269         if (bondedDevices != null) {
270             for (BluetoothDevice device : bondedDevices) {
271                 if (device.equals(mDevice)) {
272                     // Not required to check the remote BT "target" device
273                     // connection status, because sometimes the connection
274                     // state is not yet been updated upon disconnection.
275                     // It is enough to check the connection status for
276                     // "other" remote BT device/s.
277                     continue;
278                 }
279                 if (device.isConnected()) return true;
280             }
281         }
282         return false;
283     }
284 
replyToClient(boolean connected)285     void replyToClient(boolean connected) {
286         if (mClient == null) {
287             return;
288         }
289 
290         final int msgId = connected ? MSG_HEADSET_CONNECTED : MSG_HEADSET_NOT_CONNECTED;
291         final Message msg = Message.obtain(null, msgId);
292         msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0;
293         try {
294             mClient.send(msg);
295         } catch (RemoteException e) {
296             // Ignore
297         }
298     }
299 
300     @Override
onBind(Intent intent)301     public IBinder onBind(Intent intent) {
302         return null;
303     }
304 
305     @Override
onUnbind(Intent intent)306     public boolean onUnbind(Intent intent) {
307         // prevent any future callbacks to the client, no rebind call needed.
308         return false;
309     }
310 }
311