/* * Copyright (C) 2011 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; 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.HandoverDataParser; import com.android.nfc.handover.HandoverServer; import com.android.nfc.ndefpush.NdefPushClient; import com.android.nfc.ndefpush.NdefPushServer; import com.android.nfc.snep.SnepClient; import com.android.nfc.snep.SnepMessage; import com.android.nfc.snep.SnepServer; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.nfc.BeamShareData; import android.nfc.IAppCallback; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; /** * Interface to listen for P2P events. * All callbacks are made from the UI thread. */ interface P2pEventListener { /** * Indicates the user has expressed an intent to share * over NFC, but a remote device has not come into range * yet. Prompt the user to NFC tap. */ public void onP2pNfcTapRequested(); /** * Indicates the user has expressed an intent to share over * NFC, but the link hasn't come up yet and we no longer * want to wait for it */ public void onP2pTimeoutWaitingForLink(); /** * Indicates a P2P device is in range. *
onP2pInRange() and onP2pOutOfRange() will always be called * alternately. */ public void onP2pInRange(); /** * Called when a NDEF payload is prepared to send, and confirmation is * required. Call Callback.onP2pSendConfirmed() to make the confirmation. */ public void onP2pSendConfirmationRequested(); /** * Called to indicate a send was successful. */ public void onP2pSendComplete(); /** * * Called to indicate the link has broken while we were trying to send * a message. We'll start a debounce timer for the user to get the devices * back together. UI may show a hint to achieve that */ public void onP2pSendDebounce(); /** * Called to indicate a link has come back up after being temporarily * broken, and sending is resuming */ public void onP2pResumeSend(); /** * Called to indicate the remote device does not support connection handover */ public void onP2pHandoverNotSupported(); /** * Called to indicate the device is busy with another handover transfer */ public void onP2pHandoverBusy(); /** * Called to indicate a receive was successful. */ public void onP2pReceiveComplete(boolean playSound); /** * Indicates the P2P device went out of range. */ public void onP2pOutOfRange(); public interface Callback { public void onP2pSendConfirmed(); public void onP2pCanceled(); } } /** * Manages sending and receiving NDEF message over LLCP link. * Does simple debouncing of the LLCP link - so that even if the link * drops and returns the user does not know. */ class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { static final String TAG = "NfcP2pLinkManager"; static final boolean DBG = true; /** Include this constant as a meta-data entry in the manifest * of an application to disable beaming the market/AAR link, like this: *
{@code
*
*
*
* }
*/
static final String DISABLE_BEAM_DEFAULT = "android.nfc.disable_beam_default";
/** Enables the LLCP EchoServer, which can be used to test the android
* LLCP stack against nfcpy.
*/
static final boolean ECHOSERVER_ENABLED = false;
// TODO dynamically assign SAP values
static final int NDEFPUSH_SAP = 0x10;
static final int HANDOVER_SAP = 0x14;
static final int LINK_SEND_PENDING_DEBOUNCE_MS = 3000;
static final int LINK_SEND_CONFIRMED_DEBOUNCE_MS = 5000;
static final int LINK_SEND_COMPLETE_DEBOUNCE_MS = 500;
static final int LINK_SEND_CANCELED_DEBOUNCE_MS = 250;
// The amount of time we wait for the link to come up
// after a user has manually invoked Beam.
static final int WAIT_FOR_LINK_TIMEOUT_MS = 10000;
static final int MSG_DEBOUNCE_TIMEOUT = 1;
static final int MSG_RECEIVE_COMPLETE = 2;
static final int MSG_RECEIVE_HANDOVER = 3;
static final int MSG_SEND_COMPLETE = 4;
static final int MSG_START_ECHOSERVER = 5;
static final int MSG_STOP_ECHOSERVER = 6;
static final int MSG_HANDOVER_NOT_SUPPORTED = 7;
static final int MSG_SHOW_CONFIRMATION_UI = 8;
static final int MSG_WAIT_FOR_LINK_TIMEOUT = 9;
static final int MSG_HANDOVER_BUSY = 10;
// values for mLinkState
static final int LINK_STATE_DOWN = 1;
static final int LINK_STATE_UP = 2;
static final int LINK_STATE_DEBOUNCE = 3;
// values for mSendState
static final int SEND_STATE_NOTHING_TO_SEND = 1;
static final int SEND_STATE_NEED_CONFIRMATION = 2;
static final int SEND_STATE_PENDING = 3;
static final int SEND_STATE_SENDING = 4;
static final int SEND_STATE_COMPLETE = 5;
static final int SEND_STATE_CANCELED = 6;
// return values for doSnepProtocol
static final int SNEP_SUCCESS = 0;
static final int SNEP_FAILURE = 1;
// return values for doHandover
static final int HANDOVER_SUCCESS = 0;
static final int HANDOVER_FAILURE = 1;
static final int HANDOVER_UNSUPPORTED = 2;
static final int HANDOVER_BUSY = 3;
final NdefPushServer mNdefPushServer;
final SnepServer mDefaultSnepServer;
final HandoverServer mHandoverServer;
final EchoServer mEchoServer;
final Context mContext;
final P2pEventListener mEventListener;
final Handler mHandler;
final HandoverDataParser mHandoverDataParser;
final ForegroundUtils mForegroundUtils;
final int mDefaultMiu;
final int mDefaultRwSize;
// Locked on NdefP2pManager.this
PackageManager mPackageManager;
int mLinkState;
int mSendState; // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE
boolean mIsSendEnabled;
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;
SendTask mSendTask;
SharedPreferences mPrefs;
SnepClient mSnepClient;
HandoverClient mHandoverClient;
NdefPushClient mNdefPushClient;
ConnectTask mConnectTask;
boolean mLlcpServicesConnected;
long mLastLlcpActivationTime;
byte mPeerLlcpVersion;
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(context, HANDOVER_SAP, handoverDataParser, mHandoverCallback);
if (ECHOSERVER_ENABLED) {
mEchoServer = new EchoServer();
} else {
mEchoServer = null;
}
mPackageManager = context.getPackageManager();
mContext = context;
mEventListener = new P2pEventManager(context, this);
mHandler = new Handler(this);
mLinkState = LINK_STATE_DOWN;
mSendState = SEND_STATE_NOTHING_TO_SEND;
mIsSendEnabled = false;
mIsReceiveEnabled = false;
mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE);
mHandoverDataParser = handoverDataParser;
mDefaultMiu = defaultMiu;
mDefaultRwSize = defaultRwSize;
mLlcpServicesConnected = false;
mNdefCallbackUid = -1;
mForegroundUtils = ForegroundUtils.getInstance();
}
/**
* May be called from any thread.
* Assumes that NFC is already on if any parameter is true.
*/
public void enableDisable(boolean sendEnable, boolean receiveEnable) {
synchronized (this) {
if (!mIsReceiveEnabled && receiveEnable) {
mDefaultSnepServer.start();
mNdefPushServer.start();
mHandoverServer.start();
if (mEchoServer != null) {
mHandler.sendEmptyMessage(MSG_START_ECHOSERVER);
}
} else if (mIsReceiveEnabled && !receiveEnable) {
if (DBG) Log.d(TAG, "enableDisable: llcp deactivate");
onLlcpDeactivated ();
mDefaultSnepServer.stop();
mNdefPushServer.stop();
mHandoverServer.stop();
if (mEchoServer != null) {
mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER);
}
}
mIsSendEnabled = sendEnable;
mIsReceiveEnabled = receiveEnable;
}
}
/**
* May be called from any thread.
* @return whether the LLCP link is in an active or debounce state
*/
public boolean isLlcpActive() {
synchronized (this) {
return mLinkState != LINK_STATE_DOWN;
}
}
/**
* Set NDEF callback for sending.
* May be called from any thread.
* NDEF callbacks may be set at any time (even if NFC is
* currently off or P2P send is currently off). They will become
* active as soon as P2P send is enabled.
*/
public void setNdefCallback(IAppCallback callbackNdef, int callingUid) {
synchronized (this) {
mCallbackNdef = callbackNdef;
mNdefCallbackUid = callingUid;
}
}
public void onManualBeamInvoke(BeamShareData shareData) {
synchronized (P2pLinkManager.this) {
if (mLinkState != LINK_STATE_DOWN) {
return;
}
if (mForegroundUtils.getForegroundUids().contains(mNdefCallbackUid)) {
// Try to get data from the registered NDEF callback
prepareMessageToSend(false);
} else {
mMessageToSend = null;
mUrisToSend = null;
}
if (mMessageToSend == null && mUrisToSend == null && shareData != null) {
// No data from the NDEF callback, get data from ShareData
if (shareData.uris != null) {
mUrisToSend = shareData.uris;
} else if (shareData.ndefMessage != null) {
mMessageToSend = shareData.ndefMessage;
}
mUserHandle = shareData.userHandle;
}
if (mMessageToSend != null ||
(mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) {
mSendState = SEND_STATE_PENDING;
mEventListener.onP2pNfcTapRequested();
scheduleTimeoutLocked(MSG_WAIT_FOR_LINK_TIMEOUT, WAIT_FOR_LINK_TIMEOUT_MS);
}
}
}
/**
* Must be called on UI Thread.
*/
public void onLlcpActivated(byte peerLlcpVersion) {
Log.i(TAG, "LLCP activated");
synchronized (P2pLinkManager.this) {
if (mEchoServer != null) {
mEchoServer.onLlcpActivated();
}
mLastLlcpActivationTime = SystemClock.elapsedRealtime();
mPeerLlcpVersion = peerLlcpVersion;
switch (mLinkState) {
case LINK_STATE_DOWN:
if (DBG) Log.d(TAG, "onP2pInRange()");
// Start taking a screenshot
mEventListener.onP2pInRange();
mLinkState = LINK_STATE_UP;
// If we had a pending send (manual Beam invoke),
// mark it as sending
if (mSendState == SEND_STATE_PENDING) {
mSendState = SEND_STATE_SENDING;
mHandler.removeMessages(MSG_WAIT_FOR_LINK_TIMEOUT);
// Immediately try to connect LLCP services
connectLlcpServices();
} else {
mSendState = SEND_STATE_NOTHING_TO_SEND;
prepareMessageToSend(true);
if (mMessageToSend != null ||
(mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) {
// We have data to send, connect LLCP services
connectLlcpServices();
if ((mSendFlags & NfcAdapter.FLAG_NDEF_PUSH_NO_CONFIRM) != 0) {
mSendState = SEND_STATE_SENDING;
} else {
mSendState = SEND_STATE_NEED_CONFIRMATION;
}
}
}
break;
case LINK_STATE_UP:
if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()");
return;
case LINK_STATE_DEBOUNCE:
// Immediately connect and try to send again
mLinkState = LINK_STATE_UP;
if (mSendState == SEND_STATE_SENDING ||
mSendState == SEND_STATE_NEED_CONFIRMATION) {
// If we have something to send still, connect LLCP
connectLlcpServices();
}
mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
break;
}
}
}
/**
* Must be called on UI Thread.
*/
public void onLlcpFirstPacketReceived() {
synchronized (P2pLinkManager.this) {
long totalTime = SystemClock.elapsedRealtime() - mLastLlcpActivationTime;
if (DBG) Log.d(TAG, "Took " + Long.toString(totalTime) + " to get first LLCP PDU");
}
}
public void onUserSwitched(int userId) {
// Update the cached package manager in case of user switch
synchronized (P2pLinkManager.this) {
try {
mPackageManager = mContext.createPackageContextAsUser("android", 0,
new UserHandle(userId)).getPackageManager();
} catch (NameNotFoundException e) {
Log.e(TAG, "Failed to retrieve PackageManager for user");
}
}
}
void prepareMessageToSend(boolean generatePlayLink) {
synchronized (P2pLinkManager.this) {
mMessageToSend = null;
mUrisToSend = null;
if (!mIsSendEnabled) {
return;
}
List