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