/*
 * 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.content.ContentProvider;
import android.content.Intent;
import android.net.Uri;
import android.nfc.NfcAdapter.ReaderCallback;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
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 Application.registerActivityLifecycleCallbacks to hook into activity life-cycle events such as onPause() and onResume().

@hide
/** * 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; 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
NFC State associated with an Application.
/** * 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 Activity
/** * NFC state associated with an {@link Activity} */
class NfcActivityState { boolean resumed = false; Activity activity; NdefMessage ndefMessage = null; // static NDEF message NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null; NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null; NfcAdapter.CreateBeamUrisCallback uriCallback = null; Uri[] uris = null; int flags = 0; int readerModeFlags = 0; NfcAdapter.ReaderCallback readerCallback = null; Bundle readerModeExtras = null; Binder token; public NfcActivityState(Activity activity) { if (activity.getWindow().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; ndefMessage = null; ndefMessageCallback = null; onNdefPushCompleteCallback = null; uriCallback = null; uris = null; readerModeFlags = 0; token = null; } @Override public String toString() { StringBuilder s = new StringBuilder("[").append(" "); s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); s.append(uriCallback).append(" "); if (uris != null) { for (Uri uri : uris) { s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); } } return s.toString(); } }
find activity state from mActivities
/** 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
/** 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<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; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.readerCallback = callback; state.readerModeFlags = flags; state.readerModeExtras = extras; token = state.token; isResumed = state.resumed; } if (isResumed) { 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); } } public void setNdefPushContentUri(Activity activity, Uri[] uris) { boolean isResumed; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.uris = uris; isResumed = state.resumed; } if (isResumed) { // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); } else { // Crash API calls early in case NFC permission is missing verifyNfcPermission(); } } public void setNdefPushContentUriCallback(Activity activity, NfcAdapter.CreateBeamUrisCallback callback) { boolean isResumed; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.uriCallback = callback; isResumed = state.resumed; } if (isResumed) { // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); } else { // Crash API calls early in case NFC permission is missing verifyNfcPermission(); } } public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) { boolean isResumed; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.ndefMessage = message; state.flags = flags; isResumed = state.resumed; } if (isResumed) { // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); } else { // Crash API calls early in case NFC permission is missing verifyNfcPermission(); } } public void setNdefPushMessageCallback(Activity activity, NfcAdapter.CreateNdefMessageCallback callback, int flags) { boolean isResumed; synchronized (NfcActivityManager.this) { NfcActivityState state = getActivityState(activity); state.ndefMessageCallback = callback; state.flags = flags; isResumed = state.resumed; } if (isResumed) { // requestNfcServiceCallback() verifies permission also requestNfcServiceCallback(); } else { // Crash API calls early in case NFC permission is missing verifyNfcPermission(); } } 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() verifies permission also requestNfcServiceCallback(); } else { // Crash API calls early in case NFC permission is missing verifyNfcPermission(); } }
Request or unrequest NFC service callbacks. Makes IPC call - do not hold lock.
/** * 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); } }
Callback from NFC service, usually on binder thread
/** Callback from NFC service, usually on binder thread */
@Override public BeamShareData createBeamShareData(byte peerLlcpVersion) { NfcAdapter.CreateNdefMessageCallback ndefCallback; NfcAdapter.CreateBeamUrisCallback urisCallback; NdefMessage message; Activity activity; Uri[] uris; int flags; NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion); synchronized (NfcActivityManager.this) { NfcActivityState state = findResumedActivityState(); if (state == null) return null; ndefCallback = state.ndefMessageCallback; urisCallback = state.uriCallback; message = state.ndefMessage; uris = state.uris; flags = state.flags; activity = state.activity; } final long ident = Binder.clearCallingIdentity(); try { // Make callbacks without lock if (ndefCallback != null) { message = ndefCallback.createNdefMessage(event); } if (urisCallback != null) { uris = urisCallback.createBeamUris(event); if (uris != null) { ArrayList<Uri> validUris = new ArrayList<Uri>(); for (Uri uri : uris) { if (uri == null) { Log.e(TAG, "Uri not allowed to be null."); continue; } String scheme = uri.getScheme(); if (scheme == null || (!scheme.equalsIgnoreCase("file") && !scheme.equalsIgnoreCase("content"))) { Log.e(TAG, "Uri needs to have " + "either scheme file or scheme content"); continue; } uri = ContentProvider.maybeAddUserId(uri, activity.getUserId()); validUris.add(uri); } uris = validUris.toArray(new Uri[validUris.size()]); } } if (uris != null && uris.length > 0) { for (Uri uri : uris) { // Grant the NFC process permission to read these URIs activity.grantUriPermission("com.android.nfc", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } } finally { Binder.restoreCallingIdentity(ident); } return new BeamShareData(message, uris, activity.getUser(), flags); }
Callback from NFC service, usually on binder thread
/** Callback from NFC service, usually on binder thread */
@Override public void onNdefPushComplete(byte peerLlcpVersion) { NfcAdapter.OnNdefPushCompleteCallback callback; synchronized (NfcActivityManager.this) { NfcActivityState state = findResumedActivityState(); if (state == null) return; callback = state.onNdefPushCompleteCallback; } NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion); // Make callback without lock if (callback != null) { callback.onNdefPushComplete(event); } } @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
/** 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
/** Callback from Activity life-cycle, on main thread */
@Override public void onActivityStarted(Activity activity) { /* NO-OP */ }
Callback from Activity life-cycle, on main thread
/** Callback from Activity life-cycle, on main thread */
@Override public void onActivityResumed(Activity activity) { int readerModeFlags = 0; Bundle readerModeExtras = null; Binder token; 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; } if (readerModeFlags != 0) { setReaderMode(token, readerModeFlags, readerModeExtras); } requestNfcServiceCallback(); }
Callback from Activity life-cycle, on main thread
/** Callback from Activity life-cycle, on main thread */
@Override public void onActivityPaused(Activity activity) { boolean readerModeFlagsSet; Binder token; 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; } if (readerModeFlagsSet) { // Restore default p2p modes setReaderMode(token, 0, null); } }
Callback from Activity life-cycle, on main thread
/** Callback from Activity life-cycle, on main thread */
@Override public void onActivityStopped(Activity activity) { /* NO-OP */ }
Callback from Activity life-cycle, on main thread
/** 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
/** 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); } } } }