summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl2
-rw-r--r--core/java/android/nfc/NfcActivityManager.java347
-rw-r--r--core/java/android/nfc/NfcAdapter.java241
-rw-r--r--core/java/android/nfc/NfcFragment.java96
4 files changed, 406 insertions, 280 deletions
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 10da9ef1df97..ddd00a4368b8 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -42,7 +42,7 @@ interface INfcAdapter
void setForegroundDispatch(in PendingIntent intent,
in IntentFilter[] filters, in TechListParcel techLists);
- void setForegroundNdefPush(in NdefMessage msg, in INdefPushCallback callback);
+ void setNdefPushCallback(in INdefPushCallback callback);
void dispatch(in Tag tag);
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 5fe58e902f82..2c730564fb7b 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -17,210 +17,303 @@
package android.nfc;
import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
-import java.util.WeakHashMap;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
/**
* 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.
+ * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
+ * into activity life-cycle events such as onPause() and onResume().
*
* @hide
*/
-public final class NfcActivityManager extends INdefPushCallback.Stub {
+public final class NfcActivityManager extends INdefPushCallback.Stub
+ implements Application.ActivityLifecycleCallbacks {
static final String TAG = NfcAdapter.TAG;
static final Boolean DBG = false;
final NfcAdapter mAdapter;
- final WeakHashMap<Activity, NfcActivityState> mNfcState; // contents protected by this
- final NfcEvent mDefaultEvent; // can re-use one NfcEvent because it just contains adapter
+ final NfcEvent mDefaultEvent; // cached NfcEvent (its currently always the same)
+
+ // All objects in the lists are protected by this
+ final List<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one
+ final List<NfcActivityState> mActivities; // Activities that have NFC state
/**
- * NFC state associated with an {@link Activity}
+ * NFC State associated with an {@link Application}.
*/
- 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();
+ class NfcApplicationState {
+ int refCount = 0;
+ final Application app;
+ public NfcApplicationState(Application app) {
+ this.app = app;
+ }
+ public void register() {
+ refCount++;
+ if (refCount == 1) {
+ this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
+ }
+ }
+ public void unregister() {
+ refCount--;
+ if (refCount == 0) {
+ this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
+ } else if (refCount < 0) {
+ Log.e(TAG, "-ve refcount for " + app);
+ }
}
}
- public NfcActivityManager(NfcAdapter adapter) {
- mAdapter = adapter;
- mNfcState = new WeakHashMap<Activity, NfcActivityState>();
- mDefaultEvent = new NfcEvent(mAdapter);
+ NfcApplicationState findAppState(Application app) {
+ for (NfcApplicationState appState : mApps) {
+ if (appState.app == app) {
+ return appState;
+ }
+ }
+ return null;
}
- /**
- * 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);
+ void registerApplication(Application app) {
+ NfcApplicationState appState = findAppState(app);
+ if (appState == null) {
+ appState = new NfcApplicationState(app);
+ mApps.add(appState);
}
+ appState.register();
}
- /**
- * 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);
+ void unregisterApplication(Application app) {
+ NfcApplicationState appState = findAppState(app);
+ if (appState == null) {
+ Log.e(TAG, "app was not registered " + app);
+ return;
}
+ appState.unregister();
}
/**
- * onDestroy hook from fragment attached to activity
+ * NFC state associated with an {@link Activity}
*/
- public void onDestroy(Activity activity) {
- mNfcState.remove(activity);
- }
-
- 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;
+ class NfcActivityState {
+ boolean resumed = false;
+ Activity activity;
+ NdefMessage ndefMessage = null; // static NDEF message
+ NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
+ NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
+ public NfcActivityState(Activity activity) {
+ if (activity.getWindow().isDestroyed()) {
+ throw new IllegalStateException("activity is already destroyed");
+ }
+ this.activity = activity;
+ registerApplication(activity.getApplication());
}
- state.ndefMessage = message;
- if (message == null) {
- maybeRemoveState(activity, state);
+ public void destroy() {
+ unregisterApplication(activity.getApplication());
+ resumed = false;
+ activity = null;
+ ndefMessage = null;
+ ndefMessageCallback = null;
+ onNdefPushCompleteCallback = null;
}
- if (state.resumed) {
- updateNfcService(state);
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder("[").append(" ");
+ s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
+ s.append(onNdefPushCompleteCallback).append("]");
+ return s.toString();
}
}
- 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;
+ /** find activity state from mActivities */
+ synchronized NfcActivityState findActivityState(Activity activity) {
+ for (NfcActivityState state : mActivities) {
+ if (state.activity == activity) {
+ return state;
+ }
}
- state.ndefMessageCallback = callback;
- if (callback == null) {
- maybeRemoveState(activity, state);
+ return null;
+ }
+
+ /** find or create activity state from mActivities */
+ synchronized NfcActivityState getActivityState(Activity activity) {
+ NfcActivityState state = findActivityState(activity);
+ if (state == null) {
+ state = new NfcActivityState(activity);
+ mActivities.add(state);
}
- if (state.resumed) {
- updateNfcService(state);
+ return state;
+ }
+
+ synchronized NfcActivityState findResumedActivityState() {
+ for (NfcActivityState state : mActivities) {
+ if (state.resumed) {
+ return state;
+ }
}
+ return null;
}
- 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;
+ synchronized void destroyActivityState(Activity activity) {
+ NfcActivityState activityState = findActivityState(activity);
+ if (activityState != null) {
+ activityState.destroy();
+ mActivities.remove(activityState);
}
- state.onNdefPushCompleteCallback = callback;
- if (callback == null) {
- maybeRemoveState(activity, state);
+ }
+
+ public NfcActivityManager(NfcAdapter adapter) {
+ mAdapter = adapter;
+ mActivities = new LinkedList<NfcActivityState>();
+ mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app
+ mDefaultEvent = new NfcEvent(mAdapter);
+ }
+
+ public void setNdefPushMessage(Activity activity, NdefMessage message) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.ndefMessage = message;
+ isResumed = state.resumed;
}
- if (state.resumed) {
- updateNfcService(state);
+ if (isResumed) {
+ requestNfcServiceCallback(true);
}
}
- /**
- * 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);
+ public void setNdefPushMessageCallback(Activity activity,
+ NfcAdapter.CreateNdefMessageCallback callback) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.ndefMessageCallback = callback;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ requestNfcServiceCallback(true);
}
- 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) {
- NfcFragment.remove(activity);
- mNfcState.remove(activity);
+ public void setOnNdefPushCompleteCallback(Activity activity,
+ NfcAdapter.OnNdefPushCompleteCallback callback) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.onNdefPushCompleteCallback = callback;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ requestNfcServiceCallback(true);
}
}
/**
- * Register NfcActivityState with the NFC service.
+ * Request or unrequest NFC service callbacks for NDEF push.
+ * Makes IPC call - do not hold lock.
+ * TODO: Do not do IPC on every onPause/onResume
*/
- synchronized void updateNfcService(NfcActivityState state) {
- boolean serviceCallbackNeeded = state.ndefMessageCallback != null ||
- state.onNdefPushCompleteCallback != null;
-
+ void requestNfcServiceCallback(boolean request) {
try {
- NfcAdapter.sService.setForegroundNdefPush(state.resumed ? state.ndefMessage : null,
- state.resumed && serviceCallbackNeeded ? this : null);
+ NfcAdapter.sService.setNdefPushCallback(request ? this : null);
} catch (RemoteException e) {
mAdapter.attemptDeadServiceRecovery(e);
}
}
- /**
- * Callback from NFC service
- */
+ /** Callback from NFC service, usually on binder thread */
@Override
public NdefMessage createMessage() {
- NfcAdapter.CreateNdefMessageCallback callback = null;
+ NfcAdapter.CreateNdefMessageCallback callback;
+ NdefMessage message;
synchronized (NfcActivityManager.this) {
- for (NfcActivityState state : mNfcState.values()) {
- if (state.resumed) {
- callback = state.ndefMessageCallback;
- }
- }
+ NfcActivityState state = findResumedActivityState();
+ if (state == null) return null;
+
+ callback = state.ndefMessageCallback;
+ message = state.ndefMessage;
}
- // drop lock before making callback
+ // Make callback without lock
if (callback != null) {
return callback.createNdefMessage(mDefaultEvent);
+ } else {
+ return message;
}
- return null;
}
- /**
- * Callback from NFC service
- */
+ /** Callback from NFC service, usually on binder thread */
@Override
public void onNdefPushComplete() {
- NfcAdapter.OnNdefPushCompleteCallback callback = null;
+ NfcAdapter.OnNdefPushCompleteCallback callback;
synchronized (NfcActivityManager.this) {
- for (NfcActivityState state : mNfcState.values()) {
- if (state.resumed) {
- callback = state.onNdefPushCompleteCallback;
- }
- }
+ NfcActivityState state = findResumedActivityState();
+ if (state == null) return;
+
+ callback = state.onNdefPushCompleteCallback;
}
- // drop lock before making callback
+ // Make callback without lock
if (callback != null) {
callback.onNdefPushComplete(mDefaultEvent);
}
}
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityStarted(Activity activity) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityResumed(Activity activity) {
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findActivityState(activity);
+ if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
+ if (state == null) return;
+ state.resumed = true;
+ }
+ requestNfcServiceCallback(true);
+ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityPaused(Activity activity) {
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findActivityState(activity);
+ if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
+ if (state == null) return;
+ state.resumed = false;
+ }
+ requestNfcServiceCallback(false);
+ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityStopped(Activity activity) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
+
+ /** Callback from Activity life-cycle, on main thread */
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findActivityState(activity);
+ if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
+ if (state != null) {
+ // release all associated references
+ destroyActivityState(activity);
+ }
+ }
+ }
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 23f96e39cb1e..b7a7bd5dad5b 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -556,109 +556,230 @@ public final class NfcAdapter {
}
/**
- * Set the {@link NdefMessage} to push over NFC during the specified activities.
+ * Set a static {@link NdefMessage} to send using Android Beam (TM).
*
- * <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>This method may be called at any time before {@link Activity#onDestroy},
+ * but the NDEF message is only made available for NDEF push when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
*
* <p>Only one NDEF message can be pushed by the currently resumed activity.
* If both {@link #setNdefPushMessage} and
- * {@link #setNdefPushMessageCallback} are set then
+ * {@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>If neither {@link #setNdefPushMessage} or
+ * {@link #setNdefPushMessageCallback} have been called for your activity, then
+ * the Android OS may choose to send a default NDEF message on your behalf,
+ * such as a URI for your application.
*
- * <p>At least one activity must be specified, and usually only one is necessary.
+ * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
+ * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
+ * then NDEF push will be completely disabled for the specified activity(s).
+ * This also disables any default NDEF message the Android OS would have
+ * otherwise sent on your behalf.
+ *
+ * <p>The API allows for multiple activities to be specified at a time,
+ * but it is strongly recommended to just register one at a time,
+ * and to do so during the activity's {@link Activity#onCreate}. For example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setNdefPushMessage(ndefMessage, this);
+ * }
+ * </pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the NDEF message and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p>If your Activity wants to dynamically generate an NDEF message,
+ * then set a callback using {@link #setNdefPushMessageCallback} instead
+ * of a static message.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param message NDEF message to push over NFC, or null to disable
- * @param activity an activity in which NDEF push should be enabled to share the provided
- * NDEF message
- * @param activities optional additional activities that should also enable NDEF push with
- * the provided NDEF message
+ * @param activity activity for which the NDEF message will be pushed
+ * @param activities optional additional activities, however we strongly recommend
+ * to only register one at a time, and to do so in that activity's
+ * {@link Activity#onCreate}
*/
public void setNdefPushMessage(NdefMessage message, Activity activity,
Activity ... activities) {
- if (activity == null) {
- throw new NullPointerException("activity cannot be null");
- }
- mNfcActivityManager.setNdefPushMessage(activity, message);
- for (Activity a : activities) {
- if (a == null) {
- throw new NullPointerException("activities cannot contain null");
+ int targetSdkVersion = getSdkVersion();
+ try {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessage(activity, message);
+ for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
+ mNfcActivityManager.setNdefPushMessage(a, message);
+ }
+ } catch (IllegalStateException e) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ // Less strict on old applications - just log the error
+ Log.e(TAG, "Cannot call API with Activity that has already " +
+ "been destroyed", e);
+ } else {
+ // Prevent new applications from making this mistake, re-throw
+ throw(e);
}
- mNfcActivityManager.setNdefPushMessage(a, message);
}
}
/**
- * Set the callback to create a {@link NdefMessage} to push over NFC.
+ * Set a callback that dynamically generates NDEF messages to send using Android Beam (TM).
*
- * <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>This method may be called at any time before {@link Activity#onDestroy},
+ * but the NDEF message callback can only occur when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
*
* <p>Only one NDEF message can be pushed by the currently resumed activity.
* If both {@link #setNdefPushMessage} and
- * {@link #setNdefPushMessageCallback} are set then
+ * {@link #setNdefPushMessageCallback} are set, then
* the callback will take priority.
*
- * <p>Pass a null callback to disable the callback in the
- * specified activities.
+ * <p>If neither {@link #setNdefPushMessage} or
+ * {@link #setNdefPushMessageCallback} have been called for your activity, then
+ * the Android OS may choose to send a default NDEF message on your behalf,
+ * such as a URI for your application.
*
- * <p>At least one activity must be specified, and usually only one is necessary.
+ * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
+ * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
+ * then NDEF push will be completely disabled for the specified activity(s).
+ * This also disables any default NDEF message the Android OS would have
+ * otherwise sent on your behalf.
+ *
+ * <p>The API allows for multiple activities to be specified at a time,
+ * but it is strongly recommended to just register one at a time,
+ * and to do so during the activity's {@link Activity#onCreate}. For example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setNdefPushMessageCallback(callback, this);
+ * }
+ * </pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the callback and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param callback callback, or null to disable
- * @param activity an activity in which NDEF push should be enabled to share an NDEF message
- * that's retrieved from the provided callback
- * @param activities optional additional activities that should also enable NDEF push using
- * the provided callback
+ * @param activity activity for which the NDEF message will be pushed
+ * @param activities optional additional activities, however we strongly recommend
+ * to only register one at a time, and to do so in that activity's
+ * {@link Activity#onCreate}
*/
public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
Activity ... activities) {
- if (activity == null) {
- throw new NullPointerException("activity cannot be null");
- }
- mNfcActivityManager.setNdefPushMessageCallback(activity, callback);
- for (Activity a : activities) {
- if (a == null) {
- throw new NullPointerException("activities cannot contain null");
+ int targetSdkVersion = getSdkVersion();
+ try {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callback);
+ for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
+ mNfcActivityManager.setNdefPushMessageCallback(a, callback);
+ }
+ } catch (IllegalStateException e) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ // Less strict on old applications - just log the error
+ Log.e(TAG, "Cannot call API with Activity that has already " +
+ "been destroyed", e);
+ } else {
+ // Prevent new applications from making this mistake, re-throw
+ throw(e);
}
- 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.
+ * Set a callback on successful Android Beam (TM).
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the callback can only occur when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>The API allows for multiple activities to be specified at a time,
+ * but it is strongly recommended to just register one at a time,
+ * and to do so during the activity's {@link Activity#onCreate}. For example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setOnNdefPushCompleteCallback(callback, this);
+ * }
+ * </pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the callback and the
+ * Activity object when it is destroyed if you follow this pattern.
*
- * <p>One or more activities must be specified.
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param callback callback, or null to disable
- * @param activity an activity to enable the callback (at least one is required)
- * @param activities zero or more additional activities to enable to callback
+ * @param activity activity for which the NDEF message will be pushed
+ * @param activities optional additional activities, however we strongly recommend
+ * to only register one at a time, and to do so in that activity's
+ * {@link Activity#onCreate}
*/
public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
Activity activity, Activity ... activities) {
- if (activity == null) {
- throw new NullPointerException("activity cannot be null");
- }
- mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
- for (Activity a : activities) {
- if (a == null) {
- throw new NullPointerException("activities cannot contain null");
+ int targetSdkVersion = getSdkVersion();
+ try {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
+ for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
+ mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
+ }
+ } catch (IllegalStateException e) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ // Less strict on old applications - just log the error
+ Log.e(TAG, "Cannot call API with Activity that has already " +
+ "been destroyed", e);
+ } else {
+ // Prevent new applications from making this mistake, re-throw
+ throw(e);
}
- mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
}
}
@@ -932,4 +1053,12 @@ public final class NfcAdapter {
throw new IllegalStateException("API cannot be called while activity is paused");
}
}
+
+ int getSdkVersion() {
+ if (mContext == null) {
+ return android.os.Build.VERSION_CODES.GINGERBREAD; // best guess
+ } else {
+ return mContext.getApplicationInfo().targetSdkVersion;
+ }
+ }
}
diff --git a/core/java/android/nfc/NfcFragment.java b/core/java/android/nfc/NfcFragment.java
deleted file mode 100644
index d6b15ad1bf5a..000000000000
--- a/core/java/android/nfc/NfcFragment.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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) {
- // We allow state loss at this point, because the state is only
- // lost when activity is being paused *AND* subsequently destroyed.
- // In that case, the app will setup foreground dispatch again anyway.
- manager.beginTransaction().remove(fragment).commitAllowingStateLoss();
- }
- }
-
- @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());
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (sNfcActivityManager != null) {
- sNfcActivityManager.onDestroy(getActivity());
- }
- }
-
-
-}