Improve NDEF push API
Introduce
setNdefPushMessage()
setNdefPushMessageCallback()
setNdefPushCompleteCallback()
Deprecate public API
enableForegroundNdefPush()
disableForegroundNdefPush()
Hide & Deprecate staged (public but never released) API
enableForegroundNdefPushCallback()
The new API's do not require the application to explicitly call
enable()/disable() in onPause()/onResume(), we use a Fragment behind
the scenes to manager this automatically.
NDEF Push can be disabled by using a null parameter, so each
enable()/disable() pair is collapsed to a single set() call.
Application code should now look something like:
public void onCreate() {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
if (adapter != null) { // check that NFC is available on this device
adapter.setNdefPushMessage(myNdefMessage, this);
}
}
And that's it - no need to explicitly hook into onPause() and onResume() events.
Also - introduce a generic NfcEvent class that is provided as a parameter on
all NFC callbacks. Right now it just provides the NfcAdapter, but using
the wrapper classes allows us to add more fields later without changing
the callback signature. (i'm thinking Bluetooth).
Change-Id: I371dcb026b535b8199225c1262eca64ce644458a
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 0dba18b..e6c4183 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -107,6 +107,7 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc/)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/current.txt b/api/current.txt
index 01b07a6..a086959 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12412,13 +12412,15 @@
public final class NfcAdapter {
method public void disableForegroundDispatch(android.app.Activity);
- method public void disableForegroundNdefPush(android.app.Activity);
+ method public deprecated void disableForegroundNdefPush(android.app.Activity);
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.String[][]);
- method public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
- method public void enableForegroundNdefPush(android.app.Activity, android.nfc.NfcAdapter.NdefPushCallback);
+ method public deprecated void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method public static deprecated android.nfc.NfcAdapter getDefaultAdapter();
method public boolean isEnabled();
+ method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity...);
+ method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity...);
+ method public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity...);
field public static final java.lang.String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field public static final java.lang.String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
field public static final java.lang.String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
@@ -12427,9 +12429,16 @@
field public static final java.lang.String EXTRA_TAG = "android.nfc.extra.TAG";
}
- public static abstract interface NfcAdapter.NdefPushCallback {
- method public abstract android.nfc.NdefMessage createMessage();
- method public abstract void onMessagePushed();
+ public static abstract interface NfcAdapter.CreateNdefMessageCallback {
+ method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+ }
+
+ public static abstract interface NfcAdapter.OnNdefPushCompleteCallback {
+ method public abstract void onNdefPushComplete(android.nfc.NfcEvent);
+ }
+
+ public final class NfcEvent {
+ field public final android.nfc.NfcAdapter nfcAdapter;
}
public final class NfcManager {
diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl
index 80ba2ed..e60a5b0 100644
--- a/core/java/android/nfc/INdefPushCallback.aidl
+++ b/core/java/android/nfc/INdefPushCallback.aidl
@@ -23,6 +23,6 @@
*/
interface INdefPushCallback
{
- NdefMessage onConnect();
- void onMessagePushed();
+ NdefMessage createMessage();
+ void onNdefPushComplete();
}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 4fc248f..016af58 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -23,8 +23,8 @@
import android.nfc.Tag;
import android.nfc.TechListParcel;
import android.nfc.INdefPushCallback;
-import android.nfc.INfcTag;
import android.nfc.INfcAdapterExtras;
+import android.nfc.INfcTag;
/**
* @hide
@@ -34,19 +34,14 @@
INfcTag getNfcTagInterface();
INfcAdapterExtras getNfcAdapterExtrasInterface();
- // NfcAdapter-class related methods
int getState();
- void enableForegroundDispatch(in ComponentName activity, in PendingIntent intent,
- in IntentFilter[] filters, in TechListParcel techLists);
- void disableForegroundDispatch(in ComponentName activity);
- void enableForegroundNdefPush(in ComponentName activity, in NdefMessage msg);
- void enableForegroundNdefPushWithCallback(in ComponentName activity, in INdefPushCallback callback);
- void disableForegroundNdefPush(in ComponentName activity);
-
- // Non-public methods
boolean disable();
boolean enable();
- boolean enableZeroClick();
- boolean disableZeroClick();
- boolean isZeroClickEnabled();
+ boolean enableNdefPush();
+ boolean disableNdefPush();
+ boolean isNdefPushEnabled();
+
+ void setForegroundDispatch(in PendingIntent intent,
+ in IntentFilter[] filters, in TechListParcel techLists);
+ void setForegroundNdefPush(in NdefMessage msg, in INdefPushCallback callback);
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
new file mode 100644
index 0000000..3cc6820
--- /dev/null
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -0,0 +1,217 @@
+/*
+ * 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 android.nfc;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Manages NFC API's that are coupled to the life-cycle of an Activity.
+ *
+ * <p>Uses a fragment to hook into onPause() and onResume() of the host
+ * activities.
+ *
+ * <p>Ideally all of this management would be done in the NFC Service,
+ * but right now it is much easier to do it in the application process.
+ *
+ * @hide
+ */
+public final class NfcActivityManager extends INdefPushCallback.Stub {
+ static final String TAG = NfcAdapter.TAG;
+ static final Boolean DBG = false;
+
+ final NfcAdapter mAdapter;
+ final HashMap<Activity, NfcActivityState> mNfcState; // contents protected by this
+ final NfcEvent mDefaultEvent; // can re-use one NfcEvent because it just contains adapter
+
+ /**
+ * NFC state associated with an {@link Activity}
+ */
+ class NfcActivityState {
+ boolean resumed = false; // is the activity resumed
+ NdefMessage ndefMessage;
+ NfcAdapter.CreateNdefMessageCallback ndefMessageCallback;
+ NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback;
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder("[").append(resumed).append(" ");
+ s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
+ s.append(onNdefPushCompleteCallback).append("]");
+ return s.toString();
+ }
+ }
+
+ public NfcActivityManager(NfcAdapter adapter) {
+ mAdapter = adapter;
+ mNfcState = new HashMap<Activity, NfcActivityState>();
+ mDefaultEvent = new NfcEvent(mAdapter);
+ }
+
+ /**
+ * onResume hook from fragment attached to activity
+ */
+ public synchronized void onResume(Activity activity) {
+ NfcActivityState state = mNfcState.get(activity);
+ if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
+ if (state != null) {
+ state.resumed = true;
+ updateNfcService(state);
+ }
+ }
+
+ /**
+ * onPause hook from fragment attached to activity
+ */
+ public synchronized void onPause(Activity activity) {
+ NfcActivityState state = mNfcState.get(activity);
+ if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
+ if (state != null) {
+ state.resumed = false;
+ updateNfcService(state);
+ }
+ }
+
+ public synchronized void setNdefPushMessage(Activity activity, NdefMessage message) {
+ NfcActivityState state = getOrCreateState(activity, message != null);
+ if (state == null || state.ndefMessage == message) {
+ return; // nothing more to do;
+ }
+ state.ndefMessage = message;
+ if (message == null) {
+ maybeRemoveState(activity, state);
+ }
+ if (state.resumed) {
+ updateNfcService(state);
+ }
+ }
+
+ public synchronized void setNdefPushMessageCallback(Activity activity,
+ NfcAdapter.CreateNdefMessageCallback callback) {
+ NfcActivityState state = getOrCreateState(activity, callback != null);
+ if (state == null || state.ndefMessageCallback == callback) {
+ return; // nothing more to do;
+ }
+ state.ndefMessageCallback = callback;
+ if (callback == null) {
+ maybeRemoveState(activity, state);
+ }
+ if (state.resumed) {
+ updateNfcService(state);
+ }
+ }
+
+ public synchronized void setOnNdefPushCompleteCallback(Activity activity,
+ NfcAdapter.OnNdefPushCompleteCallback callback) {
+ NfcActivityState state = getOrCreateState(activity, callback != null);
+ if (state == null || state.onNdefPushCompleteCallback == callback) {
+ return; // nothing more to do;
+ }
+ state.onNdefPushCompleteCallback = callback;
+ if (callback == null) {
+ maybeRemoveState(activity, state);
+ }
+ if (state.resumed) {
+ updateNfcService(state);
+ }
+ }
+
+ /**
+ * Get the NfcActivityState for the specified Activity.
+ * If create is true, then create it if it doesn't already exist,
+ * and ensure the NFC fragment is attached to the activity.
+ */
+ synchronized NfcActivityState getOrCreateState(Activity activity, boolean create) {
+ if (DBG) Log.d(TAG, "getOrCreateState " + activity + " " + create);
+ NfcActivityState state = mNfcState.get(activity);
+ if (state == null && create) {
+ state = new NfcActivityState();
+ mNfcState.put(activity, state);
+ NfcFragment.attach(activity);
+ }
+ return state;
+ }
+
+ /**
+ * If the NfcActivityState is empty then remove it, and
+ * detach it from the Activity.
+ */
+ synchronized void maybeRemoveState(Activity activity, NfcActivityState state) {
+ if (state.ndefMessage == null && state.ndefMessageCallback == null &&
+ state.onNdefPushCompleteCallback == null) {
+ mNfcState.remove(activity);
+ }
+ }
+
+ /**
+ * Register NfcActivityState with the NFC service.
+ */
+ synchronized void updateNfcService(NfcActivityState state) {
+ boolean serviceCallbackNeeded = state.ndefMessageCallback != null ||
+ state.onNdefPushCompleteCallback != null;
+
+ try {
+ NfcAdapter.sService.setForegroundNdefPush(state.resumed ? state.ndefMessage : null,
+ state.resumed && serviceCallbackNeeded ? this : null);
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
+ * Callback from NFC service
+ */
+ @Override
+ public NdefMessage createMessage() {
+ NfcAdapter.CreateNdefMessageCallback callback = null;
+ synchronized (NfcActivityManager.this) {
+ for (NfcActivityState state : mNfcState.values()) {
+ if (state.resumed) {
+ callback = state.ndefMessageCallback;
+ }
+ }
+ }
+
+ // drop lock before making callback
+ if (callback != null) {
+ return callback.createNdefMessage(mDefaultEvent);
+ }
+ return null;
+ }
+
+ /**
+ * Callback from NFC service
+ */
+ @Override
+ public void onNdefPushComplete() {
+ NfcAdapter.OnNdefPushCompleteCallback callback = null;
+ synchronized (NfcActivityManager.this) {
+ for (NfcActivityState state : mNfcState.values()) {
+ if (state.resumed) {
+ callback = state.onNdefPushCompleteCallback;
+ }
+ }
+ }
+
+ // drop lock before making callback
+ if (callback != null) {
+ callback.onNdefPushComplete(mDefaultEvent);
+ }
+ }
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 6a904ae..d58b249 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -42,7 +42,7 @@
* adapter for this Android device.
*/
public final class NfcAdapter {
- private static final String TAG = "NFC";
+ static final String TAG = "NFC";
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -187,106 +187,67 @@
/** @hide */
public static final int STATE_TURNING_OFF = 4;
- /**
- * LLCP link status: The LLCP link is activated.
- * @hide
- */
- public static final int LLCP_LINK_STATE_ACTIVATED = 0;
-
- /**
- * LLCP link status: The LLCP link is deactivated.
- * @hide
- */
- public static final int LLCP_LINK_STATE_DEACTIVATED = 1;
-
- /**
- * Broadcast Action: the LLCP link state changed.
- * <p>
- * Always contains the extra field
- * {@link android.nfc.NfcAdapter#EXTRA_LLCP_LINK_STATE_CHANGED}.
- * @hide
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_LLCP_LINK_STATE_CHANGED =
- "android.nfc.action.LLCP_LINK_STATE_CHANGED";
-
- /**
- * Used as int extra field in
- * {@link android.nfc.NfcAdapter#ACTION_LLCP_LINK_STATE_CHANGED}.
- * <p>
- * It contains the new state of the LLCP link.
- * @hide
- */
- public static final String EXTRA_LLCP_LINK_STATE_CHANGED = "android.nfc.extra.LLCP_LINK_STATE";
-
- /**
- * Tag Reader Discovery mode
- * @hide
- */
- private static final int DISCOVERY_MODE_TAG_READER = 0;
-
- /**
- * NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an
- * NFC-IP1 communication. Implementations should not assume that the
- * controller will end up behaving as an NFC-IP1 target or initiator and
- * should handle both cases, depending on the type of the remote peer type.
- * @hide
- */
- private static final int DISCOVERY_MODE_NFCIP1 = 1;
-
- /**
- * Card Emulation mode Enables the manager to act as an NFC tag. Provided
- * that a Secure Element (an UICC for instance) is connected to the NFC
- * controller through its SWP interface, it can be exposed to the outside
- * NFC world and be addressed by external readers the same way they would
- * with a tag.
- * <p>
- * Which Secure Element is exposed is implementation-dependent.
- *
- * @hide
- */
- private static final int DISCOVERY_MODE_CARD_EMULATION = 2;
-
- /**
- * Callback passed into {@link #enableForegroundNdefPush(Activity,NdefPushCallback)}. This
- */
- public interface NdefPushCallback {
- /**
- * Called when a P2P connection is created.
- */
- NdefMessage createMessage();
- /**
- * Called when the message is pushed.
- */
- void onMessagePushed();
- }
-
- private static class NdefPushCallbackWrapper extends INdefPushCallback.Stub {
- private NdefPushCallback mCallback;
-
- public NdefPushCallbackWrapper(NdefPushCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public NdefMessage onConnect() {
- return mCallback.createMessage();
- }
-
- @Override
- public void onMessagePushed() {
- mCallback.onMessagePushed();
- }
- }
-
// Guarded by NfcAdapter.class
- private static boolean sIsInitialized = false;
+ static boolean sIsInitialized = false;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
// recovery
- private static INfcAdapter sService;
- private static INfcTag sTagService;
+ static INfcAdapter sService;
+ static INfcTag sTagService;
+
+ /**
+ * NfcAdapter is currently a singleton, and does not require a context.
+ * However all the public API's are future-proofed to require a context.
+ * If we start using that then we'll need to keep a HashMap of
+ * Context.getApplicationContext() -> NfcAdapter, such that NfcAdapter
+ * is a singleton within each application context.
+ */
+ static NfcAdapter sSingleton; // protected by NfcAdapter.class
+
+ final NfcActivityManager mNfcActivityManager;
+
+ /**
+ * @see {@link #setNdefPushMessageCallback}
+ */
+ public interface OnNdefPushCompleteCallback {
+ /**
+ * Called on successful NDEF push.
+ *
+ * <p>This callback is usually made on a binder thread (not the UI thread).
+ *
+ * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
+ * @see {@link #setNdefPushMessageCallback}
+ */
+ public void onNdefPushComplete(NfcEvent event);
+ }
+
+ /**
+ * @see {@link #setCeateNdefMessageCallback}
+ */
+ public interface CreateNdefMessageCallback {
+ /**
+ * Called to provide a {@link NdefMessage} to push.
+ *
+ * <p>This callback is usually made on a binder thread (not the UI thread).
+ *
+ * <p>Called when this device is in range of another device
+ * that might support NDEF push. It allows the application to
+ * create the NDEF message only when it is required.
+ *
+ * <p>NDEF push cannot occur until this method returns, so do not
+ * block for too long.
+ *
+ * <p>The Android operating system will usually show a system UI
+ * on top of your activity during this time, so do not try to request
+ * input from the user to complete the callback, or provide custom NDEF
+ * push UI. The user probably will not see it.
+ *
+ * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
+ * @return NDEF message to push, or null to not provide a message
+ */
+ public NdefMessage createNdefMessage(NfcEvent event);
+ }
/**
* Helper to check if this device has FEATURE_NFC, but without using
@@ -308,29 +269,36 @@
}
}
- private static synchronized INfcAdapter setupService() {
+ /**
+ * Returns the singleton, or throws if NFC is not available.
+ */
+ static synchronized NfcAdapter getSingleton() {
if (!sIsInitialized) {
sIsInitialized = true;
/* is this device meant to have NFC */
if (!hasNfcFeature()) {
Log.v(TAG, "this device does not have NFC support");
- return null;
+ throw new UnsupportedOperationException();
}
sService = getServiceInterface();
if (sService == null) {
Log.e(TAG, "could not retrieve NFC service");
- return null;
+ throw new UnsupportedOperationException();
}
try {
sTagService = sService.getNfcTagInterface();
} catch (RemoteException e) {
Log.e(TAG, "could not retrieve NFC Tag service");
- return null;
+ throw new UnsupportedOperationException();
}
+ sSingleton = new NfcAdapter();
}
- return sService;
+ if (sSingleton == null) {
+ throw new UnsupportedOperationException();
+ }
+ return sSingleton;
}
/** get handle to NFC service interface */
@@ -376,13 +344,14 @@
public static NfcAdapter getDefaultAdapter() {
Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
"NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
- return new NfcAdapter(null);
+ return getSingleton();
}
- /*package*/ NfcAdapter(Context context) {
- if (setupService() == null) {
- throw new UnsupportedOperationException();
- }
+ /**
+ * Does not currently need a context.
+ */
+ NfcAdapter() {
+ mNfcActivityManager = new NfcActivityManager(this);
}
/**
@@ -524,6 +493,87 @@
}
/**
+ * Set the {@link NdefMessage} to push over NFC during the specified activities.
+ *
+ * <p>This method may be called at any time, but the NDEF message is
+ * only made available for NDEF push when one of the specified activities
+ * is in resumed (foreground) state.
+ *
+ * <p>Only one NDEF message can be pushed by the currently resumed activity.
+ * If both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback} are set then
+ * the callback will take priority.
+ *
+ * <p>Pass a null NDEF message to disable foreground NDEF push in the
+ * specified activities.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param message NDEF message to push over NFC, or null to disable
+ * @param activities one or more {@link Activity} to enable for NDEF push
+ */
+ public void setNdefPushMessage(NdefMessage message, Activity ... activities) {
+ if (activities.length == 0) {
+ throw new NullPointerException("Must specificy one or more activities");
+ }
+ for (Activity a : activities) {
+ mNfcActivityManager.setNdefPushMessage(a, message);
+ }
+ }
+
+ /**
+ * Set the callback to create a {@link NdefMessage} to push over NFC.
+ *
+ * <p>This method may be called at any time, but this callback is
+ * only made if one of the specified activities
+ * is in resumed (foreground) state.
+ *
+ * <p>Only one NDEF message can be pushed by the currently resumed activity.
+ * If both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback} are set then
+ * the callback will take priority.
+ *
+ * <p>Pass a null callback to disable the callback in the
+ * specified activities.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param callback callback, or null to disable
+ * @param activities one or more {@link Activity} to enable for NDEF push
+ */
+ public void setNdefPushMessageCallback(CreateNdefMessageCallback callback,
+ Activity ... activities) {
+ if (activities.length == 0) {
+ throw new NullPointerException("Must specificy one or more activities");
+ }
+ for (Activity a : activities) {
+ mNfcActivityManager.setNdefPushMessageCallback(a, callback);
+ }
+ }
+
+ /**
+ * Set the callback on a successful NDEF push over NFC.
+ *
+ * <p>This method may be called at any time, but NDEF push and this callback
+ * can only occur when one of the specified activities is in resumed
+ * (foreground) state.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param callback callback, or null to disable
+ * @param activities one or more {@link Activity} to enable the callback
+ */
+ public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
+ Activity ... activities) {
+ if (activities.length == 0) {
+ throw new NullPointerException("Must specificy one or more activities");
+ }
+ for (Activity a : activities) {
+ mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
+ }
+ }
+
+ /**
* Enable foreground dispatch to the given Activity.
*
* <p>This will give give priority to the foreground activity when
@@ -562,7 +612,7 @@
throw new NullPointerException();
}
if (!activity.isResumed()) {
- throw new IllegalStateException("Foregorund dispatching can only be enabled " +
+ throw new IllegalStateException("Foreground dispatch can only be enabled " +
"when your activity is resumed");
}
try {
@@ -572,8 +622,7 @@
}
ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
mForegroundDispatchListener);
- sService.enableForegroundDispatch(activity.getComponentName(), intent, filters,
- parcel);
+ sService.setForegroundDispatch(intent, filters, parcel);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
@@ -608,9 +657,9 @@
void disableForegroundDispatchInternal(Activity activity, boolean force) {
try {
- sService.disableForegroundDispatch(activity.getComponentName());
+ sService.setForegroundDispatch(null, null, null);
if (!force && !activity.isResumed()) {
- throw new IllegalStateException("You must disable forgeground dispatching " +
+ throw new IllegalStateException("You must disable foreground dispatching " +
"while your activity is still resumed");
}
} catch (RemoteException e) {
@@ -619,78 +668,38 @@
}
/**
- * Enable NDEF message push over P2P while this Activity is in the foreground.
+ * Enable NDEF message push over NFC while this Activity is in the foreground.
*
- * <p>For this to function properly the other NFC device being scanned must
- * support the "com.android.npp" NDEF push protocol. Support for this
- * protocol is currently optional for Android NFC devices.
+ * <p>You must explicitly call this method every time the activity is
+ * resumed, and you must call {@link #disableForegroundNdefPush} before
+ * your activity completes {@link Activity#onPause}.
+ *
+ * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
+ * instead: it automatically hooks into your activity life-cycle,
+ * so you do not need to call enable/disable in your onResume/onPause.
+ *
+ * <p>For NDEF push to function properly the other NFC device must
+ * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or
+ * Android's "com.android.npp" (Ndef Push Protocol). This was optional
+ * on Gingerbread level Android NFC devices, but SNEP is mandatory on
+ * Ice-Cream-Sandwich and beyond.
*
* <p>This method must be called from the main thread.
*
- * <p class="note"><em>NOTE:</em> While foreground NDEF push is active standard tag dispatch is disabled.
- * Only the foreground activity may receive tag discovered dispatches via
- * {@link #enableForegroundDispatch}.
- *
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
- * @param activity the foreground Activity
- * @param msg a NDEF Message to push over P2P
- * @throws IllegalStateException if the Activity is not currently in the foreground
- * @throws OperationNotSupportedException if this Android device does not support NDEF push
+ * @param activity foreground activity
+ * @param message a NDEF Message to push over NFC
+ * @throws IllegalStateException if the activity is not currently in the foreground
+ * @deprecated use {@link #setNdefPushMessage} instead
*/
- public void enableForegroundNdefPush(Activity activity, NdefMessage msg) {
- if (activity == null || msg == null) {
+ @Deprecated
+ public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
+ if (activity == null || message == null) {
throw new NullPointerException();
}
- if (!activity.isResumed()) {
- throw new IllegalStateException("Foregorund NDEF push can only be enabled " +
- "when your activity is resumed");
- }
- try {
- ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
- mForegroundNdefPushListener);
- sService.enableForegroundNdefPush(activity.getComponentName(), msg);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
- }
-
- /**
- * Enable NDEF message push over P2P while this Activity is in the foreground.
- *
- * <p>For this to function properly the other NFC device being scanned must
- * support the "com.android.npp" NDEF push protocol. Support for this
- * protocol is currently optional for Android NFC devices.
- *
- * <p>This method must be called from the main thread.
- *
- * <p class="note"><em>NOTE:</em> While foreground NDEF push is active standard tag dispatch is disabled.
- * Only the foreground activity may receive tag discovered dispatches via
- * {@link #enableForegroundDispatch}.
- *
- * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
- *
- * @param activity the foreground Activity
- * @param callback is called on when the P2P connection is established
- * @throws IllegalStateException if the Activity is not currently in the foreground
- * @throws OperationNotSupportedException if this Android device does not support NDEF push
- */
- public void enableForegroundNdefPush(Activity activity, NdefPushCallback callback) {
- if (activity == null || callback == null) {
- throw new NullPointerException();
- }
- if (!activity.isResumed()) {
- throw new IllegalStateException("Foregorund NDEF push can only be enabled " +
- "when your activity is resumed");
- }
- try {
- ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
- mForegroundNdefPushListener);
- sService.enableForegroundNdefPushWithCallback(activity.getComponentName(),
- new NdefPushCallbackWrapper(callback));
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ enforceResumed(activity);
+ mNfcActivityManager.setNdefPushMessage(activity, message);
}
/**
@@ -700,47 +709,91 @@
* must call this method before its {@link Activity#onPause} callback
* completes.
*
+ * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
+ * instead: it automatically hooks into your activity life-cycle,
+ * so you do not need to call enable/disable in your onResume/onPause.
+ *
* <p>This method must be called from the main thread.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param activity the Foreground activity
* @throws IllegalStateException if the Activity has already been paused
- * @throws OperationNotSupportedException if this Android device does not support NDEF push
+ * @deprecated use {@link #setNdefPushMessage} instead
*/
public void disableForegroundNdefPush(Activity activity) {
- ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
- mForegroundNdefPushListener);
- disableForegroundNdefPushInternal(activity, false);
+ if (activity == null) {
+ throw new NullPointerException();
+ }
+ enforceResumed(activity);
+ mNfcActivityManager.setNdefPushMessage(activity, null);
+ mNfcActivityManager.setNdefPushMessageCallback(activity, null);
+ mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
}
- OnActivityPausedListener mForegroundNdefPushListener = new OnActivityPausedListener() {
+ /**
+ * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
+ * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback}
+ * @hide
+ */
+ @Deprecated
+ public interface NdefPushCallback {
+ /**
+ * @deprecated use {@link CreateNdefMessageCallback} instead
+ */
+ @Deprecated
+ NdefMessage createMessage();
+ /**
+ * @deprecated use{@link OnNdefPushCompleteCallback} instead
+ */
+ @Deprecated
+ void onMessagePushed();
+ }
+
+ /**
+ * TODO: Remove this
+ * Converts new callbacks to old callbacks.
+ */
+ static final class LegacyCallbackWrapper implements CreateNdefMessageCallback,
+ OnNdefPushCompleteCallback {
+ final NdefPushCallback mLegacyCallback;
+ LegacyCallbackWrapper(NdefPushCallback legacyCallback) {
+ mLegacyCallback = legacyCallback;
+ }
@Override
- public void onPaused(Activity activity) {
- disableForegroundNdefPushInternal(activity, true);
+ public void onNdefPushComplete(NfcEvent event) {
+ mLegacyCallback.onMessagePushed();
}
- };
-
- void disableForegroundNdefPushInternal(Activity activity, boolean force) {
- try {
- sService.disableForegroundNdefPush(activity.getComponentName());
- if (!force && !activity.isResumed()) {
- throw new IllegalStateException("You must disable forgeground NDEF push " +
- "while your activity is still resumed");
- }
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
+ @Override
+ public NdefMessage createNdefMessage(NfcEvent event) {
+ return mLegacyCallback.createMessage();
}
}
/**
- * Enable zero-click sharing.
- *
+ * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
+ * @deprecated use {@link #setNdefPushMessageCallback} instead
* @hide
*/
- public boolean enableZeroClick() {
+ @Deprecated
+ public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) {
+ if (activity == null || callback == null) {
+ throw new NullPointerException();
+ }
+ enforceResumed(activity);
+ LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback);
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper);
+ mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper);
+ }
+
+ /**
+ * Enable NDEF Push feature.
+ * <p>This API is for the Settings application.
+ * @hide
+ */
+ public boolean enableNdefPush() {
try {
- return sService.enableZeroClick();
+ return sService.enableNdefPush();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
@@ -748,13 +801,13 @@
}
/**
- * Disable zero-click sharing.
- *
+ * Disable NDEF Push feature.
+ * <p>This API is for the Settings application.
* @hide
*/
- public boolean disableZeroClick() {
+ public boolean disableNdefPush() {
try {
- return sService.disableZeroClick();
+ return sService.disableNdefPush();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
@@ -762,20 +815,20 @@
}
/**
- * Return true if zero-click sharing feature is enabled.
+ * Return true if NDEF Push feature is enabled.
* <p>This function can return true even if NFC is currently turned-off.
- * This indicates that zero-click is not currently active, but it has
+ * This indicates that NDEF Push is not currently active, but it has
* been requested by the user and will be active as soon as NFC is turned
* on.
- * <p>If you want to check if zero-click sharing is currently active, use
- * <code>{@link #isEnabled()} && {@link #isZeroClickEnabled()}</code>
+ * <p>If you want to check if NDEF PUsh sharing is currently active, use
+ * <code>{@link #isEnabled()} && {@link #isNdefPushEnabled()}</code>
*
- * @return true if zero-click sharing is enabled
+ * @return true if NDEF Push feature is enabled
* @hide
*/
- public boolean isZeroClickEnabled() {
+ public boolean isNdefPushEnabled() {
try {
- return sService.isZeroClickEnabled();
+ return sService.isNdefPushEnabled();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
@@ -793,4 +846,10 @@
return null;
}
}
+
+ void enforceResumed(Activity activity) {
+ if (!activity.isResumed()) {
+ throw new IllegalStateException("API cannot be called while activity is paused");
+ }
+ }
}
diff --git a/core/java/android/nfc/NfcEvent.java b/core/java/android/nfc/NfcEvent.java
new file mode 100644
index 0000000..b00d8b7
--- /dev/null
+++ b/core/java/android/nfc/NfcEvent.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.nfc;
+
+/**
+ * Wraps information associated with any NFC event.
+ *
+ * <p>Immutable object, with direct access to the (final) fields.
+ *
+ * <p>An {@link NfcEvent} object is usually included in callbacks from
+ * {@link NfcAdapter}. Check the documentation of the callback to see
+ * which fields may be set.
+ *
+ * <p>This wrapper object is used (instead of parameters
+ * in the callback) because it allows new fields to be added without breaking
+ * API compatibility.
+ *
+ * @see {@link NfcAdapter.OnNdefPushCompleteCallback#onNdefPushComplete}
+ * @see {@link NfcAdapter.CreateNdefMessageCallback#createNdefMessage}
+ */
+public final class NfcEvent {
+ /**
+ * The {@link NfcAdapter} associated with the NFC event.
+ */
+ public final NfcAdapter nfcAdapter;
+
+ NfcEvent(NfcAdapter nfcAdapter) {
+ this.nfcAdapter = nfcAdapter;
+ }
+}
diff --git a/core/java/android/nfc/NfcFragment.java b/core/java/android/nfc/NfcFragment.java
new file mode 100644
index 0000000..c48859b
--- /dev/null
+++ b/core/java/android/nfc/NfcFragment.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.nfc;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+
+/**
+ * Used by {@link NfcActivityManager} to attach to activity life-cycle.
+ * @hide
+ */
+public final class NfcFragment extends Fragment {
+ static final String FRAGMENT_TAG = "android.nfc.NfcFragment";
+
+ // only used on UI thread
+ static boolean sIsInitialized = false;
+ static NfcActivityManager sNfcActivityManager;
+
+ /**
+ * Attach NfcFragment to an activity (if not already attached).
+ */
+ public static void attach(Activity activity) {
+ FragmentManager manager = activity.getFragmentManager();
+ if (manager.findFragmentByTag(FRAGMENT_TAG) == null) {
+ manager.beginTransaction().add(new NfcFragment(), FRAGMENT_TAG).commit();
+ }
+ }
+
+ /**
+ * Remove NfcFragment from activity.
+ */
+ public static void remove(Activity activity) {
+ FragmentManager manager = activity.getFragmentManager();
+ Fragment fragment = manager.findFragmentByTag(FRAGMENT_TAG);
+ if (fragment != null) {
+ manager.beginTransaction().remove(fragment).commit();
+ }
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (!sIsInitialized) {
+ sIsInitialized = true;
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(
+ activity.getApplicationContext());
+ if (adapter != null) {
+ sNfcActivityManager = adapter.mNfcActivityManager;
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (sNfcActivityManager != null) {
+ sNfcActivityManager.onResume(getActivity());
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (sNfcActivityManager != null) {
+ sNfcActivityManager.onPause(getActivity());
+ }
+ }
+}
diff --git a/core/java/android/nfc/NfcManager.java b/core/java/android/nfc/NfcManager.java
index 5fa6483..300ab45 100644
--- a/core/java/android/nfc/NfcManager.java
+++ b/core/java/android/nfc/NfcManager.java
@@ -40,7 +40,7 @@
public NfcManager(Context context) {
NfcAdapter adapter;
try {
- adapter = new NfcAdapter(context);
+ adapter = NfcAdapter.getSingleton();
} catch (UnsupportedOperationException e) {
adapter = null;
}