/* * 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 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.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.nfc.INdefPushCallback; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Profile; import android.util.Log; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.List; /** * Interface to listen for P2P events. * All callbacks are made from the UI thread. */ interface P2pEventListener { /** * Indicates a P2P device is in range. *
onP2pInRange() and onP2pOutOfRange() will always be called * alternately. *
All other callbacks will only occur while a P2P device is in range.
*/
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 a receive was successful.
*/
public void onP2pReceiveComplete();
/**
* Indicates the P2P device went out of range.
*/
public void onP2pOutOfRange();
public interface Callback {
public void onP2pSendConfirmed();
}
}
/**
* 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.
*/
public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback {
static final String TAG = "NfcP2pLinkManager";
static final boolean DBG = true;
// TODO dynamically assign SAP values
static final int NDEFPUSH_SAP = 0x10;
static final int LINK_DEBOUNCE_MS = 750;
static final int MSG_DEBOUNCE_TIMEOUT = 1;
static final int MSG_RECEIVE_COMPLETE = 2;
static final int MSG_SEND_COMPLETE = 3;
// 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_SENDING = 3;
static final Uri PROFILE_URI = Profile.CONTENT_VCARD_URI.buildUpon().
appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true").
build();
final NdefPushServer mNdefPushServer;
final SnepServer mDefaultSnepServer;
final ActivityManager mActivityManager;
final PackageManager mPackageManager;
final Context mContext;
final P2pEventListener mEventListener;
final Handler mHandler;
// Locked on NdefP2pManager.this
int mLinkState;
int mSendState; // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE
boolean mIsSendEnabled;
boolean mIsReceiveEnabled;
NdefMessage mMessageToSend; // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING
NdefMessage mStaticNdef;
INdefPushCallback mCallbackNdef;
SendTask mSendTask;
public P2pLinkManager(Context context) {
mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
mDefaultSnepServer = new SnepServer(mDefaultSnepCallback);
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
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;
}
/**
* 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();
} else if (mIsReceiveEnabled && !receiveEnable) {
mDefaultSnepServer.stop();
mNdefPushServer.stop();
}
mIsSendEnabled = sendEnable;
mIsReceiveEnabled = receiveEnable;
}
}
/**
* Set NDEF message or callback for sending.
* May be called from any thread.
* NDEF messages or 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 setNdefToSend(NdefMessage staticNdef, INdefPushCallback callbackNdef) {
synchronized (this) {
mStaticNdef = staticNdef;
mCallbackNdef = callbackNdef;
}
}
/**
* Must be called on UI Thread.
*/
public void onLlcpActivated() {
Log.i(TAG, "LLCP activated");
synchronized (P2pLinkManager.this) {
switch (mLinkState) {
case LINK_STATE_DOWN:
mLinkState = LINK_STATE_UP;
mSendState = SEND_STATE_NOTHING_TO_SEND;
if (DBG) Log.d(TAG, "onP2pInRange()");
mEventListener.onP2pInRange();
prepareMessageToSend();
if (mMessageToSend != null) {
mSendState = SEND_STATE_NEED_CONFIRMATION;
if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()");
mEventListener.onP2pSendConfirmationRequested();
}
break;
case LINK_STATE_UP:
if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()");
return;
case LINK_STATE_DEBOUNCE:
mLinkState = LINK_STATE_UP;
mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
if (mSendState == SEND_STATE_SENDING) {
Log.i(TAG, "Retry send...");
sendNdefMessage();
}
break;
}
}
}
void prepareMessageToSend() {
synchronized (P2pLinkManager.this) {
if (!mIsSendEnabled) {
mMessageToSend = null;
return;
}
NdefMessage messageToSend = mStaticNdef;
INdefPushCallback callback = mCallbackNdef;
if (callback != null) {
try {
messageToSend = callback.createMessage();
} catch (RemoteException e) {
// Ignore
}
}
if (messageToSend == null) {
messageToSend = createDefaultNdef();
}
mMessageToSend = messageToSend;
}
}
NdefMessage createDefaultNdef() {
List