summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xres/values/strings.xml14
-rw-r--r--src/com/android/nfc/P2pEventManager.java6
-rwxr-xr-xsrc/com/android/nfc/P2pLinkManager.java30
-rw-r--r--src/com/android/nfc/handover/BluetoothHeadsetHandover.java26
-rw-r--r--src/com/android/nfc/handover/BluetoothOppHandover.java55
-rw-r--r--src/com/android/nfc/handover/HandoverManager.java289
6 files changed, 307 insertions, 113 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 903103a..21cfab8 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -18,4 +18,18 @@
<!-- Content description of the NFC enabled notification icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_nfc_enabled">NFC enabled.</string>
<string name="touch">Touch to beam</string>
+
+ <string name="beam_progress">Beam in progress</string>
+ <string name="beam_complete">Beam complete</string>
+ <string name="beam_failed">Beam failed</string>
+ <string name="beam_touch_to_view">Touch to view</string>
+
+ <string name="connecting_headset">Connecting</string>
+ <string name="connected_headset">Connected</string>
+ <string name="connect_headset_failed">Failed to connect</string>
+ <string name="disconnecting_headset">Disconnecting</string>
+ <string name="disconnected_headset">Disconnected</string>
+ <string name="pairing_headset">Pairing</string>
+ <string name="pairing_headset_failed">Failed to pair</string>
+ <string name="failed_to_enable_bt">Failed to enable Bluetooth</string>
</resources>
diff --git a/src/com/android/nfc/P2pEventManager.java b/src/com/android/nfc/P2pEventManager.java
index 4a57deb..9050288 100644
--- a/src/com/android/nfc/P2pEventManager.java
+++ b/src/com/android/nfc/P2pEventManager.java
@@ -87,16 +87,16 @@ public class P2pEventManager implements P2pEventListener, SendUi.Callback {
}
@Override
- public void onP2pReceiveComplete() {
+ public void onP2pReceiveComplete(boolean playSound) {
mVibrator.vibrate(VIBRATION_PATTERN, -1);
- mNfcService.playSound(NfcService.SOUND_END);
+ if (playSound) mNfcService.playSound(NfcService.SOUND_END);
// TODO we still don't have a nice receive solution
// The sanest solution right now is just to scale back up what we had
// and start the new activity. It is not perfect, but at least it is
// consistent behavior. All other variants involve making the old
// activity screenshot disappear, and then removing the animation
// window hoping the new activity has started by then. This just goes
- // wrong too often and can looks weird.
+ // wrong too often and can look weird.
mSendUi.finish(SendUi.FINISH_SCALE_UP);
mNdefReceived = true;
}
diff --git a/src/com/android/nfc/P2pLinkManager.java b/src/com/android/nfc/P2pLinkManager.java
index 89c3dc8..ecded75 100755
--- a/src/com/android/nfc/P2pLinkManager.java
+++ b/src/com/android/nfc/P2pLinkManager.java
@@ -79,7 +79,7 @@ interface P2pEventListener {
/**
* Called to indicate a receive was successful.
*/
- public void onP2pReceiveComplete();
+ public void onP2pReceiveComplete(boolean playSound);
/**
* Indicates the P2P device went out of range.
@@ -123,9 +123,10 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
static final int MSG_DEBOUNCE_TIMEOUT = 1;
static final int MSG_RECEIVE_COMPLETE = 2;
- static final int MSG_SEND_COMPLETE = 3;
- static final int MSG_START_ECHOSERVER = 4;
- static final int MSG_STOP_ECHOSERVER = 5;
+ static final int MSG_RECEIVE_HANDOVER = 3;
+ static final int MSG_SEND_COMPLETE = 4;
+ static final int MSG_START_ECHOSERVER = 5;
+ static final int MSG_STOP_ECHOSERVER = 6;
// values for mLinkState
static final int LINK_STATE_DOWN = 1;
@@ -467,6 +468,7 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
NdefMessage response = mHandoverManager.tryHandoverRequest(msg);
if (response != null) {
+ onReceiveHandover();
return SnepMessage.getSuccessResponse(response);
} else {
return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_FOUND);
@@ -474,6 +476,10 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
}
};
+ void onReceiveHandover() {
+ mHandler.obtainMessage(MSG_RECEIVE_HANDOVER).sendToTarget();
+ }
+
void onReceiveComplete(NdefMessage msg) {
EventLogTags.writeNfcNdefReceived(getMessageSize(msg), getMessageTnf(msg),
getMessageType(msg), getMessageAarPresent(msg));
@@ -513,6 +519,20 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
mEventListener.onP2pOutOfRange();
}
break;
+ case MSG_RECEIVE_HANDOVER:
+ // We're going to do a handover request
+ synchronized (this) {
+ if (mLinkState == LINK_STATE_DOWN) {
+ break;
+ }
+ if (mSendState == SEND_STATE_SENDING) {
+ cancelSendNdefMessage();
+ }
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
+ mEventListener.onP2pReceiveComplete(false);
+ }
+ break;
case MSG_RECEIVE_COMPLETE:
NdefMessage m = (NdefMessage) msg.obj;
synchronized (this) {
@@ -524,7 +544,7 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
}
mSendState = SEND_STATE_NOTHING_TO_SEND;
if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
- mEventListener.onP2pReceiveComplete();
+ mEventListener.onP2pReceiveComplete(true);
NfcService.getInstance().sendMockNdefTag(m);
}
break;
diff --git a/src/com/android/nfc/handover/BluetoothHeadsetHandover.java b/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
index 0cf195d..7cb0424 100644
--- a/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
+++ b/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
@@ -33,6 +33,7 @@ import android.view.KeyEvent;
import android.widget.Toast;
import com.android.nfc.handover.HandoverManager.HandoverPowerManager;
+import com.android.nfc.R;
/**
* Connects / Disconnects from a Bluetooth headset (or any device that
@@ -43,9 +44,7 @@ import com.android.nfc.handover.HandoverManager.HandoverPowerManager;
* designed to be re-used after the sequence has completed or timed out.
* Subsequent NFC interactions should use new objects.
*
- * TODO: prevent auto-connecting to other devices and other incoming a2dp/hsp
- * connects.
- * TODO: il8n / UI review
+ * TODO: UI review
*/
public class BluetoothHeadsetHandover {
static final String TAG = HandoverManager.TAG;
@@ -156,7 +155,8 @@ public class BluetoothHeadsetHandover {
mA2dpResult = RESULT_DISCONNECTED;
}
if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
- toast("Disconnecting " + mName + "...");
+ toast(mContext.getString(R.string.disconnecting_headset ) + " " +
+ mName + "...");
break;
}
// fall-through
@@ -166,7 +166,7 @@ public class BluetoothHeadsetHandover {
break;
}
if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) {
- toast("Disconnected " + mName);
+ toast(mContext.getString(R.string.disconnected_headset) + " " + mName);
}
complete(false);
break;
@@ -181,7 +181,7 @@ public class BluetoothHeadsetHandover {
// Bluetooth is being enabled
mState = STATE_TURNING_ON;
} else {
- toast("Failed to enable Bluetooth");
+ toast(mContext.getString(R.string.failed_to_enable_bt));
complete(false);
}
break;
@@ -210,7 +210,7 @@ public class BluetoothHeadsetHandover {
mA2dpResult = RESULT_CONNECTED;
}
if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
- toast("Connecting " + mName + "...");
+ toast(mContext.getString(R.string.connecting_headset) + " " + mName + "...");
break;
}
// fall-through
@@ -221,11 +221,11 @@ public class BluetoothHeadsetHandover {
}
if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) {
// we'll take either as success
- toast("Connected " + mName);
+ toast(mContext.getString(R.string.connected_headset) + " " + mName);
if (mA2dpResult == RESULT_CONNECTED) startTheMusic();
complete(true);
} else {
- toast ("Failed to connect " + mName);
+ toast (mContext.getString(R.string.connect_headset_failed) + " " + mName);
complete(false);
}
break;
@@ -234,9 +234,9 @@ public class BluetoothHeadsetHandover {
void startBonding() {
mState = STATE_BONDING;
- toast("Pairing " + mName + "...");
+ toast(mContext.getString(R.string.pairing_headset) + " " + mName + "...");
if (!mDevice.createBond()) {
- toast("Failed to pair " + mName);
+ toast(mContext.getString(R.string.pairing_headset_failed) + " " + mName);
complete(false);
}
}
@@ -248,7 +248,7 @@ public class BluetoothHeadsetHandover {
if (state == BluetoothAdapter.STATE_ON) {
nextStepConnect();
} else if (state == BluetoothAdapter.STATE_OFF) {
- toast("Failed to enable Bluetooth");
+ toast(mContext.getString(R.string.failed_to_enable_bt));
complete(false);
}
return;
@@ -264,7 +264,7 @@ public class BluetoothHeadsetHandover {
if (bond == BluetoothDevice.BOND_BONDED) {
nextStepConnect();
} else if (bond == BluetoothDevice.BOND_NONE) {
- toast("Failed to pair " + mName);
+ toast(mContext.getString(R.string.pairing_headset_failed) + " " + mName);
complete(false);
}
} else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
diff --git a/src/com/android/nfc/handover/BluetoothOppHandover.java b/src/com/android/nfc/handover/BluetoothOppHandover.java
index 3f3718f..325691d 100644
--- a/src/com/android/nfc/handover/BluetoothOppHandover.java
+++ b/src/com/android/nfc/handover/BluetoothOppHandover.java
@@ -8,40 +8,55 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
-import android.nfc.NfcAdapter;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
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;
-public class BluetoothOppHandover {
+public class BluetoothOppHandover implements Handler.Callback {
static final String TAG = "BluetoothOppHandover";
static final boolean D = true;
static final int STATE_INIT = 0;
static final int STATE_TURNING_ON = 1;
- static final int STATE_COMPLETE = 2;
+ static final int STATE_WAITING = 2; // Need to wait for remote side turning on BT
+ static final int STATE_COMPLETE = 3;
+
+ static final int MSG_START_SEND = 0;
+
+ static final int REMOTE_BT_ENABLE_DELAY_MS = 3000;
public static final String EXTRA_CONNECTION_HANDOVER =
"com.android.intent.extra.CONNECTION_HANDOVER";
final Context mContext;
final BluetoothDevice mDevice;
+
final Uri[] mUris;
final HandoverPowerManager mHandoverPowerManager;
+ final boolean mRemoteActivating;
+ final Handler mHandler;
int mState;
+ Long mStartTime;
public BluetoothOppHandover(Context context, BluetoothDevice device, Uri[] uris,
- HandoverPowerManager powerManager) {
+ HandoverPowerManager powerManager, boolean remoteActivating) {
mContext = context;
mDevice = device;
mUris = uris;
mHandoverPowerManager = powerManager;
+ mRemoteActivating = remoteActivating;
+ mHandler = new Handler(context.getMainLooper(),this);
mState = STATE_INIT;
}
@@ -67,6 +82,8 @@ public class BluetoothOppHandover {
* 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);
@@ -74,11 +91,18 @@ public class BluetoothOppHandover {
if (mHandoverPowerManager.enableBluetooth()) {
mState = STATE_TURNING_ON;
} else {
- // TODO deal with this: toast or tie in to Beam failure?
+ Toast.makeText(mContext, mContext.getString(R.string.beam_failed),
+ Toast.LENGTH_SHORT).show();
+ complete();
}
} else {
// BT already enabled
- sendIntent();
+ if (mRemoteActivating) {
+ mHandler.sendEmptyMessageDelayed(MSG_START_SEND, REMOTE_BT_ENABLE_DELAY_MS);
+ } else {
+ // Remote BT enabled too, start send immediately
+ sendIntent();
+ }
}
}
@@ -101,7 +125,7 @@ public class BluetoothOppHandover {
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, mUris[0]);
} else {
- ArrayList<Uri> uris = (ArrayList<Uri>)Arrays.asList(mUris);
+ ArrayList<Uri> uris = new ArrayList<Uri>(Arrays.asList(mUris));
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
}
@@ -117,7 +141,14 @@ public class BluetoothOppHandover {
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) {
- sendIntent();
+ // 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();
}
@@ -132,4 +163,12 @@ public class BluetoothOppHandover {
}
};
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_START_SEND) {
+ sendIntent();
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/com/android/nfc/handover/HandoverManager.java b/src/com/android/nfc/handover/HandoverManager.java
index 997afa6..6ebfc2d 100644
--- a/src/com/android/nfc/handover/HandoverManager.java
+++ b/src/com/android/nfc/handover/HandoverManager.java
@@ -22,6 +22,8 @@ import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
import java.util.Random;
import android.app.Notification;
@@ -38,6 +40,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
+import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.os.Handler;
@@ -45,6 +48,11 @@ import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
+import android.widget.Toast;
+
+import com.android.nfc.NfcService;
+import com.android.nfc.R;
+
/**
* Manages handover of NFC to other technologies.
@@ -58,37 +66,37 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob".
getBytes(Charset.forName("US_ASCII"));
- public static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
+ static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
"android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS";
- public static final String ACTION_BT_OPP_TRANSFER_DONE =
+ static final String ACTION_BT_OPP_TRANSFER_DONE =
"android.btopp.intent.action.BT_OPP_TRANSFER_DONE";
- public static final String EXTRA_BT_OPP_TRANSFER_STATUS =
+ static final String EXTRA_BT_OPP_TRANSFER_STATUS =
"android.btopp.intent.extra.BT_OPP_TRANSFER_STATUS";
- public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+ static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
- public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+ static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
- public static final String EXTRA_BT_OPP_TRANSFER_DIRECTION =
+ static final String EXTRA_BT_OPP_TRANSFER_DIRECTION =
"android.btopp.intent.extra.BT_OPP_TRANSFER_DIRECTION";
- public static final int DIRECTION_BLUETOOTH_INCOMING = 0;
+ static final int DIRECTION_BLUETOOTH_INCOMING = 0;
- public static final int DIRECTION_BLUETOOTH_OUTGOING = 1;
+ static final int DIRECTION_BLUETOOTH_OUTGOING = 1;
- public static final String EXTRA_BT_OPP_TRANSFER_ID =
+ static final String EXTRA_BT_OPP_TRANSFER_ID =
"android.btopp.intent.extra.BT_OPP_TRANSFER_ID";
- public static final String EXTRA_BT_OPP_TRANSFER_PROGRESS =
+ static final String EXTRA_BT_OPP_TRANSFER_PROGRESS =
"android.btopp.intent.extra.BT_OPP_TRANSFER_PROGRESS";
- public static final String EXTRA_BT_OPP_TRANSFER_URI =
+ 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
- public static final String HANDOVER_STATUS_PERMISSION =
+ static final String HANDOVER_STATUS_PERMISSION =
"com.android.permission.HANDOVER_STATUS";
static final int MSG_HANDOVER_POWER_CHECK = 0;
@@ -96,13 +104,17 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
// We poll whether we can safely disable BT every POWER_CHECK_MS
static final int POWER_CHECK_MS = 20000;
- public static final String ACTION_WHITELIST_DEVICE =
+ static final String ACTION_WHITELIST_DEVICE =
"android.btopp.intent.action.WHITELIST_DEVICE";
- public static final int SOURCE_BLUETOOTH_INCOMING = 0;
+ static final int SOURCE_BLUETOOTH_INCOMING = 0;
- public static final int SOURCE_BLUETOOTH_OUTGOING = 1;
+ 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;
final Context mContext;
final BluetoothAdapter mBluetoothAdapter;
@@ -115,18 +127,18 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
BluetoothHeadsetHandover mBluetoothHeadsetHandover;
boolean mBluetoothHeadsetConnected;
+ String mLocalBluetoothAddress;
int mNotificationId;
- // TODO regular cleanup of finished transfers.
HashMap<Pair<Integer, Integer>, HandoverTransfer> mTransfers;
static class BluetoothHandoverData {
public boolean valid = false;
public BluetoothDevice device;
public String name;
+ public boolean carrierActivating = false;
}
class HandoverPowerManager implements Handler.Callback {
- // TODO stop monitoring if BT is turned off by the user
final Handler handler;
final Context context;
@@ -156,6 +168,10 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
return mBluetoothAdapter.isEnabled();
}
+ void stopMonitoring() {
+ handler.removeMessages(MSG_HANDOVER_POWER_CHECK);
+ }
+
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
@@ -252,15 +268,15 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
int progressInt = (int) (progress * 100);
notBuilder.setAutoCancel(false);
notBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
- notBuilder.setTicker("Beam incoming...");
- notBuilder.setContentTitle("Beam incoming...");
+ notBuilder.setTicker(mContext.getString(R.string.beam_progress));
+ notBuilder.setContentTitle(mContext.getString(R.string.beam_progress));
notBuilder.setProgress(100, progressInt, progress == -1);
} else if (state == STATE_SUCCESS) {
notBuilder.setAutoCancel(true);
notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
- notBuilder.setTicker("Beam complete.");
- notBuilder.setContentTitle("Beam complete");
- notBuilder.setContentText("Touch to view");
+ 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 notificationIntent = new Intent(Intent.ACTION_VIEW);
String mimeType = BluetoothOppHandover.getMimeTypeForUri(mContext, uri);
@@ -268,17 +284,19 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
notBuilder.setContentIntent(contentIntent);
+
+ // Play Beam success sound
+ NfcService.getInstance().playSound(NfcService.SOUND_END);
} else if (state == STATE_FAILED) {
notBuilder.setAutoCancel(true);
notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done);
- notBuilder.setTicker("Beam failed.");
- notBuilder.setContentTitle("Beam failed");
- // TODO content text
+ notBuilder.setTicker(mContext.getString(R.string.beam_failed));
+ notBuilder.setContentTitle(mContext.getString(R.string.beam_failed));
} else {
return;
}
- mNotificationManager.notify(mNotificationId, notBuilder.getNotification());
+ mNotificationManager.notify(mNotificationId, notBuilder.build());
}
}
@@ -311,24 +329,35 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
IntentFilter filter = new IntentFilter(ACTION_BT_OPP_TRANSFER_DONE);
filter.addAction(ACTION_BT_OPP_TRANSFER_PROGRESS);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mReceiver, filter, HANDOVER_STATUS_PERMISSION, null);
}
+ synchronized void cleanupTransfers() {
+ Iterator<Map.Entry<Pair<Integer, Integer>, HandoverTransfer>> it = mTransfers.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Pair<Integer, Integer>, HandoverTransfer> pair = it.next();
+ HandoverTransfer transfer = pair.getValue();
+ if (!transfer.isRunning()) {
+ it.remove();
+ }
+ }
+ }
+
static NdefRecord createCollisionRecord() {
byte[] random = new byte[2];
new Random().nextBytes(random);
- return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, new byte[] {0x48, 0x72}, null, random);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null, random);
}
- NdefRecord createBluetoothAlternateCarrierRecord() {
+ NdefRecord createBluetoothAlternateCarrierRecord(boolean activating) {
byte[] payload = new byte[4];
- //TODO: Encode 'activating' if BT is not on yet
- payload[0] = 0x01; // Carrier Power State: Active
+ payload[0] = (byte) (activating ? CARRIER_POWER_STATE_ACTIVATING :
+ CARRIER_POWER_STATE_ACTIVE); // Carrier Power State: Activating or active
payload[1] = 1; // length of carrier data reference
payload[2] = 'b'; // carrier data reference: ID for Bluetooth OOB data record
payload[3] = 0; // Auxiliary data reference count
- // 0x61, 0x63 is "ac"
- return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, new byte[] {0x61, 0x63}, null, payload);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null, payload);
}
NdefRecord createBluetoothOobDataRecord() {
@@ -336,10 +365,15 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
payload[0] = 0;
payload[1] = (byte)payload.length;
- //TODO: check getAddress() works with BT off
- String address = mBluetoothAdapter.getAddress();
- byte[] addressBytes = addressToReverseBytes(address);
- System.arraycopy(addressBytes, 0, payload, 2, 6);
+ synchronized (HandoverManager.this) {
+ if (mLocalBluetoothAddress == null) {
+ mLocalBluetoothAddress = mBluetoothAdapter.getAddress();
+ }
+
+ byte[] addressBytes = addressToReverseBytes(mLocalBluetoothAddress);
+ System.arraycopy(addressBytes, 0, payload, 2, 6);
+ }
+
return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload);
}
@@ -347,23 +381,40 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
return new NdefMessage(createHandoverRequestRecord(), createBluetoothOobDataRecord());
}
- NdefRecord createHandoverRequestRecord() {
- ByteBuffer payload = ByteBuffer.allocate(100); //TODO figure out size
+ NdefMessage createHandoverSelectMessage(boolean activating) {
+ return new NdefMessage(createHandoverSelectRecord(activating), createBluetoothOobDataRecord());
+ }
+
+ NdefRecord createHandoverSelectRecord(boolean activating) {
+ NdefMessage nestedMessage = new NdefMessage(createBluetoothAlternateCarrierRecord(activating));
+ byte[] nestedPayload = nestedMessage.toByteArray();
+
+ ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1);
payload.put((byte)0x12); // connection handover v1.2
+ payload.put(nestedPayload);
+
+ byte[] payloadBytes = new byte[payload.position()];
+ payload.position(0);
+ payload.get(payloadBytes);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null,
+ payloadBytes);
+
+ }
- //TODO: spec is not clear if we encode each nested NdefRecord as a
- // a stand-alone message (with MB and ME flags set), or as a combined
- // message (MB only set on first, ME only set on last). Current
- // implementation assumes the later.
+ NdefRecord createHandoverRequestRecord() {
NdefMessage nestedMessage = new NdefMessage(createCollisionRecord(),
- createBluetoothAlternateCarrierRecord());
+ createBluetoothAlternateCarrierRecord(false));
+ byte[] nestedPayload = nestedMessage.toByteArray();
+
+ ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1);
+ payload.put((byte)0x12); // connection handover v1.2
payload.put(nestedMessage.toByteArray());
byte[] payloadBytes = new byte[payload.position()];
payload.position(0);
payload.get(payloadBytes);
- // 0x48, 0x72 is "Hr"
- return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, new byte[]{0x48, 0x72}, null, payloadBytes);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null,
+ payloadBytes);
}
/**
@@ -376,9 +427,9 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
NdefRecord r = m.getRecords()[0];
if (r.getTnf() != NdefRecord.TNF_WELL_KNOWN) return null;
- if (!Arrays.equals(r.getType(), new byte[]{0x48, 0x72})) return null;
+ if (!Arrays.equals(r.getType(), NdefRecord.RTD_HANDOVER_REQUEST)) return null;
- // we have a handover select, look for BT OOB record
+ // we have a handover request, look for BT OOB record
BluetoothHandoverData bluetoothData = null;
for (NdefRecord oob : m.getRecords()) {
if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
@@ -389,17 +440,21 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
}
if (bluetoothData == null) return null;
+ boolean bluetoothActivating = false;
+
if (!mHandoverPowerManager.isBluetoothEnabled()) {
- mHandoverPowerManager.enableBluetooth();
- // TODO determine how to deal with failure (toast?)
+ if (!mHandoverPowerManager.enableBluetooth()) {
+ toast(mContext.getString(R.string.beam_failed));
+ return null;
+ }
+ bluetoothActivating = true;
}
// BT OOB found, whitelist it for incoming OPP data
whitelistOppDevice(bluetoothData.device);
// return BT OOB record so they can perform handover
- //TODO: Should use full Carrier Select form with power state to handle BT enabling...
- return new NdefMessage(createBluetoothOobDataRecord());
+ return (createHandoverSelectMessage(bluetoothActivating));
}
void whitelistOppDevice(BluetoothDevice device) {
@@ -437,11 +492,63 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
// This starts sending an Uri over BT
public void doHandoverUri(Uri[] uris, NdefMessage m) {
-
BluetoothHandoverData data = parse(m);
- BluetoothOppHandover handover = new BluetoothOppHandover(mContext, data.device,
- uris, mHandoverPowerManager);
- handover.start();
+ if (data != null && data.valid) {
+ BluetoothOppHandover handover = new BluetoothOppHandover(mContext, data.device,
+ uris, mHandoverPowerManager, data.carrierActivating);
+ handover.start();
+ }
+ }
+
+ boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) {
+ byte[] payload = handoverRec.getPayload();
+ if (payload == null || payload.length <= 1) return false;
+ // Skip version
+ byte[] payloadNdef = new byte[payload.length - 1];
+ System.arraycopy(payload, 1, payloadNdef, 0, payload.length - 1);
+ NdefMessage msg;
+ try {
+ msg = new NdefMessage(payloadNdef);
+ } catch (FormatException e) {
+ return false;
+ }
+
+ for (NdefRecord alt : msg.getRecords()) {
+ byte[] acPayload = alt.getPayload();
+ if (acPayload != null) {
+ ByteBuffer buf = ByteBuffer.wrap(acPayload);
+ int cps = buf.get() & 0x03; // Carrier Power State is in lower 2 bits
+ int carrierRefLength = buf.get() & 0xFF;
+ if (carrierRefLength != carrierId.length) return false;
+
+ byte[] carrierRefId = new byte[carrierRefLength];
+ buf.get(carrierRefId);
+ if (Arrays.equals(carrierRefId, carrierId)) {
+ // Found match, returning whether power state is activating
+ return (cps == CARRIER_POWER_STATE_ACTIVATING);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ BluetoothHandoverData parseHandoverSelect(NdefMessage m) {
+ // TODO we could parse this a lot more strictly; right now
+ // we just search for a BT OOB record, and try to cross-reference
+ // the carrier state inside the 'hs' payload.
+ for (NdefRecord oob : m.getRecords()) {
+ if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
+ Arrays.equals(oob.getType(), TYPE_BT_OOB)) {
+ BluetoothHandoverData data = parseBtOob(ByteBuffer.wrap(oob.getPayload()));
+ if (data != null && isCarrierActivating(m.getRecords()[0], oob.getId())) {
+ data.carrierActivating = true;
+ }
+ return data;
+ }
+ }
+
+ return null;
}
BluetoothHandoverData parse(NdefMessage m) {
@@ -457,13 +564,9 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
// Check for Handover Select, followed by a BT OOB record
if (tnf == NdefRecord.TNF_WELL_KNOWN &&
Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) {
- for (NdefRecord oob : m.getRecords()) {
- if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
- Arrays.equals(oob.getType(), TYPE_BT_OOB)) {
- return parseBtOob(ByteBuffer.wrap(oob.getPayload()));
- }
- }
+ return parseHandoverSelect(m);
}
+
// Check for Nokia BT record, found on some Nokia BH-505 Headsets
if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) {
return parseNokia(ByteBuffer.wrap(r.getPayload()));
@@ -592,35 +695,53 @@ public class HandoverManager implements BluetoothProfile.ServiceListener,
}
}
+ void toast(CharSequence text) {
+ Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
+ }
+
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- int direction = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_DIRECTION, -1);
- int id = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_ID, -1);
- if (direction == -1 || id == -1) return;
- int source = (direction == DIRECTION_BLUETOOTH_INCOMING) ?
- SOURCE_BLUETOOTH_INCOMING : SOURCE_BLUETOOTH_OUTGOING;
- HandoverTransfer transfer = getHandoverTransfer(source, id);
- if (transfer == null) 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);
- Uri uri = Uri.parse(uriString);
- if (uri.getScheme() == null) {
- uri = Uri.fromFile(new File(uri.getPath()));
+
+ 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_BT_OPP_TRANSFER_PROGRESS) ||
+ action.equals(ACTION_BT_OPP_TRANSFER_DONE)) {
+ // Clean up old transfers in progress
+ cleanupTransfers();
+
+ int direction = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_DIRECTION, -1);
+ int id = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_ID, -1);
+ if (direction == -1 || id == -1) return;
+ int source = (direction == DIRECTION_BLUETOOTH_INCOMING) ?
+ SOURCE_BLUETOOTH_INCOMING : SOURCE_BLUETOOTH_OUTGOING;
+ HandoverTransfer transfer = getHandoverTransfer(source, id);
+ if (transfer == null) 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);
+ Uri uri = Uri.parse(uriString);
+ if (uri.getScheme() == null) {
+ uri = Uri.fromFile(new File(uri.getPath()));
+ }
+ transfer.finishTransfer(true, uri);
+ } else {
+ transfer.finishTransfer(false, null);
}
- transfer.finishTransfer(true, uri);
- } else {
- transfer.finishTransfer(false, null);
+ } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS)) {
+ float progress = intent.getFloatExtra(EXTRA_BT_OPP_TRANSFER_PROGRESS, 0.0f);
+ transfer.updateTransferProgress(progress);
}
- } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS)) {
- float progress = intent.getFloatExtra(EXTRA_BT_OPP_TRANSFER_PROGRESS, 0.0f);
- transfer.updateTransferProgress(progress);
}
}