Home | History | Annotate | Download | only in handover
      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