| /* | |
| * 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. | |
| * | |
| * <p>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<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one | |
| final List<NfcActivityState> 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<NfcActivityState>(); | |
| mApps = new ArrayList<NfcApplicationState>(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())); | |
| } | |
| } |