diff options
| -rw-r--r-- | core/java/android/nfc/INfcAdapter.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/nfc/NfcActivityManager.java | 347 | ||||
| -rw-r--r-- | core/java/android/nfc/NfcAdapter.java | 241 | ||||
| -rw-r--r-- | core/java/android/nfc/NfcFragment.java | 96 |
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()); - } - } - - -} |