1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.nfc;
18 
19 import android.app.Activity;
20 import android.app.Application;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ContentProvider;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.nfc.NfcAdapter.ReaderCallback;
26 import android.os.Binder;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.util.Log;
30 
31 import java.util.ArrayList;
32 import java.util.LinkedList;
33 import java.util.List;
34 
35 /**
36  * Manages NFC API's that are coupled to the life-cycle of an Activity.
37  *
38  * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
39  * into activity life-cycle events such as onPause() and onResume().
40  *
41  * @hide
42  */
43 public final class NfcActivityManager extends IAppCallback.Stub
44         implements Application.ActivityLifecycleCallbacks {
45     static final String TAG = NfcAdapter.TAG;
46     static final Boolean DBG = false;
47 
48     @UnsupportedAppUsage
49     final NfcAdapter mAdapter;
50 
51     // All objects in the lists are protected by this
52     final List<NfcApplicationState> mApps;  // Application(s) that have NFC state. Usually one
53     final List<NfcActivityState> mActivities;  // Activities that have NFC state
54 
55     /**
56      * NFC State associated with an {@link Application}.
57      */
58     class NfcApplicationState {
59         int refCount = 0;
60         final Application app;
NfcApplicationState(Application app)61         public NfcApplicationState(Application app) {
62             this.app = app;
63         }
register()64         public void register() {
65             refCount++;
66             if (refCount == 1) {
67                 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
68             }
69         }
unregister()70         public void unregister() {
71             refCount--;
72             if (refCount == 0) {
73                 this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
74             } else if (refCount < 0) {
75                 Log.e(TAG, "-ve refcount for " + app);
76             }
77         }
78     }
79 
findAppState(Application app)80     NfcApplicationState findAppState(Application app) {
81         for (NfcApplicationState appState : mApps) {
82             if (appState.app == app) {
83                 return appState;
84             }
85         }
86         return null;
87     }
88 
registerApplication(Application app)89     void registerApplication(Application app) {
90         NfcApplicationState appState = findAppState(app);
91         if (appState == null) {
92             appState = new NfcApplicationState(app);
93             mApps.add(appState);
94         }
95         appState.register();
96     }
97 
unregisterApplication(Application app)98     void unregisterApplication(Application app) {
99         NfcApplicationState appState = findAppState(app);
100         if (appState == null) {
101             Log.e(TAG, "app was not registered " + app);
102             return;
103         }
104         appState.unregister();
105     }
106 
107     /**
108      * NFC state associated with an {@link Activity}
109      */
110     class NfcActivityState {
111         boolean resumed = false;
112         Activity activity;
113         NdefMessage ndefMessage = null;  // static NDEF message
114         NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
115         NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
116         NfcAdapter.CreateBeamUrisCallback uriCallback = null;
117         Uri[] uris = null;
118         int flags = 0;
119         int readerModeFlags = 0;
120         NfcAdapter.ReaderCallback readerCallback = null;
121         Bundle readerModeExtras = null;
122         Binder token;
123 
NfcActivityState(Activity activity)124         public NfcActivityState(Activity activity) {
125             if (activity.getWindow().isDestroyed()) {
126                 throw new IllegalStateException("activity is already destroyed");
127             }
128             // Check if activity is resumed right now, as we will not
129             // immediately get a callback for that.
130             resumed = activity.isResumed();
131 
132             this.activity = activity;
133             this.token = new Binder();
134             registerApplication(activity.getApplication());
135         }
destroy()136         public void destroy() {
137             unregisterApplication(activity.getApplication());
138             resumed = false;
139             activity = null;
140             ndefMessage = null;
141             ndefMessageCallback = null;
142             onNdefPushCompleteCallback = null;
143             uriCallback = null;
144             uris = null;
145             readerModeFlags = 0;
146             token = null;
147         }
148         @Override
toString()149         public String toString() {
150             StringBuilder s = new StringBuilder("[").append(" ");
151             s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
152             s.append(uriCallback).append(" ");
153             if (uris != null) {
154                 for (Uri uri : uris) {
155                     s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
156                 }
157             }
158             return s.toString();
159         }
160     }
161 
162     /** find activity state from mActivities */
findActivityState(Activity activity)163     synchronized NfcActivityState findActivityState(Activity activity) {
164         for (NfcActivityState state : mActivities) {
165             if (state.activity == activity) {
166                 return state;
167             }
168         }
169         return null;
170     }
171 
172     /** find or create activity state from mActivities */
getActivityState(Activity activity)173     synchronized NfcActivityState getActivityState(Activity activity) {
174         NfcActivityState state = findActivityState(activity);
175         if (state == null) {
176             state = new NfcActivityState(activity);
177             mActivities.add(state);
178         }
179         return state;
180     }
181 
findResumedActivityState()182     synchronized NfcActivityState findResumedActivityState() {
183         for (NfcActivityState state : mActivities) {
184             if (state.resumed) {
185                 return state;
186             }
187         }
188         return null;
189     }
190 
destroyActivityState(Activity activity)191     synchronized void destroyActivityState(Activity activity) {
192         NfcActivityState activityState = findActivityState(activity);
193         if (activityState != null) {
194             activityState.destroy();
195             mActivities.remove(activityState);
196         }
197     }
198 
NfcActivityManager(NfcAdapter adapter)199     public NfcActivityManager(NfcAdapter adapter) {
200         mAdapter = adapter;
201         mActivities = new LinkedList<NfcActivityState>();
202         mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
203     }
204 
enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)205     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
206             Bundle extras) {
207         boolean isResumed;
208         Binder token;
209         synchronized (NfcActivityManager.this) {
210             NfcActivityState state = getActivityState(activity);
211             state.readerCallback = callback;
212             state.readerModeFlags = flags;
213             state.readerModeExtras = extras;
214             token = state.token;
215             isResumed = state.resumed;
216         }
217         if (isResumed) {
218             setReaderMode(token, flags, extras);
219         }
220     }
221 
disableReaderMode(Activity activity)222     public void disableReaderMode(Activity activity) {
223         boolean isResumed;
224         Binder token;
225         synchronized (NfcActivityManager.this) {
226             NfcActivityState state = getActivityState(activity);
227             state.readerCallback = null;
228             state.readerModeFlags = 0;
229             state.readerModeExtras = null;
230             token = state.token;
231             isResumed = state.resumed;
232         }
233         if (isResumed) {
234             setReaderMode(token, 0, null);
235         }
236 
237     }
238 
setReaderMode(Binder token, int flags, Bundle extras)239     public void setReaderMode(Binder token, int flags, Bundle extras) {
240         if (DBG) Log.d(TAG, "Setting reader mode");
241         try {
242             NfcAdapter.sService.setReaderMode(token, this, flags, extras);
243         } catch (RemoteException e) {
244             mAdapter.attemptDeadServiceRecovery(e);
245         }
246     }
247 
setNdefPushContentUri(Activity activity, Uri[] uris)248     public void setNdefPushContentUri(Activity activity, Uri[] uris) {
249         boolean isResumed;
250         synchronized (NfcActivityManager.this) {
251             NfcActivityState state = getActivityState(activity);
252             state.uris = uris;
253             isResumed = state.resumed;
254         }
255         if (isResumed) {
256             // requestNfcServiceCallback() verifies permission also
257             requestNfcServiceCallback();
258         } else {
259             // Crash API calls early in case NFC permission is missing
260             verifyNfcPermission();
261         }
262     }
263 
264 
setNdefPushContentUriCallback(Activity activity, NfcAdapter.CreateBeamUrisCallback callback)265     public void setNdefPushContentUriCallback(Activity activity,
266             NfcAdapter.CreateBeamUrisCallback callback) {
267         boolean isResumed;
268         synchronized (NfcActivityManager.this) {
269             NfcActivityState state = getActivityState(activity);
270             state.uriCallback = callback;
271             isResumed = state.resumed;
272         }
273         if (isResumed) {
274             // requestNfcServiceCallback() verifies permission also
275             requestNfcServiceCallback();
276         } else {
277             // Crash API calls early in case NFC permission is missing
278             verifyNfcPermission();
279         }
280     }
281 
setNdefPushMessage(Activity activity, NdefMessage message, int flags)282     public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
283         boolean isResumed;
284         synchronized (NfcActivityManager.this) {
285             NfcActivityState state = getActivityState(activity);
286             state.ndefMessage = message;
287             state.flags = flags;
288             isResumed = state.resumed;
289         }
290         if (isResumed) {
291             // requestNfcServiceCallback() verifies permission also
292             requestNfcServiceCallback();
293         } else {
294             // Crash API calls early in case NFC permission is missing
295             verifyNfcPermission();
296         }
297     }
298 
setNdefPushMessageCallback(Activity activity, NfcAdapter.CreateNdefMessageCallback callback, int flags)299     public void setNdefPushMessageCallback(Activity activity,
300             NfcAdapter.CreateNdefMessageCallback callback, int flags) {
301         boolean isResumed;
302         synchronized (NfcActivityManager.this) {
303             NfcActivityState state = getActivityState(activity);
304             state.ndefMessageCallback = callback;
305             state.flags = flags;
306             isResumed = state.resumed;
307         }
308         if (isResumed) {
309             // requestNfcServiceCallback() verifies permission also
310             requestNfcServiceCallback();
311         } else {
312             // Crash API calls early in case NFC permission is missing
313             verifyNfcPermission();
314         }
315     }
316 
setOnNdefPushCompleteCallback(Activity activity, NfcAdapter.OnNdefPushCompleteCallback callback)317     public void setOnNdefPushCompleteCallback(Activity activity,
318             NfcAdapter.OnNdefPushCompleteCallback callback) {
319         boolean isResumed;
320         synchronized (NfcActivityManager.this) {
321             NfcActivityState state = getActivityState(activity);
322             state.onNdefPushCompleteCallback = callback;
323             isResumed = state.resumed;
324         }
325         if (isResumed) {
326             // requestNfcServiceCallback() verifies permission also
327             requestNfcServiceCallback();
328         } else {
329             // Crash API calls early in case NFC permission is missing
330             verifyNfcPermission();
331         }
332     }
333 
334     /**
335      * Request or unrequest NFC service callbacks.
336      * Makes IPC call - do not hold lock.
337      */
requestNfcServiceCallback()338     void requestNfcServiceCallback() {
339         try {
340             NfcAdapter.sService.setAppCallback(this);
341         } catch (RemoteException e) {
342             mAdapter.attemptDeadServiceRecovery(e);
343         }
344     }
345 
verifyNfcPermission()346     void verifyNfcPermission() {
347         try {
348             NfcAdapter.sService.verifyNfcPermission();
349         } catch (RemoteException e) {
350             mAdapter.attemptDeadServiceRecovery(e);
351         }
352     }
353 
354     /** Callback from NFC service, usually on binder thread */
355     @Override
createBeamShareData(byte peerLlcpVersion)356     public BeamShareData createBeamShareData(byte peerLlcpVersion) {
357         NfcAdapter.CreateNdefMessageCallback ndefCallback;
358         NfcAdapter.CreateBeamUrisCallback urisCallback;
359         NdefMessage message;
360         Activity activity;
361         Uri[] uris;
362         int flags;
363         NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
364         synchronized (NfcActivityManager.this) {
365             NfcActivityState state = findResumedActivityState();
366             if (state == null) return null;
367 
368             ndefCallback = state.ndefMessageCallback;
369             urisCallback = state.uriCallback;
370             message = state.ndefMessage;
371             uris = state.uris;
372             flags = state.flags;
373             activity = state.activity;
374         }
375         final long ident = Binder.clearCallingIdentity();
376         try {
377             // Make callbacks without lock
378             if (ndefCallback != null) {
379                 message = ndefCallback.createNdefMessage(event);
380             }
381             if (urisCallback != null) {
382                 uris = urisCallback.createBeamUris(event);
383                 if (uris != null) {
384                     ArrayList<Uri> validUris = new ArrayList<Uri>();
385                     for (Uri uri : uris) {
386                         if (uri == null) {
387                             Log.e(TAG, "Uri not allowed to be null.");
388                             continue;
389                         }
390                         String scheme = uri.getScheme();
391                         if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
392                                 !scheme.equalsIgnoreCase("content"))) {
393                             Log.e(TAG, "Uri needs to have " +
394                                     "either scheme file or scheme content");
395                             continue;
396                         }
397                         uri = ContentProvider.maybeAddUserId(uri, activity.getUserId());
398                         validUris.add(uri);
399                     }
400 
401                     uris = validUris.toArray(new Uri[validUris.size()]);
402                 }
403             }
404             if (uris != null && uris.length > 0) {
405                 for (Uri uri : uris) {
406                     // Grant the NFC process permission to read these URIs
407                     activity.grantUriPermission("com.android.nfc", uri,
408                             Intent.FLAG_GRANT_READ_URI_PERMISSION);
409                 }
410             }
411         } finally {
412             Binder.restoreCallingIdentity(ident);
413         }
414         return new BeamShareData(message, uris, activity.getUser(), flags);
415     }
416 
417     /** Callback from NFC service, usually on binder thread */
418     @Override
onNdefPushComplete(byte peerLlcpVersion)419     public void onNdefPushComplete(byte peerLlcpVersion) {
420         NfcAdapter.OnNdefPushCompleteCallback callback;
421         synchronized (NfcActivityManager.this) {
422             NfcActivityState state = findResumedActivityState();
423             if (state == null) return;
424 
425             callback = state.onNdefPushCompleteCallback;
426         }
427         NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
428         // Make callback without lock
429         if (callback != null) {
430             callback.onNdefPushComplete(event);
431         }
432     }
433 
434     @Override
onTagDiscovered(Tag tag)435     public void onTagDiscovered(Tag tag) throws RemoteException {
436         NfcAdapter.ReaderCallback callback;
437         synchronized (NfcActivityManager.this) {
438             NfcActivityState state = findResumedActivityState();
439             if (state == null) return;
440 
441             callback = state.readerCallback;
442         }
443 
444         // Make callback without lock
445         if (callback != null) {
446             callback.onTagDiscovered(tag);
447         }
448 
449     }
450     /** Callback from Activity life-cycle, on main thread */
451     @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)452     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
453 
454     /** Callback from Activity life-cycle, on main thread */
455     @Override
onActivityStarted(Activity activity)456     public void onActivityStarted(Activity activity) { /* NO-OP */ }
457 
458     /** Callback from Activity life-cycle, on main thread */
459     @Override
onActivityResumed(Activity activity)460     public void onActivityResumed(Activity activity) {
461         int readerModeFlags = 0;
462         Bundle readerModeExtras = null;
463         Binder token;
464         synchronized (NfcActivityManager.this) {
465             NfcActivityState state = findActivityState(activity);
466             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
467             if (state == null) return;
468             state.resumed = true;
469             token = state.token;
470             readerModeFlags = state.readerModeFlags;
471             readerModeExtras = state.readerModeExtras;
472         }
473         if (readerModeFlags != 0) {
474             setReaderMode(token, readerModeFlags, readerModeExtras);
475         }
476         requestNfcServiceCallback();
477     }
478 
479     /** Callback from Activity life-cycle, on main thread */
480     @Override
onActivityPaused(Activity activity)481     public void onActivityPaused(Activity activity) {
482         boolean readerModeFlagsSet;
483         Binder token;
484         synchronized (NfcActivityManager.this) {
485             NfcActivityState state = findActivityState(activity);
486             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
487             if (state == null) return;
488             state.resumed = false;
489             token = state.token;
490             readerModeFlagsSet = state.readerModeFlags != 0;
491         }
492         if (readerModeFlagsSet) {
493             // Restore default p2p modes
494             setReaderMode(token, 0, null);
495         }
496     }
497 
498     /** Callback from Activity life-cycle, on main thread */
499     @Override
onActivityStopped(Activity activity)500     public void onActivityStopped(Activity activity) { /* NO-OP */ }
501 
502     /** Callback from Activity life-cycle, on main thread */
503     @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)504     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
505 
506     /** Callback from Activity life-cycle, on main thread */
507     @Override
onActivityDestroyed(Activity activity)508     public void onActivityDestroyed(Activity activity) {
509         synchronized (NfcActivityManager.this) {
510             NfcActivityState state = findActivityState(activity);
511             if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
512             if (state != null) {
513                 // release all associated references
514                 destroyActivityState(activity);
515             }
516         }
517     }
518 
519 }
520