/* * 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 /** * @hide */ public NfcApplicationState findAppState(Application app) { for (NfcApplicationState appState : mApps) { if (appState.app == app) { return appState; } } return null; } /** * @hide */ public void registerApplication(Application app) { NfcApplicationState appState = findAppState(app); if (appState == null) { appState = new NfcApplicationState(app, this); mApps.add(appState); } appState.register(); } /** * @hide */ public void unregisterApplication(Application app) { NfcApplicationState appState = findAppState(app); if (appState == null) { Log.e(TAG, "unregisterApplication: app was not registered " + app); return; } appState.unregister(); } /** find activity state from mActivities * * @hide */ public synchronized NfcActivityState findActivityState(Activity activity) { for (NfcActivityState state : mActivities) { if (state.activity == activity) { return state; } } return null; } /** find or create activity state from mActivities * * @hide */ public synchronized NfcActivityState getActivityState(Activity activity) { NfcActivityState state = findActivityState(activity); if (state == null) { state = new NfcActivityState(activity, this); mActivities.add(state); } return state; } /** * @hide */ public synchronized NfcActivityState findResumedActivityState() { for (NfcActivityState state : mActivities) { if (state.resumed) { return state; } } return null; } /** * @hide */ public 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, "setReaderModee"); NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode( token, this, flags, extras, mAdapter.getContext().getPackageName())); } /** * Request or unrequest NFC service callbacks. * Makes IPC call - do not hold lock. */ void requestNfcServiceCallback() { NfcAdapter.callService(() -> NfcAdapter.sService.setAppCallback(this)); } void verifyNfcPermission() { NfcAdapter.callService(() -> NfcAdapter.sService.verifyNfcPermission()); } @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, "onActivityResumed: " + 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, "onActivityPaused: " + 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, "onActivityDestroyed: " + 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) { NfcAdapter.callService( () -> NfcAdapter.sService.updateDiscoveryTechnology( token, pollTech, listenTech, mAdapter.getContext().getPackageName())); } }