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.BluetoothDevice;
22 import android.bluetooth.OobData;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.media.AudioManager;
28 import android.media.SoundPool;
29 import android.nfc.NfcAdapter;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.Message;
34 import android.os.Messenger;
35 import android.os.RemoteException;
36 import android.util.Log;
37 
38 import com.android.nfc.R;
39 
40 public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback {
41     static final String TAG = "PeripheralHandoverService";
42     static final boolean DBG = true;
43 
44     static final int MSG_PAUSE_POLLING = 0;
45 
46     public static final String BUNDLE_TRANSFER = "transfer";
47     public static final String EXTRA_PERIPHERAL_DEVICE = "device";
48     public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
49     public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
50     public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
51 
52     // Amount of time to pause polling when connecting to peripherals
53     private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
54     private static final int PAUSE_DELAY_MILLIS = 300;
55 
56     private static final Object sLock = new Object();
57 
58     // Variables below only accessed on main thread
59     final Messenger mMessenger;
60 
61     SoundPool mSoundPool;
62     int mSuccessSound;
63     int mStartId;
64 
65     BluetoothAdapter mBluetoothAdapter;
66     NfcAdapter mNfcAdapter;
67     Handler mHandler;
68     BluetoothPeripheralHandover mBluetoothPeripheralHandover;
69     boolean mBluetoothHeadsetConnected;
70     boolean mBluetoothEnabledByNfc;
71 
72     class MessageHandler extends Handler {
73         @Override
handleMessage(Message msg)74         public void handleMessage(Message msg) {
75             switch (msg.what) {
76                 case MSG_PAUSE_POLLING:
77                     mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
78                     break;
79             }
80         }
81     }
82 
83     final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
84         @Override
85         public void onReceive(Context context, Intent intent) {
86             String action = intent.getAction();
87             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
88                 handleBluetoothStateChanged(intent);
89             }
90         }
91     };
92 
PeripheralHandoverService()93     public PeripheralHandoverService() {
94         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
95         mHandler = new MessageHandler();
96         mMessenger = new Messenger(mHandler);
97         mBluetoothHeadsetConnected = false;
98         mBluetoothEnabledByNfc = false;
99         mStartId = 0;
100     }
101 
102     @Override
onStartCommand(Intent intent, int flags, int startId)103     public int onStartCommand(Intent intent, int flags, int startId) {
104 
105         synchronized (sLock) {
106             if (mStartId != 0) {
107                 mStartId = startId;
108                 // already running
109                 return START_STICKY;
110             }
111             mStartId = startId;
112         }
113 
114         if (intent == null) {
115             if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
116             stopSelf(startId);
117             return START_NOT_STICKY;
118         }
119 
120         if (doPeripheralHandover(intent.getExtras())) {
121             return START_STICKY;
122         } else {
123             stopSelf(startId);
124             return START_NOT_STICKY;
125         }
126     }
127 
128     @Override
onCreate()129     public void onCreate() {
130         super.onCreate();
131 
132         mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
133         mSuccessSound = mSoundPool.load(this, R.raw.end, 1);
134         mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
135 
136         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
137         registerReceiver(mBluetoothStatusReceiver, filter);
138     }
139 
140     @Override
onDestroy()141     public void onDestroy() {
142         super.onDestroy();
143         if (mSoundPool != null) {
144             mSoundPool.release();
145         }
146         unregisterReceiver(mBluetoothStatusReceiver);
147     }
148 
doPeripheralHandover(Bundle msgData)149     boolean doPeripheralHandover(Bundle msgData) {
150         if (mBluetoothPeripheralHandover != null) {
151             Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
152             return true;
153         }
154 
155         if (msgData == null) {
156             return false;
157         }
158 
159         BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
160         String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
161         int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
162         OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
163 
164         mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
165                 this, device, name, transport, oobData, this);
166 
167         if (transport == BluetoothDevice.TRANSPORT_LE) {
168             mHandler.sendMessageDelayed(
169                     mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
170         }
171         if (mBluetoothAdapter.isEnabled()) {
172             if (!mBluetoothPeripheralHandover.start()) {
173                 mHandler.removeMessages(MSG_PAUSE_POLLING);
174                 mNfcAdapter.resumePolling();
175             }
176         } else {
177             // Once BT is enabled, the headset pairing will be started
178             if (!enableBluetooth()) {
179                 Log.e(TAG, "Error enabling Bluetooth.");
180                 mBluetoothPeripheralHandover = null;
181                 return false;
182             }
183         }
184 
185         return true;
186     }
187 
handleBluetoothStateChanged(Intent intent)188     private void handleBluetoothStateChanged(Intent intent) {
189         int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
190                 BluetoothAdapter.ERROR);
191         if (state == BluetoothAdapter.STATE_ON) {
192             // If there is a pending device pairing, start it
193             if (mBluetoothPeripheralHandover != null &&
194                     !mBluetoothPeripheralHandover.hasStarted()) {
195                 if (!mBluetoothPeripheralHandover.start()) {
196                     mNfcAdapter.resumePolling();
197                 }
198             }
199         } else if (state == BluetoothAdapter.STATE_OFF) {
200             mBluetoothEnabledByNfc = false;
201             mBluetoothHeadsetConnected = false;
202         }
203     }
204 
205     @Override
onBluetoothPeripheralHandoverComplete(boolean connected)206     public void onBluetoothPeripheralHandoverComplete(boolean connected) {
207         // Called on the main thread
208         int transport = mBluetoothPeripheralHandover.mTransport;
209         mBluetoothPeripheralHandover = null;
210         mBluetoothHeadsetConnected = connected;
211 
212         // <hack> resume polling immediately if the connection failed,
213         // otherwise just wait for polling to come back up after the timeout
214         // This ensures we don't disconnect if the user left the volantis
215         // on the tag after pairing completed, which results in automatic
216         // disconnection </hack>
217         if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
218             if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
219                 mHandler.removeMessages(MSG_PAUSE_POLLING);
220             }
221 
222             // do this unconditionally as the polling could have been paused as we were removing
223             // the message in the handler. It's a no-op if polling is already enabled.
224             mNfcAdapter.resumePolling();
225         }
226         disableBluetoothIfNeeded();
227 
228         synchronized (sLock) {
229             stopSelf(mStartId);
230             mStartId = 0;
231         }
232     }
233 
234 
enableBluetooth()235     boolean enableBluetooth() {
236         if (!mBluetoothAdapter.isEnabled()) {
237             mBluetoothEnabledByNfc = true;
238             return mBluetoothAdapter.enableNoAutoConnect();
239         }
240         return true;
241     }
242 
disableBluetoothIfNeeded()243     void disableBluetoothIfNeeded() {
244         if (!mBluetoothEnabledByNfc) return;
245 
246         if (!mBluetoothHeadsetConnected) {
247             mBluetoothAdapter.disable();
248             mBluetoothEnabledByNfc = false;
249         }
250     }
251 
252     @Override
onBind(Intent intent)253     public IBinder onBind(Intent intent) {
254         return null;
255     }
256 
257     @Override
onUnbind(Intent intent)258     public boolean onUnbind(Intent intent) {
259         // prevent any future callbacks to the client, no rebind call needed.
260         return false;
261     }
262 }
263