blob: 45521da99ea3c7e3904c6cc8a9d73afe2e730a83 [file] [log] [blame]
package com.android.nfc.handover;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.SoundPool;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import com.android.nfc.R;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class HandoverService extends Service implements HandoverTransfer.Callback,
BluetoothHeadsetHandover.Callback {
static final String TAG = "HandoverService";
static final boolean DBG = true;
static final int MSG_REGISTER_CLIENT = 0;
static final int MSG_DEREGISTER_CLIENT = 1;
static final int MSG_START_INCOMING_TRANSFER = 2;
static final int MSG_START_OUTGOING_TRANSFER = 3;
static final int MSG_HEADSET_HANDOVER = 4;
static final String BUNDLE_TRANSFER = "transfer";
static final String EXTRA_HEADSET_DEVICE = "device";
static final String EXTRA_HEADSET_NAME = "headsetname";
static final String ACTION_CANCEL_HANDOVER_TRANSFER =
"com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER";
static final String EXTRA_SOURCE_ADDRESS =
"com.android.nfc.handover.extra.SOURCE_ADDRESS";
static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
"android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS";
static final String ACTION_BT_OPP_TRANSFER_DONE =
"android.btopp.intent.action.BT_OPP_TRANSFER_DONE";
static final String EXTRA_BT_OPP_TRANSFER_STATUS =
"android.btopp.intent.extra.BT_OPP_TRANSFER_STATUS";
static final String EXTRA_BT_OPP_TRANSFER_MIMETYPE =
"android.btopp.intent.extra.BT_OPP_TRANSFER_MIMETYPE";
static final String EXTRA_BT_OPP_ADDRESS =
"android.btopp.intent.extra.BT_OPP_ADDRESS";
static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
static final String EXTRA_BT_OPP_TRANSFER_DIRECTION =
"android.btopp.intent.extra.BT_OPP_TRANSFER_DIRECTION";
static final int DIRECTION_BLUETOOTH_INCOMING = 0;
static final int DIRECTION_BLUETOOTH_OUTGOING = 1;
static final String EXTRA_BT_OPP_TRANSFER_ID =
"android.btopp.intent.extra.BT_OPP_TRANSFER_ID";
static final String EXTRA_BT_OPP_TRANSFER_PROGRESS =
"android.btopp.intent.extra.BT_OPP_TRANSFER_PROGRESS";
static final String EXTRA_BT_OPP_TRANSFER_URI =
"android.btopp.intent.extra.BT_OPP_TRANSFER_URI";
// permission needed to be able to receive handover status requests
static final String HANDOVER_STATUS_PERMISSION =
"com.android.permission.HANDOVER_STATUS";
// Variables below only accessed on main thread
final Queue<BluetoothOppHandover> mPendingOutTransfers;
final HashMap<Pair<String, Boolean>, HandoverTransfer> mTransfers;
final Messenger mMessenger;
SoundPool mSoundPool;
int mSuccessSound;
BluetoothAdapter mBluetoothAdapter;
Messenger mClient;
Handler mHandler;
BluetoothHeadsetHandover mBluetoothHeadsetHandover;
boolean mBluetoothHeadsetConnected;
boolean mBluetoothEnabledByNfc;
public HandoverService() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mPendingOutTransfers = new LinkedList<BluetoothOppHandover>();
mTransfers = new HashMap<Pair<String, Boolean>, HandoverTransfer>();
mHandler = new MessageHandler();
mMessenger = new Messenger(mHandler);
mBluetoothHeadsetConnected = false;
mBluetoothEnabledByNfc = false;
}
@Override
public void onCreate() {
super.onCreate();
mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
mSuccessSound = mSoundPool.load(this, R.raw.end, 1);
IntentFilter filter = new IntentFilter(ACTION_BT_OPP_TRANSFER_DONE);
filter.addAction(ACTION_BT_OPP_TRANSFER_PROGRESS);
filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mReceiver, filter, HANDOVER_STATUS_PERMISSION, mHandler);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mSoundPool != null) {
mSoundPool.release();
}
unregisterReceiver(mReceiver);
}
void doOutgoingTransfer(Message msg) {
Bundle msgData = msg.getData();
msgData.setClassLoader(getClassLoader());
PendingHandoverTransfer pendingTransfer = (PendingHandoverTransfer)
msgData.getParcelable(BUNDLE_TRANSFER);
createHandoverTransfer(pendingTransfer);
// Create the actual transfer
BluetoothOppHandover handover = new BluetoothOppHandover(HandoverService.this,
pendingTransfer.remoteDevice, pendingTransfer.uris,
pendingTransfer.remoteActivating);
if (mBluetoothAdapter.isEnabled()) {
// Start the transfer
handover.start();
} else {
if (!enableBluetooth()) {
Log.e(TAG, "Error enabling Bluetooth.");
notifyClientTransferComplete(pendingTransfer.id);
return;
}
if (DBG) Log.d(TAG, "Queueing out transfer " + Integer.toString(pendingTransfer.id));
mPendingOutTransfers.add(handover);
// Queue the transfer and enable Bluetooth - when it is enabled
// the transfer will be started.
}
}
void doIncomingTransfer(Message msg) {
Bundle msgData = msg.getData();
msgData.setClassLoader(getClassLoader());
PendingHandoverTransfer pendingTransfer = (PendingHandoverTransfer)
msgData.getParcelable(BUNDLE_TRANSFER);
if (!mBluetoothAdapter.isEnabled() && !enableBluetooth()) {
Log.e(TAG, "Error enabling Bluetooth.");
notifyClientTransferComplete(pendingTransfer.id);
return;
}
createHandoverTransfer(pendingTransfer);
// Remote device will connect and finish the transfer
}
void doHeadsetHandover(Message msg) {
Bundle msgData = msg.getData();
BluetoothDevice device = (BluetoothDevice) msgData.getParcelable(EXTRA_HEADSET_DEVICE);
String name = (String) msgData.getString(EXTRA_HEADSET_NAME);
mBluetoothHeadsetHandover = new BluetoothHeadsetHandover(HandoverService.this,
device, name, HandoverService.this);
if (mBluetoothAdapter.isEnabled()) {
mBluetoothHeadsetHandover.start();
} else {
// Once BT is enabled, the headset pairing will be started
if (!enableBluetooth()) {
Log.e(TAG, "Error enabling Bluetooth.");
mBluetoothHeadsetHandover = null;
}
}
}
void startPendingTransfers() {
while (!mPendingOutTransfers.isEmpty()) {
BluetoothOppHandover handover = mPendingOutTransfers.remove();
handover.start();
}
}
class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClient = msg.replyTo;
break;
case MSG_DEREGISTER_CLIENT:
mClient = null;
break;
case MSG_START_INCOMING_TRANSFER:
doIncomingTransfer(msg);
break;
case MSG_START_OUTGOING_TRANSFER:
doOutgoingTransfer(msg);
break;
case MSG_HEADSET_HANDOVER:
doHeadsetHandover(msg);
break;
}
}
}
boolean enableBluetooth() {
if (!mBluetoothAdapter.isEnabled()) {
mBluetoothEnabledByNfc = true;
return mBluetoothAdapter.enableNoAutoConnect();
}
return true;
}
void disableBluetoothIfNeeded() {
if (!mBluetoothEnabledByNfc) return;
if (mTransfers.size() == 0 && !mBluetoothHeadsetConnected) {
mBluetoothAdapter.disable();
mBluetoothEnabledByNfc = false;
}
}
void createHandoverTransfer(PendingHandoverTransfer pendingTransfer) {
Pair<String, Boolean> key = new Pair<String, Boolean>(
pendingTransfer.remoteDevice.getAddress(), pendingTransfer.incoming);
if (mTransfers.containsKey(key)) {
HandoverTransfer transfer = mTransfers.get(key);
if (!transfer.isRunning()) {
mTransfers.remove(key); // new one created below
} else {
// There is already a transfer running to this
// device - it will automatically get combined
// with the existing transfer.
notifyClientTransferComplete(pendingTransfer.id);
return;
}
}
HandoverTransfer transfer = new HandoverTransfer(this, this, pendingTransfer);
mTransfers.put(key, transfer);
transfer.updateNotification();
}
HandoverTransfer findHandoverTransfer(String sourceAddress, boolean incoming) {
Pair<String, Boolean> key = new Pair<String, Boolean>(sourceAddress, incoming);
if (mTransfers.containsKey(key)) {
HandoverTransfer transfer = mTransfers.get(key);
if (transfer.isRunning()) {
return transfer;
}
}
return null;
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_ON) {
// If there is a pending headset pairing, start it
if (mBluetoothHeadsetHandover != null &&
!mBluetoothHeadsetHandover.hasStarted()) {
mBluetoothHeadsetHandover.start();
}
// Start any pending transfers
startPendingTransfers();
} else if (state == BluetoothAdapter.STATE_OFF) {
mBluetoothEnabledByNfc = false;
mBluetoothHeadsetConnected = false;
}
}
else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) {
String sourceAddress = intent.getStringExtra(EXTRA_SOURCE_ADDRESS);
HandoverTransfer transfer = findHandoverTransfer(sourceAddress, true);
if (transfer != null) {
if (DBG) Log.d(TAG, "Cancelling transfer " +
Integer.toString(transfer.mTransferId));
transfer.cancel();
}
} else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS) ||
action.equals(ACTION_BT_OPP_TRANSFER_DONE)) {
int direction = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_DIRECTION, -1);
int id = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_ID, -1);
String sourceAddress = intent.getStringExtra(EXTRA_BT_OPP_ADDRESS);
if (direction == -1 || id == -1 || sourceAddress == null) return;
boolean incoming = (direction == DIRECTION_BLUETOOTH_INCOMING);
HandoverTransfer transfer = findHandoverTransfer(sourceAddress, incoming);
if (transfer == null) {
// There is no transfer running for this source address; most likely
// the transfer was cancelled. We need to tell BT OPP to stop transferring
// in case this was an incoming transfer
if (DBG) Log.d(TAG, "Didn't find transfer, stopping");
Intent cancelIntent = new Intent("android.btopp.intent.action.STOP_HANDOVER_TRANSFER");
cancelIntent.putExtra(EXTRA_BT_OPP_TRANSFER_ID, id);
sendBroadcast(cancelIntent);
return;
}
if (action.equals(ACTION_BT_OPP_TRANSFER_DONE)) {
int handoverStatus = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_STATUS,
HANDOVER_TRANSFER_STATUS_FAILURE);
if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) {
String uriString = intent.getStringExtra(EXTRA_BT_OPP_TRANSFER_URI);
String mimeType = intent.getStringExtra(EXTRA_BT_OPP_TRANSFER_MIMETYPE);
Uri uri = Uri.parse(uriString);
if (uri.getScheme() == null) {
uri = Uri.fromFile(new File(uri.getPath()));
}
transfer.finishTransfer(true, uri, mimeType);
} else {
transfer.finishTransfer(false, null, null);
}
} else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS)) {
float progress = intent.getFloatExtra(EXTRA_BT_OPP_TRANSFER_PROGRESS, 0.0f);
transfer.updateFileProgress(progress);
}
}
}
};
void notifyClientTransferComplete(int transferId) {
if (mClient != null) {
Message msg = Message.obtain(null, HandoverManager.MSG_HANDOVER_COMPLETE);
msg.arg1 = transferId;
try {
mClient.send(msg);
} catch (RemoteException e) {
// Ignore
}
}
}
@Override
public void onTransferComplete(HandoverTransfer transfer, boolean success) {
// Called on the main thread
// First, remove the transfer from our list
Iterator it = mTransfers.entrySet().iterator();
while (it.hasNext()) {
Map.Entry hashPair = (Map.Entry)it.next();
HandoverTransfer transferEntry = (HandoverTransfer) hashPair.getValue();
if (transferEntry == transfer) {
it.remove();
}
}
// Notify any clients of the service
notifyClientTransferComplete(transfer.getTransferId());
// Play success sound
if (success) {
mSoundPool.play(mSuccessSound, 1.0f, 1.0f, 0, 0, 1.0f);
} else {
if (DBG) Log.d(TAG, "Transfer failed, final state: " +
Integer.toString(transfer.mState));
}
disableBluetoothIfNeeded();
}
@Override
public void onBluetoothHeadsetHandoverComplete(boolean connected) {
// Called on the main thread
mBluetoothHeadsetHandover = null;
mBluetoothHeadsetConnected = connected;
if (mClient != null) {
Message msg = Message.obtain(null,
connected ? HandoverManager.MSG_HEADSET_CONNECTED
: HandoverManager.MSG_HEADSET_NOT_CONNECTED);
try {
mClient.send(msg);
} catch (RemoteException e) {
// Ignore
}
}
disableBluetoothIfNeeded();
}
}