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