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