1 package com.android.nfc.handover; 2 3 import android.app.Service; 4 import android.bluetooth.BluetoothAdapter; 5 import android.bluetooth.BluetoothDevice; 6 import android.content.BroadcastReceiver; 7 import android.content.Context; 8 import android.content.Intent; 9 import android.content.IntentFilter; 10 import android.media.AudioManager; 11 import android.media.SoundPool; 12 import android.net.Uri; 13 import android.os.Bundle; 14 import android.os.Handler; 15 import android.os.IBinder; 16 import android.os.Message; 17 import android.os.Messenger; 18 import android.os.RemoteException; 19 import android.util.Log; 20 import android.util.Pair; 21 22 import com.android.nfc.R; 23 24 import java.io.File; 25 import java.util.HashMap; 26 import java.util.Iterator; 27 import java.util.LinkedList; 28 import java.util.Map; 29 import java.util.Queue; 30 31 public class HandoverService extends Service implements HandoverTransfer.Callback, 32 BluetoothHeadsetHandover.Callback { 33 34 static final String TAG = "HandoverService"; 35 static final boolean DBG = true; 36 37 static final int MSG_REGISTER_CLIENT = 0; 38 static final int MSG_DEREGISTER_CLIENT = 1; 39 static final int MSG_START_INCOMING_TRANSFER = 2; 40 static final int MSG_START_OUTGOING_TRANSFER = 3; 41 static final int MSG_HEADSET_HANDOVER = 4; 42 43 static final String BUNDLE_TRANSFER = "transfer"; 44 45 static final String EXTRA_HEADSET_DEVICE = "device"; 46 static final String EXTRA_HEADSET_NAME = "headsetname"; 47 48 static final String ACTION_CANCEL_HANDOVER_TRANSFER = 49 "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER"; 50 51 static final String EXTRA_SOURCE_ADDRESS = 52 "com.android.nfc.handover.extra.SOURCE_ADDRESS"; 53 54 static final String EXTRA_INCOMING = 55 "com.android.nfc.handover.extra.INCOMING"; 56 57 static final String ACTION_HANDOVER_STARTED = 58 "android.btopp.intent.action.BT_OPP_HANDOVER_STARTED"; 59 60 static final String ACTION_BT_OPP_TRANSFER_PROGRESS = 61 "android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS"; 62 63 static final String ACTION_BT_OPP_TRANSFER_DONE = 64 "android.btopp.intent.action.BT_OPP_TRANSFER_DONE"; 65 66 static final String EXTRA_BT_OPP_TRANSFER_STATUS = 67 "android.btopp.intent.extra.BT_OPP_TRANSFER_STATUS"; 68 69 static final String EXTRA_BT_OPP_TRANSFER_MIMETYPE = 70 "android.btopp.intent.extra.BT_OPP_TRANSFER_MIMETYPE"; 71 72 static final String EXTRA_BT_OPP_ADDRESS = 73 "android.btopp.intent.extra.BT_OPP_ADDRESS"; 74 75 static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0; 76 77 static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1; 78 79 static final String EXTRA_BT_OPP_TRANSFER_DIRECTION = 80 "android.btopp.intent.extra.BT_OPP_TRANSFER_DIRECTION"; 81 82 static final int DIRECTION_BLUETOOTH_INCOMING = 0; 83 84 static final int DIRECTION_BLUETOOTH_OUTGOING = 1; 85 86 static final String EXTRA_BT_OPP_TRANSFER_ID = 87 "android.btopp.intent.extra.BT_OPP_TRANSFER_ID"; 88 89 static final String EXTRA_BT_OPP_TRANSFER_PROGRESS = 90 "android.btopp.intent.extra.BT_OPP_TRANSFER_PROGRESS"; 91 92 static final String EXTRA_BT_OPP_TRANSFER_URI = 93 "android.btopp.intent.extra.BT_OPP_TRANSFER_URI"; 94 95 public static final String EXTRA_BT_OPP_OBJECT_COUNT = 96 "android.btopp.intent.extra.BT_OPP_OBJECT_COUNT"; 97 98 // permission needed to be able to receive handover status requests 99 static final String HANDOVER_STATUS_PERMISSION = 100 "com.android.permission.HANDOVER_STATUS"; 101 102 // Variables below only accessed on main thread 103 final Queue<BluetoothOppHandover> mPendingOutTransfers; 104 final HashMap<Pair<String, Boolean>, HandoverTransfer> mTransfers; 105 final Messenger mMessenger; 106 107 SoundPool mSoundPool; 108 int mSuccessSound; 109 110 BluetoothAdapter mBluetoothAdapter; 111 Messenger mClient; 112 Handler mHandler; 113 BluetoothHeadsetHandover mBluetoothHeadsetHandover; 114 boolean mBluetoothHeadsetConnected; 115 boolean mBluetoothEnabledByNfc; 116 117 public HandoverService() { 118 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 119 mPendingOutTransfers = new LinkedList<BluetoothOppHandover>(); 120 mTransfers = new HashMap<Pair<String, Boolean>, HandoverTransfer>(); 121 mHandler = new MessageHandler(); 122 mMessenger = new Messenger(mHandler); 123 mBluetoothHeadsetConnected = false; 124 mBluetoothEnabledByNfc = false; 125 } 126 127 @Override 128 public void onCreate() { 129 super.onCreate(); 130 131 mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); 132 mSuccessSound = mSoundPool.load(this, R.raw.end, 1); 133 134 IntentFilter filter = new IntentFilter(ACTION_BT_OPP_TRANSFER_DONE); 135 filter.addAction(ACTION_BT_OPP_TRANSFER_PROGRESS); 136 filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER); 137 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 138 filter.addAction(ACTION_HANDOVER_STARTED); 139 registerReceiver(mReceiver, filter, HANDOVER_STATUS_PERMISSION, mHandler); 140 } 141 142 @Override 143 public void onDestroy() { 144 super.onDestroy(); 145 if (mSoundPool != null) { 146 mSoundPool.release(); 147 } 148 unregisterReceiver(mReceiver); 149 } 150 151 void doOutgoingTransfer(Message msg) { 152 Bundle msgData = msg.getData(); 153 154 msgData.setClassLoader(getClassLoader()); 155 PendingHandoverTransfer pendingTransfer = (PendingHandoverTransfer) 156 msgData.getParcelable(BUNDLE_TRANSFER); 157 createHandoverTransfer(pendingTransfer); 158 159 // Create the actual transfer 160 BluetoothOppHandover handover = new BluetoothOppHandover(HandoverService.this, 161 pendingTransfer.remoteDevice, pendingTransfer.uris, 162 pendingTransfer.remoteActivating); 163 if (mBluetoothAdapter.isEnabled()) { 164 // Start the transfer 165 handover.start(); 166 } else { 167 if (!enableBluetooth()) { 168 Log.e(TAG, "Error enabling Bluetooth."); 169 notifyClientTransferComplete(pendingTransfer.id); 170 return; 171 } 172 if (DBG) Log.d(TAG, "Queueing out transfer " + Integer.toString(pendingTransfer.id)); 173 mPendingOutTransfers.add(handover); 174 // Queue the transfer and enable Bluetooth - when it is enabled 175 // the transfer will be started. 176 } 177 } 178 179 void doIncomingTransfer(Message msg) { 180 Bundle msgData = msg.getData(); 181 182 msgData.setClassLoader(getClassLoader()); 183 PendingHandoverTransfer pendingTransfer = (PendingHandoverTransfer) 184 msgData.getParcelable(BUNDLE_TRANSFER); 185 if (!mBluetoothAdapter.isEnabled() && !enableBluetooth()) { 186 Log.e(TAG, "Error enabling Bluetooth."); 187 notifyClientTransferComplete(pendingTransfer.id); 188 return; 189 } 190 createHandoverTransfer(pendingTransfer); 191 // Remote device will connect and finish the transfer 192 } 193 194 void doHeadsetHandover(Message msg) { 195 Bundle msgData = msg.getData(); 196 BluetoothDevice device = (BluetoothDevice) msgData.getParcelable(EXTRA_HEADSET_DEVICE); 197 String name = (String) msgData.getString(EXTRA_HEADSET_NAME); 198 mBluetoothHeadsetHandover = new BluetoothHeadsetHandover(HandoverService.this, 199 device, name, HandoverService.this); 200 if (mBluetoothAdapter.isEnabled()) { 201 mBluetoothHeadsetHandover.start(); 202 } else { 203 // Once BT is enabled, the headset pairing will be started 204 if (!enableBluetooth()) { 205 Log.e(TAG, "Error enabling Bluetooth."); 206 mBluetoothHeadsetHandover = null; 207 } 208 } 209 } 210 211 void startPendingTransfers() { 212 while (!mPendingOutTransfers.isEmpty()) { 213 BluetoothOppHandover handover = mPendingOutTransfers.remove(); 214 handover.start(); 215 } 216 } 217 218 class MessageHandler extends Handler { 219 @Override 220 public void handleMessage(Message msg) { 221 switch (msg.what) { 222 case MSG_REGISTER_CLIENT: 223 mClient = msg.replyTo; 224 break; 225 case MSG_DEREGISTER_CLIENT: 226 mClient = null; 227 break; 228 case MSG_START_INCOMING_TRANSFER: 229 doIncomingTransfer(msg); 230 break; 231 case MSG_START_OUTGOING_TRANSFER: 232 doOutgoingTransfer(msg); 233 break; 234 case MSG_HEADSET_HANDOVER: 235 doHeadsetHandover(msg); 236 break; 237 } 238 } 239 } 240 241 boolean enableBluetooth() { 242 if (!mBluetoothAdapter.isEnabled()) { 243 mBluetoothEnabledByNfc = true; 244 return mBluetoothAdapter.enableNoAutoConnect(); 245 } 246 return true; 247 } 248 249 void disableBluetoothIfNeeded() { 250 if (!mBluetoothEnabledByNfc) return; 251 252 if (mTransfers.size() == 0 && !mBluetoothHeadsetConnected) { 253 mBluetoothAdapter.disable(); 254 mBluetoothEnabledByNfc = false; 255 } 256 } 257 258 void createHandoverTransfer(PendingHandoverTransfer pendingTransfer) { 259 Pair<String, Boolean> key = new Pair<String, Boolean>( 260 pendingTransfer.remoteDevice.getAddress(), pendingTransfer.incoming); 261 if (mTransfers.containsKey(key)) { 262 HandoverTransfer transfer = mTransfers.get(key); 263 if (!transfer.isRunning()) { 264 mTransfers.remove(key); // new one created below 265 } else { 266 // There is already a transfer running to this 267 // device - it will automatically get combined 268 // with the existing transfer. 269 notifyClientTransferComplete(pendingTransfer.id); 270 return; 271 } 272 } 273 274 HandoverTransfer transfer = new HandoverTransfer(this, this, pendingTransfer); 275 mTransfers.put(key, transfer); 276 transfer.updateNotification(); 277 } 278 279 HandoverTransfer findHandoverTransfer(String sourceAddress, boolean incoming) { 280 Pair<String, Boolean> key = new Pair<String, Boolean>(sourceAddress, incoming); 281 if (mTransfers.containsKey(key)) { 282 HandoverTransfer transfer = mTransfers.get(key); 283 if (transfer.isRunning()) { 284 return transfer; 285 } 286 } 287 return null; 288 } 289 290 @Override 291 public IBinder onBind(Intent intent) { 292 return mMessenger.getBinder(); 293 } 294 295 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 296 @Override 297 public void onReceive(Context context, Intent intent) { 298 String action = intent.getAction(); 299 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 300 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 301 BluetoothAdapter.ERROR); 302 if (state == BluetoothAdapter.STATE_ON) { 303 // If there is a pending headset pairing, start it 304 if (mBluetoothHeadsetHandover != null && 305 !mBluetoothHeadsetHandover.hasStarted()) { 306 mBluetoothHeadsetHandover.start(); 307 } 308 309 // Start any pending file transfers 310 startPendingTransfers(); 311 } else if (state == BluetoothAdapter.STATE_OFF) { 312 mBluetoothEnabledByNfc = false; 313 mBluetoothHeadsetConnected = false; 314 } 315 } else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) { 316 String sourceAddress = intent.getStringExtra(EXTRA_SOURCE_ADDRESS); 317 boolean incoming = (intent.getIntExtra(EXTRA_INCOMING, 1)) == 1; 318 HandoverTransfer transfer = findHandoverTransfer(sourceAddress, incoming); 319 if (transfer != null) { 320 if (DBG) Log.d(TAG, "Cancelling transfer " + 321 Integer.toString(transfer.mTransferId)); 322 transfer.cancel(); 323 } 324 } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS) || 325 action.equals(ACTION_BT_OPP_TRANSFER_DONE) || 326 action.equals(ACTION_HANDOVER_STARTED)) { 327 int direction = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_DIRECTION, -1); 328 int id = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_ID, -1); 329 if (action.equals(ACTION_HANDOVER_STARTED)) { 330 // This is always for incoming transfers 331 direction = DIRECTION_BLUETOOTH_INCOMING; 332 } 333 String sourceAddress = intent.getStringExtra(EXTRA_BT_OPP_ADDRESS); 334 335 if (direction == -1 || sourceAddress == null) return; 336 boolean incoming = (direction == DIRECTION_BLUETOOTH_INCOMING); 337 338 HandoverTransfer transfer = findHandoverTransfer(sourceAddress, incoming); 339 if (transfer == null) { 340 // There is no transfer running for this source address; most likely 341 // the transfer was cancelled. We need to tell BT OPP to stop transferring. 342 if (id != -1) { 343 if (DBG) Log.d(TAG, "Didn't find transfer, stopping"); 344 Intent cancelIntent = new Intent( 345 "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"); 346 cancelIntent.putExtra(EXTRA_BT_OPP_TRANSFER_ID, id); 347 sendBroadcast(cancelIntent); 348 } 349 return; 350 } 351 if (action.equals(ACTION_BT_OPP_TRANSFER_DONE)) { 352 int handoverStatus = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_STATUS, 353 HANDOVER_TRANSFER_STATUS_FAILURE); 354 if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) { 355 String uriString = intent.getStringExtra(EXTRA_BT_OPP_TRANSFER_URI); 356 String mimeType = intent.getStringExtra(EXTRA_BT_OPP_TRANSFER_MIMETYPE); 357 Uri uri = Uri.parse(uriString); 358 if (uri.getScheme() == null) { 359 uri = Uri.fromFile(new File(uri.getPath())); 360 } 361 transfer.finishTransfer(true, uri, mimeType); 362 } else { 363 transfer.finishTransfer(false, null, null); 364 } 365 } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS)) { 366 float progress = intent.getFloatExtra(EXTRA_BT_OPP_TRANSFER_PROGRESS, 0.0f); 367 transfer.updateFileProgress(progress); 368 } else if (action.equals(ACTION_HANDOVER_STARTED)) { 369 int count = intent.getIntExtra(EXTRA_BT_OPP_OBJECT_COUNT, 0); 370 if (count > 0) { 371 transfer.setObjectCount(count); 372 } 373 } 374 } 375 } 376 }; 377 378 void notifyClientTransferComplete(int transferId) { 379 if (mClient != null) { 380 Message msg = Message.obtain(null, HandoverManager.MSG_HANDOVER_COMPLETE); 381 msg.arg1 = transferId; 382 try { 383 mClient.send(msg); 384 } catch (RemoteException e) { 385 // Ignore 386 } 387 } 388 } 389 390 @Override 391 public void onTransferComplete(HandoverTransfer transfer, boolean success) { 392 // Called on the main thread 393 394 // First, remove the transfer from our list 395 Iterator it = mTransfers.entrySet().iterator(); 396 while (it.hasNext()) { 397 Map.Entry hashPair = (Map.Entry)it.next(); 398 HandoverTransfer transferEntry = (HandoverTransfer) hashPair.getValue(); 399 if (transferEntry == transfer) { 400 it.remove(); 401 } 402 } 403 404 // Notify any clients of the service 405 notifyClientTransferComplete(transfer.getTransferId()); 406 407 // Play success sound 408 if (success) { 409 mSoundPool.play(mSuccessSound, 1.0f, 1.0f, 0, 0, 1.0f); 410 } else { 411 if (DBG) Log.d(TAG, "Transfer failed, final state: " + 412 Integer.toString(transfer.mState)); 413 } 414 disableBluetoothIfNeeded(); 415 } 416 417 @Override 418 public void onBluetoothHeadsetHandoverComplete(boolean connected) { 419 // Called on the main thread 420 mBluetoothHeadsetHandover = null; 421 mBluetoothHeadsetConnected = connected; 422 if (mClient != null) { 423 Message msg = Message.obtain(null, 424 connected ? HandoverManager.MSG_HEADSET_CONNECTED 425 : HandoverManager.MSG_HEADSET_NOT_CONNECTED); 426 try { 427 mClient.send(msg); 428 } catch (RemoteException e) { 429 // Ignore 430 } 431 } 432 disableBluetoothIfNeeded(); 433 } 434 } 435