/* * 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.Application; import android.compat.annotation.UnsupportedAppUsage; import android.nfc.NfcAdapter.ReaderCallback; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; 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 {@link Application#registerActivityLifecycleCallbacks} to hook * into activity life-cycle events such as onPause() and onResume(). * * @hide */ public final class NfcActivityManager extends IAppCallback.Stub implements Application.ActivityLifecycleCallbacks { static final String TAG = NfcAdapter.TAG; static final Boolean DBG = false; @UnsupportedAppUsage final NfcAdapter mAdapter; // 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; Activity activity; NfcAdapter.ReaderCallback readerCallback = null; int readerModeFlags = 0; Bundle readerModeExtras = null; Binder token; int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; public NfcActivityState(Activity activity) { if (activity.isDestroyed()) { throw new IllegalStateException("activity is already destroyed"); } // Check if activity is resumed right now, as we will not // immediately get a callback for that. resumed = activity.isResumed(); this.activity = activity; this.token = new Binder(); registerApplication(activity.getApplication()); } public void destroy() { unregisterApplication(activity.getApplication()); resumed = false; activity = null; readerCallback = null; readerModeFlags = 0; readerModeExtras = null; token = null; mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; } @Override public String toString() { StringBuilder s = new StringBuilder("["); s.append(readerCallback); s.append("]"); return s.toString(); } } /** find activity state from mActivities */ synchronized NfcActivityState findActivityState(Activity activity) { for (NfcActivityState state : mActivities) { if (state.activity == activity) { return 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); } return state; } 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 } public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras) { boolean isResumed; Binder token; int pollTech, listenTech; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.readerCallback = callback; state.readerModeFlags = flags; state.readerModeExtras = extras; pollTech = state.mPollTech; listenTech = state.mListenTech; token = state.token; isResumed = state.resumed; } if (isResumed) { if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { throw new IllegalStateException( "Cannot be used when alternative DiscoveryTechnology is set"); } else { setReaderMode(token, flags, extras); } } } public void disableReaderMode(Activity activity) { boolean isResumed; Binder token; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.readerCallback = null; state.readerModeFlags = 0; state.readerModeExtras = null; token = state.token; isResumed = state.resumed; } if (isResumed) { setReaderMode(token, 0, null); } } public void setReaderMode(Binder token, int flags, Bundle extras) { if (DBG) Log.d(TAG, "Setting reader mode"); try { NfcAdapter.sService.setReaderMode(token, this, flags, extras); } catch (RemoteException e) { mAdapter.attemptDeadServiceRecovery(e); } } /** * Request or unrequest NFC service callbacks. * Makes IPC call - do not hold lock. */ void requestNfcServiceCallback() { try { NfcAdapter.sService.setAppCallback(this); } catch (RemoteException e) { mAdapter.attemptDeadServiceRecovery(e); } } void verifyNfcPermission() { try { NfcAdapter.sService.verifyNfcPermission(); } catch (RemoteException e) { mAdapter.attemptDeadServiceRecovery(e); } } @Override public void onTagDiscovered(Tag tag) throws RemoteException { NfcAdapter.ReaderCallback callback; synchronized (NfcActivityManager.this) { NfcActivityState state = findResumedActivityState(); if (state == null) return; callback = state.readerCallback; } // Make callback without lock if (callback != null) { callback.onTagDiscovered(tag); } } /** 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) { int readerModeFlags = 0; Bundle readerModeExtras = null; Binder token; int pollTech; int listenTech; synchronized (NfcActivityManager.this) { NfcActivityState state = findActivityState(activity); if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); if (state == null) return; state.resumed = true; token = state.token; readerModeFlags = state.readerModeFlags; readerModeExtras = state.readerModeExtras; pollTech = state.mPollTech; listenTech = state.mListenTech; } if (readerModeFlags != 0) { setReaderMode(token, readerModeFlags, readerModeExtras); } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { changeDiscoveryTech(token, pollTech, listenTech); } requestNfcServiceCallback(); } /** Callback from Activity life-cycle, on main thread */ @Override public void onActivityPaused(Activity activity) { boolean readerModeFlagsSet; Binder token; int pollTech; int listenTech; synchronized (NfcActivityManager.this) { NfcActivityState state = findActivityState(activity); if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); if (state == null) return; state.resumed = false; token = state.token; readerModeFlagsSet = state.readerModeFlags != 0; pollTech = state.mPollTech; listenTech = state.mListenTech; } if (readerModeFlagsSet) { // Restore default p2p modes setReaderMode(token, 0, null); } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH); } } /** 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); } } } /** setDiscoveryTechnology() implementation */ public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) { boolean isResumed; Binder token; boolean readerModeFlagsSet; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); readerModeFlagsSet = state.readerModeFlags != 0; state.mListenTech = listenTech; state.mPollTech = pollTech; token = state.token; isResumed = state.resumed; } if (!readerModeFlagsSet && isResumed) { changeDiscoveryTech(token, pollTech, listenTech); } else if (readerModeFlagsSet) { throw new IllegalStateException("Cannot be used when the Reader Mode is enabled"); } } /** resetDiscoveryTechnology() implementation */ public void resetDiscoveryTech(Activity activity) { boolean isResumed; Binder token; boolean readerModeFlagsSet; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; token = state.token; isResumed = state.resumed; } if (isResumed) { changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH); } } private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) { try { NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech); } catch (RemoteException e) { mAdapter.attemptDeadServiceRecovery(e); } } }