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.media.AudioManager; 29 import android.media.SoundPool; 30 import android.nfc.NfcAdapter; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.Message; 35 import android.os.Messenger; 36 import android.os.Parcelable; 37 import android.os.ParcelUuid; 38 import android.os.RemoteException; 39 import android.util.Log; 40 41 import com.android.nfc.R; 42 43 public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback { 44 static final String TAG = "PeripheralHandoverService"; 45 static final boolean DBG = true; 46 47 static final int MSG_PAUSE_POLLING = 0; 48 49 public static final String BUNDLE_TRANSFER = "transfer"; 50 public static final String EXTRA_PERIPHERAL_DEVICE = "device"; 51 public static final String EXTRA_PERIPHERAL_NAME = "headsetname"; 52 public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype"; 53 public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata"; 54 public static final String EXTRA_PERIPHERAL_UUIDS = "uuids"; 55 public static final String EXTRA_PERIPHERAL_CLASS = "class"; 56 57 // Amount of time to pause polling when connecting to peripherals 58 private static final int PAUSE_POLLING_TIMEOUT_MS = 35000; 59 private static final int PAUSE_DELAY_MILLIS = 300; 60 61 private static final Object sLock = new Object(); 62 63 // Variables below only accessed on main thread 64 final Messenger mMessenger; 65 66 SoundPool mSoundPool; 67 int mSuccessSound; 68 int mStartId; 69 70 BluetoothAdapter mBluetoothAdapter; 71 NfcAdapter mNfcAdapter; 72 Handler mHandler; 73 BluetoothPeripheralHandover mBluetoothPeripheralHandover; 74 boolean mBluetoothHeadsetConnected; 75 boolean mBluetoothEnabledByNfc; 76 77 class MessageHandler extends Handler { 78 @Override handleMessage(Message msg)79 public void handleMessage(Message msg) { 80 switch (msg.what) { 81 case MSG_PAUSE_POLLING: 82 mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS); 83 break; 84 } 85 } 86 } 87 88 final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() { 89 @Override 90 public void onReceive(Context context, Intent intent) { 91 String action = intent.getAction(); 92 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 93 handleBluetoothStateChanged(intent); 94 } 95 } 96 }; 97 PeripheralHandoverService()98 public PeripheralHandoverService() { 99 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 100 mHandler = new MessageHandler(); 101 mMessenger = new Messenger(mHandler); 102 mBluetoothHeadsetConnected = false; 103 mBluetoothEnabledByNfc = false; 104 mStartId = 0; 105 } 106 107 @Override onStartCommand(Intent intent, int flags, int startId)108 public int onStartCommand(Intent intent, int flags, int startId) { 109 110 synchronized (sLock) { 111 if (mStartId != 0) { 112 mStartId = startId; 113 // already running 114 return START_STICKY; 115 } 116 mStartId = startId; 117 } 118 119 if (intent == null) { 120 if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover."); 121 stopSelf(startId); 122 return START_NOT_STICKY; 123 } 124 125 if (doPeripheralHandover(intent.getExtras())) { 126 return START_STICKY; 127 } else { 128 stopSelf(startId); 129 return START_NOT_STICKY; 130 } 131 } 132 133 @Override onCreate()134 public void onCreate() { 135 super.onCreate(); 136 137 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); 138 mSuccessSound = mSoundPool.load(this, R.raw.end, 1); 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 if (mSoundPool != null) { 149 mSoundPool.release(); 150 } 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 BluetoothDevice device = 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 mBluetoothPeripheralHandover = new BluetoothPeripheralHandover( 180 this, device, 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 } else if (state == BluetoothAdapter.STATE_OFF) { 215 mBluetoothEnabledByNfc = false; 216 mBluetoothHeadsetConnected = false; 217 } 218 } 219 220 @Override onBluetoothPeripheralHandoverComplete(boolean connected)221 public void onBluetoothPeripheralHandoverComplete(boolean connected) { 222 // Called on the main thread 223 int transport = mBluetoothPeripheralHandover.mTransport; 224 mBluetoothPeripheralHandover = null; 225 mBluetoothHeadsetConnected = connected; 226 227 // <hack> resume polling immediately if the connection failed, 228 // otherwise just wait for polling to come back up after the timeout 229 // This ensures we don't disconnect if the user left the volantis 230 // on the tag after pairing completed, which results in automatic 231 // disconnection </hack> 232 if (transport == BluetoothDevice.TRANSPORT_LE && !connected) { 233 if (mHandler.hasMessages(MSG_PAUSE_POLLING)) { 234 mHandler.removeMessages(MSG_PAUSE_POLLING); 235 } 236 237 // do this unconditionally as the polling could have been paused as we were removing 238 // the message in the handler. It's a no-op if polling is already enabled. 239 mNfcAdapter.resumePolling(); 240 } 241 disableBluetoothIfNeeded(); 242 243 synchronized (sLock) { 244 stopSelf(mStartId); 245 mStartId = 0; 246 } 247 } 248 249 enableBluetooth()250 boolean enableBluetooth() { 251 if (!mBluetoothAdapter.isEnabled()) { 252 mBluetoothEnabledByNfc = true; 253 return mBluetoothAdapter.enableNoAutoConnect(); 254 } 255 return true; 256 } 257 disableBluetoothIfNeeded()258 void disableBluetoothIfNeeded() { 259 if (!mBluetoothEnabledByNfc) return; 260 261 if (!mBluetoothHeadsetConnected) { 262 mBluetoothAdapter.disable(); 263 mBluetoothEnabledByNfc = false; 264 } 265 } 266 267 @Override onBind(Intent intent)268 public IBinder onBind(Intent intent) { 269 return null; 270 } 271 272 @Override onUnbind(Intent intent)273 public boolean onUnbind(Intent intent) { 274 // prevent any future callbacks to the client, no rebind call needed. 275 return false; 276 } 277 } 278