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         int flags = 0;
    114         public NfcActivityState(Activity activity) {
    115             if (activity.getWindow().isDestroyed()) {
    116                 throw new IllegalStateException("activity is already destroyed");
    117             }
    118             // Check if activity is resumed right now, as we will not
    119             // immediately get a callback for that.
    120             resumed = activity.isResumed();
    121 
    122             this.activity = activity;
    123             registerApplication(activity.getApplication());
    124         }
    125         public void destroy() {
    126             unregisterApplication(activity.getApplication());
    127             resumed = false;
    128             activity = null;
    129             ndefMessage = null;
    130             ndefMessageCallback = null;
    131             onNdefPushCompleteCallback = null;
    132             uriCallback = null;
    133             uris = null;
    134         }
    135         @Override
    136         public String toString() {
    137             StringBuilder s = new StringBuilder("[").append(" ");
    138             s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
    139             s.append(uriCallback).append(" ");
    140             if (uris != null) {
    141                 for (Uri uri : uris) {
    142                     s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
    143                 }
    144             }
    145             return s.toString();
    146         }
    147     }
    148 
    149     /** find activity state from mActivities */
    150     synchronized NfcActivityState findActivityState(Activity activity) {
    151         for (NfcActivityState state : mActivities) {
    152             if (state.activity == activity) {
    153                 return state;
    154             }
    155         }
    156         return null;
    157     }
    158 
    159     /** find or create activity state from mActivities */
    160     synchronized NfcActivityState getActivityState(Activity activity) {
    161         NfcActivityState state = findActivityState(activity);
    162         if (state == null) {
    163             state = new NfcActivityState(activity);
    164             mActivities.add(state);
    165         }
    166         return state;
    167     }
    168 
    169     synchronized NfcActivityState findResumedActivityState() {
    170         for (NfcActivityState state : mActivities) {
    171             if (state.resumed) {
    172                 return state;
    173             }
    174         }
    175         return null;
    176     }
    177 
    178     synchronized void destroyActivityState(Activity activity) {
    179         NfcActivityState activityState = findActivityState(activity);
    180         if (activityState != null) {
    181             activityState.destroy();
    182             mActivities.remove(activityState);
    183         }
    184     }
    185 
    186     public NfcActivityManager(NfcAdapter adapter) {
    187         mAdapter = adapter;
    188         mActivities = new LinkedList<NfcActivityState>();
    189         mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
    190         mDefaultEvent = new NfcEvent(mAdapter);
    191     }
    192 
    193     public void setNdefPushContentUri(Activity activity, Uri[] uris) {
    194         boolean isResumed;
    195         synchronized (NfcActivityManager.this) {
    196             NfcActivityState state = getActivityState(activity);
    197             state.uris = uris;
    198             isResumed = state.resumed;
    199         }
    200         if (isResumed) {
    201             requestNfcServiceCallback();
    202         }
    203     }
    204 
    205 
    206     public void setNdefPushContentUriCallback(Activity activity,
    207             NfcAdapter.CreateBeamUrisCallback callback) {
    208         boolean isResumed;
    209         synchronized (NfcActivityManager.this) {
    210             NfcActivityState state = getActivityState(activity);
    211             state.uriCallback = callback;
    212             isResumed = state.resumed;
    213         }
    214         if (isResumed) {
    215             requestNfcServiceCallback();
    216         }
    217     }
    218 
    219     public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
    220         boolean isResumed;
    221         synchronized (NfcActivityManager.this) {
    222             NfcActivityState state = getActivityState(activity);
    223             state.ndefMessage = message;
    224             state.flags = flags;
    225             isResumed = state.resumed;
    226         }
    227         if (isResumed) {
    228             requestNfcServiceCallback();
    229         }
    230     }
    231 
    232     public void setNdefPushMessageCallback(Activity activity,
    233             NfcAdapter.CreateNdefMessageCallback callback, int flags) {
    234         boolean isResumed;
    235         synchronized (NfcActivityManager.this) {
    236             NfcActivityState state = getActivityState(activity);
    237             state.ndefMessageCallback = callback;
    238             state.flags = flags;
    239             isResumed = state.resumed;
    240         }
    241         if (isResumed) {
    242             requestNfcServiceCallback();
    243         }
    244     }
    245 
    246     public void setOnNdefPushCompleteCallback(Activity activity,
    247             NfcAdapter.OnNdefPushCompleteCallback callback) {
    248         boolean isResumed;
    249         synchronized (NfcActivityManager.this) {
    250             NfcActivityState state = getActivityState(activity);
    251             state.onNdefPushCompleteCallback = callback;
    252             isResumed = state.resumed;
    253         }
    254         if (isResumed) {
    255             requestNfcServiceCallback();
    256         }
    257     }
    258 
    259     /**
    260      * Request or unrequest NFC service callbacks for NDEF push.
    261      * Makes IPC call - do not hold lock.
    262      */
    263     void requestNfcServiceCallback() {
    264         try {
    265             NfcAdapter.sService.setNdefPushCallback(this);
    266         } catch (RemoteException e) {
    267             mAdapter.attemptDeadServiceRecovery(e);
    268         }
    269     }
    270 
    271     /** Callback from NFC service, usually on binder thread */
    272     @Override
    273     public BeamShareData createBeamShareData() {
    274         NfcAdapter.CreateNdefMessageCallback ndefCallback;
    275         NfcAdapter.CreateBeamUrisCallback urisCallback;
    276         NdefMessage message;
    277         Uri[] uris;
    278         int flags;
    279         synchronized (NfcActivityManager.this) {
    280             NfcActivityState state = findResumedActivityState();
    281             if (state == null) return null;
    282 
    283             ndefCallback = state.ndefMessageCallback;
    284             urisCallback = state.uriCallback;
    285             message = state.ndefMessage;
    286             uris = state.uris;
    287             flags = state.flags;
    288         }
    289 
    290         // Make callbacks without lock
    291         if (ndefCallback != null) {
    292             message  = ndefCallback.createNdefMessage(mDefaultEvent);
    293         }
    294         if (urisCallback != null) {
    295             uris = urisCallback.createBeamUris(mDefaultEvent);
    296             if (uris != null) {
    297                 for (Uri uri : uris) {
    298                     if (uri == null) {
    299                         Log.e(TAG, "Uri not allowed to be null.");
    300                         return null;
    301                     }
    302                     String scheme = uri.getScheme();
    303                     if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
    304                             !scheme.equalsIgnoreCase("content"))) {
    305                         Log.e(TAG, "Uri needs to have " +
    306                                 "either scheme file or scheme content");
    307                         return null;
    308                     }
    309                 }
    310             }
    311         }
    312 
    313         return new BeamShareData(message, uris, flags);
    314     }
    315 
    316     /** Callback from NFC service, usually on binder thread */
    317     @Override
    318     public void onNdefPushComplete() {
    319         NfcAdapter.OnNdefPushCompleteCallback callback;
    320         synchronized (NfcActivityManager.this) {
    321             NfcActivityState state = findResumedActivityState();
    322             if (state == null) return;
    323 
    324             callback = state.onNdefPushCompleteCallback;
    325         }
    326 
    327         // Make callback without lock
    328         if (callback != null) {
    329             callback.onNdefPushComplete(mDefaultEvent);
    330         }
    331     }
    332 
    333     /** Callback from Activity life-cycle, on main thread */
    334     @Override
    335     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
    336 
    337     /** Callback from Activity life-cycle, on main thread */
    338     @Override
    339     public void onActivityStarted(Activity activity) { /* NO-OP */ }
    340 
    341     /** Callback from Activity life-cycle, on main thread */
    342     @Override
    343     public void onActivityResumed(Activity activity) {
    344         synchronized (NfcActivityManager.this) {
    345             NfcActivityState state = findActivityState(activity);
    346             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
    347             if (state == null) return;
    348             state.resumed = true;
    349         }
    350         requestNfcServiceCallback();
    351     }
    352 
    353     /** Callback from Activity life-cycle, on main thread */
    354     @Override
    355     public void onActivityPaused(Activity activity) {
    356         synchronized (NfcActivityManager.this) {
    357             NfcActivityState state = findActivityState(activity);
    358             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
    359             if (state == null) return;
    360             state.resumed = false;
    361         }
    362     }
    363 
    364     /** Callback from Activity life-cycle, on main thread */
    365     @Override
    366     public void onActivityStopped(Activity activity) { /* NO-OP */ }
    367 
    368     /** Callback from Activity life-cycle, on main thread */
    369     @Override
    370     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
    371 
    372     /** Callback from Activity life-cycle, on main thread */
    373     @Override
    374     public void onActivityDestroyed(Activity activity) {
    375         synchronized (NfcActivityManager.this) {
    376             NfcActivityState state = findActivityState(activity);
    377             if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
    378             if (state != null) {
    379                 // release all associated references
    380                 destroyActivityState(activity);
    381             }
    382         }
    383     }
    384 
    385 }
    386