summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2012-12-13 16:44:23 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2012-12-13 16:44:23 -0800
commitbe1939b4b6003ac7a65fcb95a3912f5e1ce8e75f (patch)
tree694dc2546dd2397528d4b78d067716f451792662
parent525c260303268a83da4c3413b953d13c9084e834 (diff)
downloadpackages_apps_nfc-be1939b4b6003ac7a65fcb95a3912f5e1ce8e75f.zip
packages_apps_nfc-be1939b4b6003ac7a65fcb95a3912f5e1ce8e75f.tar.gz
packages_apps_nfc-be1939b4b6003ac7a65fcb95a3912f5e1ce8e75f.tar.bz2
Snapshot b80adb2c263702442cf2f2d771168400e6ceb9f8
Change-Id: I391d8e1be1a61e68b01f0db371dbb4ed3e5b5933
-rwxr-xr-xAndroidManifest.xml3
-rwxr-xr-xnci/jni/NativeNfcManager.cpp10
-rw-r--r--nci/jni/PeerToPeer.cpp4
-rwxr-xr-xnci/jni/SecureElement.cpp72
-rw-r--r--src/com/android/nfc/NfcApplication.java28
-rw-r--r--src/com/android/nfc/RegisteredComponentCache.java5
-rw-r--r--src/com/android/nfc/handover/BluetoothHeadsetHandover.java58
-rw-r--r--src/com/android/nfc/handover/BluetoothOppHandover.java72
-rw-r--r--src/com/android/nfc/handover/HandoverManager.java818
-rw-r--r--src/com/android/nfc/handover/HandoverServer.java3
-rw-r--r--src/com/android/nfc/handover/HandoverService.java403
-rw-r--r--src/com/android/nfc/handover/HandoverTransfer.java423
-rw-r--r--src/com/android/nfc/handover/PendingHandoverTransfer.java63
13 files changed, 1163 insertions, 799 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 710e2ee..7b41af2 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -52,5 +52,8 @@
android:noHistory="true"
/>
+ <service android:name=".handover.HandoverService"
+ android:process=":handover"
+ />
</application>
</manifest>
diff --git a/nci/jni/NativeNfcManager.cpp b/nci/jni/NativeNfcManager.cpp
index f15c92f..61fc052 100755
--- a/nci/jni/NativeNfcManager.cpp
+++ b/nci/jni/NativeNfcManager.cpp
@@ -1276,6 +1276,7 @@ static void nfcManager_doDeselectSecureElement(JNIEnv *e, jobject o)
{
ALOGD ("%s: enter", __FUNCTION__);
bool stat = false;
+ bool bRestartDiscovery = false;
if (! sIsSecElemSelected)
{
@@ -1290,6 +1291,12 @@ static void nfcManager_doDeselectSecureElement(JNIEnv *e, jobject o)
goto TheEnd;
}
+ if (sRfEnabled) {
+ // Stop RF Discovery if we were polling
+ startRfDiscovery (false);
+ bRestartDiscovery = true;
+ }
+
stat = SecureElement::getInstance().routeToDefault ();
sIsSecElemSelected = false;
@@ -1299,6 +1306,9 @@ static void nfcManager_doDeselectSecureElement(JNIEnv *e, jobject o)
SecureElement::getInstance().deactivate (0xABCDEF);
TheEnd:
+ if (bRestartDiscovery)
+ startRfDiscovery (true);
+
//if nothing is active after this, then tell the controller to power down
if (! PowerSwitch::getInstance ().setModeOff (PowerSwitch::SE_ROUTING))
PowerSwitch::getInstance ().setLevel (PowerSwitch::LOW_POWER);
diff --git a/nci/jni/PeerToPeer.cpp b/nci/jni/PeerToPeer.cpp
index 7994e2a..b40e366 100644
--- a/nci/jni/PeerToPeer.cpp
+++ b/nci/jni/PeerToPeer.cpp
@@ -1431,9 +1431,9 @@ void PeerToPeer::nfaClientCallback (tNFA_P2P_EVT p2pEvent, tNFA_P2P_EVT_DATA* ev
if ((pConn = sP2p.findConnection(eventData->disc.handle)) == NULL)
{
// If no connection, may be a client that is trying to connect
- if ((pClient = sP2p.findClientCon ((tNFA_HANDLE)NFA_HANDLE_INVALID)) == NULL)
+ if ((pClient = sP2p.findClient (eventData->disc.handle)) == NULL)
{
- ALOGE ("%s: NFA_P2P_DISC_EVT: can't find conn for NFA handle: 0x%04x", fn, eventData->disc.handle);
+ ALOGE ("%s: NFA_P2P_DISC_EVT: can't find client for NFA handle: 0x%04x", fn, eventData->disc.handle);
return;
}
// Unblock createDataLinkConn()
diff --git a/nci/jni/SecureElement.cpp b/nci/jni/SecureElement.cpp
index 3c9256b..a4c4d57 100755
--- a/nci/jni/SecureElement.cpp
+++ b/nci/jni/SecureElement.cpp
@@ -156,12 +156,12 @@ bool SecureElement::initialize (nfc_jni_native_data* native)
if (GetNumValue("NFA_HCI_DEFAULT_DEST_GATE", &num, sizeof(num)))
mDestinationGate = num;
- ALOGD ("%s: Default destination gate: %d", fn, mDestinationGate);
+ ALOGD ("%s: Default destination gate: 0x%X", fn, mDestinationGate);
// active SE, if not set active all SEs
if (GetNumValue("ACTIVE_SE", &num, sizeof(num)))
mActiveSeOverride = num;
- ALOGD ("%s: Active SE override: %d", fn, mActiveSeOverride);
+ ALOGD ("%s: Active SE override: 0x%X", fn, mActiveSeOverride);
if (GetNumValue("OBERTHUR_WARM_RESET_COMMAND", &num, sizeof(num)))
{
@@ -442,7 +442,7 @@ jintArray SecureElement::getListOfEeHandles (JNIEnv* e)
** Function: activate
**
** Description: Turn on the secure element.
-** seID: ID of secure element.
+** seID: ID of secure element; 0xF3 or 0xF4.
**
** Returns: True if ok.
**
@@ -474,24 +474,16 @@ bool SecureElement::activate (jint seID)
return false;
}
- mActiveEeHandle = getDefaultEeHandle();
- ALOGD ("%s: active ee h=0x%X, override se=0x%X", fn, mActiveEeHandle, mActiveSeOverride);
- if (mActiveEeHandle == NFA_HANDLE_INVALID)
- {
- ALOGE ("%s: ee not found", fn);
- return false;
- }
-
- UINT16 override_se = 0;
+ UINT16 overrideEeHandle = 0;
if (mActiveSeOverride)
- override_se = NFA_HANDLE_GROUP_EE | mActiveSeOverride;
+ overrideEeHandle = NFA_HANDLE_GROUP_EE | mActiveSeOverride;
if (mRfFieldIsOn) {
ALOGE("%s: RF field indication still on, resetting", fn);
mRfFieldIsOn = false;
}
- ALOGD ("%s: override seid=0x%X", fn, override_se );
+ ALOGD ("%s: override ee h=0x%X", fn, overrideEeHandle );
//activate every discovered secure element
for (int index=0; index < mActualNumEe; index++)
{
@@ -499,7 +491,7 @@ bool SecureElement::activate (jint seID)
if ((eeItem.ee_handle == EE_HANDLE_0xF3) || (eeItem.ee_handle == EE_HANDLE_0xF4))
{
- if (override_se && (override_se != eeItem.ee_handle) )
+ if (overrideEeHandle && (overrideEeHandle != eeItem.ee_handle) )
continue; // do not enable all SEs; only the override one
if (eeItem.ee_status != NFC_NFCEE_STATUS_INACTIVE)
@@ -524,18 +516,11 @@ bool SecureElement::activate (jint seID)
}
} //for
- for (UINT8 xx = 0; xx < mActualNumEe; xx++)
- {
- if ((mEeInfo[xx].num_interface != 0) && (mEeInfo[xx].ee_interface[0] != NCI_NFCEE_INTERFACE_HCI_ACCESS) &&
- (mEeInfo[xx].ee_status != NFC_NFCEE_STATUS_INACTIVE))
- {
- mActiveEeHandle = mEeInfo[xx].ee_handle;
- break;
- }
- }
-
- ALOGD ("%s: exit; ok=%u", fn, numActivatedEe > 0);
- return numActivatedEe > 0;
+ mActiveEeHandle = getDefaultEeHandle();
+ if (mActiveEeHandle == NFA_HANDLE_INVALID)
+ ALOGE ("%s: ee handle not found", fn);
+ ALOGD ("%s: exit; active ee h=0x%X", fn, mActiveEeHandle);
+ return mActiveEeHandle != NFA_HANDLE_INVALID;
}
@@ -544,7 +529,7 @@ bool SecureElement::activate (jint seID)
** Function: deactivate
**
** Description: Turn off the secure element.
-** seID: ID of secure element.
+** seID: ID of secure element; 0xF3 or 0xF4.
**
** Returns: True if ok.
**
@@ -1417,7 +1402,7 @@ void SecureElement::adjustProtocolRoutes (RouteDataSet::Database* db, RouteSelec
{
tNFA_HANDLE eeHandle = NFA_EE_HANDLE_DH;
if (routeSelection == SecElemRoute)
- eeHandle = getDefaultEeHandle ();
+ eeHandle = mActiveEeHandle;
ALOGD ("%s: route to default EE h=0x%X", fn, eeHandle);
SyncEventGuard guard (mRoutingEvent);
nfaStat = NFA_EeSetDefaultProtoRouting (eeHandle, protoMask, 0, 0);
@@ -1573,7 +1558,7 @@ void SecureElement::adjustTechnologyRoutes (RouteDataSet::Database* db, RouteSel
{
tNFA_HANDLE eeHandle = NFA_EE_HANDLE_DH;
if (routeSelection == SecElemRoute)
- eeHandle = getDefaultEeHandle ();
+ eeHandle = mActiveEeHandle;
ALOGD ("%s: route to default EE h=0x%X", fn, eeHandle);
SyncEventGuard guard (mRoutingEvent);
nfaStat = NFA_EeSetDefaultTechRouting (eeHandle, techMask, 0, 0);
@@ -1975,26 +1960,31 @@ tNFA_EE_INFO *SecureElement::findEeByHandle (tNFA_HANDLE eeHandle)
*******************************************************************************/
tNFA_HANDLE SecureElement::getDefaultEeHandle ()
{
+ UINT16 overrideEeHandle = NFA_HANDLE_GROUP_EE | mActiveSeOverride;
// Find the first EE that is not the HCI Access i/f.
for (UINT8 xx = 0; xx < mActualNumEe; xx++)
{
- if ((mEeInfo[xx].num_interface != 0) && (mEeInfo[xx].ee_interface[0] != NCI_NFCEE_INTERFACE_HCI_ACCESS) )
+ if (mActiveSeOverride && (overrideEeHandle != mEeInfo[xx].ee_handle))
+ continue; //skip all the EE's that are ignored
+ if ((mEeInfo[xx].num_interface != 0) &&
+ (mEeInfo[xx].ee_interface[0] != NCI_NFCEE_INTERFACE_HCI_ACCESS) &&
+ (mEeInfo[xx].ee_status != NFC_NFCEE_STATUS_INACTIVE))
return (mEeInfo[xx].ee_handle);
}
return NFA_HANDLE_INVALID;
}
- /*******************************************************************************
- **
- ** Function: findUiccByHandle
- **
- ** Description: Find information about an execution environment.
- ** eeHandle: Handle of the execution environment.
- **
- ** Returns: Information about the execution environment.
- **
- *******************************************************************************/
+/*******************************************************************************
+**
+** Function: findUiccByHandle
+**
+** Description: Find information about an execution environment.
+** eeHandle: Handle of the execution environment.
+**
+** Returns: Information about the execution environment.
+**
+*******************************************************************************/
tNFA_EE_DISCOVER_INFO *SecureElement::findUiccByHandle (tNFA_HANDLE eeHandle)
{
for (UINT8 index = 0; index < mUiccInfo.num_ee; index++)
diff --git a/src/com/android/nfc/NfcApplication.java b/src/com/android/nfc/NfcApplication.java
index 867b8bb..3e7194d 100644
--- a/src/com/android/nfc/NfcApplication.java
+++ b/src/com/android/nfc/NfcApplication.java
@@ -1,12 +1,18 @@
package com.android.nfc;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Application;
+import android.os.Process;
import android.os.UserHandle;
-import android.util.Log;
+import java.util.Iterator;
+import java.util.List;
public class NfcApplication extends Application {
- public static final String TAG = "NfcApplication";
+ static final String TAG = "NfcApplication";
+ static final String NFC_PROCESS = "com.android.nfc";
+
NfcService mNfcService;
public NfcApplication() {
@@ -17,7 +23,23 @@ public class NfcApplication extends Application {
public void onCreate() {
super.onCreate();
- if (UserHandle.myUserId() == 0) {
+ boolean isMainProcess = false;
+ // We start a service in a separate process to do
+ // handover transfer. We don't want to instantiate an NfcService
+ // object in those cases, hence check the name of the process
+ // to determine whether we're the main NFC service, or the
+ // handover process
+ ActivityManager am = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
+ List processes = am.getRunningAppProcesses();
+ Iterator i = processes.iterator();
+ while (i.hasNext()) {
+ RunningAppProcessInfo appInfo = (RunningAppProcessInfo)(i.next());
+ if (appInfo.pid == Process.myPid()) {
+ isMainProcess = (NFC_PROCESS.equals(appInfo.processName));
+ break;
+ }
+ }
+ if (UserHandle.myUserId() == 0 && isMainProcess) {
mNfcService = new NfcService(this);
}
}
diff --git a/src/com/android/nfc/RegisteredComponentCache.java b/src/com/android/nfc/RegisteredComponentCache.java
index 5da2cd4..8d73317 100644
--- a/src/com/android/nfc/RegisteredComponentCache.java
+++ b/src/com/android/nfc/RegisteredComponentCache.java
@@ -43,6 +43,7 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class RegisteredComponentCache {
private static final String TAG = "RegisteredComponentCache";
+ private static final boolean DEBUG = false;
final Context mContext;
final String mAction;
@@ -165,7 +166,9 @@ public class RegisteredComponentCache {
}
}
- dump(components);
+ if (DEBUG) {
+ dump(components);
+ }
synchronized (this) {
mComponents = components;
diff --git a/src/com/android/nfc/handover/BluetoothHeadsetHandover.java b/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
index 1377160..c845f89 100644
--- a/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
+++ b/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
@@ -16,7 +16,6 @@
package com.android.nfc.handover;
-import android.app.ActivityManager;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -29,12 +28,10 @@ import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.UserHandle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;
-import com.android.nfc.handover.HandoverManager.HandoverPowerManager;
import com.android.nfc.R;
/**
@@ -57,14 +54,13 @@ public class BluetoothHeadsetHandover implements BluetoothProfile.ServiceListene
static final int TIMEOUT_MS = 20000;
static final int STATE_INIT = 0;
- static final int STATE_TURNING_ON = 1;
- static final int STATE_WAITING_FOR_PROXIES = 2;
- static final int STATE_INIT_COMPLETE = 3;
- static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 4;
- static final int STATE_BONDING = 5;
- static final int STATE_CONNECTING = 6;
- static final int STATE_DISCONNECTING = 7;
- static final int STATE_COMPLETE = 8;
+ static final int STATE_WAITING_FOR_PROXIES = 1;
+ static final int STATE_INIT_COMPLETE = 2;
+ static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 3;
+ static final int STATE_BONDING = 4;
+ static final int STATE_CONNECTING = 5;
+ static final int STATE_DISCONNECTING = 6;
+ static final int STATE_COMPLETE = 7;
static final int RESULT_PENDING = 0;
static final int RESULT_CONNECTED = 1;
@@ -80,7 +76,6 @@ public class BluetoothHeadsetHandover implements BluetoothProfile.ServiceListene
final Context mContext;
final BluetoothDevice mDevice;
final String mName;
- final HandoverPowerManager mHandoverPowerManager;
final Callback mCallback;
final BluetoothAdapter mBluetoothAdapter;
@@ -101,18 +96,21 @@ public class BluetoothHeadsetHandover implements BluetoothProfile.ServiceListene
}
public BluetoothHeadsetHandover(Context context, BluetoothDevice device, String name,
- HandoverPowerManager powerManager, Callback callback) {
+ Callback callback) {
checkMainThread(); // mHandler must get get constructed on Main Thread for toasts to work
mContext = context;
mDevice = device;
mName = name;
- mHandoverPowerManager = powerManager;
mCallback = callback;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mState = STATE_INIT;
}
+ public boolean hasStarted() {
+ return mState != STATE_INIT;
+ }
+
/**
* Main entry point. This method is usually called after construction,
* to begin the BT sequence. Must be called on Main thread.
@@ -156,18 +154,6 @@ public class BluetoothHeadsetHandover implements BluetoothProfile.ServiceListene
void nextStepInit() {
switch (mState) {
case STATE_INIT:
- if (!mHandoverPowerManager.isBluetoothEnabled()) {
- if (mHandoverPowerManager.enableBluetooth()) {
- // Bluetooth is being enabled
- mState = STATE_TURNING_ON;
- } else {
- toast(mContext.getString(R.string.failed_to_enable_bt));
- complete(false);
- }
- break;
- }
- // fall-through
- case STATE_TURNING_ON:
if (mA2dp == null || mHeadset == null) {
mState = STATE_WAITING_FOR_PROXIES;
if (!getProfileProxys()) {
@@ -310,18 +296,7 @@ public class BluetoothHeadsetHandover implements BluetoothProfile.ServiceListene
void handleIntent(Intent intent) {
String action = intent.getAction();
- if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action) && mState == STATE_TURNING_ON) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
- if (state == BluetoothAdapter.STATE_ON) {
- nextStep();
- } else if (state == BluetoothAdapter.STATE_OFF) {
- toast(mContext.getString(R.string.failed_to_enable_bt));
- complete(false);
- }
- return;
- }
-
- // Everything else requires the device to match...
+ // Everything requires the device to match...
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (!mDevice.equals(device)) return;
@@ -387,19 +362,18 @@ public class BluetoothHeadsetHandover implements BluetoothProfile.ServiceListene
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_MEDIA_PLAY));
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, null, null, 0, null, null);
+ mContext.sendOrderedBroadcast(intent, null);
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_MEDIA_PLAY));
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, null, null, 0, null, null);
+ mContext.sendOrderedBroadcast(intent, null);
}
void requestPairConfirmation() {
Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class);
dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
- mContext.startActivityAsUser(dialogIntent, new UserHandle(UserHandle.USER_CURRENT));
+ mContext.startActivity(dialogIntent);
}
final Handler mHandler = new Handler() {
diff --git a/src/com/android/nfc/handover/BluetoothOppHandover.java b/src/com/android/nfc/handover/BluetoothOppHandover.java
index ceb3c62..fdb5eff 100644
--- a/src/com/android/nfc/handover/BluetoothOppHandover.java
+++ b/src/com/android/nfc/handover/BluetoothOppHandover.java
@@ -16,25 +16,17 @@
package com.android.nfc.handover;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
import android.webkit.MimeTypeMap;
-import android.widget.Toast;
-
-import com.android.nfc.handover.HandoverManager.HandoverPowerManager;
-import com.android.nfc.R;
-
import java.util.ArrayList;
import java.util.Arrays;
@@ -61,20 +53,19 @@ public class BluetoothOppHandover implements Handler.Callback {
final BluetoothDevice mDevice;
final Uri[] mUris;
- final HandoverPowerManager mHandoverPowerManager;
final boolean mRemoteActivating;
final Handler mHandler;
+ final Long mCreateTime;
int mState;
- Long mStartTime;
public BluetoothOppHandover(Context context, BluetoothDevice device, Uri[] uris,
- HandoverPowerManager powerManager, boolean remoteActivating) {
+ boolean remoteActivating) {
mContext = context;
mDevice = device;
mUris = uris;
- mHandoverPowerManager = powerManager;
mRemoteActivating = remoteActivating;
+ mCreateTime = SystemClock.elapsedRealtime();
mHandler = new Handler(context.getMainLooper(),this);
mState = STATE_INIT;
@@ -104,33 +95,24 @@ public class BluetoothOppHandover implements Handler.Callback {
* to begin the BT sequence. Must be called on Main thread.
*/
public void start() {
- mStartTime = SystemClock.elapsedRealtime();
-
- IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- mContext.registerReceiver(mReceiver, filter);
-
- if (!mHandoverPowerManager.isBluetoothEnabled()) {
- if (mHandoverPowerManager.enableBluetooth()) {
- mState = STATE_TURNING_ON;
- } else {
- Toast.makeText(mContext, mContext.getString(R.string.beam_failed),
- Toast.LENGTH_SHORT).show();
- complete();
- }
- } else {
- // BT already enabled
- if (mRemoteActivating) {
- mHandler.sendEmptyMessageDelayed(MSG_START_SEND, REMOTE_BT_ENABLE_DELAY_MS);
+ if (mRemoteActivating) {
+ Long timeElapsed = SystemClock.elapsedRealtime() - mCreateTime;
+ if (timeElapsed < REMOTE_BT_ENABLE_DELAY_MS) {
+ mHandler.sendEmptyMessageDelayed(MSG_START_SEND,
+ REMOTE_BT_ENABLE_DELAY_MS - timeElapsed);
} else {
- // Remote BT enabled too, start send immediately
+ // Already waited long enough for BT to come up
+ // - start send.
sendIntent();
}
+ } else {
+ // Remote BT enabled already, start send immediately
+ sendIntent();
}
}
void complete() {
mState = STATE_COMPLETE;
- mContext.unregisterReceiver(mReceiver);
}
void sendIntent() {
@@ -153,32 +135,6 @@ public class BluetoothOppHandover implements Handler.Callback {
complete();
}
- void handleIntent(Intent intent) {
- String action = intent.getAction();
- if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action) && mState == STATE_TURNING_ON) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
- if (state == BluetoothAdapter.STATE_ON) {
- // Add additional delay if needed
- Long timeElapsed = SystemClock.elapsedRealtime() - mStartTime;
- if (mRemoteActivating && timeElapsed < REMOTE_BT_ENABLE_DELAY_MS) {
- mHandler.sendEmptyMessageDelayed(MSG_START_SEND,
- REMOTE_BT_ENABLE_DELAY_MS - timeElapsed);
- } else {
- sendIntent();
- }
- } else if (state == BluetoothAdapter.STATE_OFF) {
- complete();
- }
- return;
- }
- }
-
- final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- handleIntent(intent);
- }
- };
@Override
public boolean handleMessage(Message msg) {
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);
- }
- }
- }
- };
-
}
diff --git a/src/com/android/nfc/handover/HandoverServer.java b/src/com/android/nfc/handover/HandoverServer.java
index e789387..093d1dd 100644
--- a/src/com/android/nfc/handover/HandoverServer.java
+++ b/src/com/android/nfc/handover/HandoverServer.java
@@ -210,7 +210,8 @@ public final class HandoverServer {
}
// We're done
mCallback.onHandoverRequestReceived();
- break;
+ // We can process another handover transfer
+ byteStream = new ByteArrayOutputStream();
}
synchronized (HandoverServer.this) {
diff --git a/src/com/android/nfc/handover/HandoverService.java b/src/com/android/nfc/handover/HandoverService.java
new file mode 100644
index 0000000..261cbca
--- /dev/null
+++ b/src/com/android/nfc/handover/HandoverService.java
@@ -0,0 +1,403 @@
+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 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;
+ }
+ 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) {
+ 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
+ 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);
+ }
+ 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();
+ }
+}
diff --git a/src/com/android/nfc/handover/HandoverTransfer.java b/src/com/android/nfc/handover/HandoverTransfer.java
new file mode 100644
index 0000000..98b59a6
--- /dev/null
+++ b/src/com/android/nfc/handover/HandoverTransfer.java
@@ -0,0 +1,423 @@
+package com.android.nfc.handover;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Notification.Builder;
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.nfc.R;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+
+/**
+ * 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.
+ *
+ */
+public class HandoverTransfer implements Handler.Callback,
+ MediaScannerConnection.OnScanCompletedListener {
+
+ interface Callback {
+ void onTransferComplete(HandoverTransfer transfer, boolean success);
+ };
+
+ static final String TAG = "HandoverTransfer";
+
+ static final Boolean DBG = true;
+
+ // 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 boolean mIncoming; // whether this is an incoming transfer
+ final int mTransferId; // Unique ID of this transfer used for notifications
+ final PendingIntent mCancelIntent;
+ final Context mContext;
+ final Handler mHandler;
+ final NotificationManager mNotificationManager;
+ final BluetoothDevice mRemoteDevice;
+ final Callback mCallback;
+
+ // Variables below are only accessed on the main thread
+ int mState;
+ boolean mCalledBack;
+ Long mLastUpdate; // Last time an event occurred for this transfer
+ float mProgress; // Progress in range [0..1]
+ ArrayList<Uri> mBtUris; // Received uris from Bluetooth OPP
+ ArrayList<String> mBtMimeTypes; // Mime-types received from Bluetooth OPP
+
+ ArrayList<String> mPaths; // Raw paths on the filesystem for Beam-stored files
+ HashMap<String, String> mMimeTypes; // Mime-types associated with each path
+ HashMap<String, Uri> mMediaUris; // URIs found by the media scanner for each path
+ int mUrisScanned;
+
+ public HandoverTransfer(Context context, Callback callback,
+ PendingHandoverTransfer pendingTransfer) {
+ mContext = context;
+ mCallback = callback;
+ mRemoteDevice = pendingTransfer.remoteDevice;
+ mIncoming = pendingTransfer.incoming;
+ mTransferId = pendingTransfer.id;
+ mLastUpdate = SystemClock.elapsedRealtime();
+ mProgress = 0.0f;
+ mState = STATE_NEW;
+ mBtUris = new ArrayList<Uri>();
+ mBtMimeTypes = new ArrayList<String>();
+ mPaths = new ArrayList<String>();
+ mMimeTypes = new HashMap<String, String>();
+ mMediaUris = new HashMap<String, Uri>();
+ mCancelIntent = buildCancelIntent();
+ mUrisScanned = 0;
+
+ mHandler = new Handler(Looper.getMainLooper(), this);
+ mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ }
+
+ void whitelistOppDevice(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
+ Intent intent = new Intent(HandoverManager.ACTION_WHITELIST_DEVICE);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+
+ public void updateFileProgress(float progress) {
+ if (!isRunning()) return; // Ignore when we're no longer running
+
+ mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
+
+ this.mProgress = progress;
+
+ // We're still receiving data from this device - keep it in
+ // the whitelist for a while longer
+ if (mIncoming) whitelistOppDevice(mRemoteDevice);
+
+ updateStateAndNotification(STATE_IN_PROGRESS);
+ }
+
+ public 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.mProgress = 1.0f;
+ if (mimeType == null) {
+ mimeType = BluetoothOppHandover.getMimeTypeForUri(mContext, uri);
+ }
+ if (mimeType != null) {
+ mBtUris.add(uri);
+ mBtMimeTypes.add(mimeType);
+ } else {
+ if (DBG) Log.d(TAG, "Could not get mimeType for file.");
+ }
+ } else {
+ Log.e(TAG, "Handover transfer failed");
+ // Do wait to see if there's another file coming.
+ }
+ mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
+ mHandler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS);
+ updateStateAndNotification(STATE_W4_NEXT_TRANSFER);
+ }
+
+ public boolean isRunning() {
+ if (mState != STATE_NEW && mState != STATE_IN_PROGRESS && mState != STATE_W4_NEXT_TRANSFER) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ void cancel() {
+ if (!isRunning()) return;
+
+ // Delete all files received so far
+ for (Uri uri : mBtUris) {
+ File file = new File(uri.getPath());
+ if (file.exists()) file.delete();
+ }
+
+ updateStateAndNotification(STATE_CANCELLED);
+ }
+
+ void updateNotification() {
+ if (!mIncoming) return; // No notifications for outgoing transfers
+
+ Builder notBuilder = new Notification.Builder(mContext);
+
+ if (mState == STATE_NEW || mState == STATE_IN_PROGRESS ||
+ mState == STATE_W4_NEXT_TRANSFER || mState == 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), mCancelIntent);
+ notBuilder.setDeleteIntent(mCancelIntent);
+ // 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 (mState == 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.getActivity(
+ mContext, 0, viewIntent, 0, null);
+
+ notBuilder.setContentIntent(contentIntent);
+ } else if (mState == 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 (mState == 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.notify(null, mTransferId, notBuilder.build());
+ }
+
+ void updateStateAndNotification(int newState) {
+ this.mState = newState;
+ this.mLastUpdate = SystemClock.elapsedRealtime();
+
+ if (mHandler.hasMessages(MSG_TRANSFER_TIMEOUT)) {
+ // Update timeout timer
+ mHandler.removeMessages(MSG_TRANSFER_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
+ }
+
+ updateNotification();
+
+ if ((mState == STATE_SUCCESS || mState == STATE_FAILED || mState == STATE_CANCELLED)
+ && !mCalledBack) {
+ mCalledBack = true;
+ // Notify that we're done with this transfer
+ mCallback.onTransferComplete(this, mState == STATE_SUCCESS);
+ }
+ }
+
+ 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) || mBtUris.size() == 0) {
+ Log.e(TAG, "Media storage not valid or no uris received.");
+ updateStateAndNotification(STATE_FAILED);
+ return;
+ }
+
+ if (mBtUris.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 < mBtUris.size(); i++) {
+ Uri uri = mBtUris.get(i);
+ String mimeType = mBtMimeTypes.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 {
+ mPaths.add(dstFile.getAbsolutePath());
+ mMimeTypes.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 = mMimeTypes.get(mPaths.get(0));
+ if (mimeType.startsWith("image/") || mimeType.startsWith("video/") ||
+ mimeType.startsWith("audio/")) {
+ String[] arrayPaths = new String[mPaths.size()];
+ MediaScannerConnection.scanFile(mContext, mPaths.toArray(arrayPaths), null, this);
+ updateStateAndNotification(STATE_W4_MEDIA_SCANNER);
+ } else {
+ // We're done.
+ updateStateAndNotification(STATE_SUCCESS);
+ }
+
+ }
+
+ public int getTransferId() {
+ return mTransferId;
+ }
+
+ 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 (mIncoming) {
+ 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);
+ }
+ }
+ return false;
+ }
+
+ public synchronized void onScanCompleted(String path, Uri uri) {
+ if (DBG) Log.d(TAG, "Scan completed, path " + path + " uri " + uri);
+ if (uri != null) {
+ mMediaUris.put(path, uri);
+ }
+ mUrisScanned++;
+ if (mUrisScanned == mPaths.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;
+ }
+ return true;
+ } else {
+ Log.e(TAG, "External storage not mounted, can't store file.");
+ return false;
+ }
+ }
+
+ Intent buildViewIntent() {
+ if (mPaths.size() == 0) return null;
+
+ Intent viewIntent = new Intent(Intent.ACTION_VIEW);
+
+ String filePath = mPaths.get(0);
+ Uri mediaUri = mMediaUris.get(filePath);
+ Uri uri = mediaUri != null ? mediaUri :
+ Uri.parse(ContentResolver.SCHEME_FILE + "://" + filePath);
+ viewIntent.setDataAndTypeAndNormalize(uri, mMimeTypes.get(filePath));
+ viewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return viewIntent;
+ }
+
+ PendingIntent buildCancelIntent() {
+ Intent intent = new Intent(HandoverService.ACTION_CANCEL_HANDOVER_TRANSFER);
+ intent.putExtra(HandoverService.EXTRA_SOURCE_ADDRESS, mRemoteDevice.getAddress());
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+
+ return pi;
+ }
+
+ 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++;
+ }
+ return dstFile;
+ }
+
+ 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;
+ }
+}
+
diff --git a/src/com/android/nfc/handover/PendingHandoverTransfer.java b/src/com/android/nfc/handover/PendingHandoverTransfer.java
new file mode 100644
index 0000000..db5c68b
--- /dev/null
+++ b/src/com/android/nfc/handover/PendingHandoverTransfer.java
@@ -0,0 +1,63 @@
+package com.android.nfc.handover;
+
+import android.bluetooth.BluetoothDevice;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class PendingHandoverTransfer implements Parcelable {
+ public int id;
+ public boolean incoming;
+ public BluetoothDevice remoteDevice;
+ public boolean remoteActivating;
+ public Uri[] uris;
+
+ PendingHandoverTransfer(int id, boolean incoming, BluetoothDevice remoteDevice,
+ boolean remoteActivating, Uri[] uris) {
+ this.id = id;
+ this.incoming = incoming;
+ this.remoteDevice = remoteDevice;
+ this.remoteActivating = remoteActivating;
+ this.uris = uris;
+ }
+
+ public static final Parcelable.Creator<PendingHandoverTransfer> CREATOR
+ = new Parcelable.Creator<PendingHandoverTransfer>() {
+ public PendingHandoverTransfer createFromParcel(Parcel in) {
+ int id = in.readInt();
+ boolean incoming = (in.readInt() == 1) ? true : false;
+ BluetoothDevice remoteDevice = in.readParcelable(getClass().getClassLoader());
+ boolean remoteActivating = (in.readInt() == 1) ? true : false;
+ int numUris = in.readInt();
+ Uri[] uris = null;
+ if (numUris > 0) {
+ uris = new Uri[numUris];
+ in.readTypedArray(uris, Uri.CREATOR);
+ }
+ return new PendingHandoverTransfer(id, incoming, remoteDevice,
+ remoteActivating, uris);
+ }
+
+ @Override
+ public PendingHandoverTransfer[] newArray(int size) {
+ return new PendingHandoverTransfer[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeInt(incoming ? 1 : 0);
+ dest.writeParcelable(remoteDevice, 0);
+ dest.writeInt(remoteActivating ? 1 : 0);
+ dest.writeInt(uris != null ? uris.length : 0);
+ if (uris != null && uris.length > 0) {
+ dest.writeTypedArray(uris, 0);
+ }
+ }
+}