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.
5 package org.chromium.base;
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;
13 import java.lang.ref.WeakReference;
14 import java.util.ArrayList;
15 import java.util.List;
17 import java.util.concurrent.ConcurrentHashMap;
20 * Provides information about the current activity's status, and a way
21 * to register / unregister listeners for state changes.
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>();
31 * @return The current {@link ActivityState} of the activity.
33 public int getStatus() {
38 * @param status The new {@link ActivityState} of the activity.
40 public void setStatus(int status) {
45 * @return A list of {@link ActivityStateListener}s listening to this activity.
47 public ObserverList<ActivityStateListener> getListeners() {
52 private static Application sApplication;
54 private static Object sCachedApplicationStateLock = new Object();
55 private static Integer sCachedApplicationState;
57 /** Last activity that was shown (or null if none or it was destroyed). */
58 private static Activity sActivity;
60 /** A lazily initialized listener that forwards application state changes to native. */
61 private static ApplicationStateListener sNativeApplicationStateListener;
64 * A map of which observers listen to state changes from which {@link Activity}.
66 private static final Map<Activity, ActivityInfo> sActivityInfo =
67 new ConcurrentHashMap<Activity, ActivityInfo>();
70 * A list of observers to be notified when any {@link Activity} has a state change.
72 private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
73 new ObserverList<ActivityStateListener>();
76 * A list of observers to be notified when the visibility state of this {@link Application}
77 * changes. See {@link #getStateForApplication()}.
79 private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
80 new ObserverList<ApplicationStateListener>();
83 * Interface to be implemented by listeners.
85 public interface ApplicationStateListener {
87 * Called when the application's state changes.
88 * @param newState The application state.
90 public void onApplicationStateChange(int newState);
94 * Interface to be implemented by listeners.
96 public interface ActivityStateListener {
98 * Called when the activity's state changes.
99 * @param activity The activity that had a state change.
100 * @param newState New activity state.
102 public void onActivityStateChange(Activity activity, int newState);
105 private ApplicationStatus() {}
108 * Initializes the activity status for a specified application.
110 * @param application The application whose status you wish to monitor.
112 public static void initialize(Application app) {
115 ApplicationStatusManager.registerWindowFocusChangedListener(
116 new ApplicationStatusManager.WindowFocusChangedListener() {
118 public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
119 if (!hasFocus || activity == sActivity) return;
121 int state = getStateForActivity(activity);
123 if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) {
124 sActivity = activity;
127 // TODO(dtrainor): Notify of active activity change?
131 sApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
133 public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
134 onStateChange(activity, ActivityState.CREATED);
138 public void onActivityDestroyed(Activity activity) {
139 onStateChange(activity, ActivityState.DESTROYED);
143 public void onActivityPaused(Activity activity) {
144 onStateChange(activity, ActivityState.PAUSED);
148 public void onActivityResumed(Activity activity) {
149 onStateChange(activity, ActivityState.RESUMED);
153 public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
156 public void onActivityStarted(Activity activity) {
157 onStateChange(activity, ActivityState.STARTED);
161 public void onActivityStopped(Activity activity) {
162 onStateChange(activity, ActivityState.STOPPED);
168 * Must be called by the main activity when it changes state.
170 * @param activity Current activity.
171 * @param newState New state value.
173 private static void onStateChange(Activity activity, int newState) {
174 if (activity == null) throw new IllegalArgumentException("null activity is not supported");
176 if (sActivity == null
177 || newState == ActivityState.CREATED
178 || newState == ActivityState.RESUMED
179 || newState == ActivityState.STARTED) {
180 sActivity = activity;
183 int oldApplicationState = getStateForApplication();
185 if (newState == ActivityState.CREATED) {
186 assert !sActivityInfo.containsKey(activity);
187 sActivityInfo.put(activity, new ActivityInfo());
190 // Invalidate the cached application state.
191 synchronized (sCachedApplicationStateLock) {
192 sCachedApplicationState = null;
195 ActivityInfo info = sActivityInfo.get(activity);
196 // Ignore status from none tracked activitys.
197 if (info == null) return;
199 info.setStatus(newState);
201 // Notify all state observers that are specifically listening to this activity.
202 for (ActivityStateListener listener : info.getListeners()) {
203 listener.onActivityStateChange(activity, newState);
206 // Notify all state observers that are listening globally for all activity state
208 for (ActivityStateListener listener : sGeneralActivityStateListeners) {
209 listener.onActivityStateChange(activity, newState);
212 int applicationState = getStateForApplication();
213 if (applicationState != oldApplicationState) {
214 for (ApplicationStateListener listener : sApplicationStateListeners) {
215 listener.onApplicationStateChange(applicationState);
219 if (newState == ActivityState.DESTROYED) {
220 sActivityInfo.remove(activity);
221 if (activity == sActivity) sActivity = null;
226 * Testing method to update the state of the specified activity.
228 public static void onStateChangeForTesting(Activity activity, int newState) {
229 onStateChange(activity, newState);
233 * @return The most recent focused {@link Activity} tracked by this class. Being focused means
234 * out of all the activities tracked here, it has most recently gained window focus.
236 public static Activity getLastTrackedFocusedActivity() {
241 * @return A {@link List} of all non-destroyed {@link Activity}s.
243 public static List<WeakReference<Activity>> getRunningActivities() {
244 List<WeakReference<Activity>> activities = new ArrayList<WeakReference<Activity>>();
245 for (Activity activity : sActivityInfo.keySet()) {
246 activities.add(new WeakReference<Activity>(activity));
252 * @return The {@link Context} for the {@link Application}.
254 public static Context getApplicationContext() {
255 return sApplication != null ? sApplication.getApplicationContext() : null;
259 * Query the state for a given activity. If the activity is not being tracked, this will
260 * return {@link ActivityState#DESTROYED}.
263 * Please note that Chrome can have multiple activities running simultaneously. Please also
264 * look at {@link #getStateForApplication()} for more details.
267 * When relying on this method, be familiar with the expected life cycle state
269 * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
274 * During activity transitions (activity B launching in front of activity A), A will completely
275 * paused before the creation of activity B begins.
278 * A basic flow for activity A starting, followed by activity B being opened and then closed:
280 * <li> -- Starting Activity A --
281 * <li> Activity A - ActivityState.CREATED
282 * <li> Activity A - ActivityState.STARTED
283 * <li> Activity A - ActivityState.RESUMED
284 * <li> -- Starting Activity B --
285 * <li> Activity A - ActivityState.PAUSED
286 * <li> Activity B - ActivityState.CREATED
287 * <li> Activity B - ActivityState.STARTED
288 * <li> Activity B - ActivityState.RESUMED
289 * <li> Activity A - ActivityState.STOPPED
290 * <li> -- Closing Activity B, Activity A regaining focus --
291 * <li> Activity B - ActivityState.PAUSED
292 * <li> Activity A - ActivityState.STARTED
293 * <li> Activity A - ActivityState.RESUMED
294 * <li> Activity B - ActivityState.STOPPED
295 * <li> Activity B - ActivityState.DESTROYED
298 * @param activity The activity whose state is to be returned.
299 * @return The state of the specified activity (see {@link ActivityState}).
301 public static int getStateForActivity(Activity activity) {
302 ActivityInfo info = sActivityInfo.get(activity);
303 return info != null ? info.getStatus() : ActivityState.DESTROYED;
307 * @return The state of the application (see {@link ApplicationState}).
309 public static int getStateForApplication() {
310 synchronized (sCachedApplicationStateLock) {
311 if (sCachedApplicationState == null) {
312 sCachedApplicationState = determineApplicationState();
316 return sCachedApplicationState.intValue();
320 * Checks whether or not any Activity in this Application is visible to the user. Note that
321 * this includes the PAUSED state, which can happen when the Activity is temporarily covered
322 * by another Activity's Fragment (e.g.).
323 * @return Whether any Activity under this Application is visible.
325 public static boolean hasVisibleActivities() {
326 int state = getStateForApplication();
327 return state == ApplicationState.HAS_RUNNING_ACTIVITIES
328 || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
332 * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
333 * @return True if all Activities have been destroyed.
335 public static boolean isEveryActivityDestroyed() {
336 return sActivityInfo.isEmpty();
340 * Registers the given listener to receive state changes for all activities.
341 * @param listener Listener to receive state changes.
343 public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
344 sGeneralActivityStateListeners.addObserver(listener);
348 * Registers the given listener to receive state changes for {@code activity}. After a call to
349 * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
350 * {@link ActivityState#DESTROYED} all listeners associated with that particular
351 * {@link Activity} are removed.
352 * @param listener Listener to receive state changes.
353 * @param activity Activity to track or {@code null} to track all activities.
355 public static void registerStateListenerForActivity(ActivityStateListener listener,
357 assert activity != null;
359 ActivityInfo info = sActivityInfo.get(activity);
360 assert info != null && info.getStatus() != ActivityState.DESTROYED;
361 info.getListeners().addObserver(listener);
365 * Unregisters the given listener from receiving activity state changes.
366 * @param listener Listener that doesn't want to receive state changes.
368 public static void unregisterActivityStateListener(ActivityStateListener listener) {
369 sGeneralActivityStateListeners.removeObserver(listener);
371 // Loop through all observer lists for all activities and remove the listener.
372 for (ActivityInfo info : sActivityInfo.values()) {
373 info.getListeners().removeObserver(listener);
378 * Registers the given listener to receive state changes for the application.
379 * @param listener Listener to receive state state changes.
381 public static void registerApplicationStateListener(ApplicationStateListener listener) {
382 sApplicationStateListeners.addObserver(listener);
386 * Unregisters the given listener from receiving state changes.
387 * @param listener Listener that doesn't want to receive state changes.
389 public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
390 sApplicationStateListeners.removeObserver(listener);
394 * When ApplicationStatus initialized after application started, the onActivityCreated(),
395 * onActivityStarted() and onActivityResumed() callbacks will be missed.
396 * This function will give the chance to simulate these three callbacks.
398 public static void informActivityStarted(Activity activity) {
399 onStateChange(activity, ActivityState.CREATED);
400 onStateChange(activity, ActivityState.STARTED);
401 onStateChange(activity, ActivityState.RESUMED);
405 * Registers the single thread-safe native activity status listener.
406 * This handles the case where the caller is not on the main thread.
407 * Note that this is used by a leaky singleton object from the native
408 * side, hence lifecycle management is greatly simplified.
411 private static void registerThreadSafeNativeApplicationStateListener() {
412 ThreadUtils.runOnUiThread(new Runnable () {
415 if (sNativeApplicationStateListener != null) return;
417 sNativeApplicationStateListener = new ApplicationStateListener() {
419 public void onApplicationStateChange(int newState) {
420 nativeOnApplicationStateChange(newState);
423 registerApplicationStateListener(sNativeApplicationStateListener);
429 * Determines the current application state as defined by {@link ApplicationState}. This will
430 * loop over all the activities and check their state to determine what the general application
432 * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
433 * HAS_PAUSED_ACTIVITIES if none are running and one is paused.
434 * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
435 * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
437 private static int determineApplicationState() {
438 boolean hasPausedActivity = false;
439 boolean hasStoppedActivity = false;
441 for (ActivityInfo info : sActivityInfo.values()) {
442 int state = info.getStatus();
443 if (state != ActivityState.PAUSED
444 && state != ActivityState.STOPPED
445 && state != ActivityState.DESTROYED) {
446 return ApplicationState.HAS_RUNNING_ACTIVITIES;
447 } else if (state == ActivityState.PAUSED) {
448 hasPausedActivity = true;
449 } else if (state == ActivityState.STOPPED) {
450 hasStoppedActivity = true;
454 if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
455 if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
456 return ApplicationState.HAS_DESTROYED_ACTIVITIES;
459 // Called to notify the native side of state changes.
460 // IMPORTANT: This is always called on the main thread!
461 private static native void nativeOnApplicationStateChange(int newState);