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