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.ParcelUuid; 35 import android.os.Parcelable; 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 synchronized (sLock) { 125 stopSelf(startId); 126 mStartId = 0; 127 } 128 return START_NOT_STICKY; 129 } 130 131 if (doPeripheralHandover(intent.getExtras())) { 132 return START_STICKY; 133 } else { 134 onBluetoothPeripheralHandoverComplete(false); 135 return START_NOT_STICKY; 136 } 137 } 138 139 @Override onCreate()140 public void onCreate() { 141 super.onCreate(); 142 mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); 143 144 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 145 registerReceiver(mBluetoothStatusReceiver, filter); 146 } 147 148 @Override onDestroy()149 public void onDestroy() { 150 super.onDestroy(); 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 mDevice = 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 mClient = msgData.getParcelable(EXTRA_CLIENT); 180 mBluetoothEnabledByNfc = msgData.getBoolean(EXTRA_BT_ENABLED); 181 182 mBluetoothPeripheralHandover = new BluetoothPeripheralHandover( 183 this, mDevice, name, transport, oobData, uuids, btClass, this); 184 185 if (transport == BluetoothDevice.TRANSPORT_LE) { 186 mHandler.sendMessageDelayed( 187 mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS); 188 } 189 if (mBluetoothAdapter.isEnabled()) { 190 if (!mBluetoothPeripheralHandover.start()) { 191 return false; 192 } 193 } else { 194 // Once BT is enabled, the headset pairing will be started 195 if (!enableBluetooth()) { 196 Log.e(TAG, "Error enabling Bluetooth."); 197 return false; 198 } 199 } 200 201 return true; 202 } 203 handleBluetoothStateChanged(Intent intent)204 private void handleBluetoothStateChanged(Intent intent) { 205 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 206 BluetoothAdapter.ERROR); 207 if (state == BluetoothAdapter.STATE_ON) { 208 // If there is a pending device pairing, start it 209 if (mBluetoothPeripheralHandover != null && 210 !mBluetoothPeripheralHandover.hasStarted()) { 211 if (!mBluetoothPeripheralHandover.start()) { 212 onBluetoothPeripheralHandoverComplete(false); 213 } 214 } 215 } 216 } 217 218 @Override onBluetoothPeripheralHandoverComplete(boolean connected)219 public void onBluetoothPeripheralHandoverComplete(boolean connected) { 220 // Called on the main thread 221 int transport = mBluetoothPeripheralHandover.mTransport; 222 mBluetoothPeripheralHandover = null; 223 mBluetoothHeadsetConnected = connected; 224 225 // <hack> resume polling immediately if the connection failed, 226 // otherwise just wait for polling to come back up after the timeout 227 // This ensures we don't disconnect if the user left the volantis 228 // on the tag after pairing completed, which results in automatic 229 // disconnection </hack> 230 if (transport == BluetoothDevice.TRANSPORT_LE && !connected) { 231 if (mHandler.hasMessages(MSG_PAUSE_POLLING)) { 232 mHandler.removeMessages(MSG_PAUSE_POLLING); 233 } 234 235 // do this unconditionally as the polling could have been paused as we were removing 236 // the message in the handler. It's a no-op if polling is already enabled. 237 mNfcAdapter.resumePolling(); 238 } 239 disableBluetoothIfNeeded(); 240 replyToClient(connected); 241 242 synchronized (sLock) { 243 stopSelf(mStartId); 244 mStartId = 0; 245 } 246 } 247 248 enableBluetooth()249 boolean enableBluetooth() { 250 if (!mBluetoothAdapter.isEnabled()) { 251 mBluetoothEnabledByNfc = true; 252 return mBluetoothAdapter.enableNoAutoConnect(); 253 } 254 return true; 255 } 256 disableBluetoothIfNeeded()257 void disableBluetoothIfNeeded() { 258 if (!mBluetoothEnabledByNfc) return; 259 if (hasConnectedBluetoothDevices()) return; 260 261 if (!mBluetoothHeadsetConnected) { 262 mBluetoothAdapter.disable(); 263 mBluetoothEnabledByNfc = false; 264 } 265 } 266 hasConnectedBluetoothDevices()267 boolean hasConnectedBluetoothDevices() { 268 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); 269 270 if (bondedDevices != null) { 271 for (BluetoothDevice device : bondedDevices) { 272 if (device.equals(mDevice)) { 273 // Not required to check the remote BT "target" device 274 // connection status, because sometimes the connection 275 // state is not yet been updated upon disconnection. 276 // It is enough to check the connection status for 277 // "other" remote BT device/s. 278 continue; 279 } 280 if (device.isConnected()) return true; 281 } 282 } 283 return false; 284 } 285 replyToClient(boolean connected)286 void replyToClient(boolean connected) { 287 if (mClient == null) { 288 return; 289 } 290 291 final int msgId = connected ? MSG_HEADSET_CONNECTED : MSG_HEADSET_NOT_CONNECTED; 292 final Message msg = Message.obtain(null, msgId); 293 msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0; 294 try { 295 mClient.send(msg); 296 } catch (RemoteException e) { 297 // Ignore 298 } 299 } 300 301 @Override onBind(Intent intent)302 public IBinder onBind(Intent intent) { 303 return null; 304 } 305 306 @Override onUnbind(Intent intent)307 public boolean onUnbind(Intent intent) { 308 // prevent any future callbacks to the client, no rebind call needed. 309 return false; 310 } 311 } 312