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