1 // Copyright 2012 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.os.Bundle; 11 12 import java.util.HashMap; 13 import java.util.Map; 14 15 /** 16 * Provides information about the current activity's status, and a way 17 * to register / unregister listeners for state changes. 18 */ 19 @JNINamespace("base::android") 20 public class ActivityStatus { 21 22 // Constants matching activity states reported to StateListener.onStateChange 23 // As an implementation detail, these are now defined in the auto-generated 24 // ActivityState interface, to be shared with C++. 25 public static final int CREATED = ActivityState.CREATED; 26 public static final int STARTED = ActivityState.STARTED; 27 public static final int RESUMED = ActivityState.RESUMED; 28 public static final int PAUSED = ActivityState.PAUSED; 29 public static final int STOPPED = ActivityState.STOPPED; 30 public static final int DESTROYED = ActivityState.DESTROYED; 31 32 // Last activity that was shown (or null if none or it was destroyed). 33 private static Activity sActivity; 34 35 private static final Map<Activity, Integer> sActivityStates = 36 new HashMap<Activity, Integer>(); 37 38 private static final ObserverList<StateListener> sStateListeners = 39 new ObserverList<StateListener>(); 40 41 /** 42 * Interface to be implemented by listeners. 43 */ 44 public interface StateListener { 45 /** 46 * Called when the activity's state changes. 47 * @param newState New activity state. 48 */ onActivityStateChange(int newState)49 public void onActivityStateChange(int newState); 50 } 51 ActivityStatus()52 private ActivityStatus() {} 53 54 /** 55 * Initializes the activity status for a specified application. 56 * 57 * @param application The application whose status you wish to monitor. 58 */ initialize(Application application)59 public static void initialize(Application application) { 60 application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { 61 @Override 62 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 63 onStateChange(activity, CREATED); 64 } 65 66 @Override 67 public void onActivityDestroyed(Activity activity) { 68 onStateChange(activity, DESTROYED); 69 } 70 71 @Override 72 public void onActivityPaused(Activity activity) { 73 onStateChange(activity, PAUSED); 74 } 75 76 @Override 77 public void onActivityResumed(Activity activity) { 78 onStateChange(activity, RESUMED); 79 } 80 81 @Override 82 public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} 83 84 @Override 85 public void onActivityStarted(Activity activity) { 86 onStateChange(activity, STARTED); 87 } 88 89 @Override 90 public void onActivityStopped(Activity activity) { 91 onStateChange(activity, STOPPED); 92 } 93 }); 94 } 95 96 /** 97 * Must be called by the main activity when it changes state. 98 * 99 * @param activity Current activity. 100 * @param newState New state value. 101 */ onStateChange(Activity activity, int newState)102 private static void onStateChange(Activity activity, int newState) { 103 if (activity == null) throw new IllegalArgumentException("null activity is not supported"); 104 105 if (sActivity != activity) { 106 // ActivityStatus is notified with the CREATED event very late during the main activity 107 // creation to avoid making startup performance worse than it is by notifying observers 108 // that could do some expensive work. This can lead to non-CREATED events being fired 109 // before the CREATED event which is problematic. 110 // TODO(pliard): fix http://crbug.com/176837. 111 if (sActivity == null 112 || newState == CREATED || newState == RESUMED || newState == STARTED) { 113 sActivity = activity; 114 } 115 } 116 117 if (newState != DESTROYED) { 118 sActivityStates.put(activity, newState); 119 } else { 120 sActivityStates.remove(activity); 121 } 122 123 if (sActivity == activity) { 124 for (StateListener listener : sStateListeners) { 125 listener.onActivityStateChange(newState); 126 } 127 if (newState == DESTROYED) { 128 sActivity = null; 129 } 130 } 131 } 132 133 /** 134 * Testing method to update the state of the specified activity. 135 */ onStateChangeForTesting(Activity activity, int newState)136 public static void onStateChangeForTesting(Activity activity, int newState) { 137 onStateChange(activity, newState); 138 } 139 140 /** 141 * @return The current activity. 142 */ getActivity()143 public static Activity getActivity() { 144 return sActivity; 145 } 146 147 /** 148 * @return The current activity's state (if no activity is registered, then DESTROYED will 149 * be returned). 150 */ getState()151 public static int getState() { 152 return getStateForActivity(sActivity); 153 } 154 155 /** 156 * Query the state for a given activity. If the activity is not being tracked, this will 157 * return {@link #DESTROYED}. 158 * 159 * <p> 160 * When relying on this method, be familiar with the expected life cycle state 161 * transitions: 162 * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle"> 163 * Activity Lifecycle 164 * </a> 165 * 166 * <p> 167 * During activity transitions (activity B launching in front of activity A), A will completely 168 * paused before the creation of activity B begins. 169 * 170 * <p> 171 * A basic flow for activity A starting, followed by activity B being opened and then closed: 172 * <ul> 173 * <li> -- Starting Activity A -- 174 * <li> Activity A - CREATED 175 * <li> Activity A - STARTED 176 * <li> Activity A - RESUMED 177 * <li> -- Starting Activity B -- 178 * <li> Activity A - PAUSED 179 * <li> Activity B - CREATED 180 * <li> Activity B - STARTED 181 * <li> Activity B - RESUMED 182 * <li> Activity A - STOPPED 183 * <li> -- Closing Activity B, Activity A regaining focus -- 184 * <li> Activity B - PAUSED 185 * <li> Activity A - STARTED 186 * <li> Activity A - RESUMED 187 * <li> Activity B - STOPPED 188 * <li> Activity B - DESTROYED 189 * </ul> 190 * 191 * @param activity The activity whose state is to be returned. 192 * @return The state of the specified activity. 193 */ getStateForActivity(Activity activity)194 public static int getStateForActivity(Activity activity) { 195 Integer currentStatus = sActivityStates.get(activity); 196 return currentStatus != null ? currentStatus.intValue() : DESTROYED; 197 } 198 199 /** 200 * Registers the given listener to receive activity state changes. 201 * @param listener Listener to receive state changes. 202 */ registerStateListener(StateListener listener)203 public static void registerStateListener(StateListener listener) { 204 sStateListeners.addObserver(listener); 205 } 206 207 /** 208 * Unregisters the given listener from receiving activity state changes. 209 * @param listener Listener that doesn't want to receive state changes. 210 */ unregisterStateListener(StateListener listener)211 public static void unregisterStateListener(StateListener listener) { 212 sStateListeners.removeObserver(listener); 213 } 214 215 /** 216 * Registers the single thread-safe native activity status listener. 217 * This handles the case where the caller is not on the main thread. 218 * Note that this is used by a leaky singleton object from the native 219 * side, hence lifecycle management is greatly simplified. 220 */ 221 @CalledByNative registerThreadSafeNativeStateListener()222 private static void registerThreadSafeNativeStateListener() { 223 ThreadUtils.runOnUiThread(new Runnable () { 224 @Override 225 public void run() { 226 // Register a new listener that calls nativeOnActivityStateChange. 227 sStateListeners.addObserver(new StateListener() { 228 @Override 229 public void onActivityStateChange(int newState) { 230 nativeOnActivityStateChange(newState); 231 } 232 }); 233 } 234 }); 235 } 236 237 // Called to notify the native side of state changes. 238 // IMPORTANT: This is always called on the main thread! nativeOnActivityStateChange(int newState)239 private static native void nativeOnActivityStateChange(int newState); 240 241 /** 242 * Checks whether or not the Application's current Activity is visible to the user. Note that 243 * this includes the PAUSED state, which can happen when the Activity is temporarily covered 244 * by another Activity's Fragment (e.g.). 245 * @return True if the Activity is visible, false otherwise. 246 */ isApplicationVisible()247 public static boolean isApplicationVisible() { 248 int state = getState(); 249 return state != STOPPED && state != DESTROYED; 250 } 251 252 /** 253 * Checks to see if there are any active Activity instances being watched by ActivityStatus. 254 * @return True if all Activities have been destroyed. 255 */ isEveryActivityDestroyed()256 public static boolean isEveryActivityDestroyed() { 257 return sActivityStates.isEmpty(); 258 } 259 } 260