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