From 8ce7a27770735791b5c38e4128f4ab9cee86bc43 Mon Sep 17 00:00:00 2001 From: Nick Pelly Date: Wed, 21 Mar 2012 15:14:09 -0700 Subject: [PATCH] Use Application#registerLifecycleEvents() instead of a Fragment for Beam. Fragments don't work as desired if called after life-cycle events such as onDestory() or onSaveInstanceState(). The new approach doesn't work after onDestroy() either, but we can more easily detect this now. For pre-JB apps, we will log an error, and for JB and onwards we will throw. Update documentation to make these rules clear, and to encourage the use of a single Activity per API call, and to make the call in onCreate(). Bug: 5199662 Bug: 5994691 Bug: 6034901 Bug: 6125297 Change-Id: Ib0dde6abfa44cd56c7ddc13ba0ad0e83bbe30058 --- core/java/android/nfc/INfcAdapter.aidl | 2 +- core/java/android/nfc/NfcActivityManager.java | 377 +++++++++++------- core/java/android/nfc/NfcAdapter.java | 239 ++++++++--- core/java/android/nfc/NfcFragment.java | 96 ----- 4 files changed, 420 insertions(+), 294 deletions(-) delete mode 100644 core/java/android/nfc/NfcFragment.java diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 10da9ef1df97a..ddd00a4368b82 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 5fe58e902f826..2c730564fb7bb 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. * - *

Uses a fragment to hook into onPause() and onResume() of the host - * activities. - * - *

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. + *

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 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 mApps; // Application(s) that have NFC state. Usually one + final List mActivities; // Activities that have NFC state + + /** + * NFC State associated with an {@link Application}. + */ + 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); + } + } + } + + NfcApplicationState findAppState(Application app) { + for (NfcApplicationState appState : mApps) { + if (appState.app == app) { + return appState; + } + } + return null; + } + + void registerApplication(Application app) { + NfcApplicationState appState = findAppState(app); + if (appState == null) { + appState = new NfcApplicationState(app); + mApps.add(appState); + } + appState.register(); + } + + void unregisterApplication(Application app) { + NfcApplicationState appState = findAppState(app); + if (appState == null) { + Log.e(TAG, "app was not registered " + app); + return; + } + appState.unregister(); + } /** * NFC state associated with an {@link Activity} */ class NfcActivityState { - boolean resumed = false; // is the activity resumed - NdefMessage ndefMessage; - NfcAdapter.CreateNdefMessageCallback ndefMessageCallback; - NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback; + 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()); + } + public void destroy() { + unregisterApplication(activity.getApplication()); + resumed = false; + activity = null; + ndefMessage = null; + ndefMessageCallback = null; + onNdefPushCompleteCallback = null; + } @Override public String toString() { - StringBuilder s = new StringBuilder("[").append(resumed).append(" "); + StringBuilder s = new StringBuilder("[").append(" "); s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); s.append(onNdefPushCompleteCallback).append("]"); return s.toString(); } } - public NfcActivityManager(NfcAdapter adapter) { - mAdapter = adapter; - mNfcState = new WeakHashMap(); - mDefaultEvent = new NfcEvent(mAdapter); + /** find activity state from mActivities */ + synchronized NfcActivityState findActivityState(Activity activity) { + for (NfcActivityState state : mActivities) { + if (state.activity == activity) { + return state; + } + } + 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); - } - } - - /** - * 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); - } - } - - /** - * onDestroy hook from fragment attached to 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; - } - 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); + /** 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); } 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); + synchronized NfcActivityState findResumedActivityState() { + for (NfcActivityState state : mActivities) { + if (state.resumed) { + return state; + } + } + return null; + } + + synchronized void destroyActivityState(Activity activity) { + NfcActivityState activityState = findActivityState(activity); + if (activityState != null) { + activityState.destroy(); + mActivities.remove(activityState); + } + } + + public NfcActivityManager(NfcAdapter adapter) { + mAdapter = adapter; + mActivities = new LinkedList(); + mApps = new ArrayList(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 (isResumed) { + requestNfcServiceCallback(true); + } + } + + 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); + } + } + + 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 23f96e39cb1e2..b7a7bd5dad5ba 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). * - *

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. + *

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. * *

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. * - *

Pass a null NDEF message to disable foreground NDEF push in the - * specified activities. + *

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. * - *

At least one activity must be specified, and usually only one is necessary. + *

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. + * + *

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: + *

+     * 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);
+     * }
+     * 
+ * 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. + * + *

If your Activity wants to dynamically generate an NDEF message, + * then set a callback using {@link #setNdefPushMessageCallback} instead + * of a static message. + * + *

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}. * *

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). * - *

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. + *

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. * *

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. * - *

Pass a null callback to disable the callback in the - * specified activities. + *

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. * - *

At least one activity must be specified, and usually only one is necessary. + *

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. + * + *

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: + *

+     * 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);
+     * }
+     * 
+ * 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. + * + *

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}. * *

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. + * Set a callback on successful Android Beam (TM). * - *

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. + *

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. * - *

One or more activities must be specified. + *

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: + *

+     * 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);
+     * }
+     * 
+ * 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. + * + *

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}. * *

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 d6b15ad1bf5a4..0000000000000 --- 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()); - } - } - - -}