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.media.AudioManager;
29 import android.media.SoundPool;
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.Parcelable;
37 import android.os.ParcelUuid;
38 import android.os.RemoteException;
39 import android.util.Log;
40 
41 import com.android.nfc.R;
42 
43 public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback {
44     static final String TAG = "PeripheralHandoverService";
45     static final boolean DBG = true;
46 
47     static final int MSG_PAUSE_POLLING = 0;
48 
49     public static final String BUNDLE_TRANSFER = "transfer";
50     public static final String EXTRA_PERIPHERAL_DEVICE = "device";
51     public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
52     public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
53     public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
54     public static final String EXTRA_PERIPHERAL_UUIDS = "uuids";
55     public static final String EXTRA_PERIPHERAL_CLASS = "class";
56 
57     // Amount of time to pause polling when connecting to peripherals
58     private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
59     private static final int PAUSE_DELAY_MILLIS = 300;
60 
61     private static final Object sLock = new Object();
62 
63     // Variables below only accessed on main thread
64     final Messenger mMessenger;
65 
66     SoundPool mSoundPool;
67     int mSuccessSound;
68     int mStartId;
69 
70     BluetoothAdapter mBluetoothAdapter;
71     NfcAdapter mNfcAdapter;
72     Handler mHandler;
73     BluetoothPeripheralHandover mBluetoothPeripheralHandover;
74     boolean mBluetoothHeadsetConnected;
75     boolean mBluetoothEnabledByNfc;
76 
77     class MessageHandler extends Handler {
78         @Override
handleMessage(Message msg)79         public void handleMessage(Message msg) {
80             switch (msg.what) {
81                 case MSG_PAUSE_POLLING:
82                     mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
83                     break;
84             }
85         }
86     }
87 
88     final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
89         @Override
90         public void onReceive(Context context, Intent intent) {
91             String action = intent.getAction();
92             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
93                 handleBluetoothStateChanged(intent);
94             }
95         }
96     };
97 
PeripheralHandoverService()98     public PeripheralHandoverService() {
99         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
100         mHandler = new MessageHandler();
101         mMessenger = new Messenger(mHandler);
102         mBluetoothHeadsetConnected = false;
103         mBluetoothEnabledByNfc = false;
104         mStartId = 0;
105     }
106 
107     @Override
onStartCommand(Intent intent, int flags, int startId)108     public int onStartCommand(Intent intent, int flags, int startId) {
109 
110         synchronized (sLock) {
111             if (mStartId != 0) {
112                 mStartId = startId;
113                 // already running
114                 return START_STICKY;
115             }
116             mStartId = startId;
117         }
118 
119         if (intent == null) {
120             if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
121             stopSelf(startId);
122             return START_NOT_STICKY;
123         }
124 
125         if (doPeripheralHandover(intent.getExtras())) {
126             return START_STICKY;
127         } else {
128             stopSelf(startId);
129             return START_NOT_STICKY;
130         }
131     }
132 
133     @Override
onCreate()134     public void onCreate() {
135         super.onCreate();
136 
137         mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
138         mSuccessSound = mSoundPool.load(this, R.raw.end, 1);
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         if (mSoundPool != null) {
149             mSoundPool.release();
150         }
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         BluetoothDevice device = 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         mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
180                 this, device, 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         } else if (state == BluetoothAdapter.STATE_OFF) {
215             mBluetoothEnabledByNfc = false;
216             mBluetoothHeadsetConnected = false;
217         }
218     }
219 
220     @Override
onBluetoothPeripheralHandoverComplete(boolean connected)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();
242 
243         synchronized (sLock) {
244             stopSelf(mStartId);
245             mStartId = 0;
246         }
247     }
248 
249 
enableBluetooth()250     boolean enableBluetooth() {
251         if (!mBluetoothAdapter.isEnabled()) {
252             mBluetoothEnabledByNfc = true;
253             return mBluetoothAdapter.enableNoAutoConnect();
254         }
255         return true;
256     }
257 
disableBluetoothIfNeeded()258     void disableBluetoothIfNeeded() {
259         if (!mBluetoothEnabledByNfc) return;
260 
261         if (!mBluetoothHeadsetConnected) {
262             mBluetoothAdapter.disable();
263             mBluetoothEnabledByNfc = false;
264         }
265     }
266 
267     @Override
onBind(Intent intent)268     public IBinder onBind(Intent intent) {
269         return null;
270     }
271 
272     @Override
onUnbind(Intent intent)273     public boolean onUnbind(Intent intent) {
274         // prevent any future callbacks to the client, no rebind call needed.
275         return false;
276     }
277 }
278