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