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     final NfcEvent mDefaultEvent;  // cached NfcEvent (its currently always the same)
     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;
     61         public NfcApplicationState(Application app) {
     62             this.app = app;
     63         }
     64         public void register() {
     65             refCount++;
     66             if (refCount == 1) {
     67                 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
     68             }
     69         }
     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 
     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 
     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 
     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 
    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         }
    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
    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 */
    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 */
    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 
    182     synchronized NfcActivityState findResumedActivityState() {
    183         for (NfcActivityState state : mActivities) {
    184             if (state.resumed) {
    185                 return state;
    186             }
    187         }
    188         return null;
    189     }
    190 
    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 
    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         mDefaultEvent = new NfcEvent(mAdapter);
    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() {
    358         NfcAdapter.CreateNdefMessageCallback ndefCallback;
    359         NfcAdapter.CreateBeamUrisCallback urisCallback;
    360         NdefMessage message;
    361         Activity activity;
    362         Uri[] uris;
    363         int flags;
    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 
    376         // Make callbacks without lock
    377         if (ndefCallback != null) {
    378             message  = ndefCallback.createNdefMessage(mDefaultEvent);
    379         }
    380         if (urisCallback != null) {
    381             uris = urisCallback.createBeamUris(mDefaultEvent);
    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, UserHandle.myUserId());
    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         return new BeamShareData(message, uris, UserHandle.CURRENT, flags);
    411     }
    412 
    413     /** Callback from NFC service, usually on binder thread */
    414     @Override
    415     public void onNdefPushComplete() {
    416         NfcAdapter.OnNdefPushCompleteCallback callback;
    417         synchronized (NfcActivityManager.this) {
    418             NfcActivityState state = findResumedActivityState();
    419             if (state == null) return;
    420 
    421             callback = state.onNdefPushCompleteCallback;
    422         }
    423 
    424         // Make callback without lock
    425         if (callback != null) {
    426             callback.onNdefPushComplete(mDefaultEvent);
    427         }
    428     }
    429 
    430     @Override
    431     public void onTagDiscovered(Tag tag) throws RemoteException {
    432         NfcAdapter.ReaderCallback callback;
    433         synchronized (NfcActivityManager.this) {
    434             NfcActivityState state = findResumedActivityState();
    435             if (state == null) return;
    436 
    437             callback = state.readerCallback;
    438         }
    439 
    440         // Make callback without lock
    441         if (callback != null) {
    442             callback.onTagDiscovered(tag);
    443         }
    444 
    445     }
    446     /** Callback from Activity life-cycle, on main thread */
    447     @Override
    448     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
    449 
    450     /** Callback from Activity life-cycle, on main thread */
    451     @Override
    452     public void onActivityStarted(Activity activity) { /* NO-OP */ }
    453 
    454     /** Callback from Activity life-cycle, on main thread */
    455     @Override
    456     public void onActivityResumed(Activity activity) {
    457         int readerModeFlags = 0;
    458         Bundle readerModeExtras = null;
    459         Binder token;
    460         synchronized (NfcActivityManager.this) {
    461             NfcActivityState state = findActivityState(activity);
    462             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
    463             if (state == null) return;
    464             state.resumed = true;
    465             token = state.token;
    466             readerModeFlags = state.readerModeFlags;
    467             readerModeExtras = state.readerModeExtras;
    468         }
    469         if (readerModeFlags != 0) {
    470             setReaderMode(token, readerModeFlags, readerModeExtras);
    471         }
    472         requestNfcServiceCallback();
    473     }
    474 
    475     /** Callback from Activity life-cycle, on main thread */
    476     @Override
    477     public void onActivityPaused(Activity activity) {
    478         boolean readerModeFlagsSet;
    479         Binder token;
    480         synchronized (NfcActivityManager.this) {
    481             NfcActivityState state = findActivityState(activity);
    482             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
    483             if (state == null) return;
    484             state.resumed = false;
    485             token = state.token;
    486             readerModeFlagsSet = state.readerModeFlags != 0;
    487         }
    488         if (readerModeFlagsSet) {
    489             // Restore default p2p modes
    490             setReaderMode(token, 0, null);
    491         }
    492     }
    493 
    494     /** Callback from Activity life-cycle, on main thread */
    495     @Override
    496     public void onActivityStopped(Activity activity) { /* NO-OP */ }
    497 
    498     /** Callback from Activity life-cycle, on main thread */
    499     @Override
    500     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
    501 
    502     /** Callback from Activity life-cycle, on main thread */
    503     @Override
    504     public void onActivityDestroyed(Activity activity) {
    505         synchronized (NfcActivityManager.this) {
    506             NfcActivityState state = findActivityState(activity);
    507             if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
    508             if (state != null) {
    509                 // release all associated references
    510                 destroyActivityState(activity);
    511             }
    512         }
    513     }
    514 
    515 }
    516