diff options
author | Andres Morales <anmorales@google.com> | 2014-11-17 21:54:16 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-11-17 21:54:17 +0000 |
commit | 2ca2ced60d0cebbe8041c4318cff9975d3bff2c4 (patch) | |
tree | 78bc0fabe42dc0dd4eb59c2d41b3768af560b2c2 | |
parent | 8172b1eaa4a1aa57f38b66a57dd1109ad887cd96 (diff) | |
parent | b82d80d891077ccd74729e4143925a66eb89eef2 (diff) | |
download | android_packages_apps_Nfc-2ca2ced60d0cebbe8041c4318cff9975d3bff2c4.tar.gz android_packages_apps_Nfc-2ca2ced60d0cebbe8041c4318cff9975d3bff2c4.tar.bz2 android_packages_apps_Nfc-2ca2ced60d0cebbe8041c4318cff9975d3bff2c4.zip |
Merge "Refactor Beam to work with managed profiles" into lmp-mr1-dev
22 files changed, 1274 insertions, 1116 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 56ef3d41..690a4f59 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -125,8 +125,14 @@ android:noHistory="true" /> - <service android:name=".handover.HandoverService" - android:process=":handover" + <service android:name=".beam.BeamSendService" + android:process=":beam" + /> + <service android:name=".beam.BeamReceiveService" + android:process=":beam" + /> + + <service android:name=".handover.PeripheralHandoverService" /> </application> </manifest> diff --git a/src/com/android/nfc/BeamShareActivity.java b/src/com/android/nfc/BeamShareActivity.java index c22c184f..10f6632c 100644 --- a/src/com/android/nfc/BeamShareActivity.java +++ b/src/com/android/nfc/BeamShareActivity.java @@ -205,18 +205,18 @@ public class BeamShareActivity extends Activity { } } if (numValidUris > 0) { - shareData = new BeamShareData(null, uriArray, 0); + shareData = new BeamShareData(null, uriArray, UserHandle.CURRENT, 0); } else { // No uris left - shareData = new BeamShareData(null, null, 0); + shareData = new BeamShareData(null, null, UserHandle.CURRENT, 0); } } else if (mNdefMessage != null) { - shareData = new BeamShareData(mNdefMessage, null, 0); + shareData = new BeamShareData(mNdefMessage, null, UserHandle.CURRENT, 0); if (DBG) Log.d(TAG, "Created NDEF message:" + mNdefMessage.toString()); } else { if (DBG) Log.d(TAG, "Could not find any data to parse."); // Activity may have set something to share over NFC, so pass on anyway - shareData = new BeamShareData(null, null, 0); + shareData = new BeamShareData(null, null, UserHandle.CURRENT, 0); } mNfcAdapter.invokeBeam(shareData); finish(); diff --git a/src/com/android/nfc/NfcDispatcher.java b/src/com/android/nfc/NfcDispatcher.java index 90261c40..9fd30a60 100644 --- a/src/com/android/nfc/NfcDispatcher.java +++ b/src/com/android/nfc/NfcDispatcher.java @@ -16,9 +16,11 @@ package com.android.nfc; -import android.nfc.INfcUnlockHandler; +import android.bluetooth.BluetoothAdapter; + import com.android.nfc.RegisteredComponentCache.ComponentInfo; -import com.android.nfc.handover.HandoverManager; +import com.android.nfc.handover.HandoverDataParser; +import com.android.nfc.handover.PeripheralHandoverService; import android.app.Activity; import android.app.ActivityManager; @@ -50,7 +52,6 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -69,10 +70,11 @@ class NfcDispatcher { private final IActivityManager mIActivityManager; private final RegisteredComponentCache mTechListFilters; private final ContentResolver mContentResolver; - private final HandoverManager mHandoverManager; + private final HandoverDataParser mHandoverDataParser; private final String[] mProvisioningMimes; private final ScreenStateHelper mScreenStateHelper; private final NfcUnlockManager mNfcUnlockManager; + private final boolean mDeviceSupportsBluetooth; // Locked on this private PendingIntent mOverrideIntent; @@ -81,16 +83,17 @@ class NfcDispatcher { private boolean mProvisioningOnly; NfcDispatcher(Context context, - HandoverManager handoverManager, + HandoverDataParser handoverDataParser, boolean provisionOnly) { mContext = context; mIActivityManager = ActivityManagerNative.getDefault(); mTechListFilters = new RegisteredComponentCache(mContext, NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED); mContentResolver = context.getContentResolver(); - mHandoverManager = handoverManager; + mHandoverDataParser = handoverDataParser; mScreenStateHelper = new ScreenStateHelper(context); mNfcUnlockManager = NfcUnlockManager.getInstance(); + mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null; synchronized (this) { mProvisioningOnly = provisionOnly; @@ -259,7 +262,7 @@ class NfcDispatcher { return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; } - if (mHandoverManager.tryHandover(message)) { + if (tryPeripheralHandover(message)) { if (DBG) Log.i(TAG, "matched BT HANDOVER"); return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; } @@ -505,6 +508,24 @@ class NfcDispatcher { return false; } + public boolean tryPeripheralHandover(NdefMessage m) { + if (m == null || !mDeviceSupportsBluetooth) return false; + + if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); + + HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m); + if (handover == null || !handover.valid) return false; + + Intent intent = new Intent(mContext, PeripheralHandoverService.class); + intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device); + intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name); + intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport); + mContext.startServiceAsUser(intent, UserHandle.CURRENT); + + return true; + } + + /** * Tells the ActivityManager to resume allowing app switches. * diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java index 86531bdf..46697717 100755 --- a/src/com/android/nfc/NfcService.java +++ b/src/com/android/nfc/NfcService.java @@ -75,7 +75,7 @@ import com.android.nfc.DeviceHost.NfcDepEndpoint; import com.android.nfc.DeviceHost.TagEndpoint; import com.android.nfc.cardemulation.CardEmulationManager; import com.android.nfc.dhimpl.NativeNfcManager; -import com.android.nfc.handover.HandoverManager; +import com.android.nfc.handover.HandoverDataParser; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -216,7 +216,7 @@ public class NfcService implements DeviceHostListener { private NfcDispatcher mNfcDispatcher; private PowerManager mPowerManager; private KeyguardManager mKeyguard; - private HandoverManager mHandoverManager; + private HandoverDataParser mHandoverDataParser; private ContentResolver mContentResolver; private CardEmulationManager mCardEmulationManager; @@ -314,7 +314,7 @@ public class NfcService implements DeviceHostListener { mNfcUnlockManager = NfcUnlockManager.getInstance(); - mHandoverManager = new HandoverManager(mContext); + mHandoverDataParser = new HandoverDataParser(); boolean isNfcProvisioningEnabled = false; try { isNfcProvisioningEnabled = mContext.getResources().getBoolean( @@ -329,8 +329,8 @@ public class NfcService implements DeviceHostListener { mInProvisionMode = false; } - mNfcDispatcher = new NfcDispatcher(mContext, mHandoverManager, mInProvisionMode); - mP2pLinkManager = new P2pLinkManager(mContext, mHandoverManager, + mNfcDispatcher = new NfcDispatcher(mContext, mHandoverDataParser, mInProvisionMode); + mP2pLinkManager = new P2pLinkManager(mContext, mHandoverDataParser, mDeviceHost.getDefaultLlcpMiu(), mDeviceHost.getDefaultLlcpRwSize()); mPrefs = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE); @@ -1445,7 +1445,6 @@ public class NfcService implements DeviceHostListener { // Notify dispatcher it's fine to dispatch to any package now // and allow handover transfers. mNfcDispatcher.disableProvisioningMode(); - mHandoverManager.setEnabled(true); } } // Special case: if we're transitioning to unlocked state while diff --git a/src/com/android/nfc/P2pEventManager.java b/src/com/android/nfc/P2pEventManager.java index f5db5443..468aeaff 100644 --- a/src/com/android/nfc/P2pEventManager.java +++ b/src/com/android/nfc/P2pEventManager.java @@ -16,6 +16,8 @@ package com.android.nfc; +import com.android.nfc.beam.SendUi; + import android.app.NotificationManager; import android.content.Context; import android.content.res.Configuration; diff --git a/src/com/android/nfc/P2pLinkManager.java b/src/com/android/nfc/P2pLinkManager.java index 480d1d10..0effe4e3 100755 --- a/src/com/android/nfc/P2pLinkManager.java +++ b/src/com/android/nfc/P2pLinkManager.java @@ -16,11 +16,17 @@ package com.android.nfc; +import android.content.Intent; import android.content.pm.UserInfo; + +import com.android.nfc.beam.BeamManager; +import com.android.nfc.beam.BeamSendService; +import com.android.nfc.beam.BeamTransferRecord; + import android.os.UserManager; import com.android.nfc.echoserver.EchoServer; import com.android.nfc.handover.HandoverClient; -import com.android.nfc.handover.HandoverManager; +import com.android.nfc.handover.HandoverDataParser; import com.android.nfc.handover.HandoverServer; import com.android.nfc.ndefpush.NdefPushClient; import com.android.nfc.ndefpush.NdefPushServer; @@ -204,7 +210,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { final Context mContext; final P2pEventListener mEventListener; final Handler mHandler; - final HandoverManager mHandoverManager; + final HandoverDataParser mHandoverDataParser; final ForegroundUtils mForegroundUtils; final int mDefaultMiu; @@ -218,6 +224,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { boolean mIsReceiveEnabled; NdefMessage mMessageToSend; // not valid in SEND_STATE_NOTHING_TO_SEND Uri[] mUrisToSend; // not valid in SEND_STATE_NOTHING_TO_SEND + UserHandle mUserHandle; // not valid in SEND_STATE_NOTHING_TO_SEND int mSendFlags; // not valid in SEND_STATE_NOTHING_TO_SEND IAppCallback mCallbackNdef; int mNdefCallbackUid; @@ -231,11 +238,11 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { boolean mLlcpConnectDelayed; long mLastLlcpActivationTime; - public P2pLinkManager(Context context, HandoverManager handoverManager, int defaultMiu, + public P2pLinkManager(Context context, HandoverDataParser handoverDataParser, int defaultMiu, int defaultRwSize) { mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback); mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize); - mHandoverServer = new HandoverServer(HANDOVER_SAP, handoverManager, mHandoverCallback); + mHandoverServer = new HandoverServer(context, HANDOVER_SAP, handoverDataParser, mHandoverCallback); if (ECHOSERVER_ENABLED) { mEchoServer = new EchoServer(); @@ -251,7 +258,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { mIsSendEnabled = false; mIsReceiveEnabled = false; mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE); - mHandoverManager = handoverManager; + mHandoverDataParser = handoverDataParser; mDefaultMiu = defaultMiu; mDefaultRwSize = defaultRwSize; mLlcpServicesConnected = false; @@ -330,7 +337,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { } } if (mMessageToSend != null || - (mUrisToSend != null && mHandoverManager.isHandoverSupported())) { + (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) { mSendState = SEND_STATE_PENDING; mEventListener.onP2pNfcTapRequested(); scheduleTimeoutLocked(MSG_WAIT_FOR_LINK_TIMEOUT, WAIT_FOR_LINK_TIMEOUT_MS); @@ -364,7 +371,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { mSendState = SEND_STATE_NOTHING_TO_SEND; prepareMessageToSend(true); if (mMessageToSend != null || - (mUrisToSend != null && mHandoverManager.isHandoverSupported())) { + (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) { // Ideally we would delay showing the Beam animation until // we know for certain the other side has SNEP/handover. // Unfortunately, the NXP LLCP implementation has a bug that @@ -466,6 +473,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { BeamShareData shareData = mCallbackNdef.createBeamShareData(); mMessageToSend = shareData.ndefMessage; mUrisToSend = shareData.uris; + mUserHandle = shareData.userHandle; mSendFlags = shareData.flags; return; } catch (Exception e) { @@ -747,9 +755,15 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { SnepClient snepClient; HandoverClient handoverClient; - int doHandover(Uri[] uris) throws IOException { + int doHandover(Uri[] uris, UserHandle userHandle) throws IOException { NdefMessage response = null; - NdefMessage request = mHandoverManager.createHandoverRequestMessage(); + BeamManager beamManager = BeamManager.getInstance(); + + if (beamManager.isBeamInProgress()) { + return HANDOVER_FAILURE; + } + + NdefMessage request = mHandoverDataParser.createHandoverRequestMessage(); if (request != null) { if (handoverClient != null) { response = handoverClient.sendHandoverRequest(request); @@ -767,8 +781,13 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { } else { return HANDOVER_UNSUPPORTED; } - mHandoverManager.doHandoverUri(uris, response); - return HANDOVER_SUCCESS; + + if (beamManager.startBeamSend(mContext, + mHandoverDataParser.getOutgoingHandoverData(response), uris, userHandle)) { + return HANDOVER_SUCCESS; + } + + return HANDOVER_FAILURE; } int doSnepProtocol(NdefMessage msg) throws IOException { @@ -784,6 +803,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { public Void doInBackground(Void... args) { NdefMessage m; Uri[] uris; + UserHandle userHandle; boolean result = false; synchronized (P2pLinkManager.this) { @@ -792,6 +812,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { } m = mMessageToSend; uris = mUrisToSend; + userHandle = mUserHandle; snepClient = mSnepClient; handoverClient = mHandoverClient; nppClient = mNdefPushClient; @@ -802,7 +823,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { if (uris != null) { if (DBG) Log.d(TAG, "Trying handover request"); try { - int handoverResult = doHandover(uris); + int handoverResult = doHandover(uris, userHandle); switch (handoverResult) { case HANDOVER_SUCCESS: result = true; @@ -882,7 +903,7 @@ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { // since Android 4.1 used the NFC Forum default server to // implement connection handover, we will support this // until we can deprecate it. - NdefMessage response = mHandoverManager.tryHandoverRequest(msg); + NdefMessage response = mHandoverDataParser.getIncomingHandoverData(msg).handoverSelect; if (response != null) { onReceiveHandover(); return SnepMessage.getSuccessResponse(response); diff --git a/src/com/android/nfc/beam/BeamManager.java b/src/com/android/nfc/beam/BeamManager.java new file mode 100644 index 00000000..ba16aa5e --- /dev/null +++ b/src/com/android/nfc/beam/BeamManager.java @@ -0,0 +1,133 @@ +/* +* Copyright (C) 2008 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package com.android.nfc.beam; + +import com.android.nfc.handover.HandoverDataParser; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.UserHandle; +import android.util.Log; + +/** + * Manager for starting and stopping Beam transfers. Prevents more than one transfer from + * happening at a time. + */ +public class BeamManager implements Handler.Callback { + private static final String TAG = "BeamManager"; + private static final boolean DBG = false; + + private static final String ACTION_WHITELIST_DEVICE = + "android.btopp.intent.action.WHITELIST_DEVICE"; + public static final int MSG_BEAM_COMPLETE = 0; + + private final Object mLock; + + private boolean mBeamInProgress; + private final Handler mCallback; + + private static final class Singleton { + public static final BeamManager INSTANCE = new BeamManager(); + } + + private BeamManager() { + mLock = new Object(); + mBeamInProgress = false; + mCallback = new Handler(Looper.getMainLooper(), this); + } + + public static BeamManager getInstance() { + return Singleton.INSTANCE; + } + + public boolean isBeamInProgress() { + synchronized (mLock) { + return mBeamInProgress; + } + } + + public boolean startBeamReceive(Context context, + HandoverDataParser.BluetoothHandoverData handoverData) { + synchronized (mLock) { + if (mBeamInProgress) { + return false; + } else { + mBeamInProgress = true; + } + } + + BeamTransferRecord transferRecord = + BeamTransferRecord.forBluetoothDevice( + handoverData.device, handoverData.carrierActivating, null); + + Intent receiveIntent = new Intent(context.getApplicationContext(), + BeamReceiveService.class); + receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord); + receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_COMPLETE_CALLBACK, + new Messenger(mCallback)); + whitelistOppDevice(context, handoverData.device); + context.startServiceAsUser(receiveIntent, UserHandle.CURRENT); + return true; + } + + public boolean startBeamSend(Context context, + HandoverDataParser.BluetoothHandoverData outgoingHandoverData, + Uri[] uris, UserHandle userHandle) { + synchronized (mLock) { + if (mBeamInProgress) { + return false; + } else { + mBeamInProgress = true; + } + } + + BeamTransferRecord transferRecord = BeamTransferRecord.forBluetoothDevice( + outgoingHandoverData.device, outgoingHandoverData.carrierActivating, + uris); + Intent sendIntent = new Intent(context.getApplicationContext(), + BeamSendService.class); + sendIntent.putExtra(BeamSendService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord); + sendIntent.putExtra(BeamSendService.EXTRA_BEAM_COMPLETE_CALLBACK, + new Messenger(mCallback)); + context.startServiceAsUser(sendIntent, userHandle); + return true; + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_BEAM_COMPLETE) { + synchronized (mLock) { + mBeamInProgress = false; + } + return true; + } + return false; + } + + void whitelistOppDevice(Context context, 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); + context.sendBroadcastAsUser(intent, UserHandle.CURRENT); + } + +} diff --git a/src/com/android/nfc/beam/BeamReceiveService.java b/src/com/android/nfc/beam/BeamReceiveService.java new file mode 100644 index 00000000..7deb0148 --- /dev/null +++ b/src/com/android/nfc/beam/BeamReceiveService.java @@ -0,0 +1,175 @@ +package com.android.nfc.beam; + +import com.android.nfc.R; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +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.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + + +/** + * @hide + */ +public class BeamReceiveService extends Service implements BeamTransferManager.Callback { + private static String TAG = "BeamReceiveService"; + private static boolean DBG = true; + + public static final String EXTRA_BEAM_TRANSFER_RECORD + = "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD"; + public static final String EXTRA_BEAM_COMPLETE_CALLBACK + = "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK"; + + private BeamStatusReceiver mBeamStatusReceiver; + private boolean mBluetoothEnabledByNfc; + private int mStartId; + private SoundPool mSoundPool; + private int mSuccessSound; + private BeamTransferManager mTransferManager; + private Messenger mCompleteCallback; + + private final BluetoothAdapter mBluetoothAdapter; + private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_OFF) { + mBluetoothEnabledByNfc = false; + } + } + } + }; + + public BeamReceiveService() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mStartId = startId; + + BeamTransferRecord transferRecord; + if (intent == null || + (transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) { + if (DBG) Log.e(TAG, "No transfer record provided. Stopping."); + stopSelf(startId); + return START_NOT_STICKY; + } + + mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK); + + if (prepareToReceive(transferRecord)) { + if (DBG) Log.i(TAG, "Ready for incoming Beam transfer"); + return START_STICKY; + } else { + invokeCompleteCallback(); + stopSelf(startId); + return START_NOT_STICKY; + } + } + + // TODO: figure out a way to not duplicate this code + @Override + public void onCreate() { + super.onCreate(); + + mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); + mSuccessSound = mSoundPool.load(this, R.raw.end, 1); + + // register BT state receiver + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + registerReceiver(mBluetoothStateReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mSoundPool != null) { + mSoundPool.release(); + } + + if (mBeamStatusReceiver != null) { + unregisterReceiver(mBeamStatusReceiver); + } + unregisterReceiver(mBluetoothStateReceiver); + } + + boolean prepareToReceive(BeamTransferRecord transferRecord) { + if (mTransferManager != null) { + return false; + } + + if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + // only support BT + return false; + } + + if (!mBluetoothAdapter.isEnabled()) { + if (!mBluetoothAdapter.enableNoAutoConnect()) { + Log.e(TAG, "Error enabling Bluetooth."); + return false; + } + mBluetoothEnabledByNfc = true; + if (DBG) Log.d(TAG, "Queueing out transfer " + + Integer.toString(transferRecord.id)); + } + + mTransferManager = new BeamTransferManager(this, this, transferRecord, true); + + // register Beam status receiver + mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager); + registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(), + BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler()); + + mTransferManager.start(); + mTransferManager.updateNotification(); + return true; + } + + private void invokeCompleteCallback() { + if (mCompleteCallback != null) { + try { + mCompleteCallback.send(Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE)); + } catch (RemoteException e) { + Log.e(TAG, "failed to invoke Beam complete callback", e); + } + } + } + + @Override + public void onTransferComplete(BeamTransferManager transfer, boolean success) { + // Play success sound + if (success) { + mSoundPool.play(mSuccessSound, 1.0f, 1.0f, 0, 0, 1.0f); + } else { + if (DBG) Log.d(TAG, "Transfer failed, final state: " + + Integer.toString(transfer.mState)); + } + + if (mBluetoothEnabledByNfc) { + mBluetoothEnabledByNfc = false; + mBluetoothAdapter.disable(); + } + + invokeCompleteCallback(); + stopSelf(mStartId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/src/com/android/nfc/beam/BeamSendService.java b/src/com/android/nfc/beam/BeamSendService.java new file mode 100644 index 00000000..59019d53 --- /dev/null +++ b/src/com/android/nfc/beam/BeamSendService.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.nfc.beam; + +import com.android.nfc.R; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +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.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +public class BeamSendService extends Service implements BeamTransferManager.Callback { + private static String TAG = "BeamSendService"; + private static boolean DBG = true; + + public static String EXTRA_BEAM_TRANSFER_RECORD + = "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD"; + public static final String EXTRA_BEAM_COMPLETE_CALLBACK + = "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK"; + + private BeamTransferManager mTransferManager; + private BeamStatusReceiver mBeamStatusReceiver; + private boolean mBluetoothEnabledByNfc; + private Messenger mCompleteCallback; + private int mStartId; + SoundPool mSoundPool; + int mSuccessSound; + + private final BluetoothAdapter mBluetoothAdapter; + private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + handleBluetoothStateChanged(intent); + } + } + }; + + public BeamSendService() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public void onCreate() { + super.onCreate(); + + mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); + mSuccessSound = mSoundPool.load(this, R.raw.end, 1); + + // register BT state receiver + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + registerReceiver(mBluetoothStateReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mSoundPool != null) { + mSoundPool.release(); + } + + if (mBeamStatusReceiver != null) { + unregisterReceiver(mBeamStatusReceiver); + } + unregisterReceiver(mBluetoothStateReceiver); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mStartId = startId; + + BeamTransferRecord transferRecord; + if (intent == null || + (transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) { + if (DBG) Log.e(TAG, "No transfer record provided. Stopping."); + stopSelf(startId); + return START_NOT_STICKY; + } + + mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK); + + if (doTransfer(transferRecord)) { + if (DBG) Log.i(TAG, "Starting outgoing Beam transfer"); + return START_STICKY; + } else { + invokeCompleteCallback(); + stopSelf(startId); + return START_NOT_STICKY; + } + } + + boolean doTransfer(BeamTransferRecord transferRecord) { + if (createBeamTransferManager(transferRecord)) { + // register Beam status receiver + mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager); + registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(), + BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler()); + + if (transferRecord.dataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + if (mBluetoothAdapter.isEnabled()) { + // Start the transfer + mTransferManager.start(); + } else { + if (!mBluetoothAdapter.enableNoAutoConnect()) { + Log.e(TAG, "Error enabling Bluetooth."); + mTransferManager = null; + return false; + } + mBluetoothEnabledByNfc = true; + if (DBG) Log.d(TAG, "Queueing out transfer " + + Integer.toString(transferRecord.id)); + } + } + return true; + } + + return false; + } + + boolean createBeamTransferManager(BeamTransferRecord transferRecord) { + if (mTransferManager != null) { + return false; + } + + if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + // only support BT + return false; + } + + mTransferManager = new BeamTransferManager(this, this, transferRecord, false); + mTransferManager.updateNotification(); + return true; + } + + private void handleBluetoothStateChanged(Intent intent) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_ON) { + if (mTransferManager != null && + mTransferManager.mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + mTransferManager.start(); + } + } else if (state == BluetoothAdapter.STATE_OFF) { + mBluetoothEnabledByNfc = false; + } + } + + private void invokeCompleteCallback() { + if (mCompleteCallback != null) { + try { + mCompleteCallback.send(Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE)); + } catch (RemoteException e) { + Log.e(TAG, "failed to invoke Beam complete callback", e); + } + } + } + + @Override + public void onTransferComplete(BeamTransferManager transfer, boolean success) { + // Play success sound + if (success) { + mSoundPool.play(mSuccessSound, 1.0f, 1.0f, 0, 0, 1.0f); + } else { + if (DBG) Log.d(TAG, "Transfer failed, final state: " + + Integer.toString(transfer.mState)); + } + + if (mBluetoothEnabledByNfc) { + mBluetoothEnabledByNfc = false; + mBluetoothAdapter.disable(); + } + + invokeCompleteCallback(); + stopSelf(mStartId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/src/com/android/nfc/beam/BeamStatusReceiver.java b/src/com/android/nfc/beam/BeamStatusReceiver.java new file mode 100644 index 00000000..67b5b82a --- /dev/null +++ b/src/com/android/nfc/beam/BeamStatusReceiver.java @@ -0,0 +1,155 @@ +package com.android.nfc.beam; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.util.Log; + +import java.io.File; + +/** + * @hide + */ +public class BeamStatusReceiver extends BroadcastReceiver { + private static final boolean DBG = true; + private static final String TAG = "BeamStatusReceiver"; + + private static final String ACTION_HANDOVER_STARTED = + "android.nfc.handover.intent.action.HANDOVER_STARTED"; + + private static final String ACTION_TRANSFER_PROGRESS = + "android.nfc.handover.intent.action.TRANSFER_PROGRESS"; + + private static final String ACTION_TRANSFER_DONE = + "android.nfc.handover.intent.action.TRANSFER_DONE"; + + private static final String EXTRA_HANDOVER_DATA_LINK_TYPE = + "android.nfc.handover.intent.extra.HANDOVER_DATA_LINK_TYPE"; + + + private static final String EXTRA_TRANSFER_PROGRESS = + "android.nfc.handover.intent.extra.TRANSFER_PROGRESS"; + + private static final String EXTRA_TRANSFER_URI = + "android.nfc.handover.intent.extra.TRANSFER_URI"; + + private static final String EXTRA_OBJECT_COUNT = + "android.nfc.handover.intent.extra.OBJECT_COUNT"; + + private static final String EXTRA_TRANSFER_STATUS = + "android.nfc.handover.intent.extra.TRANSFER_STATUS"; + + private static final String EXTRA_TRANSFER_MIMETYPE = + "android.nfc.handover.intent.extra.TRANSFER_MIME_TYPE"; + + private static final String ACTION_STOP_BLUETOOTH_TRANSFER = + "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"; + + // FIXME: Needs to stay in sync with com.android.bluetooth.opp.Constants + private static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0; + private static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1; + + // permission needed to be able to receive handover status requests + public static final String BEAM_STATUS_PERMISSION = + "android.permission.NFC_HANDOVER_STATUS"; + + // Needed to build cancel intent in Beam notification + public static final String EXTRA_INCOMING = + "com.android.nfc.handover.extra.INCOMING"; + + public static final String EXTRA_TRANSFER_ID = + "android.nfc.handover.intent.extra.TRANSFER_ID"; + + public static final String EXTRA_ADDRESS = + "android.nfc.handover.intent.extra.ADDRESS"; + + public static final String ACTION_CANCEL_HANDOVER_TRANSFER = + "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER"; + + public static final int DIRECTION_INCOMING = 0; + public static final int DIRECTION_OUTGOING = 1; + + private final Context mContext; + private final BeamTransferManager mTransferManager; + + BeamStatusReceiver(Context context, BeamTransferManager transferManager) { + mContext = context; + mTransferManager = transferManager; + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + int dataLinkType = intent.getIntExtra(EXTRA_HANDOVER_DATA_LINK_TYPE, + BeamTransferManager.DATA_LINK_TYPE_BLUETOOTH); + + if (ACTION_CANCEL_HANDOVER_TRANSFER.equals(action)) { + if (mTransferManager != null) { + mTransferManager.cancel(); + } + } else if (ACTION_TRANSFER_PROGRESS.equals(action) || + ACTION_TRANSFER_DONE.equals(action) || + ACTION_HANDOVER_STARTED.equals(action)) { + handleTransferEvent(intent, dataLinkType); + } + } + + public IntentFilter getIntentFilter() { + IntentFilter filter = new IntentFilter(ACTION_TRANSFER_DONE); + filter.addAction(ACTION_TRANSFER_PROGRESS); + filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER); + filter.addAction(ACTION_HANDOVER_STARTED); + return filter; + } + + private void handleTransferEvent(Intent intent, int deviceType) { + String action = intent.getAction(); + int id = intent.getIntExtra(EXTRA_TRANSFER_ID, -1); + + String sourceAddress = intent.getStringExtra(EXTRA_ADDRESS); + + if (sourceAddress == null) return; + + if (mTransferManager == 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. + if (id != -1) { + if (deviceType == BeamTransferManager.DATA_LINK_TYPE_BLUETOOTH) { + if (DBG) Log.d(TAG, "Didn't find transfer, stopping"); + Intent cancelIntent = new Intent(ACTION_STOP_BLUETOOTH_TRANSFER); + cancelIntent.putExtra(EXTRA_TRANSFER_ID, id); + mContext.sendBroadcast(cancelIntent); + } + } + return; + } + + mTransferManager.setBluetoothTransferId(id); + + if (action.equals(ACTION_TRANSFER_DONE)) { + int handoverStatus = intent.getIntExtra(EXTRA_TRANSFER_STATUS, + HANDOVER_TRANSFER_STATUS_FAILURE); + if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) { + String uriString = intent.getStringExtra(EXTRA_TRANSFER_URI); + String mimeType = intent.getStringExtra(EXTRA_TRANSFER_MIMETYPE); + Uri uri = Uri.parse(uriString); + if (uri != null && uri.getScheme() == null) { + uri = Uri.fromFile(new File(uri.getPath())); + } + mTransferManager.finishTransfer(true, uri, mimeType); + } else { + mTransferManager.finishTransfer(false, null, null); + } + } else if (action.equals(ACTION_TRANSFER_PROGRESS)) { + float progress = intent.getFloatExtra(EXTRA_TRANSFER_PROGRESS, 0.0f); + mTransferManager.updateFileProgress(progress); + } else if (action.equals(ACTION_HANDOVER_STARTED)) { + int count = intent.getIntExtra(EXTRA_OBJECT_COUNT, 0); + if (count > 0) { + mTransferManager.setObjectCount(count); + } + } + } +} diff --git a/src/com/android/nfc/handover/HandoverTransfer.java b/src/com/android/nfc/beam/BeamTransferManager.java index b0baf182..b6785010 100644 --- a/src/com/android/nfc/handover/HandoverTransfer.java +++ b/src/com/android/nfc/beam/BeamTransferManager.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.nfc.handover; +package com.android.nfc.beam; + +import com.android.nfc.R; import android.app.Notification; import android.app.NotificationManager; @@ -34,20 +36,22 @@ 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.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Locale; /** - * A HandoverTransfer object represents a set of files + * A BeamTransferManager object represents a set of files * that were received through NFC connection handover * from the same source address. * + * It manages starting, stopping, and processing the transfer, as well + * as the user visible notification. + * * For Bluetooth, files are received through OPP, and * we have no knowledge how many files will be transferred * as part of a single transaction. @@ -57,21 +61,21 @@ import java.util.Locale; * same source address as part of the same transfer. * The corresponding URIs will be grouped in a single folder. * + * @hide */ -public class HandoverTransfer implements Handler.Callback, - MediaScannerConnection.OnScanCompletedListener { +public class BeamTransferManager implements Handler.Callback, + MediaScannerConnection.OnScanCompletedListener { interface Callback { - void onTransferComplete(HandoverTransfer transfer, boolean success); + void onTransferComplete(BeamTransferManager transfer, boolean success); }; - static final String TAG = "HandoverTransfer"; + static final String TAG = "BeamTransferManager"; 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. @@ -83,9 +87,8 @@ public class HandoverTransfer implements Handler.Callback, static final int MSG_NEXT_TRANSFER_TIMER = 0; static final int MSG_TRANSFER_TIMEOUT = 1; - static final int DEVICE_TYPE_BLUETOOTH = 1; + static final int DATA_LINK_TYPE_BLUETOOTH = 1; - public static final int DEVICE_TYPE_WIFI = 2; // 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). @@ -97,6 +100,12 @@ public class HandoverTransfer implements Handler.Callback, static final String BEAM_DIR = "beam"; + static final String ACTION_WHITELIST_DEVICE = + "android.btopp.intent.action.WHITELIST_DEVICE"; + + static final String ACTION_STOP_BLUETOOTH_TRANSFER = + "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"; + final boolean mIncoming; // whether this is an incoming transfer final int mTransferId; // Unique ID of this transfer used for notifications @@ -107,49 +116,51 @@ public class HandoverTransfer implements Handler.Callback, final Handler mHandler; final NotificationManager mNotificationManager; final BluetoothDevice mRemoteDevice; - final String mRemoteMac; final Callback mCallback; - final Long mStartTime; + final boolean mRemoteActivating; // Variables below are only accessed on the main thread int mState; int mCurrentCount; int mSuccessCount; int mTotalCount; - int mDeviceType; + int mDataLinkType; boolean mCalledBack; Long mLastUpdate; // Last time an event occurred for this transfer float mProgress; // Progress in range [0..1] ArrayList<Uri> mUris; // Received uris from transport ArrayList<String> mTransferMimeTypes; // Mime-types received from transport - Uri[] mOutgoingUris; // URIs to send via Wifi Direct - + Uri[] mOutgoingUris; // URIs to send 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; + Long mStartTime; - public HandoverTransfer(Context context, Callback callback, - PendingHandoverTransfer pendingTransfer) { + public BeamTransferManager(Context context, Callback callback, + BeamTransferRecord pendingTransfer, boolean incoming) { mContext = context; mCallback = callback; mRemoteDevice = pendingTransfer.remoteDevice; - mRemoteMac = pendingTransfer.remoteMacAddress; - mIncoming = pendingTransfer.incoming; + mIncoming = incoming; mTransferId = pendingTransfer.id; mBluetoothTransferId = -1; - mDeviceType = pendingTransfer.deviceType; + mDataLinkType = pendingTransfer.dataLinkType; + mRemoteActivating = pendingTransfer.remoteActivating; + mStartTime = 0L; // For incoming transfers, count can be set later mTotalCount = (pendingTransfer.uris != null) ? pendingTransfer.uris.length : 0; mLastUpdate = SystemClock.elapsedRealtime(); mProgress = 0.0f; mState = STATE_NEW; - mUris = new ArrayList<Uri>(); + mUris = pendingTransfer.uris == null + ? new ArrayList<Uri>() + : new ArrayList<Uri>(Arrays.asList(pendingTransfer.uris)); mTransferMimeTypes = new ArrayList<String>(); mMimeTypes = new HashMap<String, String>(); mPaths = new ArrayList<String>(); mMediaUris = new HashMap<String, Uri>(); - mCancelIntent = buildCancelIntent(mIncoming); + mCancelIntent = buildCancelIntent(); mUrisScanned = 0; mCurrentCount = 0; mSuccessCount = 0; @@ -158,17 +169,30 @@ public class HandoverTransfer implements Handler.Callback, mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS); mNotificationManager = (NotificationManager) mContext.getSystemService( Context.NOTIFICATION_SERVICE); - - mStartTime = System.currentTimeMillis(); } void whitelistOppDevice(BluetoothDevice device) { if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); - Intent intent = new Intent(HandoverManager.ACTION_WHITELIST_DEVICE); + Intent intent = new Intent(ACTION_WHITELIST_DEVICE); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } + public void start() { + if (mStartTime > 0) { + // already started + return; + } + + mStartTime = System.currentTimeMillis(); + + if (!mIncoming) { + if (mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + new BluetoothOppHandover(mContext, mRemoteDevice, mUris, mRemoteActivating).start(); + } + } + } + public void updateFileProgress(float progress) { if (!isRunning()) return; // Ignore when we're no longer running @@ -218,7 +242,6 @@ public class HandoverTransfer implements Handler.Callback, if (mIncoming) { processFiles(); } else { - Log.i(TAG, "Updating state!"); updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED); } } else { @@ -258,9 +281,8 @@ public class HandoverTransfer implements Handler.Callback, } private void sendBluetoothCancelIntentAndUpdateState() { - Intent cancelIntent = new Intent( - "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"); - cancelIntent.putExtra(HandoverService.EXTRA_TRANSFER_ID, mBluetoothTransferId); + Intent cancelIntent = new Intent(ACTION_STOP_BLUETOOTH_TRANSFER); + cancelIntent.putExtra(BeamStatusReceiver.EXTRA_TRANSFER_ID, mBluetoothTransferId); mContext.sendBroadcast(cancelIntent); updateStateAndNotification(STATE_CANCELLED); } @@ -409,10 +431,6 @@ public class HandoverTransfer implements Handler.Callback, } - 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 @@ -442,18 +460,6 @@ public class HandoverTransfer implements Handler.Callback, } } - 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; @@ -469,19 +475,31 @@ public class HandoverTransfer implements Handler.Callback, return viewIntent; } - PendingIntent buildCancelIntent(boolean incoming) { - Intent intent = new Intent(HandoverService.ACTION_CANCEL_HANDOVER_TRANSFER); - intent.putExtra(HandoverService.EXTRA_ADDRESS, mDeviceType == DEVICE_TYPE_BLUETOOTH - ? mRemoteDevice.getAddress() : mRemoteMac); - intent.putExtra(HandoverService.EXTRA_INCOMING, incoming ? - HandoverService.DIRECTION_INCOMING : HandoverService.DIRECTION_OUTGOING); + PendingIntent buildCancelIntent() { + Intent intent = new Intent(BeamStatusReceiver.ACTION_CANCEL_HANDOVER_TRANSFER); + intent.putExtra(BeamStatusReceiver.EXTRA_ADDRESS, mRemoteDevice.getAddress()); + intent.putExtra(BeamStatusReceiver.EXTRA_INCOMING, mIncoming ? + BeamStatusReceiver.DIRECTION_INCOMING : BeamStatusReceiver.DIRECTION_OUTGOING); PendingIntent pi = PendingIntent.getBroadcast(mContext, mTransferId, intent, PendingIntent.FLAG_ONE_SHOT); return pi; } - File generateUniqueDestination(String path, String fileName) { + static 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; + } + } + + static File generateUniqueDestination(String path, String fileName) { int dotIndex = fileName.lastIndexOf("."); String extension = null; String fileNameWithoutExtension = null; @@ -502,7 +520,7 @@ public class HandoverTransfer implements Handler.Callback, return dstFile; } - File generateMultiplePath(String beamRoot) { + static File generateMultiplePath(String beamRoot) { // Generate a unique directory with the date String format = "yyyy-MM-dd"; SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US); diff --git a/src/com/android/nfc/beam/BeamTransferRecord.aidl b/src/com/android/nfc/beam/BeamTransferRecord.aidl new file mode 100644 index 00000000..93af205d --- /dev/null +++ b/src/com/android/nfc/beam/BeamTransferRecord.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.nfc.beam; + +parcelable BeamTransferRecord; diff --git a/src/com/android/nfc/beam/BeamTransferRecord.java b/src/com/android/nfc/beam/BeamTransferRecord.java new file mode 100644 index 00000000..d8f86689 --- /dev/null +++ b/src/com/android/nfc/beam/BeamTransferRecord.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.nfc.beam; + +import android.bluetooth.BluetoothDevice; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +public class BeamTransferRecord implements Parcelable { + + public static int DATA_LINK_TYPE_BLUETOOTH = 0; + + public int id; + public boolean remoteActivating; + public Uri[] uris; + public int dataLinkType; + + // Data link specific fields + public BluetoothDevice remoteDevice; + + + private BeamTransferRecord(BluetoothDevice remoteDevice, + boolean remoteActivating, Uri[] uris) { + this.id = 0; + this.remoteDevice = remoteDevice; + this.remoteActivating = remoteActivating; + this.uris = uris; + + this.dataLinkType = DATA_LINK_TYPE_BLUETOOTH; + } + + public static final BeamTransferRecord forBluetoothDevice( + BluetoothDevice remoteDevice, boolean remoteActivating, + Uri[] uris) { + return new BeamTransferRecord(remoteDevice, remoteActivating, uris); + } + + public static final Parcelable.Creator<BeamTransferRecord> CREATOR + = new Parcelable.Creator<BeamTransferRecord>() { + public BeamTransferRecord createFromParcel(Parcel in) { + int deviceType = in.readInt(); + + if (deviceType != DATA_LINK_TYPE_BLUETOOTH) { + // only support BLUETOOTH + return null; + } + + BluetoothDevice remoteDevice = in.readParcelable(getClass().getClassLoader()); + boolean remoteActivating = (in.readInt() == 1); + int numUris = in.readInt(); + Uri[] uris = null; + if (numUris > 0) { + uris = new Uri[numUris]; + in.readTypedArray(uris, Uri.CREATOR); + } + + return new BeamTransferRecord(remoteDevice, + remoteActivating, uris); + + } + + @Override + public BeamTransferRecord[] newArray(int size) { + return new BeamTransferRecord[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(dataLinkType); + 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); + } + } +} diff --git a/src/com/android/nfc/handover/BluetoothOppHandover.java b/src/com/android/nfc/beam/BluetoothOppHandover.java index 85e7f133..84d3c209 100644 --- a/src/com/android/nfc/handover/BluetoothOppHandover.java +++ b/src/com/android/nfc/beam/BluetoothOppHandover.java @@ -14,21 +14,18 @@ * limitations under the License. */ -package com.android.nfc.handover; +package com.android.nfc.beam; import android.bluetooth.BluetoothDevice; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; 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 java.util.ArrayList; -import java.util.Arrays; public class BluetoothOppHandover implements Handler.Callback { static final String TAG = "BluetoothOppHandover"; @@ -52,14 +49,14 @@ public class BluetoothOppHandover implements Handler.Callback { final Context mContext; final BluetoothDevice mDevice; - final Uri[] mUris; + final ArrayList<Uri> mUris; final boolean mRemoteActivating; final Handler mHandler; final Long mCreateTime; int mState; - public BluetoothOppHandover(Context context, BluetoothDevice device, Uri[] uris, + public BluetoothOppHandover(Context context, BluetoothDevice device, ArrayList<Uri> uris, boolean remoteActivating) { mContext = context; mDevice = device; @@ -67,7 +64,7 @@ public class BluetoothOppHandover implements Handler.Callback { mRemoteActivating = remoteActivating; mCreateTime = SystemClock.elapsedRealtime(); - mHandler = new Handler(context.getMainLooper(),this); + mHandler = new Handler(context.getMainLooper(), this); mState = STATE_INIT; } @@ -99,7 +96,7 @@ public class BluetoothOppHandover implements Handler.Callback { void sendIntent() { Intent intent = new Intent(); intent.setPackage("com.android.bluetooth"); - String mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, mUris[0]); + String mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, mUris.get(0)); intent.setType(mimeType); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); for (Uri uri : mUris) { @@ -113,13 +110,12 @@ public class BluetoothOppHandover implements Handler.Callback { Log.e(TAG, "Failed to transfer permission to Bluetooth process."); } } - if (mUris.length == 1) { + if (mUris.size() == 1) { intent.setAction(ACTION_HANDOVER_SEND); - intent.putExtra(Intent.EXTRA_STREAM, mUris[0]); + intent.putExtra(Intent.EXTRA_STREAM, mUris.get(0)); } else { - ArrayList<Uri> uris = new ArrayList<Uri>(Arrays.asList(mUris)); intent.setAction(ACTION_HANDOVER_SEND_MULTIPLE); - intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mUris); } if (DBG) Log.d(TAG, "Handing off outging transfer to BT"); mContext.sendBroadcast(intent); diff --git a/src/com/android/nfc/FireflyRenderer.java b/src/com/android/nfc/beam/FireflyRenderer.java index 40c931d1..d87a5d97 100644 --- a/src/com/android/nfc/FireflyRenderer.java +++ b/src/com/android/nfc/beam/FireflyRenderer.java @@ -15,7 +15,7 @@ */ -package com.android.nfc; +package com.android.nfc.beam; import android.content.Context; import android.graphics.Bitmap; diff --git a/src/com/android/nfc/handover/MimeTypeUtil.java b/src/com/android/nfc/beam/MimeTypeUtil.java index 7a0556d9..73d7fd6e 100644 --- a/src/com/android/nfc/handover/MimeTypeUtil.java +++ b/src/com/android/nfc/beam/MimeTypeUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.nfc.handover; +package com.android.nfc.beam; import android.content.ContentResolver; import android.content.Context; @@ -46,4 +46,4 @@ public final class MimeTypeUtil { return null; } } -}
\ No newline at end of file +} diff --git a/src/com/android/nfc/SendUi.java b/src/com/android/nfc/beam/SendUi.java index 58a38888..0761ba52 100644 --- a/src/com/android/nfc/SendUi.java +++ b/src/com/android/nfc/beam/SendUi.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.android.nfc; +package com.android.nfc.beam; import com.android.internal.policy.PolicyManager; +import com.android.nfc.R; +import com.android.nfc.beam.FireflyRenderer; import android.animation.Animator; import android.animation.AnimatorSet; @@ -110,8 +112,8 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, static final int TEXT_HINT_ALPHA_DURATION_MS = 500; static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300; - static final int FINISH_SCALE_UP = 0; - static final int FINISH_SEND_SUCCESS = 1; + public static final int FINISH_SCALE_UP = 0; + public static final int FINISH_SEND_SUCCESS = 1; static final int STATE_IDLE = 0; static final int STATE_W4_SCREENSHOT = 1; @@ -185,7 +187,7 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, int mSurfaceWidth; int mSurfaceHeight; - interface Callback { + public interface Callback { public void onSendConfirmed(); public void onCanceled(); } diff --git a/src/com/android/nfc/handover/HandoverManager.java b/src/com/android/nfc/handover/HandoverDataParser.java index d48b4904..71e06ad1 100644 --- a/src/com/android/nfc/handover/HandoverManager.java +++ b/src/com/android/nfc/handover/HandoverDataParser.java @@ -19,36 +19,23 @@ package com.android.nfc.handover; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.Random; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.net.Uri; import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; -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.os.UserHandle; import android.util.Log; /** * Manages handover of NFC to other technologies. */ -public class HandoverManager { +public class HandoverDataParser { private static final String TAG = "NfcHandover"; private static final boolean DBG = false; @@ -72,33 +59,14 @@ public class HandoverManager { private static final int BT_HANDOVER_TYPE_SHORT_LOCAL_NAME = 0x08; public static final int BT_HANDOVER_LE_ROLE_CENTRAL_ONLY = 0x01; - static final String ACTION_WHITELIST_DEVICE = - "android.btopp.intent.action.WHITELIST_DEVICE"; - - static final int MSG_HANDOVER_COMPLETE = 0; - static final int MSG_HEADSET_CONNECTED = 1; - static final int MSG_HEADSET_NOT_CONNECTED = 2; - - private final Context mContext; private final BluetoothAdapter mBluetoothAdapter; - private final MessageHandler mHandler = new MessageHandler(); - private final Messenger mMessenger = new Messenger(mHandler); private final Object mLock = new Object(); // Variables below synchronized on mLock - /* package as optimization */ HashMap<Integer, PendingHandoverTransfer> mPendingTransfers; - private ArrayList<Message> mPendingServiceMessages; - /* package as optimization */ boolean mBluetoothHeadsetPending; - /* package as optimization */ boolean mBluetoothHeadsetConnected; - protected boolean mBluetoothEnabledByNfc; - private int mHandoverTransferId; - private Messenger mService = null; - private boolean mBinding = false; - private boolean mBound; + private String mLocalBluetoothAddress; - private boolean mEnabled; - static class BluetoothHandoverData { + public static class BluetoothHandoverData { public boolean valid = false; public BluetoothDevice device; public String name; @@ -106,141 +74,19 @@ public class HandoverManager { public int transport = BluetoothDevice.TRANSPORT_AUTO; } - class MessageHandler extends Handler { - @Override - 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 { - Log.e(TAG, "Could not find completed transfer id: " + - Integer.toString(transferId)); - } - break; - case MSG_HEADSET_CONNECTED: - mBluetoothEnabledByNfc = msg.arg1 != 0; - mBluetoothHeadsetConnected = true; - mBluetoothHeadsetPending = false; - break; - case MSG_HEADSET_NOT_CONNECTED: - mBluetoothEnabledByNfc = false; // No need to maintain this state any longer - mBluetoothHeadsetConnected = false; - mBluetoothHeadsetPending = false; - break; - default: - break; - } - unbindServiceIfNeededLocked(false); - } - } - }; - - private ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - mService = new Messenger(service); - mBinding = false; - mBound = true; - // Register this client and transfer last known service state - Message msg = Message.obtain(null, HandoverService.MSG_REGISTER_CLIENT); - msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0; - msg.arg2 = mBluetoothHeadsetConnected ? 1 : 0; - msg.replyTo = mMessenger; - try { - mService.send(msg); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register client"); - } - // Send all queued messages - while (!mPendingServiceMessages.isEmpty()) { - msg = mPendingServiceMessages.remove(0); - try { - mService.send(msg); - } catch (RemoteException e) { - Log.e(TAG, "Failed to send queued message to service"); - } - } - } - } + public static class IncomingHandoverData { + public final NdefMessage handoverSelect; + public final BluetoothHandoverData handoverData; - @Override - public void onServiceDisconnected(ComponentName name) { - synchronized (mLock) { - Log.d(TAG, "Service disconnected"); - if (mService != null) { - try { - Message msg = Message.obtain(null, HandoverService.MSG_DEREGISTER_CLIENT); - msg.replyTo = mMessenger; - mService.send(msg); - } catch (RemoteException e) { - // Service may have crashed - ignore - } - } - mService = null; - mBound = false; - mBluetoothHeadsetPending = false; - mPendingTransfers.clear(); - mPendingServiceMessages.clear(); - } - } - }; - - public HandoverManager(Context context) { - mContext = context; - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - mPendingTransfers = new HashMap<Integer, PendingHandoverTransfer>(); - mPendingServiceMessages = new ArrayList<Message>(); - - IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mContext.registerReceiver(mReceiver, filter, null, null); - mEnabled = true; - mBluetoothEnabledByNfc = false; - } - - final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Intent.ACTION_USER_SWITCHED)) { - // Just force unbind the service. - unbindServiceIfNeededLocked(true); - } - } - }; - - /** - * @return whether the service was bound to successfully - */ - boolean bindServiceIfNeededLocked() { - if (!mBinding) { - Log.d(TAG, "Binding to handover service"); - boolean bindSuccess = mContext.bindServiceAsUser(new Intent(mContext, - HandoverService.class), mConnection, Context.BIND_AUTO_CREATE, - UserHandle.CURRENT); - mBinding = bindSuccess; - return bindSuccess; - } else { - // A previous bind is pending - return true; + public IncomingHandoverData(NdefMessage handoverSelect, + BluetoothHandoverData handoverData) { + this.handoverSelect = handoverSelect; + this.handoverData = handoverData; } } - void unbindServiceIfNeededLocked(boolean force) { - // If no service operation is pending, unbind - if (mBound && (force || (!mBluetoothHeadsetPending && mPendingTransfers.isEmpty()))) { - Log.d(TAG, "Unbinding from service."); - mContext.unbindService(mConnection); - mBound = false; - mPendingServiceMessages.clear(); - mBluetoothHeadsetPending = false; - mPendingTransfers.clear(); - } - return; + public HandoverDataParser() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } static NdefRecord createCollisionRecord() { @@ -256,7 +102,8 @@ public class HandoverManager { 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 - return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null, payload); + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null, + payload); } NdefRecord createBluetoothOobDataRecord() { @@ -280,12 +127,6 @@ public class HandoverManager { return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload); } - public void setEnabled(boolean enabled) { - synchronized (mLock) { - mEnabled = enabled; - } - } - public boolean isHandoverSupported() { return (mBluetoothAdapter != null); } @@ -345,16 +186,16 @@ public class HandoverManager { } /** - * Return null if message is not a Handover Request, - * return the Handover Select response if it is. + * Returns null if message is not a Handover Request, + * returns the IncomingHandoverData (Hs + parsed data) if it is. */ - public NdefMessage tryHandoverRequest(NdefMessage m) { - if (m == null) return null; + public IncomingHandoverData getIncomingHandoverData(NdefMessage handoverRequest) { + if (handoverRequest == null) return null; if (mBluetoothAdapter == null) return null; - if (DBG) Log.d(TAG, "tryHandoverRequest():" + m.toString()); + if (DBG) Log.d(TAG, "getIncomingHandoverData():" + handoverRequest.toString()); - NdefRecord handoverRequestRecord = m.getRecords()[0]; + NdefRecord handoverRequestRecord = handoverRequest.getRecords()[0]; if (handoverRequestRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) { return null; } @@ -365,7 +206,7 @@ public class HandoverManager { // we have a handover request, look for BT OOB record BluetoothHandoverData bluetoothData = null; - for (NdefRecord dataRecord : m.getRecords()) { + for (NdefRecord dataRecord : handoverRequest.getRecords()) { if (dataRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA) { if (Arrays.equals(dataRecord.getType(), TYPE_BT_OOB)) { bluetoothData = parseBtOob(ByteBuffer.wrap(dataRecord.getPayload())); @@ -373,13 +214,21 @@ public class HandoverManager { } } - return tryBluetoothHandoverRequest(bluetoothData); + NdefMessage hs = tryBluetoothHandoverRequest(bluetoothData); + if (hs != null) { + return new IncomingHandoverData(hs, bluetoothData); + } + + return null; + } + + public BluetoothHandoverData getOutgoingHandoverData(NdefMessage handoverSelect) { + return parseBluetooth(handoverSelect); } private NdefMessage tryBluetoothHandoverRequest(BluetoothHandoverData bluetoothData) { NdefMessage selectMessage = null; if (bluetoothData != null) { - // 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 @@ -388,23 +237,6 @@ public class HandoverManager { // be common for the user to be changing BT settings // while waiting to receive a picture. boolean bluetoothActivating = !mBluetoothAdapter.isEnabled(); - synchronized (mLock) { - if (!mEnabled) return null; - - Message msg = Message.obtain(null, HandoverService.MSG_START_INCOMING_TRANSFER); - PendingHandoverTransfer transfer - = registerBluetoothInTransferLocked(bluetoothData.device); - Bundle transferData = new Bundle(); - transferData.putParcelable(HandoverService.BUNDLE_TRANSFER, transfer); - msg.setData(transferData); - - if (!sendOrQueueMessageLocked(msg)) { - removeTransferLocked(transfer.id); - return null; - } - } - // BT OOB found, whitelist it for incoming OPP data - whitelistOppDevice(bluetoothData.device); // return BT OOB record so they can perform handover selectMessage = (createBluetoothHandoverSelectMessage(bluetoothActivating)); @@ -415,101 +247,7 @@ public class HandoverManager { return selectMessage; } - public boolean sendOrQueueMessageLocked(Message msg) { - if (!mBound || mService == null) { - // Need to start service, let us know if we can queue the message - if (!bindServiceIfNeededLocked()) { - Log.e(TAG, "Could not start service"); - return false; - } - // Queue the message to send when the service is bound - mPendingServiceMessages.add(msg); - } else { - try { - mService.send(msg); - } catch (RemoteException e) { - Log.e(TAG, "Could not connect to handover service"); - return false; - } - } - return true; - } - - public boolean tryHandover(NdefMessage m) { - if (m == null) return false; - if (mBluetoothAdapter == null) return false; - - if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); - - BluetoothHandoverData handover = parseBluetooth(m); - if (handover == null) return false; - if (!handover.valid) return true; - - synchronized (mLock) { - if (!mEnabled) return false; - - if (mBluetoothAdapter == null) { - if (DBG) Log.d(TAG, "BT handover, but BT not available"); - return true; - } - - Message msg = Message.obtain(null, HandoverService.MSG_PERIPHERAL_HANDOVER, 0, 0); - Bundle headsetData = new Bundle(); - headsetData.putParcelable(HandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device); - headsetData.putString(HandoverService.EXTRA_PERIPHERAL_NAME, handover.name); - headsetData.putInt(HandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport); - msg.setData(headsetData); - return sendOrQueueMessageLocked(msg); - } - } - - // This starts sending an Uri over BT - public void doHandoverUri(Uri[] uris, - NdefMessage handoverResponse) { - if (mBluetoothAdapter == null) return; - - BluetoothHandoverData data = parseBluetooth(handoverResponse); - if (data != null && data.valid) { - // Register a new handover transfer object - synchronized (mLock) { - Message msg = Message.obtain(null, HandoverService.MSG_START_OUTGOING_TRANSFER, 0, 0); - PendingHandoverTransfer transfer = registerBluetoothOutTransferLocked(data, uris); - Bundle transferData = new Bundle(); - transferData.putParcelable(HandoverService.BUNDLE_TRANSFER, transfer); - msg.setData(transferData); - if (DBG) Log.d(TAG, "Initiating outgoing bluetooth transfer, [" + - mLocalBluetoothAddress + "]->[" + data.device.getAddress() + "]"); - sendOrQueueMessageLocked(msg); - } - } - } - - PendingHandoverTransfer registerBluetoothInTransferLocked(BluetoothDevice remoteDevice) { - PendingHandoverTransfer transfer = PendingHandoverTransfer.forBluetoothDevice( - mHandoverTransferId++, true, remoteDevice, false, null); - mPendingTransfers.put(transfer.id, transfer); - - return transfer; - } - - PendingHandoverTransfer registerBluetoothOutTransferLocked(BluetoothHandoverData data, - Uri[] uris) { - PendingHandoverTransfer transfer = PendingHandoverTransfer.forBluetoothDevice( - 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(); @@ -567,7 +305,7 @@ public class HandoverManager { return null; } - BluetoothHandoverData parseBluetooth(NdefMessage m) { + public BluetoothHandoverData parseBluetooth(NdefMessage m) { NdefRecord r = m.getRecords()[0]; short tnf = r.getTnf(); byte[] type = r.getType(); @@ -729,16 +467,5 @@ public class HandoverManager { return result; } - - final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); - private static String byteArrayToHexString(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for ( int j = 0; j < bytes.length; j++ ) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } } diff --git a/src/com/android/nfc/handover/HandoverServer.java b/src/com/android/nfc/handover/HandoverServer.java index 093d1dd5..56036d82 100644 --- a/src/com/android/nfc/handover/HandoverServer.java +++ b/src/com/android/nfc/handover/HandoverServer.java @@ -15,29 +15,37 @@ */ package com.android.nfc.handover; +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; +import com.android.nfc.beam.BeamManager; +import com.android.nfc.beam.BeamReceiveService; +import com.android.nfc.beam.BeamTransferRecord; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; import android.nfc.FormatException; import android.nfc.NdefMessage; +import android.os.UserHandle; import android.util.Log; -import com.android.nfc.LlcpException; -import com.android.nfc.NfcService; -import com.android.nfc.DeviceHost.LlcpServerSocket; -import com.android.nfc.DeviceHost.LlcpSocket; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; public final class HandoverServer { - public static final String HANDOVER_SERVICE_NAME = "urn:nfc:sn:handover"; - public static final String TAG = "HandoverServer"; - public static final Boolean DBG = false; + static final String HANDOVER_SERVICE_NAME = "urn:nfc:sn:handover"; + static final String TAG = "HandoverServer"; + static final Boolean DBG = false; - public static final int MIU = 128; + static final int MIU = 128; - final HandoverManager mHandoverManager; + final HandoverDataParser mHandoverDataParser; final int mSap; final Callback mCallback; + private final Context mContext; ServerThread mServerThread = null; boolean mServerRunning = false; @@ -46,9 +54,10 @@ public final class HandoverServer { void onHandoverRequestReceived(); } - public HandoverServer(int sap, HandoverManager manager, Callback callback) { + public HandoverServer(Context context, int sap, HandoverDataParser manager, Callback callback) { + mContext = context; mSap = sap; - mHandoverManager = manager; + mHandoverDataParser = manager; mCallback = callback; } @@ -191,16 +200,23 @@ public final class HandoverServer { } if (handoverRequestMsg != null) { + BeamManager beamManager = BeamManager.getInstance(); + + if (beamManager.isBeamInProgress()) { + break; + } + // 2) convert to handover response - NdefMessage resp = mHandoverManager.tryHandoverRequest(handoverRequestMsg); - if (resp == null) { + HandoverDataParser.IncomingHandoverData handoverData + = mHandoverDataParser.getIncomingHandoverData(handoverRequestMsg); + if (handoverData == null) { Log.e(TAG, "Failed to create handover response"); break; } // 3) send handover response int offset = 0; - byte[] buffer = resp.toByteArray(); + byte[] buffer = handoverData.handoverSelect.toByteArray(); int remoteMiu = mSock.getRemoteMiu(); while (offset < buffer.length) { int length = Math.min(buffer.length - offset, remoteMiu); @@ -210,6 +226,9 @@ public final class HandoverServer { } // We're done mCallback.onHandoverRequestReceived(); + if (!beamManager.startBeamReceive(mContext, handoverData.handoverData)) { + break; + } // We can process another handover transfer byteStream = new ByteArrayOutputStream(); } @@ -238,3 +257,4 @@ public final class HandoverServer { } } } + diff --git a/src/com/android/nfc/handover/HandoverService.java b/src/com/android/nfc/handover/HandoverService.java deleted file mode 100644 index b2c7814e..00000000 --- a/src/com/android/nfc/handover/HandoverService.java +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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.nfc.NfcAdapter; -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, - BluetoothPeripheralHandover.Callback { - - static final String TAG = "HandoverService"; - static final boolean DBG = true; - - static final int MSG_REGISTER_CLIENT = 0; - static final int MSG_DEREGISTER_CLIENT = 1; - static final int MSG_START_INCOMING_TRANSFER = 2; - static final int MSG_START_OUTGOING_TRANSFER = 3; - static final int MSG_PERIPHERAL_HANDOVER = 4; - static final int MSG_PAUSE_POLLING = 5; - - - static final String BUNDLE_TRANSFER = "transfer"; - - static final String EXTRA_PERIPHERAL_DEVICE = "device"; - static final String EXTRA_PERIPHERAL_NAME = "headsetname"; - static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype"; - - public static final String ACTION_CANCEL_HANDOVER_TRANSFER = - "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER"; - - public static final String EXTRA_INCOMING = - "com.android.nfc.handover.extra.INCOMING"; - - public static final String ACTION_HANDOVER_STARTED = - "android.nfc.handover.intent.action.HANDOVER_STARTED"; - - public static final String ACTION_TRANSFER_PROGRESS = - "android.nfc.handover.intent.action.TRANSFER_PROGRESS"; - - public static final String ACTION_TRANSFER_DONE = - "android.nfc.handover.intent.action.TRANSFER_DONE"; - - public static final String EXTRA_TRANSFER_STATUS = - "android.nfc.handover.intent.extra.TRANSFER_STATUS"; - - public static final String EXTRA_TRANSFER_MIMETYPE = - "android.nfc.handover.intent.extra.TRANSFER_MIME_TYPE"; - - public static final String EXTRA_ADDRESS = - "android.nfc.handover.intent.extra.ADDRESS"; - - public static final String EXTRA_TRANSFER_DIRECTION = - "android.nfc.handover.intent.extra.TRANSFER_DIRECTION"; - - public static final String EXTRA_TRANSFER_ID = - "android.nfc.handover.intent.extra.TRANSFER_ID"; - - public static final String EXTRA_TRANSFER_PROGRESS = - "android.nfc.handover.intent.extra.TRANSFER_PROGRESS"; - - public static final String EXTRA_TRANSFER_URI = - "android.nfc.handover.intent.extra.TRANSFER_URI"; - - public static final String EXTRA_OBJECT_COUNT = - "android.nfc.handover.intent.extra.OBJECT_COUNT"; - - public static final String EXTRA_HANDOVER_DEVICE_TYPE = - "android.nfc.handover.intent.extra.HANDOVER_DEVICE_TYPE"; - - public static final int DIRECTION_INCOMING = 0; - public static final int DIRECTION_OUTGOING = 1; - - public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0; - public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1; - - // permission needed to be able to receive handover status requests - public static final String HANDOVER_STATUS_PERMISSION = - "android.permission.NFC_HANDOVER_STATUS"; - - // Amount of time to pause polling when connecting to peripherals - private static final int PAUSE_POLLING_TIMEOUT_MS = 35000; - public static final int PAUSE_DELAY_MILLIS = 300; - - // Variables below only accessed on main thread - final Queue<BluetoothOppHandover> mPendingOutTransfers; - final HashMap<Pair<String, Boolean>, HandoverTransfer> mBluetoothTransfers; - final Messenger mMessenger; - - SoundPool mSoundPool; - int mSuccessSound; - - BluetoothAdapter mBluetoothAdapter; - NfcAdapter mNfcAdapter; - Messenger mClient; - Handler mHandler; - BluetoothPeripheralHandover mBluetoothPeripheralHandover; - boolean mBluetoothHeadsetConnected; - boolean mBluetoothEnabledByNfc; - - private HandoverTransfer mWifiTransfer; - - class MessageHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REGISTER_CLIENT: - mClient = msg.replyTo; - // Restore state from previous instance - mBluetoothEnabledByNfc = msg.arg1 != 0; - mBluetoothHeadsetConnected = msg.arg2 != 0; - 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_PERIPHERAL_HANDOVER: - doPeripheralHandover(msg); - break; - case MSG_PAUSE_POLLING: - mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS); - break; - } - } - - } - - final BroadcastReceiver mHandoverStatusReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - int deviceType = intent.getIntExtra(EXTRA_HANDOVER_DEVICE_TYPE, - HandoverTransfer.DEVICE_TYPE_BLUETOOTH); - - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - handleBluetoothStateChanged(intent); - } else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) { - handleCancelTransfer(intent, deviceType); - } else if (action.equals(ACTION_TRANSFER_PROGRESS) || - action.equals(ACTION_TRANSFER_DONE) || - action.equals(ACTION_HANDOVER_STARTED)) { - handleTransferEvent(intent, deviceType); - } - } - }; - - public HandoverService() { - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - mPendingOutTransfers = new LinkedList<BluetoothOppHandover>(); - mBluetoothTransfers = 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); - mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); - - IntentFilter filter = new IntentFilter(ACTION_TRANSFER_DONE); - filter.addAction(ACTION_TRANSFER_PROGRESS); - filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER); - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(ACTION_HANDOVER_STARTED); - registerReceiver(mHandoverStatusReceiver, filter, HANDOVER_STATUS_PERMISSION, mHandler); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mSoundPool != null) { - mSoundPool.release(); - } - unregisterReceiver(mHandoverStatusReceiver); - } - - void doOutgoingTransfer(Message msg) { - Bundle msgData = msg.getData(); - - msgData.setClassLoader(getClassLoader()); - PendingHandoverTransfer pendingTransfer = msgData.getParcelable(BUNDLE_TRANSFER); - createHandoverTransfer(pendingTransfer); - - if (pendingTransfer.deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH) { - // Create the actual bluetooth transfer - - BluetoothOppHandover handover = new BluetoothOppHandover(HandoverService.this, - pendingTransfer.remoteDevice, pendingTransfer.uris, - pendingTransfer.remoteActivating); - if (mBluetoothAdapter.isEnabled()) { - // Start the transfer - handover.start(); - } else { - if (!enableBluetooth()) { - Log.e(TAG, "Error enabling Bluetooth."); - notifyClientTransferComplete(pendingTransfer.id); - return; - } - if (DBG) Log.d(TAG, "Queueing out transfer " + Integer.toString(pendingTransfer.id)); - mPendingOutTransfers.add(handover); - // Queue the transfer and enable Bluetooth - when it is enabled - // the transfer will be started. - } - } - } - - void doIncomingTransfer(Message msg) { - Bundle msgData = msg.getData(); - - msgData.setClassLoader(getClassLoader()); - PendingHandoverTransfer pendingTransfer = msgData.getParcelable(BUNDLE_TRANSFER); - if (pendingTransfer.deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH && - !mBluetoothAdapter.isEnabled() && !enableBluetooth()) { - Log.e(TAG, "Error enabling Bluetooth."); - notifyClientTransferComplete(pendingTransfer.id); - return; - } - createHandoverTransfer(pendingTransfer); - // Remote device will connect and finish the transfer - } - - void doPeripheralHandover(Message msg) { - Bundle msgData = msg.getData(); - BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE); - String name = msgData.getString(EXTRA_PERIPHERAL_NAME); - int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT); - if (mBluetoothPeripheralHandover != null) { - Log.d(TAG, "Ignoring pairing request, existing handover in progress."); - return; - } - mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(HandoverService.this, - device, name, transport, HandoverService.this); - // TODO: figure out a way to disable polling without deactivating current target - if (transport == BluetoothDevice.TRANSPORT_LE) { - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS); - } - if (mBluetoothAdapter.isEnabled()) { - if (!mBluetoothPeripheralHandover.start()) { - mNfcAdapter.resumePolling(); - } - } else { - // Once BT is enabled, the headset pairing will be started - - if (!enableBluetooth()) { - Log.e(TAG, "Error enabling Bluetooth."); - mBluetoothPeripheralHandover = null; - } - } - } - - void startPendingTransfers() { - while (!mPendingOutTransfers.isEmpty()) { - BluetoothOppHandover handover = mPendingOutTransfers.remove(); - handover.start(); - } - } - - boolean enableBluetooth() { - if (!mBluetoothAdapter.isEnabled()) { - mBluetoothEnabledByNfc = true; - return mBluetoothAdapter.enableNoAutoConnect(); - } - return true; - } - - void disableBluetoothIfNeeded() { - if (!mBluetoothEnabledByNfc) return; - - if (mBluetoothTransfers.size() == 0 && !mBluetoothHeadsetConnected) { - mBluetoothAdapter.disable(); - mBluetoothEnabledByNfc = false; - } - } - - void createHandoverTransfer(PendingHandoverTransfer pendingTransfer) { - HandoverTransfer transfer; - String macAddress; - - if (pendingTransfer.deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH) { - macAddress = pendingTransfer.remoteDevice.getAddress(); - transfer = maybeCreateHandoverTransfer(macAddress, - pendingTransfer.incoming, pendingTransfer); - } else { - Log.e(TAG, "Invalid device type [" + pendingTransfer.deviceType + "] received."); - return; - } - - if (transfer != null) { - transfer.updateNotification(); - } - } - - HandoverTransfer maybeCreateHandoverTransfer(String address, boolean incoming, - PendingHandoverTransfer pendingTransfer) { - HandoverTransfer transfer; - Pair<String, Boolean> key = new Pair<String, Boolean>(address, incoming); - - if (mBluetoothTransfers.containsKey(key)) { - transfer = mBluetoothTransfers.get(key); - if (!transfer.isRunning()) { - mBluetoothTransfers.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 null; - } - } else { - transfer = new HandoverTransfer(this, this, pendingTransfer); - } - - mBluetoothTransfers.put(key, transfer); - return transfer; - } - - - HandoverTransfer findHandoverTransfer(String macAddress, boolean incoming) { - Pair<String, Boolean> key = new Pair<String, Boolean>(macAddress, incoming); - if (mBluetoothTransfers.containsKey(key)) { - HandoverTransfer transfer = mBluetoothTransfers.get(key); - if (transfer.isRunning()) { - return transfer; - } - } - - return null; - } - - @Override - public IBinder onBind(Intent intent) { - return mMessenger.getBinder(); - } - - private void handleTransferEvent(Intent intent, int deviceType) { - String action = intent.getAction(); - int direction = intent.getIntExtra(EXTRA_TRANSFER_DIRECTION, -1); - int id = intent.getIntExtra(EXTRA_TRANSFER_ID, -1); - if (action.equals(ACTION_HANDOVER_STARTED)) { - // This is always for incoming transfers - direction = DIRECTION_INCOMING; - } - String sourceAddress = intent.getStringExtra(EXTRA_ADDRESS); - - if (direction == -1 || sourceAddress == null) return; - boolean incoming = (direction == DIRECTION_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. - if (id != -1) { - if (deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH) { - if (DBG) Log.d(TAG, "Didn't find transfer, stopping"); - Intent cancelIntent = new Intent( - "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"); - cancelIntent.putExtra(EXTRA_TRANSFER_ID, id); - sendBroadcast(cancelIntent); - } - } - return; - } - - transfer.setBluetoothTransferId(id); - - if (action.equals(ACTION_TRANSFER_DONE)) { - int handoverStatus = intent.getIntExtra(EXTRA_TRANSFER_STATUS, - HANDOVER_TRANSFER_STATUS_FAILURE); - if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) { - String uriString = intent.getStringExtra(EXTRA_TRANSFER_URI); - String mimeType = intent.getStringExtra(EXTRA_TRANSFER_MIMETYPE); - Uri uri = Uri.parse(uriString); - if (uri != null && 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_TRANSFER_PROGRESS)) { - float progress = intent.getFloatExtra(EXTRA_TRANSFER_PROGRESS, 0.0f); - transfer.updateFileProgress(progress); - } else if (action.equals(ACTION_HANDOVER_STARTED)) { - int count = intent.getIntExtra(EXTRA_OBJECT_COUNT, 0); - if (count > 0) { - transfer.setObjectCount(count); - } - } - } - - private void handleCancelTransfer(Intent intent, int deviceType) { - String sourceAddress = intent.getStringExtra(EXTRA_ADDRESS); - int direction = intent.getIntExtra(EXTRA_INCOMING, -1); - - if (direction == -1) { - return; - } - - boolean incoming = direction == DIRECTION_INCOMING; - HandoverTransfer transfer = findHandoverTransfer(sourceAddress, incoming); - - if (transfer != null) { - if (DBG) Log.d(TAG, "Cancelling transfer " + Integer.toString(transfer.mTransferId)); - transfer.cancel(); - } - } - - private void handleBluetoothStateChanged(Intent intent) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - if (state == BluetoothAdapter.STATE_ON) { - // If there is a pending device pairing, start it - if (mBluetoothPeripheralHandover != null && - !mBluetoothPeripheralHandover.hasStarted()) { - if (!mBluetoothPeripheralHandover.start()) { - mNfcAdapter.resumePolling(); - } - } - - // Start any pending file transfers - startPendingTransfers(); - } else if (state == BluetoothAdapter.STATE_OFF) { - mBluetoothEnabledByNfc = false; - mBluetoothHeadsetConnected = false; - } - } - - 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 boolean onUnbind(Intent intent) { - // prevent any future callbacks to the client, no rebind call needed. - mClient = null; - return false; - } - - @Override - public void onTransferComplete(HandoverTransfer transfer, boolean success) { - // Called on the main thread - - // First, remove the transfer from our list - synchronized (this) { - if (mWifiTransfer == transfer) { - mWifiTransfer = null; - } - } - - if (mWifiTransfer == null) { - Iterator it = mBluetoothTransfers.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry hashPair = (Map.Entry)it.next(); - HandoverTransfer transferEntry = (HandoverTransfer) hashPair.getValue(); - if (transferEntry == transfer) { - it.remove(); - } - } - } - - // Notify any clients of the service - notifyClientTransferComplete(transfer.getTransferId()); - - // Play success sound - if (success) { - mSoundPool.play(mSuccessSound, 1.0f, 1.0f, 0, 0, 1.0f); - } else { - if (DBG) Log.d(TAG, "Transfer failed, final state: " + - Integer.toString(transfer.mState)); - } - disableBluetoothIfNeeded(); - } - - @Override - public void onBluetoothPeripheralHandoverComplete(boolean connected) { - // Called on the main thread - int transport = mBluetoothPeripheralHandover.mTransport; - mBluetoothPeripheralHandover = null; - mBluetoothHeadsetConnected = connected; - - // <hack> resume polling immediately if the connection failed, - // otherwise just wait for polling to come back up after the timeout - // This ensures we don't disconnect if the user left the volantis - // on the tag after pairing completed, which results in automatic - // disconnection </hack> - if (transport == BluetoothDevice.TRANSPORT_LE && !connected) { - if (mHandler.hasMessages(MSG_PAUSE_POLLING)) { - mHandler.removeMessages(MSG_PAUSE_POLLING); - } - - // do this unconditionally as the polling could have been paused as we were removing - // the message in the handler. It's a no-op if polling is already enabled. - mNfcAdapter.resumePolling(); - } - - if (mClient != null) { - Message msg = Message.obtain(null, - connected ? HandoverManager.MSG_HEADSET_CONNECTED - : HandoverManager.MSG_HEADSET_NOT_CONNECTED); - msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0; - try { - mClient.send(msg); - } catch (RemoteException e) { - // Ignore - } - } - disableBluetoothIfNeeded(); - } -} diff --git a/src/com/android/nfc/handover/PendingHandoverTransfer.java b/src/com/android/nfc/handover/PendingHandoverTransfer.java deleted file mode 100644 index 568b3ecd..00000000 --- a/src/com/android/nfc/handover/PendingHandoverTransfer.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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 String remoteMacAddress; // For type WIFI only - public BluetoothDevice remoteDevice; // For type BLUETOOTH only - public int id; - public boolean incoming; - public boolean remoteActivating; - public Uri[] uris; - public int deviceType; - - private 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; - - this.deviceType = HandoverTransfer.DEVICE_TYPE_BLUETOOTH; - } - - private PendingHandoverTransfer(int id, boolean incoming, String remoteMacAddress, - boolean remoteActivating, Uri[] uris) { - this.id = id; - this.incoming = incoming; - this.remoteMacAddress = remoteMacAddress; - this.remoteActivating = remoteActivating; - this.uris = uris; - - this.deviceType = HandoverTransfer.DEVICE_TYPE_WIFI; - } - - public static final PendingHandoverTransfer forBluetoothDevice( - int id, boolean incoming, BluetoothDevice remoteDevice, boolean remoteActivating, - Uri[] uris) { - return new PendingHandoverTransfer(id, incoming, remoteDevice, remoteActivating, uris); - } - - public static final PendingHandoverTransfer forWifiDevice( - int id, boolean incoming, String macAddress, boolean remoteActivating, Uri[] uris) { - return new PendingHandoverTransfer(id, incoming, macAddress, remoteActivating, 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; - int deviceType = in.readInt(); - BluetoothDevice remoteDevice = null; - String remoteMac = null; - if (deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH) { - remoteDevice = in.readParcelable(getClass().getClassLoader()); - } else { - remoteMac = in.readString(); - } - 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); - } - if (deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH) { - return new PendingHandoverTransfer(id, incoming, remoteDevice, - remoteActivating, uris); - } else { - return new PendingHandoverTransfer(id, incoming, remoteMac, 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.writeInt(deviceType); - if (deviceType == HandoverTransfer.DEVICE_TYPE_BLUETOOTH) { - dest.writeParcelable(remoteDevice, 0); - } else { - dest.writeString(remoteMacAddress); - } - dest.writeInt(remoteActivating ? 1 : 0); - dest.writeInt(uris != null ? uris.length : 0); - if (uris != null && uris.length > 0) { - dest.writeTypedArray(uris, 0); - } - } -} diff --git a/src/com/android/nfc/handover/PeripheralHandoverService.java b/src/com/android/nfc/handover/PeripheralHandoverService.java new file mode 100644 index 00000000..3e0f2448 --- /dev/null +++ b/src/com/android/nfc/handover/PeripheralHandoverService.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.nfc.NfcAdapter; +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 com.android.nfc.R; + +public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback { + static final String TAG = "PeripheralHandoverService"; + static final boolean DBG = true; + + static final int MSG_PAUSE_POLLING = 0; + + public static final String BUNDLE_TRANSFER = "transfer"; + public static final String EXTRA_PERIPHERAL_DEVICE = "device"; + public static final String EXTRA_PERIPHERAL_NAME = "headsetname"; + public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype"; + + // Amount of time to pause polling when connecting to peripherals + private static final int PAUSE_POLLING_TIMEOUT_MS = 35000; + private static final int PAUSE_DELAY_MILLIS = 300; + + // Variables below only accessed on main thread + final Messenger mMessenger; + + SoundPool mSoundPool; + int mSuccessSound; + int mStartId; + + BluetoothAdapter mBluetoothAdapter; + NfcAdapter mNfcAdapter; + Handler mHandler; + BluetoothPeripheralHandover mBluetoothPeripheralHandover; + boolean mBluetoothHeadsetConnected; + boolean mBluetoothEnabledByNfc; + + class MessageHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PAUSE_POLLING: + mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS); + break; + } + } + } + + final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + handleBluetoothStateChanged(intent); + } + } + }; + + public PeripheralHandoverService() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mHandler = new MessageHandler(); + mMessenger = new Messenger(mHandler); + mBluetoothHeadsetConnected = false; + mBluetoothEnabledByNfc = false; + mStartId = 0; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (mStartId != 0) { + // already running + return START_STICKY; + } + + mStartId = startId; + + if (intent == null) { + if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover."); + stopSelf(startId); + return START_NOT_STICKY; + } + + if (doPeripheralHandover(intent.getExtras())) { + return START_STICKY; + } else { + stopSelf(startId); + return START_NOT_STICKY; + } + } + + @Override + public void onCreate() { + super.onCreate(); + + mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); + mSuccessSound = mSoundPool.load(this, R.raw.end, 1); + mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); + + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + registerReceiver(mBluetoothStatusReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mSoundPool != null) { + mSoundPool.release(); + } + unregisterReceiver(mBluetoothStatusReceiver); + } + + boolean doPeripheralHandover(Bundle msgData) { + if (mBluetoothPeripheralHandover != null) { + Log.d(TAG, "Ignoring pairing request, existing handover in progress."); + return true; + } + + if (msgData == null) { + return false; + } + + BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE); + String name = msgData.getString(EXTRA_PERIPHERAL_NAME); + int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT); + + mBluetoothPeripheralHandover = new BluetoothPeripheralHandover( + this, device, name, transport, this); + + if (transport == BluetoothDevice.TRANSPORT_LE) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS); + } + if (mBluetoothAdapter.isEnabled()) { + if (!mBluetoothPeripheralHandover.start()) { + mHandler.removeMessages(MSG_PAUSE_POLLING); + mNfcAdapter.resumePolling(); + } + } else { + // Once BT is enabled, the headset pairing will be started + if (!enableBluetooth()) { + Log.e(TAG, "Error enabling Bluetooth."); + mBluetoothPeripheralHandover = null; + return false; + } + } + + return true; + } + + private void handleBluetoothStateChanged(Intent intent) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_ON) { + // If there is a pending device pairing, start it + if (mBluetoothPeripheralHandover != null && + !mBluetoothPeripheralHandover.hasStarted()) { + if (!mBluetoothPeripheralHandover.start()) { + mNfcAdapter.resumePolling(); + } + } + } else if (state == BluetoothAdapter.STATE_OFF) { + mBluetoothEnabledByNfc = false; + mBluetoothHeadsetConnected = false; + } + } + + @Override + public void onBluetoothPeripheralHandoverComplete(boolean connected) { + // Called on the main thread + int transport = mBluetoothPeripheralHandover.mTransport; + mBluetoothPeripheralHandover = null; + mBluetoothHeadsetConnected = connected; + + // <hack> resume polling immediately if the connection failed, + // otherwise just wait for polling to come back up after the timeout + // This ensures we don't disconnect if the user left the volantis + // on the tag after pairing completed, which results in automatic + // disconnection </hack> + if (transport == BluetoothDevice.TRANSPORT_LE && !connected) { + if (mHandler.hasMessages(MSG_PAUSE_POLLING)) { + mHandler.removeMessages(MSG_PAUSE_POLLING); + } + + // do this unconditionally as the polling could have been paused as we were removing + // the message in the handler. It's a no-op if polling is already enabled. + mNfcAdapter.resumePolling(); + } + disableBluetoothIfNeeded(); + stopSelf(mStartId); + } + + + boolean enableBluetooth() { + if (!mBluetoothAdapter.isEnabled()) { + mBluetoothEnabledByNfc = true; + return mBluetoothAdapter.enableNoAutoConnect(); + } + return true; + } + + void disableBluetoothIfNeeded() { + if (!mBluetoothEnabledByNfc) return; + + if (!mBluetoothHeadsetConnected) { + mBluetoothAdapter.disable(); + mBluetoothEnabledByNfc = false; + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public boolean onUnbind(Intent intent) { + // prevent any future callbacks to the client, no rebind call needed. + return false; + } +} |