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