417 lines
14 KiB
Java
417 lines
14 KiB
Java
/*
|
|
* 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
|
|
|
|
/**
|
|
* 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<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, "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);
|
|
}
|
|
}
|
|
|
|
}
|