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