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