Home | History | Annotate | Download | only in base
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.base;
      6 
      7 import android.app.Activity;
      8 import android.app.Application;
      9 import android.app.Application.ActivityLifecycleCallbacks;
     10 import android.content.Context;
     11 import android.os.Bundle;
     12 
     13 import java.lang.ref.WeakReference;
     14 import java.util.ArrayList;
     15 import java.util.List;
     16 import java.util.Map;
     17 import java.util.concurrent.ConcurrentHashMap;
     18 
     19 /**
     20  * Provides information about the current activity's status, and a way
     21  * to register / unregister listeners for state changes.
     22  */
     23 @JNINamespace("base::android")
     24 public class ApplicationStatus {
     25     private static class ActivityInfo {
     26         private int mStatus = ActivityState.DESTROYED;
     27         private ObserverList<ActivityStateListener> mListeners =
     28                 new ObserverList<ActivityStateListener>();
     29 
     30         /**
     31          * @return The current {@link ActivityState} of the activity.
     32          */
     33         public int getStatus() {
     34             return mStatus;
     35         }
     36 
     37         /**
     38          * @param status The new {@link ActivityState} of the activity.
     39          */
     40         public void setStatus(int status) {
     41             mStatus = status;
     42         }
     43 
     44         /**
     45          * @return A list of {@link ActivityStateListener}s listening to this activity.
     46          */
     47         public ObserverList<ActivityStateListener> getListeners() {
     48             return mListeners;
     49         }
     50     }
     51 
     52     private static Application sApplication;
     53 
     54     private static Object sCachedApplicationStateLock = new Object();
     55     private static Integer sCachedApplicationState;
     56 
     57     /** Last activity that was shown (or null if none or it was destroyed). */
     58     private static Activity sActivity;
     59 
     60     /** A lazily initialized listener that forwards application state changes to native. */
     61     private static ApplicationStateListener sNativeApplicationStateListener;
     62 
     63     /**
     64      * A map of which observers listen to state changes from which {@link Activity}.
     65      */
     66     private static final Map<Activity, ActivityInfo> sActivityInfo =
     67             new ConcurrentHashMap<Activity, ActivityInfo>();
     68 
     69     /**
     70      * A list of observers to be notified when any {@link Activity} has a state change.
     71      */
     72     private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
     73             new ObserverList<ActivityStateListener>();
     74 
     75     /**
     76      * A list of observers to be notified when the visibility state of this {@link Application}
     77      * changes.  See {@link #getStateForApplication()}.
     78      */
     79     private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
     80             new ObserverList<ApplicationStateListener>();
     81 
     82     /**
     83      * Interface to be implemented by listeners.
     84      */
     85     public interface ApplicationStateListener {
     86         /**
     87          * Called when the application's state changes.
     88          * @param newState The application state.
     89          */
     90         public void onApplicationStateChange(int newState);
     91     }
     92 
     93     /**
     94      * Interface to be implemented by listeners.
     95      */
     96     public interface ActivityStateListener {
     97         /**
     98          * Called when the activity's state changes.
     99          * @param activity The activity that had a state change.
    100          * @param newState New activity state.
    101          */
    102         public void onActivityStateChange(Activity activity, int newState);
    103     }
    104 
    105     private ApplicationStatus() {}
    106 
    107     /**
    108      * Initializes the activity status for a specified application.
    109      *
    110      * @param application The application whose status you wish to monitor.
    111      */
    112     public static void initialize(BaseChromiumApplication application) {
    113         sApplication = application;
    114 
    115         application.registerWindowFocusChangedListener(
    116                 new BaseChromiumApplication.WindowFocusChangedListener() {
    117             @Override
    118             public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
    119                 if (!hasFocus || activity == sActivity) return;
    120 
    121                 int state = getStateForActivity(activity);
    122 
    123                 if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) {
    124                     sActivity = activity;
    125                 }
    126 
    127                 // TODO(dtrainor): Notify of active activity change?
    128             }
    129         });
    130 
    131         application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
    132             @Override
    133             public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
    134                 onStateChange(activity, ActivityState.CREATED);
    135             }
    136 
    137             @Override
    138             public void onActivityDestroyed(Activity activity) {
    139                 onStateChange(activity, ActivityState.DESTROYED);
    140             }
    141 
    142             @Override
    143             public void onActivityPaused(Activity activity) {
    144                 onStateChange(activity, ActivityState.PAUSED);
    145             }
    146 
    147             @Override
    148             public void onActivityResumed(Activity activity) {
    149                 onStateChange(activity, ActivityState.RESUMED);
    150             }
    151 
    152             @Override
    153             public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
    154 
    155             @Override
    156             public void onActivityStarted(Activity activity) {
    157                 onStateChange(activity, ActivityState.STARTED);
    158             }
    159 
    160             @Override
    161             public void onActivityStopped(Activity activity) {
    162                 onStateChange(activity, ActivityState.STOPPED);
    163             }
    164         });
    165     }
    166 
    167     /**
    168      * Must be called by the main activity when it changes state.
    169      *
    170      * @param activity Current activity.
    171      * @param newState New state value.
    172      */
    173     private static void onStateChange(Activity activity, int newState) {
    174         if (activity == null) throw new IllegalArgumentException("null activity is not supported");
    175 
    176         if (sActivity == null
    177                 || newState == ActivityState.CREATED
    178                 || newState == ActivityState.RESUMED
    179                 || newState == ActivityState.STARTED) {
    180             sActivity = activity;
    181         }
    182 
    183         int oldApplicationState = getStateForApplication();
    184 
    185         if (newState == ActivityState.CREATED) {
    186             assert !sActivityInfo.containsKey(activity);
    187             sActivityInfo.put(activity, new ActivityInfo());
    188         }
    189 
    190         // Invalidate the cached application state.
    191         synchronized (sCachedApplicationStateLock) {
    192             sCachedApplicationState = null;
    193         }
    194 
    195         ActivityInfo info = sActivityInfo.get(activity);
    196         info.setStatus(newState);
    197 
    198         // Notify all state observers that are specifically listening to this activity.
    199         for (ActivityStateListener listener : info.getListeners()) {
    200             listener.onActivityStateChange(activity, newState);
    201         }
    202 
    203         // Notify all state observers that are listening globally for all activity state
    204         // changes.
    205         for (ActivityStateListener listener : sGeneralActivityStateListeners) {
    206             listener.onActivityStateChange(activity, newState);
    207         }
    208 
    209         int applicationState = getStateForApplication();
    210         if (applicationState != oldApplicationState) {
    211             for (ApplicationStateListener listener : sApplicationStateListeners) {
    212                 listener.onApplicationStateChange(applicationState);
    213             }
    214         }
    215 
    216         if (newState == ActivityState.DESTROYED) {
    217             sActivityInfo.remove(activity);
    218             if (activity == sActivity) sActivity = null;
    219         }
    220     }
    221 
    222     /**
    223      * Testing method to update the state of the specified activity.
    224      */
    225     public static void onStateChangeForTesting(Activity activity, int newState) {
    226         onStateChange(activity, newState);
    227     }
    228 
    229     /**
    230      * @return The most recent focused {@link Activity} tracked by this class.  Being focused means
    231      *         out of all the activities tracked here, it has most recently gained window focus.
    232      */
    233     public static Activity getLastTrackedFocusedActivity() {
    234         return sActivity;
    235     }
    236 
    237     /**
    238      * @return A {@link List} of all non-destroyed {@link Activity}s.
    239      */
    240     public static List<WeakReference<Activity>> getRunningActivities() {
    241         List<WeakReference<Activity>> activities = new ArrayList<WeakReference<Activity>>();
    242         for (Activity activity : sActivityInfo.keySet()) {
    243             activities.add(new WeakReference<Activity>(activity));
    244         }
    245         return activities;
    246     }
    247 
    248     /**
    249      * @return The {@link Context} for the {@link Application}.
    250      */
    251     public static Context getApplicationContext() {
    252         return sApplication != null ? sApplication.getApplicationContext() : null;
    253     }
    254 
    255     /**
    256      * Query the state for a given activity.  If the activity is not being tracked, this will
    257      * return {@link ActivityState#DESTROYED}.
    258      *
    259      * <p>
    260      * Please note that Chrome can have multiple activities running simultaneously.  Please also
    261      * look at {@link #getStateForApplication()} for more details.
    262      *
    263      * <p>
    264      * When relying on this method, be familiar with the expected life cycle state
    265      * transitions:
    266      * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
    267      *   Activity Lifecycle
    268      * </a>
    269      *
    270      * <p>
    271      * During activity transitions (activity B launching in front of activity A), A will completely
    272      * paused before the creation of activity B begins.
    273      *
    274      * <p>
    275      * A basic flow for activity A starting, followed by activity B being opened and then closed:
    276      * <ul>
    277      *   <li> -- Starting Activity A --
    278      *   <li> Activity A - ActivityState.CREATED
    279      *   <li> Activity A - ActivityState.STARTED
    280      *   <li> Activity A - ActivityState.RESUMED
    281      *   <li> -- Starting Activity B --
    282      *   <li> Activity A - ActivityState.PAUSED
    283      *   <li> Activity B - ActivityState.CREATED
    284      *   <li> Activity B - ActivityState.STARTED
    285      *   <li> Activity B - ActivityState.RESUMED
    286      *   <li> Activity A - ActivityState.STOPPED
    287      *   <li> -- Closing Activity B, Activity A regaining focus --
    288      *   <li> Activity B - ActivityState.PAUSED
    289      *   <li> Activity A - ActivityState.STARTED
    290      *   <li> Activity A - ActivityState.RESUMED
    291      *   <li> Activity B - ActivityState.STOPPED
    292      *   <li> Activity B - ActivityState.DESTROYED
    293      * </ul>
    294      *
    295      * @param activity The activity whose state is to be returned.
    296      * @return The state of the specified activity (see {@link ActivityState}).
    297      */
    298     public static int getStateForActivity(Activity activity) {
    299         ActivityInfo info = sActivityInfo.get(activity);
    300         return info != null ? info.getStatus() : ActivityState.DESTROYED;
    301     }
    302 
    303     /**
    304      * @return The state of the application (see {@link ApplicationState}).
    305      */
    306     public static int getStateForApplication() {
    307         synchronized (sCachedApplicationStateLock) {
    308             if (sCachedApplicationState == null) {
    309                 sCachedApplicationState = determineApplicationState();
    310             }
    311         }
    312 
    313         return sCachedApplicationState.intValue();
    314     }
    315 
    316     /**
    317      * Checks whether or not any Activity in this Application is visible to the user.  Note that
    318      * this includes the PAUSED state, which can happen when the Activity is temporarily covered
    319      * by another Activity's Fragment (e.g.).
    320      * @return Whether any Activity under this Application is visible.
    321      */
    322     public static boolean hasVisibleActivities() {
    323         int state = getStateForApplication();
    324         return state == ApplicationState.HAS_RUNNING_ACTIVITIES
    325                 || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
    326     }
    327 
    328     /**
    329      * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
    330      * @return True if all Activities have been destroyed.
    331      */
    332     public static boolean isEveryActivityDestroyed() {
    333         return sActivityInfo.isEmpty();
    334     }
    335 
    336     /**
    337      * Registers the given listener to receive state changes for all activities.
    338      * @param listener Listener to receive state changes.
    339      */
    340     public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
    341         sGeneralActivityStateListeners.addObserver(listener);
    342     }
    343 
    344     /**
    345      * Registers the given listener to receive state changes for {@code activity}.  After a call to
    346      * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
    347      * {@link ActivityState#DESTROYED} all listeners associated with that particular
    348      * {@link Activity} are removed.
    349      * @param listener Listener to receive state changes.
    350      * @param activity Activity to track or {@code null} to track all activities.
    351      */
    352     public static void registerStateListenerForActivity(ActivityStateListener listener,
    353             Activity activity) {
    354         assert activity != null;
    355 
    356         ActivityInfo info = sActivityInfo.get(activity);
    357         assert info != null && info.getStatus() != ActivityState.DESTROYED;
    358         info.getListeners().addObserver(listener);
    359     }
    360 
    361     /**
    362      * Unregisters the given listener from receiving activity state changes.
    363      * @param listener Listener that doesn't want to receive state changes.
    364      */
    365     public static void unregisterActivityStateListener(ActivityStateListener listener) {
    366         sGeneralActivityStateListeners.removeObserver(listener);
    367 
    368         // Loop through all observer lists for all activities and remove the listener.
    369         for (ActivityInfo info : sActivityInfo.values()) {
    370             info.getListeners().removeObserver(listener);
    371         }
    372     }
    373 
    374     /**
    375      * Registers the given listener to receive state changes for the application.
    376      * @param listener Listener to receive state state changes.
    377      */
    378     public static void registerApplicationStateListener(ApplicationStateListener listener) {
    379         sApplicationStateListeners.addObserver(listener);
    380     }
    381 
    382     /**
    383      * Unregisters the given listener from receiving state changes.
    384      * @param listener Listener that doesn't want to receive state changes.
    385      */
    386     public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
    387         sApplicationStateListeners.removeObserver(listener);
    388     }
    389 
    390     /**
    391      * Registers the single thread-safe native activity status listener.
    392      * This handles the case where the caller is not on the main thread.
    393      * Note that this is used by a leaky singleton object from the native
    394      * side, hence lifecycle management is greatly simplified.
    395      */
    396     @CalledByNative
    397     private static void registerThreadSafeNativeApplicationStateListener() {
    398         ThreadUtils.runOnUiThread(new Runnable () {
    399             @Override
    400             public void run() {
    401                 if (sNativeApplicationStateListener != null) return;
    402 
    403                 sNativeApplicationStateListener = new ApplicationStateListener() {
    404                     @Override
    405                     public void onApplicationStateChange(int newState) {
    406                         nativeOnApplicationStateChange(newState);
    407                     }
    408                 };
    409                 registerApplicationStateListener(sNativeApplicationStateListener);
    410             }
    411         });
    412     }
    413 
    414     /**
    415      * Determines the current application state as defined by {@link ApplicationState}.  This will
    416      * loop over all the activities and check their state to determine what the general application
    417      * state should be.
    418      * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
    419      *         HAS_PAUSED_ACTIVITIES if none are running and one is paused.
    420      *         HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
    421      *         HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
    422      */
    423     private static int determineApplicationState() {
    424         boolean hasPausedActivity = false;
    425         boolean hasStoppedActivity = false;
    426 
    427         for (ActivityInfo info : sActivityInfo.values()) {
    428             int state = info.getStatus();
    429             if (state != ActivityState.PAUSED
    430                     && state != ActivityState.STOPPED
    431                     && state != ActivityState.DESTROYED) {
    432                 return ApplicationState.HAS_RUNNING_ACTIVITIES;
    433             } else if (state == ActivityState.PAUSED) {
    434                 hasPausedActivity = true;
    435             } else if (state == ActivityState.STOPPED) {
    436                 hasStoppedActivity = true;
    437             }
    438         }
    439 
    440         if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
    441         if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
    442         return ApplicationState.HAS_DESTROYED_ACTIVITIES;
    443     }
    444 
    445     // Called to notify the native side of state changes.
    446     // IMPORTANT: This is always called on the main thread!
    447     private static native void nativeOnApplicationStateChange(int newState);
    448 }
    449