summaryrefslogtreecommitdiffstats
path: root/src/com/android/nfc/handover/HandoverManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/nfc/handover/HandoverManager.java')
-rw-r--r--src/com/android/nfc/handover/HandoverManager.java818
1 files changed, 167 insertions, 651 deletions
diff --git a/src/com/android/nfc/handover/HandoverManager.java b/src/com/android/nfc/handover/HandoverManager.java
index e7e807d..6d2271a 100644
--- a/src/com/android/nfc/handover/HandoverManager.java
+++ b/src/com/android/nfc/handover/HandoverManager.java
@@ -16,134 +16,71 @@
package com.android.nfc.handover;
-import java.io.File;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
import java.util.Random;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Notification.Builder;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.media.MediaScannerConnection;
+import android.content.ServiceConnection;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
-import android.os.Environment;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
-import android.os.SystemClock;
+import android.os.Messenger;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
-import android.util.Pair;
-
-import com.android.nfc.NfcService;
-import com.android.nfc.R;
-
/**
* Manages handover of NFC to other technologies.
*/
-public class HandoverManager implements BluetoothHeadsetHandover.Callback {
+public class HandoverManager {
static final String TAG = "NfcHandover";
static final boolean DBG = true;
+ static final String ACTION_WHITELIST_DEVICE =
+ "android.btopp.intent.action.WHITELIST_DEVICE";
+
static final byte[] TYPE_NOKIA = "nokia.com:bt".getBytes(Charset.forName("US_ASCII"));
static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob".
getBytes(Charset.forName("US_ASCII"));
static final byte[] RTD_COLLISION_RESOLUTION = {0x63, 0x72}; // "cr";
- 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";
-
- static final int MSG_HANDOVER_POWER_CHECK = 0;
-
- // We poll whether we can safely disable BT every POWER_CHECK_MS
- static final int POWER_CHECK_MS = 20000;
-
- static final String ACTION_WHITELIST_DEVICE =
- "android.btopp.intent.action.WHITELIST_DEVICE";
-
- 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 int SOURCE_BLUETOOTH_INCOMING = 0;
-
- static final int SOURCE_BLUETOOTH_OUTGOING = 1;
-
static final int CARRIER_POWER_STATE_INACTIVE = 0;
static final int CARRIER_POWER_STATE_ACTIVE = 1;
static final int CARRIER_POWER_STATE_ACTIVATING = 2;
static final int CARRIER_POWER_STATE_UNKNOWN = 3;
+ static final int MSG_HANDOVER_COMPLETE = 0;
+ static final int MSG_HEADSET_CONNECTED = 1;
+ static final int MSG_HEADSET_NOT_CONNECTED = 2;
+
final Context mContext;
final BluetoothAdapter mBluetoothAdapter;
- final NotificationManager mNotificationManager;
- final HandoverPowerManager mHandoverPowerManager;
+ final Messenger mMessenger = new Messenger (new MessageHandler());
- // Variables below synchronized on HandoverManager.this
- final HashMap<Pair<String, Boolean>, HandoverTransfer> mTransfers;
-
- BluetoothHeadsetHandover mBluetoothHeadsetHandover;
+ final Object mLock = new Object();
+ // Variables below synchronized on mLock
+ HashMap<Integer, PendingHandoverTransfer> mPendingTransfers;
boolean mBluetoothHeadsetConnected;
-
+ int mHandoverTransferId;
+ Messenger mService = null;
+ boolean mBound;
String mLocalBluetoothAddress;
- int mNotificationId;
static class BluetoothHandoverData {
public boolean valid = false;
@@ -152,487 +89,93 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
public boolean carrierActivating = false;
}
- class HandoverPowerManager implements Handler.Callback {
- final Handler handler;
- final Context context;
-
- public HandoverPowerManager(Context context) {
- this.handler = new Handler(this);
- this.context = context;
- }
-
- /**
- * Enables Bluetooth and will automatically disable it
- * when there is no Bluetooth activity intitiated by NFC
- * anymore.
- */
- synchronized boolean enableBluetooth() {
- // Enable BT
- boolean result = mBluetoothAdapter.enableNoAutoConnect();
-
- if (result) {
- // Start polling for BT activity to make sure we eventually disable
- // it again.
- handler.sendEmptyMessageDelayed(MSG_HANDOVER_POWER_CHECK, POWER_CHECK_MS);
- }
- return result;
- }
-
- synchronized boolean isBluetoothEnabled() {
- return mBluetoothAdapter.isEnabled();
- }
-
- synchronized void resetTimer() {
- if (handler.hasMessages(MSG_HANDOVER_POWER_CHECK)) {
- handler.removeMessages(MSG_HANDOVER_POWER_CHECK);
- handler.sendEmptyMessageDelayed(MSG_HANDOVER_POWER_CHECK, POWER_CHECK_MS);
- }
- }
-
- void stopMonitoring() {
- handler.removeMessages(MSG_HANDOVER_POWER_CHECK);
- }
-
+ class MessageHandler extends Handler {
@Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_HANDOVER_POWER_CHECK:
- // Check for any alive transfers
- boolean transferAlive = false;
- synchronized (HandoverManager.this) {
- for (HandoverTransfer transfer : mTransfers.values()) {
- if (transfer.isRunning()) {
- transferAlive = true;
- }
- }
-
- if (!transferAlive && !mBluetoothHeadsetConnected) {
- mBluetoothAdapter.disable();
- handler.removeMessages(MSG_HANDOVER_POWER_CHECK);
+ public void handleMessage(Message msg) {
+ synchronized (mLock) {
+ switch (msg.what) {
+ case MSG_HANDOVER_COMPLETE:
+ int transferId = msg.arg1;
+ Log.d(TAG, "Completed transfer id: " + Integer.toString(transferId));
+ if (mPendingTransfers.containsKey(transferId)) {
+ mPendingTransfers.remove(transferId);
} else {
- handler.sendEmptyMessageDelayed(MSG_HANDOVER_POWER_CHECK, POWER_CHECK_MS);
+ Log.e(TAG, "Could not find completed transfer id: " + Integer.toString(transferId));
}
- }
- return true;
- }
- return false;
- }
- }
-
- /**
- * A HandoverTransfer object represents a set of files
- * that were received through NFC connection handover
- * from the same source address.
- *
- * For Bluetooth, files are received through OPP, and
- * we have no knowledge how many files will be transferred
- * as part of a single transaction.
- * Hence, a transfer has a notion of being "alive": if
- * the last update to a transfer was within WAIT_FOR_NEXT_TRANSFER_MS
- * milliseconds, we consider a new file transfer from the
- * same source address as part of the same transfer.
- * The corresponding URIs will be grouped in a single folder.
- *
- */
- class HandoverTransfer implements Handler.Callback,
- MediaScannerConnection.OnScanCompletedListener {
- // In the states below we still accept new file transfer
- static final int STATE_NEW = 0;
- static final int STATE_IN_PROGRESS = 1;
- static final int STATE_W4_NEXT_TRANSFER = 2;
-
- // In the states below no new files are accepted.
- static final int STATE_W4_MEDIA_SCANNER = 3;
- static final int STATE_FAILED = 4;
- static final int STATE_SUCCESS = 5;
- static final int STATE_CANCELLED = 6;
-
- static final int MSG_NEXT_TRANSFER_TIMER = 0;
- static final int MSG_TRANSFER_TIMEOUT = 1;
-
- // We need to receive an update within this time period
- // to still consider this transfer to be "alive" (ie
- // a reason to keep the handover transport enabled).
- static final int ALIVE_CHECK_MS = 20000;
-
- // The amount of time to wait for a new transfer
- // once the current one completes.
- static final int WAIT_FOR_NEXT_TRANSFER_MS = 4000;
-
- static final String BEAM_DIR = "beam";
-
- final BluetoothDevice device;
- final String sourceAddress;
- final boolean incoming; // whether this is an incoming transfer
- final int notificationId; // Unique ID of this transfer used for notifications
- final Handler handler;
- final PendingIntent cancelIntent;
-
- int state;
- Long lastUpdate; // Last time an event occurred for this transfer
- float progress; // Progress in range [0..1]
- ArrayList<Uri> btUris; // Received uris from Bluetooth OPP
- ArrayList<String> btMimeTypes; // Mime-types received from Bluetooth OPP
-
- ArrayList<String> paths; // Raw paths on the filesystem for Beam-stored files
- HashMap<String, String> mimeTypes; // Mime-types associated with each path
- HashMap<String, Uri> mediaUris; // URIs found by the media scanner for each path
- int urisScanned;
-
- public HandoverTransfer(String sourceAddress, boolean incoming) {
- synchronized (HandoverManager.this) {
- this.notificationId = mNotificationId++;
- }
- this.lastUpdate = SystemClock.elapsedRealtime();
- this.progress = 0.0f;
- this.state = STATE_NEW;
- this.btUris = new ArrayList<Uri>();
- this.btMimeTypes = new ArrayList<String>();
- this.paths = new ArrayList<String>();
- this.mimeTypes = new HashMap<String, String>();
- this.mediaUris = new HashMap<String, Uri>();
- this.sourceAddress = sourceAddress;
- this.incoming = incoming;
- this.handler = new Handler(mContext.getMainLooper(), this);
- this.cancelIntent = buildCancelIntent();
- this.urisScanned = 0;
- this.device = mBluetoothAdapter.getRemoteDevice(sourceAddress);
-
- handler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
- }
-
- public synchronized void updateFileProgress(float progress) {
- if (!isRunning()) return; // Ignore when we're no longer running
-
- handler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
-
- this.progress = progress;
-
- // We're still receiving data from this device - keep it in
- // the whitelist for a while longer
- if (incoming) whitelistOppDevice(device);
-
- updateStateAndNotification(STATE_IN_PROGRESS);
- }
-
- public synchronized void finishTransfer(boolean success, Uri uri, String mimeType) {
- if (!isRunning()) return; // Ignore when we're no longer running
-
- if (success && uri != null) {
- if (DBG) Log.d(TAG, "Transfer success, uri " + uri + " mimeType " + mimeType);
- this.progress = 1.0f;
- if (mimeType == null) {
- mimeType = BluetoothOppHandover.getMimeTypeForUri(mContext, uri);
- }
- if (mimeType != null) {
- btUris.add(uri);
- btMimeTypes.add(mimeType);
- } else {
- if (DBG) Log.d(TAG, "Could not get mimeType for file.");
+ break;
+ case MSG_HEADSET_CONNECTED:
+ mBluetoothHeadsetConnected = true;
+ break;
+ case MSG_HEADSET_NOT_CONNECTED:
+ mBluetoothHeadsetConnected = false;
+ break;
+ default:
+ break;
}
- } else {
- Log.e(TAG, "Handover transfer failed");
- // Do wait to see if there's another file coming.
}
- handler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
- handler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS);
- updateStateAndNotification(STATE_W4_NEXT_TRANSFER);
- }
-
- public synchronized boolean isRunning() {
- if (state != STATE_NEW && state != STATE_IN_PROGRESS && state != STATE_W4_NEXT_TRANSFER) {
- return false;
- } else {
- return true;
- }
- }
-
- synchronized void cancel() {
- if (!isRunning()) return;
-
- // Delete all files received so far
- for (Uri uri : btUris) {
- File file = new File(uri.getPath());
- if (file.exists()) file.delete();
- }
-
- updateStateAndNotification(STATE_CANCELLED);
- }
-
- synchronized void updateNotification() {
- if (!incoming) return; // No notifications for outgoing transfers
-
- Builder notBuilder = new Notification.Builder(mContext);
-
- if (state == STATE_NEW || state == STATE_IN_PROGRESS ||
- state == STATE_W4_NEXT_TRANSFER || state == STATE_W4_MEDIA_SCANNER) {
- notBuilder.setAutoCancel(false);
- notBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
- notBuilder.setTicker(mContext.getString(R.string.beam_progress));
- notBuilder.setContentTitle(mContext.getString(R.string.beam_progress));
- notBuilder.addAction(R.drawable.ic_menu_cancel_holo_dark,
- mContext.getString(R.string.cancel), cancelIntent);
- notBuilder.setDeleteIntent(cancelIntent);
- // We do have progress indication on a per-file basis, but in a multi-file
- // transfer we don't know the total progress. So for now, just show an
- // indeterminate progress bar.
- notBuilder.setProgress(100, 0, true);
- } else if (state == STATE_SUCCESS) {
- notBuilder.setAutoCancel(true);
- notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
- notBuilder.setTicker(mContext.getString(R.string.beam_complete));
- notBuilder.setContentTitle(mContext.getString(R.string.beam_complete));
- notBuilder.setContentText(mContext.getString(R.string.beam_touch_to_view));
-
- Intent viewIntent = buildViewIntent();
- PendingIntent contentIntent = PendingIntent.getActivityAsUser(
- mContext, 0, viewIntent, 0, null, UserHandle.CURRENT);
-
- notBuilder.setContentIntent(contentIntent);
-
- // Play Beam success sound
- NfcService.getInstance().playSound(NfcService.SOUND_END);
- } else if (state == STATE_FAILED) {
- notBuilder.setAutoCancel(false);
- notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
- notBuilder.setTicker(mContext.getString(R.string.beam_failed));
- notBuilder.setContentTitle(mContext.getString(R.string.beam_failed));
- } else if (state == STATE_CANCELLED) {
- notBuilder.setAutoCancel(false);
- notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
- notBuilder.setTicker(mContext.getString(R.string.beam_canceled));
- notBuilder.setContentTitle(mContext.getString(R.string.beam_canceled));
- } else {
- return;
- }
-
- mNotificationManager.notifyAsUser(null, mNotificationId, notBuilder.build(),
- UserHandle.CURRENT);
- }
-
- synchronized void updateStateAndNotification(int newState) {
- this.state = newState;
- this.lastUpdate = SystemClock.elapsedRealtime();
-
- if (handler.hasMessages(MSG_TRANSFER_TIMEOUT)) {
- // Update timeout timer
- handler.removeMessages(MSG_TRANSFER_TIMEOUT);
- handler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
- }
- updateNotification();
- }
-
- synchronized void processFiles() {
- // Check the amount of files we received in this transfer;
- // If more than one, create a separate directory for it.
- String extRoot = Environment.getExternalStorageDirectory().getPath();
- File beamPath = new File(extRoot + "/" + BEAM_DIR);
-
- if (!checkMediaStorage(beamPath) || btUris.size() == 0) {
- Log.e(TAG, "Media storage not valid or no uris received.");
- updateStateAndNotification(STATE_FAILED);
- return;
- }
-
- if (btUris.size() > 1) {
- beamPath = generateMultiplePath(extRoot + "/" + BEAM_DIR + "/");
- if (!beamPath.isDirectory() && !beamPath.mkdir()) {
- Log.e(TAG, "Failed to create multiple path " + beamPath.toString());
- updateStateAndNotification(STATE_FAILED);
- return;
- }
- }
-
- for (int i = 0; i < btUris.size(); i++) {
- Uri uri = btUris.get(i);
- String mimeType = btMimeTypes.get(i);
-
- File srcFile = new File(uri.getPath());
-
- File dstFile = generateUniqueDestination(beamPath.getAbsolutePath(),
- uri.getLastPathSegment());
- if (!srcFile.renameTo(dstFile)) {
- if (DBG) Log.d(TAG, "Failed to rename from " + srcFile + " to " + dstFile);
- srcFile.delete();
- return;
- } else {
- paths.add(dstFile.getAbsolutePath());
- mimeTypes.put(dstFile.getAbsolutePath(), mimeType);
- if (DBG) Log.d(TAG, "Did successful rename from " + srcFile + " to " + dstFile);
- }
- }
-
- // We can either add files to the media provider, or provide an ACTION_VIEW
- // intent to the file directly. We base this decision on the mime type
- // of the first file; if it's media the platform can deal with,
- // use the media provider, if it's something else, just launch an ACTION_VIEW
- // on the file.
- String mimeType = mimeTypes.get(paths.get(0));
- if (mimeType.startsWith("image/") || mimeType.startsWith("video/") ||
- mimeType.startsWith("audio/")) {
- String[] arrayPaths = new String[paths.size()];
- MediaScannerConnection.scanFile(mContext, paths.toArray(arrayPaths), null, this);
- updateStateAndNotification(STATE_W4_MEDIA_SCANNER);
- } else {
- // We're done.
- updateStateAndNotification(STATE_SUCCESS);
- }
-
}
+ };
- public boolean handleMessage(Message msg) {
- if (msg.what == MSG_NEXT_TRANSFER_TIMER) {
- // We didn't receive a new transfer in time, finalize this one
- if (incoming) {
- processFiles();
- } else {
- updateStateAndNotification(STATE_SUCCESS);
- }
- return true;
- } else if (msg.what == MSG_TRANSFER_TIMEOUT) {
- // No update on this transfer for a while, check
- // to see if it's still running, and fail it if it is.
- if (isRunning()) {
- updateStateAndNotification(STATE_FAILED);
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = new Messenger(service);
+ mBound = true;
+ // Register this client
+ Message msg = Message.obtain(null, HandoverService.MSG_REGISTER_CLIENT);
+ msg.replyTo = mMessenger;
+ try {
+ mService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register client");
}
}
- return false;
}
- public synchronized void onScanCompleted(String path, Uri uri) {
- if (DBG) Log.d(TAG, "Scan completed, path " + path + " uri " + uri);
- if (uri != null) {
- mediaUris.put(path, uri);
- }
- urisScanned++;
- if (urisScanned == paths.size()) {
- // We're done
- updateStateAndNotification(STATE_SUCCESS);
- }
- }
-
- boolean checkMediaStorage(File path) {
- if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- if (!path.isDirectory() && !path.mkdir()) {
- Log.e(TAG, "Not dir or not mkdir " + path.getAbsolutePath());
- return false;
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ if (mBound) {
+ try {
+ Message msg = Message.obtain(null, HandoverService.MSG_DEREGISTER_CLIENT);
+ msg.replyTo = mMessenger;
+ mService.send(msg);
+ } catch (RemoteException e) {
+ // Service may have crashed - ignore
+ }
}
- return true;
- } else {
- Log.e(TAG, "External storage not mounted, can't store file.");
- return false;
- }
- }
-
- synchronized Intent buildViewIntent() {
- if (paths.size() == 0) return null;
-
- Intent viewIntent = new Intent(Intent.ACTION_VIEW);
-
- String filePath = paths.get(0);
- Uri mediaUri = mediaUris.get(filePath);
- Uri uri = mediaUri != null ? mediaUri :
- Uri.parse(ContentResolver.SCHEME_FILE + "://" + filePath);
- viewIntent.setDataAndTypeAndNormalize(uri, mimeTypes.get(filePath));
- viewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return viewIntent;
- }
-
- PendingIntent buildCancelIntent() {
- Intent intent = new Intent(ACTION_CANCEL_HANDOVER_TRANSFER);
- intent.putExtra(EXTRA_SOURCE_ADDRESS, sourceAddress);
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
-
- return pi;
- }
-
- synchronized File generateUniqueDestination(String path, String fileName) {
- int dotIndex = fileName.lastIndexOf(".");
- String extension = null;
- String fileNameWithoutExtension = null;
- if (dotIndex < 0) {
- extension = "";
- fileNameWithoutExtension = fileName;
- } else {
- extension = fileName.substring(dotIndex);
- fileNameWithoutExtension = fileName.substring(0, dotIndex);
- }
- File dstFile = new File(path + File.separator + fileName);
- int count = 0;
- while (dstFile.exists()) {
- dstFile = new File(path + File.separator + fileNameWithoutExtension + "-" +
- Integer.toString(count) + extension);
- count++;
+ mService = null;
+ mBound = false;
}
- return dstFile;
}
-
- synchronized File generateMultiplePath(String beamRoot) {
- // Generate a unique directory with the date
- String format = "yyyy-MM-dd";
- SimpleDateFormat sdf = new SimpleDateFormat(format);
- String newPath = beamRoot + "beam-" + sdf.format(new Date());
- File newFile = new File(newPath);
- int count = 0;
- while (newFile.exists()) {
- newPath = beamRoot + "beam-" + sdf.format(new Date()) + "-" +
- Integer.toString(count);
- newFile = new File(newPath);
- count++;
- }
-
- return newFile;
- }
- }
-
- synchronized HandoverTransfer getOrCreateHandoverTransfer(String sourceAddress, boolean incoming,
- boolean create) {
- Pair<String, Boolean> key = new Pair<String, Boolean>(sourceAddress, incoming);
- if (mTransfers.containsKey(key)) {
- HandoverTransfer transfer = mTransfers.get(key);
- if (transfer.isRunning()) {
- return transfer;
- } else {
- if (create) mTransfers.remove(key); // new one created below
- }
- }
- if (create) {
- HandoverTransfer transfer = new HandoverTransfer(sourceAddress, incoming);
- mTransfers.put(key, transfer);
-
- return transfer;
- } else {
- return null;
- }
- }
+ };
public HandoverManager(Context context) {
mContext = context;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mNotificationManager = (NotificationManager) mContext.getSystemService(
- Context.NOTIFICATION_SERVICE);
+ mPendingTransfers = new HashMap<Integer, PendingHandoverTransfer>();
- mTransfers = new HashMap<Pair<String, Boolean>, HandoverTransfer>();
- mHandoverPowerManager = new HandoverPowerManager(context);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(mReceiver, filter, null, null);
- IntentFilter filter = new IntentFilter(ACTION_BT_OPP_TRANSFER_DONE);
- filter.addAction(ACTION_BT_OPP_TRANSFER_PROGRESS);
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER);
- mContext.registerReceiver(mReceiver, filter, HANDOVER_STATUS_PERMISSION, null);
+ mContext.bindService(new Intent(mContext, HandoverService.class), mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT);
}
- synchronized void cleanupTransfers() {
- Iterator<Map.Entry<Pair<String, Boolean>, HandoverTransfer>> it = mTransfers.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<Pair<String, Boolean>, HandoverTransfer> pair = it.next();
- HandoverTransfer transfer = pair.getValue();
- if (!transfer.isRunning()) {
- it.remove();
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+ // Re-bind a service for the current user
+ mContext.unbindService(mConnection);
+ mContext.bindService(new Intent(mContext, HandoverService.class), mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT);
}
}
- }
+ };
static NdefRecord createCollisionRecord() {
byte[] random = new byte[2];
@@ -659,7 +202,7 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
payload[0] = (byte) (payload.length & 0xFF);
payload[1] = (byte) ((payload.length >> 8) & 0xFF);
- synchronized (HandoverManager.this) {
+ synchronized (mLock) {
if (mLocalBluetoothAddress == null) {
mLocalBluetoothAddress = mBluetoothAdapter.getAddress();
}
@@ -698,7 +241,6 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
payload.get(payloadBytes);
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null,
payloadBytes);
-
}
NdefRecord createHandoverRequestRecord() {
@@ -742,24 +284,32 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
}
if (bluetoothData == null) return null;
- boolean bluetoothActivating = false;
-
- synchronized(HandoverManager.this) {
- if (!mHandoverPowerManager.isBluetoothEnabled()) {
- if (!mHandoverPowerManager.enableBluetooth()) {
- return null;
- }
- bluetoothActivating = true;
- } else {
- mHandoverPowerManager.resetTimer();
+ // Note: there could be a race where we conclude
+ // that Bluetooth is already enabled, and shortly
+ // after the user turns it off. That will cause
+ // the transfer to fail, but there's nothing
+ // much we can do about it anyway. It shouldn't
+ // be common for the user to be changing BT settings
+ // while waiting to receive a picture.
+ boolean bluetoothActivating = !mBluetoothAdapter.isEnabled();
+ synchronized (mLock) {
+ if (!mBound) {
+ Log.e(TAG, "Could not connect to handover service");
+ return null;
+ }
+ Message msg = Message.obtain(null, HandoverService.MSG_START_INCOMING_TRANSFER);
+ PendingHandoverTransfer transfer = registerInTransferLocked(bluetoothData.device);
+ Bundle transferData = new Bundle();
+ transferData.putParcelable(HandoverService.BUNDLE_TRANSFER, transfer);
+ msg.setData(transferData);
+ try {
+ mService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not connect to handover service");
+ removeTransferLocked(transfer.id);
+ return null;
}
-
- // Create the initial transfer object
- HandoverTransfer transfer = getOrCreateHandoverTransfer(
- bluetoothData.device.getAddress(), true, true);
- transfer.updateNotification();
}
-
// BT OOB found, whitelist it for incoming OPP data
whitelistOppDevice(bluetoothData.device);
@@ -767,13 +317,6 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
return (createHandoverSelectMessage(bluetoothActivating));
}
- void whitelistOppDevice(BluetoothDevice device) {
- if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
- Intent intent = new Intent(ACTION_WHITELIST_DEVICE);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- mContext.sendBroadcast(intent);
- }
-
public boolean tryHandover(NdefMessage m) {
if (m == null) return false;
if (mBluetoothAdapter == null) return false;
@@ -784,18 +327,26 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
if (handover == null) return false;
if (!handover.valid) return true;
- synchronized (HandoverManager.this) {
+ synchronized (mLock) {
if (mBluetoothAdapter == null) {
if (DBG) Log.d(TAG, "BT handover, but BT not available");
return true;
}
- if (mBluetoothHeadsetHandover != null) {
- if (DBG) Log.d(TAG, "BT handover already in progress, ignoring");
- return true;
+ if (!mBound) {
+ Log.e(TAG, "Could not connect to handover service");
+ return false;
+ }
+
+ Message msg = Message.obtain(null, HandoverService.MSG_HEADSET_HANDOVER, 0, 0);
+ Bundle headsetData = new Bundle();
+ headsetData.putParcelable(HandoverService.EXTRA_HEADSET_DEVICE, handover.device);
+ headsetData.putString(HandoverService.EXTRA_HEADSET_NAME, handover.name);
+ msg.setData(headsetData);
+ try {
+ mService.send(msg);
+ } catch (RemoteException e) {
+ return false;
}
- mBluetoothHeadsetHandover = new BluetoothHeadsetHandover(mContext, handover.device,
- handover.name, mHandoverPowerManager, this);
- mBluetoothHeadsetHandover.start();
}
return true;
}
@@ -807,13 +358,53 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
BluetoothHandoverData data = parse(m);
if (data != null && data.valid) {
// Register a new handover transfer object
- getOrCreateHandoverTransfer(data.device.getAddress(), false, true);
- BluetoothOppHandover handover = new BluetoothOppHandover(mContext, data.device,
- uris, mHandoverPowerManager, data.carrierActivating);
- handover.start();
+ synchronized (mLock) {
+ if (!mBound) {
+ Log.e(TAG, "Could not connect to handover service");
+ return;
+ }
+
+ Message msg = Message.obtain(null, HandoverService.MSG_START_OUTGOING_TRANSFER, 0, 0);
+ PendingHandoverTransfer transfer = registerOutTransferLocked(data, uris);
+ Bundle transferData = new Bundle();
+ transferData.putParcelable(HandoverService.BUNDLE_TRANSFER, transfer);
+ msg.setData(transferData);
+ try {
+ mService.send(msg);
+ } catch (RemoteException e) {
+ removeTransferLocked(transfer.id);
+ }
+ }
}
}
+ PendingHandoverTransfer registerInTransferLocked(BluetoothDevice remoteDevice) {
+ PendingHandoverTransfer transfer = new PendingHandoverTransfer(
+ mHandoverTransferId++, true, remoteDevice, false, null);
+ mPendingTransfers.put(transfer.id, transfer);
+
+ return transfer;
+ }
+
+ PendingHandoverTransfer registerOutTransferLocked(BluetoothHandoverData data,
+ Uri[] uris) {
+ PendingHandoverTransfer transfer = new PendingHandoverTransfer(
+ mHandoverTransferId++, false, data.device, data.carrierActivating, uris);
+ mPendingTransfers.put(transfer.id, transfer);
+ return transfer;
+ }
+
+ void removeTransferLocked(int id) {
+ mPendingTransfers.remove(id);
+ }
+
+ void whitelistOppDevice(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
+ Intent intent = new Intent(ACTION_WHITELIST_DEVICE);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+
boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) {
byte[] payload = handoverRec.getPayload();
if (payload == null || payload.length <= 1) return false;
@@ -972,79 +563,4 @@ public class HandoverManager implements BluetoothHeadsetHandover.Callback {
return result;
}
-
- @Override
- public void onBluetoothHeadsetHandoverComplete(boolean connected) {
- synchronized (HandoverManager.this) {
- mBluetoothHeadsetHandover = null;
- mBluetoothHeadsetConnected = connected;
- }
- }
-
- 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_OFF) {
- mHandoverPowerManager.stopMonitoring();
- }
-
- return;
- } else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) {
- String sourceAddress = intent.getStringExtra(EXTRA_SOURCE_ADDRESS);
- HandoverTransfer transfer = getOrCreateHandoverTransfer(sourceAddress, true,
- false);
- if (transfer != null) {
- transfer.cancel();
- }
- } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS) ||
- action.equals(ACTION_BT_OPP_TRANSFER_DONE)) {
- // Clean up old transfers no longer in progress
- cleanupTransfers();
-
- 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 = getOrCreateHandoverTransfer(sourceAddress, incoming,
- false);
- 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
- Intent cancelIntent = new Intent("android.btopp.intent.action.STOP_HANDOVER_TRANSFER");
- cancelIntent.putExtra(EXTRA_BT_OPP_TRANSFER_ID, id);
- mContext.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);
- }
- }
- }
- };
-
}