From c84c89a6cacaf16c1ba41f57cc1aecdb150e85f9 Mon Sep 17 00:00:00 2001 From: Nick Pelly Date: Mon, 22 Aug 2011 22:27:11 -0700 Subject: [PATCH] Improve NDEF push API Introduce setNdefPushMessage() setNdefPushMessageCallback() setNdefPushCompleteCallback() Deprecate public API enableForegroundNdefPush() disableForegroundNdefPush() Hide & Deprecate staged (public but never released) API enableForegroundNdefPushCallback() The new API's do not require the application to explicitly call enable()/disable() in onPause()/onResume(), we use a Fragment behind the scenes to manager this automatically. NDEF Push can be disabled by using a null parameter, so each enable()/disable() pair is collapsed to a single set() call. Application code should now look something like: public void onCreate() { NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); if (adapter != null) { // check that NFC is available on this device adapter.setNdefPushMessage(myNdefMessage, this); } } And that's it - no need to explicitly hook into onPause() and onResume() events. Also - introduce a generic NfcEvent class that is provided as a parameter on all NFC callbacks. Right now it just provides the NfcAdapter, but using the wrapper classes allows us to add more fields later without changing the callback signature. (i'm thinking Bluetooth). Change-Id: I371dcb026b535b8199225c1262eca64ce644458a --- CleanSpec.mk | 1 + api/current.txt | 21 +- core/java/android/nfc/INdefPushCallback.aidl | 4 +- core/java/android/nfc/INfcAdapter.aidl | 21 +- core/java/android/nfc/NfcActivityManager.java | 217 ++++++++ core/java/android/nfc/NfcAdapter.java | 473 ++++++++++-------- core/java/android/nfc/NfcEvent.java | 44 ++ core/java/android/nfc/NfcFragment.java | 83 +++ core/java/android/nfc/NfcManager.java | 2 +- 9 files changed, 637 insertions(+), 229 deletions(-) create mode 100644 core/java/android/nfc/NfcActivityManager.java create mode 100644 core/java/android/nfc/NfcEvent.java create mode 100644 core/java/android/nfc/NfcFragment.java diff --git a/CleanSpec.mk b/CleanSpec.mk index 0dba18b4256a3..e6c4183ed3b1c 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -107,6 +107,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/R/com/android/systemui/R. $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc/) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/api/current.txt b/api/current.txt index 01b07a6a30375..a086959a391f8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12412,13 +12412,15 @@ package android.nfc { public final class NfcAdapter { method public void disableForegroundDispatch(android.app.Activity); - method public void disableForegroundNdefPush(android.app.Activity); + method public deprecated void disableForegroundNdefPush(android.app.Activity); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.String[][]); - method public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage); - method public void enableForegroundNdefPush(android.app.Activity, android.nfc.NfcAdapter.NdefPushCallback); + method public deprecated void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method public static deprecated android.nfc.NfcAdapter getDefaultAdapter(); method public boolean isEnabled(); + method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity...); + method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity...); + method public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity...); field public static final java.lang.String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED"; field public static final java.lang.String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED"; field public static final java.lang.String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED"; @@ -12427,9 +12429,16 @@ package android.nfc { field public static final java.lang.String EXTRA_TAG = "android.nfc.extra.TAG"; } - public static abstract interface NfcAdapter.NdefPushCallback { - method public abstract android.nfc.NdefMessage createMessage(); - method public abstract void onMessagePushed(); + public static abstract interface NfcAdapter.CreateNdefMessageCallback { + method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent); + } + + public static abstract interface NfcAdapter.OnNdefPushCompleteCallback { + method public abstract void onNdefPushComplete(android.nfc.NfcEvent); + } + + public final class NfcEvent { + field public final android.nfc.NfcAdapter nfcAdapter; } public final class NfcManager { diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl index 80ba2ed09fc5b..e60a5b0a4031d 100644 --- a/core/java/android/nfc/INdefPushCallback.aidl +++ b/core/java/android/nfc/INdefPushCallback.aidl @@ -23,6 +23,6 @@ import android.nfc.NdefMessage; */ interface INdefPushCallback { - NdefMessage onConnect(); - void onMessagePushed(); + NdefMessage createMessage(); + void onNdefPushComplete(); } diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 4fc248f6c3c02..016af58da74a9 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -23,8 +23,8 @@ import android.nfc.NdefMessage; import android.nfc.Tag; import android.nfc.TechListParcel; import android.nfc.INdefPushCallback; -import android.nfc.INfcTag; import android.nfc.INfcAdapterExtras; +import android.nfc.INfcTag; /** * @hide @@ -34,19 +34,14 @@ interface INfcAdapter INfcTag getNfcTagInterface(); INfcAdapterExtras getNfcAdapterExtrasInterface(); - // NfcAdapter-class related methods int getState(); - void enableForegroundDispatch(in ComponentName activity, in PendingIntent intent, - in IntentFilter[] filters, in TechListParcel techLists); - void disableForegroundDispatch(in ComponentName activity); - void enableForegroundNdefPush(in ComponentName activity, in NdefMessage msg); - void enableForegroundNdefPushWithCallback(in ComponentName activity, in INdefPushCallback callback); - void disableForegroundNdefPush(in ComponentName activity); - - // Non-public methods boolean disable(); boolean enable(); - boolean enableZeroClick(); - boolean disableZeroClick(); - boolean isZeroClickEnabled(); + boolean enableNdefPush(); + boolean disableNdefPush(); + boolean isNdefPushEnabled(); + + void setForegroundDispatch(in PendingIntent intent, + in IntentFilter[] filters, in TechListParcel techLists); + void setForegroundNdefPush(in NdefMessage msg, in INdefPushCallback callback); } diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java new file mode 100644 index 0000000000000..3cc6820356128 --- /dev/null +++ b/core/java/android/nfc/NfcActivityManager.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +import android.app.Activity; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; + +/** + * Manages NFC API's that are coupled to the life-cycle of an Activity. + * + *

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. + * + * @hide + */ +public final class NfcActivityManager extends INdefPushCallback.Stub { + static final String TAG = NfcAdapter.TAG; + static final Boolean DBG = false; + + final NfcAdapter mAdapter; + final HashMap mNfcState; // contents protected by this + final NfcEvent mDefaultEvent; // can re-use one NfcEvent because it just contains adapter + + /** + * NFC state associated with an {@link Activity} + */ + class NfcActivityState { + boolean resumed = false; // is the activity resumed + NdefMessage ndefMessage; + NfcAdapter.CreateNdefMessageCallback ndefMessageCallback; + NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback; + @Override + public String toString() { + StringBuilder s = new StringBuilder("[").append(resumed).append(" "); + s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); + s.append(onNdefPushCompleteCallback).append("]"); + return s.toString(); + } + } + + public NfcActivityManager(NfcAdapter adapter) { + mAdapter = adapter; + mNfcState = new HashMap(); + mDefaultEvent = new NfcEvent(mAdapter); + } + + /** + * onResume hook from fragment attached to activity + */ + public synchronized void onResume(Activity activity) { + NfcActivityState state = mNfcState.get(activity); + if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); + if (state != null) { + state.resumed = true; + updateNfcService(state); + } + } + + /** + * onPause hook from fragment attached to activity + */ + public synchronized void onPause(Activity activity) { + NfcActivityState state = mNfcState.get(activity); + if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); + if (state != null) { + state.resumed = false; + updateNfcService(state); + } + } + + public synchronized void setNdefPushMessage(Activity activity, NdefMessage message) { + NfcActivityState state = getOrCreateState(activity, message != null); + if (state == null || state.ndefMessage == message) { + return; // nothing more to do; + } + state.ndefMessage = message; + if (message == null) { + maybeRemoveState(activity, state); + } + if (state.resumed) { + updateNfcService(state); + } + } + + public synchronized void setNdefPushMessageCallback(Activity activity, + NfcAdapter.CreateNdefMessageCallback callback) { + NfcActivityState state = getOrCreateState(activity, callback != null); + if (state == null || state.ndefMessageCallback == callback) { + return; // nothing more to do; + } + state.ndefMessageCallback = callback; + if (callback == null) { + maybeRemoveState(activity, state); + } + if (state.resumed) { + updateNfcService(state); + } + } + + public synchronized void setOnNdefPushCompleteCallback(Activity activity, + NfcAdapter.OnNdefPushCompleteCallback callback) { + NfcActivityState state = getOrCreateState(activity, callback != null); + if (state == null || state.onNdefPushCompleteCallback == callback) { + return; // nothing more to do; + } + state.onNdefPushCompleteCallback = callback; + if (callback == null) { + maybeRemoveState(activity, state); + } + if (state.resumed) { + updateNfcService(state); + } + } + + /** + * Get the NfcActivityState for the specified Activity. + * If create is true, then create it if it doesn't already exist, + * and ensure the NFC fragment is attached to the activity. + */ + synchronized NfcActivityState getOrCreateState(Activity activity, boolean create) { + if (DBG) Log.d(TAG, "getOrCreateState " + activity + " " + create); + NfcActivityState state = mNfcState.get(activity); + if (state == null && create) { + state = new NfcActivityState(); + mNfcState.put(activity, state); + NfcFragment.attach(activity); + } + return state; + } + + /** + * If the NfcActivityState is empty then remove it, and + * detach it from the Activity. + */ + synchronized void maybeRemoveState(Activity activity, NfcActivityState state) { + if (state.ndefMessage == null && state.ndefMessageCallback == null && + state.onNdefPushCompleteCallback == null) { + mNfcState.remove(activity); + } + } + + /** + * Register NfcActivityState with the NFC service. + */ + synchronized void updateNfcService(NfcActivityState state) { + boolean serviceCallbackNeeded = state.ndefMessageCallback != null || + state.onNdefPushCompleteCallback != null; + + try { + NfcAdapter.sService.setForegroundNdefPush(state.resumed ? state.ndefMessage : null, + state.resumed && serviceCallbackNeeded ? this : null); + } catch (RemoteException e) { + mAdapter.attemptDeadServiceRecovery(e); + } + } + + /** + * Callback from NFC service + */ + @Override + public NdefMessage createMessage() { + NfcAdapter.CreateNdefMessageCallback callback = null; + synchronized (NfcActivityManager.this) { + for (NfcActivityState state : mNfcState.values()) { + if (state.resumed) { + callback = state.ndefMessageCallback; + } + } + } + + // drop lock before making callback + if (callback != null) { + return callback.createNdefMessage(mDefaultEvent); + } + return null; + } + + /** + * Callback from NFC service + */ + @Override + public void onNdefPushComplete() { + NfcAdapter.OnNdefPushCompleteCallback callback = null; + synchronized (NfcActivityManager.this) { + for (NfcActivityState state : mNfcState.values()) { + if (state.resumed) { + callback = state.onNdefPushCompleteCallback; + } + } + } + + // drop lock before making callback + if (callback != null) { + callback.onNdefPushComplete(mDefaultEvent); + } + } +} diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 6a904aeb0d4b5..d58b249bb8282 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -42,7 +42,7 @@ import android.util.Log; * adapter for this Android device. */ public final class NfcAdapter { - private static final String TAG = "NFC"; + static final String TAG = "NFC"; /** * Intent to start an activity when a tag with NDEF payload is discovered. @@ -187,106 +187,67 @@ public final class NfcAdapter { /** @hide */ public static final int STATE_TURNING_OFF = 4; - /** - * LLCP link status: The LLCP link is activated. - * @hide - */ - public static final int LLCP_LINK_STATE_ACTIVATED = 0; - - /** - * LLCP link status: The LLCP link is deactivated. - * @hide - */ - public static final int LLCP_LINK_STATE_DEACTIVATED = 1; - - /** - * Broadcast Action: the LLCP link state changed. - *

- * Always contains the extra field - * {@link android.nfc.NfcAdapter#EXTRA_LLCP_LINK_STATE_CHANGED}. - * @hide - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LLCP_LINK_STATE_CHANGED = - "android.nfc.action.LLCP_LINK_STATE_CHANGED"; - - /** - * Used as int extra field in - * {@link android.nfc.NfcAdapter#ACTION_LLCP_LINK_STATE_CHANGED}. - *

- * It contains the new state of the LLCP link. - * @hide - */ - public static final String EXTRA_LLCP_LINK_STATE_CHANGED = "android.nfc.extra.LLCP_LINK_STATE"; - - /** - * Tag Reader Discovery mode - * @hide - */ - private static final int DISCOVERY_MODE_TAG_READER = 0; - - /** - * NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an - * NFC-IP1 communication. Implementations should not assume that the - * controller will end up behaving as an NFC-IP1 target or initiator and - * should handle both cases, depending on the type of the remote peer type. - * @hide - */ - private static final int DISCOVERY_MODE_NFCIP1 = 1; - - /** - * Card Emulation mode Enables the manager to act as an NFC tag. Provided - * that a Secure Element (an UICC for instance) is connected to the NFC - * controller through its SWP interface, it can be exposed to the outside - * NFC world and be addressed by external readers the same way they would - * with a tag. - *

- * Which Secure Element is exposed is implementation-dependent. - * - * @hide - */ - private static final int DISCOVERY_MODE_CARD_EMULATION = 2; - - /** - * Callback passed into {@link #enableForegroundNdefPush(Activity,NdefPushCallback)}. This - */ - public interface NdefPushCallback { - /** - * Called when a P2P connection is created. - */ - NdefMessage createMessage(); - /** - * Called when the message is pushed. - */ - void onMessagePushed(); - } - - private static class NdefPushCallbackWrapper extends INdefPushCallback.Stub { - private NdefPushCallback mCallback; - - public NdefPushCallbackWrapper(NdefPushCallback callback) { - mCallback = callback; - } - - @Override - public NdefMessage onConnect() { - return mCallback.createMessage(); - } - - @Override - public void onMessagePushed() { - mCallback.onMessagePushed(); - } - } - // Guarded by NfcAdapter.class - private static boolean sIsInitialized = false; + static boolean sIsInitialized = false; // Final after first constructor, except for // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort // recovery - private static INfcAdapter sService; - private static INfcTag sTagService; + static INfcAdapter sService; + static INfcTag sTagService; + + /** + * NfcAdapter is currently a singleton, and does not require a context. + * However all the public API's are future-proofed to require a context. + * If we start using that then we'll need to keep a HashMap of + * Context.getApplicationContext() -> NfcAdapter, such that NfcAdapter + * is a singleton within each application context. + */ + static NfcAdapter sSingleton; // protected by NfcAdapter.class + + final NfcActivityManager mNfcActivityManager; + + /** + * @see {@link #setNdefPushMessageCallback} + */ + public interface OnNdefPushCompleteCallback { + /** + * Called on successful NDEF push. + * + *

This callback is usually made on a binder thread (not the UI thread). + * + * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set + * @see {@link #setNdefPushMessageCallback} + */ + public void onNdefPushComplete(NfcEvent event); + } + + /** + * @see {@link #setCeateNdefMessageCallback} + */ + public interface CreateNdefMessageCallback { + /** + * Called to provide a {@link NdefMessage} to push. + * + *

This callback is usually made on a binder thread (not the UI thread). + * + *

Called when this device is in range of another device + * that might support NDEF push. It allows the application to + * create the NDEF message only when it is required. + * + *

NDEF push cannot occur until this method returns, so do not + * block for too long. + * + *

The Android operating system will usually show a system UI + * on top of your activity during this time, so do not try to request + * input from the user to complete the callback, or provide custom NDEF + * push UI. The user probably will not see it. + * + * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set + * @return NDEF message to push, or null to not provide a message + */ + public NdefMessage createNdefMessage(NfcEvent event); + } /** * Helper to check if this device has FEATURE_NFC, but without using @@ -308,29 +269,36 @@ public final class NfcAdapter { } } - private static synchronized INfcAdapter setupService() { + /** + * Returns the singleton, or throws if NFC is not available. + */ + static synchronized NfcAdapter getSingleton() { if (!sIsInitialized) { sIsInitialized = true; /* is this device meant to have NFC */ if (!hasNfcFeature()) { Log.v(TAG, "this device does not have NFC support"); - return null; + throw new UnsupportedOperationException(); } sService = getServiceInterface(); if (sService == null) { Log.e(TAG, "could not retrieve NFC service"); - return null; + throw new UnsupportedOperationException(); } try { sTagService = sService.getNfcTagInterface(); } catch (RemoteException e) { Log.e(TAG, "could not retrieve NFC Tag service"); - return null; + throw new UnsupportedOperationException(); } + sSingleton = new NfcAdapter(); } - return sService; + if (sSingleton == null) { + throw new UnsupportedOperationException(); + } + return sSingleton; } /** get handle to NFC service interface */ @@ -376,13 +344,14 @@ public final class NfcAdapter { public static NfcAdapter getDefaultAdapter() { Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " + "NfcAdapter.getDefaultAdapter(Context) instead", new Exception()); - return new NfcAdapter(null); + return getSingleton(); } - /*package*/ NfcAdapter(Context context) { - if (setupService() == null) { - throw new UnsupportedOperationException(); - } + /** + * Does not currently need a context. + */ + NfcAdapter() { + mNfcActivityManager = new NfcActivityManager(this); } /** @@ -523,6 +492,87 @@ public final class NfcAdapter { } } + /** + * Set the {@link NdefMessage} to push over NFC during the specified activities. + * + *

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

Only one NDEF message can be pushed by the currently resumed activity. + * If both {@link #setNdefPushMessage} and + * {@link #setNdefPushMessageCallback} are set then + * the callback will take priority. + * + *

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

Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param message NDEF message to push over NFC, or null to disable + * @param activities one or more {@link Activity} to enable for NDEF push + */ + public void setNdefPushMessage(NdefMessage message, Activity ... activities) { + if (activities.length == 0) { + throw new NullPointerException("Must specificy one or more activities"); + } + for (Activity a : activities) { + mNfcActivityManager.setNdefPushMessage(a, message); + } + } + + /** + * Set the callback to create a {@link NdefMessage} to push over NFC. + * + *

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

Only one NDEF message can be pushed by the currently resumed activity. + * If both {@link #setNdefPushMessage} and + * {@link #setNdefPushMessageCallback} are set then + * the callback will take priority. + * + *

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

Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param callback callback, or null to disable + * @param activities one or more {@link Activity} to enable for NDEF push + */ + public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, + Activity ... activities) { + if (activities.length == 0) { + throw new NullPointerException("Must specificy one or more activities"); + } + for (Activity a : activities) { + mNfcActivityManager.setNdefPushMessageCallback(a, callback); + } + } + + /** + * Set the callback on a successful NDEF push over NFC. + * + *

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

Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param callback callback, or null to disable + * @param activities one or more {@link Activity} to enable the callback + */ + public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback, + Activity ... activities) { + if (activities.length == 0) { + throw new NullPointerException("Must specificy one or more activities"); + } + for (Activity a : activities) { + mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback); + } + } + /** * Enable foreground dispatch to the given Activity. * @@ -562,7 +612,7 @@ public final class NfcAdapter { throw new NullPointerException(); } if (!activity.isResumed()) { - throw new IllegalStateException("Foregorund dispatching can only be enabled " + + throw new IllegalStateException("Foreground dispatch can only be enabled " + "when your activity is resumed"); } try { @@ -572,8 +622,7 @@ public final class NfcAdapter { } ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, mForegroundDispatchListener); - sService.enableForegroundDispatch(activity.getComponentName(), intent, filters, - parcel); + sService.setForegroundDispatch(intent, filters, parcel); } catch (RemoteException e) { attemptDeadServiceRecovery(e); } @@ -608,9 +657,9 @@ public final class NfcAdapter { void disableForegroundDispatchInternal(Activity activity, boolean force) { try { - sService.disableForegroundDispatch(activity.getComponentName()); + sService.setForegroundDispatch(null, null, null); if (!force && !activity.isResumed()) { - throw new IllegalStateException("You must disable forgeground dispatching " + + throw new IllegalStateException("You must disable foreground dispatching " + "while your activity is still resumed"); } } catch (RemoteException e) { @@ -619,78 +668,38 @@ public final class NfcAdapter { } /** - * Enable NDEF message push over P2P while this Activity is in the foreground. + * Enable NDEF message push over NFC while this Activity is in the foreground. * - *

For this to function properly the other NFC device being scanned must - * support the "com.android.npp" NDEF push protocol. Support for this - * protocol is currently optional for Android NFC devices. + *

You must explicitly call this method every time the activity is + * resumed, and you must call {@link #disableForegroundNdefPush} before + * your activity completes {@link Activity#onPause}. + * + *

Strongly recommend to use the new {@link #setNdefPushMessage} + * instead: it automatically hooks into your activity life-cycle, + * so you do not need to call enable/disable in your onResume/onPause. + * + *

For NDEF push to function properly the other NFC device must + * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or + * Android's "com.android.npp" (Ndef Push Protocol). This was optional + * on Gingerbread level Android NFC devices, but SNEP is mandatory on + * Ice-Cream-Sandwich and beyond. * *

This method must be called from the main thread. * - *

NOTE: While foreground NDEF push is active standard tag dispatch is disabled. - * Only the foreground activity may receive tag discovered dispatches via - * {@link #enableForegroundDispatch}. - * *

Requires the {@link android.Manifest.permission#NFC} permission. * - * @param activity the foreground Activity - * @param msg a NDEF Message to push over P2P - * @throws IllegalStateException if the Activity is not currently in the foreground - * @throws OperationNotSupportedException if this Android device does not support NDEF push + * @param activity foreground activity + * @param message a NDEF Message to push over NFC + * @throws IllegalStateException if the activity is not currently in the foreground + * @deprecated use {@link #setNdefPushMessage} instead */ - public void enableForegroundNdefPush(Activity activity, NdefMessage msg) { - if (activity == null || msg == null) { + @Deprecated + public void enableForegroundNdefPush(Activity activity, NdefMessage message) { + if (activity == null || message == null) { throw new NullPointerException(); } - if (!activity.isResumed()) { - throw new IllegalStateException("Foregorund NDEF push can only be enabled " + - "when your activity is resumed"); - } - try { - ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, - mForegroundNdefPushListener); - sService.enableForegroundNdefPush(activity.getComponentName(), msg); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - } - } - - /** - * Enable NDEF message push over P2P while this Activity is in the foreground. - * - *

For this to function properly the other NFC device being scanned must - * support the "com.android.npp" NDEF push protocol. Support for this - * protocol is currently optional for Android NFC devices. - * - *

This method must be called from the main thread. - * - *

NOTE: While foreground NDEF push is active standard tag dispatch is disabled. - * Only the foreground activity may receive tag discovered dispatches via - * {@link #enableForegroundDispatch}. - * - *

Requires the {@link android.Manifest.permission#NFC} permission. - * - * @param activity the foreground Activity - * @param callback is called on when the P2P connection is established - * @throws IllegalStateException if the Activity is not currently in the foreground - * @throws OperationNotSupportedException if this Android device does not support NDEF push - */ - public void enableForegroundNdefPush(Activity activity, NdefPushCallback callback) { - if (activity == null || callback == null) { - throw new NullPointerException(); - } - if (!activity.isResumed()) { - throw new IllegalStateException("Foregorund NDEF push can only be enabled " + - "when your activity is resumed"); - } - try { - ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, - mForegroundNdefPushListener); - sService.enableForegroundNdefPushWithCallback(activity.getComponentName(), - new NdefPushCallbackWrapper(callback)); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - } + enforceResumed(activity); + mNfcActivityManager.setNdefPushMessage(activity, message); } /** @@ -700,47 +709,91 @@ public final class NfcAdapter { * must call this method before its {@link Activity#onPause} callback * completes. * + *

Strongly recommend to use the new {@link #setNdefPushMessage} + * instead: it automatically hooks into your activity life-cycle, + * so you do not need to call enable/disable in your onResume/onPause. + * *

This method must be called from the main thread. * *

Requires the {@link android.Manifest.permission#NFC} permission. * * @param activity the Foreground activity * @throws IllegalStateException if the Activity has already been paused - * @throws OperationNotSupportedException if this Android device does not support NDEF push + * @deprecated use {@link #setNdefPushMessage} instead */ public void disableForegroundNdefPush(Activity activity) { - ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity, - mForegroundNdefPushListener); - disableForegroundNdefPushInternal(activity, false); + if (activity == null) { + throw new NullPointerException(); + } + enforceResumed(activity); + mNfcActivityManager.setNdefPushMessage(activity, null); + mNfcActivityManager.setNdefPushMessageCallback(activity, null); + mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null); } - OnActivityPausedListener mForegroundNdefPushListener = new OnActivityPausedListener() { + /** + * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated + * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback} + * @hide + */ + @Deprecated + public interface NdefPushCallback { + /** + * @deprecated use {@link CreateNdefMessageCallback} instead + */ + @Deprecated + NdefMessage createMessage(); + /** + * @deprecated use{@link OnNdefPushCompleteCallback} instead + */ + @Deprecated + void onMessagePushed(); + } + + /** + * TODO: Remove this + * Converts new callbacks to old callbacks. + */ + static final class LegacyCallbackWrapper implements CreateNdefMessageCallback, + OnNdefPushCompleteCallback { + final NdefPushCallback mLegacyCallback; + LegacyCallbackWrapper(NdefPushCallback legacyCallback) { + mLegacyCallback = legacyCallback; + } @Override - public void onPaused(Activity activity) { - disableForegroundNdefPushInternal(activity, true); + public void onNdefPushComplete(NfcEvent event) { + mLegacyCallback.onMessagePushed(); } - }; - - void disableForegroundNdefPushInternal(Activity activity, boolean force) { - try { - sService.disableForegroundNdefPush(activity.getComponentName()); - if (!force && !activity.isResumed()) { - throw new IllegalStateException("You must disable forgeground NDEF push " + - "while your activity is still resumed"); - } - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); + @Override + public NdefMessage createNdefMessage(NfcEvent event) { + return mLegacyCallback.createMessage(); } } /** - * Enable zero-click sharing. - * + * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated + * @deprecated use {@link #setNdefPushMessageCallback} instead * @hide */ - public boolean enableZeroClick() { + @Deprecated + public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) { + if (activity == null || callback == null) { + throw new NullPointerException(); + } + enforceResumed(activity); + LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback); + mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper); + mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper); + } + + /** + * Enable NDEF Push feature. + *

This API is for the Settings application. + * @hide + */ + public boolean enableNdefPush() { try { - return sService.enableZeroClick(); + return sService.enableNdefPush(); } catch (RemoteException e) { attemptDeadServiceRecovery(e); return false; @@ -748,13 +801,13 @@ public final class NfcAdapter { } /** - * Disable zero-click sharing. - * + * Disable NDEF Push feature. + *

This API is for the Settings application. * @hide */ - public boolean disableZeroClick() { + public boolean disableNdefPush() { try { - return sService.disableZeroClick(); + return sService.disableNdefPush(); } catch (RemoteException e) { attemptDeadServiceRecovery(e); return false; @@ -762,20 +815,20 @@ public final class NfcAdapter { } /** - * Return true if zero-click sharing feature is enabled. + * Return true if NDEF Push feature is enabled. *

This function can return true even if NFC is currently turned-off. - * This indicates that zero-click is not currently active, but it has + * This indicates that NDEF Push is not currently active, but it has * been requested by the user and will be active as soon as NFC is turned * on. - *

If you want to check if zero-click sharing is currently active, use - * {@link #isEnabled()} && {@link #isZeroClickEnabled()} + *

If you want to check if NDEF PUsh sharing is currently active, use + * {@link #isEnabled()} && {@link #isNdefPushEnabled()} * - * @return true if zero-click sharing is enabled + * @return true if NDEF Push feature is enabled * @hide */ - public boolean isZeroClickEnabled() { + public boolean isNdefPushEnabled() { try { - return sService.isZeroClickEnabled(); + return sService.isNdefPushEnabled(); } catch (RemoteException e) { attemptDeadServiceRecovery(e); return false; @@ -793,4 +846,10 @@ public final class NfcAdapter { return null; } } + + void enforceResumed(Activity activity) { + if (!activity.isResumed()) { + throw new IllegalStateException("API cannot be called while activity is paused"); + } + } } diff --git a/core/java/android/nfc/NfcEvent.java b/core/java/android/nfc/NfcEvent.java new file mode 100644 index 0000000000000..b00d8b755b039 --- /dev/null +++ b/core/java/android/nfc/NfcEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +/** + * Wraps information associated with any NFC event. + * + *

Immutable object, with direct access to the (final) fields. + * + *

An {@link NfcEvent} object is usually included in callbacks from + * {@link NfcAdapter}. Check the documentation of the callback to see + * which fields may be set. + * + *

This wrapper object is used (instead of parameters + * in the callback) because it allows new fields to be added without breaking + * API compatibility. + * + * @see {@link NfcAdapter.OnNdefPushCompleteCallback#onNdefPushComplete} + * @see {@link NfcAdapter.CreateNdefMessageCallback#createNdefMessage} + */ +public final class NfcEvent { + /** + * The {@link NfcAdapter} associated with the NFC event. + */ + public final NfcAdapter nfcAdapter; + + NfcEvent(NfcAdapter nfcAdapter) { + this.nfcAdapter = nfcAdapter; + } +} diff --git a/core/java/android/nfc/NfcFragment.java b/core/java/android/nfc/NfcFragment.java new file mode 100644 index 0000000000000..c48859b1c6e76 --- /dev/null +++ b/core/java/android/nfc/NfcFragment.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; + +/** + * Used by {@link NfcActivityManager} to attach to activity life-cycle. + * @hide + */ +public final class NfcFragment extends Fragment { + static final String FRAGMENT_TAG = "android.nfc.NfcFragment"; + + // only used on UI thread + static boolean sIsInitialized = false; + static NfcActivityManager sNfcActivityManager; + + /** + * Attach NfcFragment to an activity (if not already attached). + */ + public static void attach(Activity activity) { + FragmentManager manager = activity.getFragmentManager(); + if (manager.findFragmentByTag(FRAGMENT_TAG) == null) { + manager.beginTransaction().add(new NfcFragment(), FRAGMENT_TAG).commit(); + } + } + + /** + * Remove NfcFragment from activity. + */ + public static void remove(Activity activity) { + FragmentManager manager = activity.getFragmentManager(); + Fragment fragment = manager.findFragmentByTag(FRAGMENT_TAG); + if (fragment != null) { + manager.beginTransaction().remove(fragment).commit(); + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (!sIsInitialized) { + sIsInitialized = true; + NfcAdapter adapter = NfcAdapter.getDefaultAdapter( + activity.getApplicationContext()); + if (adapter != null) { + sNfcActivityManager = adapter.mNfcActivityManager; + } + } + } + + @Override + public void onResume() { + super.onResume(); + if (sNfcActivityManager != null) { + sNfcActivityManager.onResume(getActivity()); + } + } + + @Override + public void onPause() { + super.onPause(); + if (sNfcActivityManager != null) { + sNfcActivityManager.onPause(getActivity()); + } + } +} diff --git a/core/java/android/nfc/NfcManager.java b/core/java/android/nfc/NfcManager.java index 5fa6483510230..300ab45460e2c 100644 --- a/core/java/android/nfc/NfcManager.java +++ b/core/java/android/nfc/NfcManager.java @@ -40,7 +40,7 @@ public final class NfcManager { public NfcManager(Context context) { NfcAdapter adapter; try { - adapter = new NfcAdapter(context); + adapter = NfcAdapter.getSingleton(); } catch (UnsupportedOperationException e) { adapter = null; }