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