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.HashMap;
16 import java.util.List;
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 Integer sCachedApplicationState;
56 /** Last activity that was shown (or null if none or it was destroyed). */
57 private static Activity sActivity;
59 /** A lazily initialized listener that forwards application state changes to native. */
60 private static ApplicationStateListener sNativeApplicationStateListener;
63 * A map of which observers listen to state changes from which {@link Activity}.
65 private static final Map<Activity, ActivityInfo> sActivityInfo =
66 new HashMap<Activity, ActivityInfo>();
69 * A list of observers to be notified when any {@link Activity} has a state change.
71 private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
72 new ObserverList<ActivityStateListener>();
75 * A list of observers to be notified when the visibility state of this {@link Application}
76 * changes. See {@link #getStateForApplication()}.
78 private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
79 new ObserverList<ApplicationStateListener>();
82 * Interface to be implemented by listeners.
84 public interface ApplicationStateListener {
86 * Called when the application's state changes.
87 * @param newState The application state.
89 public void onApplicationStateChange(int newState);
93 * Interface to be implemented by listeners.
95 public interface ActivityStateListener {
97 * Called when the activity's state changes.
98 * @param activity The activity that had a state change.
99 * @param newState New activity state.
101 public void onActivityStateChange(Activity activity, int newState);
104 private ApplicationStatus() {}
107 * Initializes the activity status for a specified application.
109 * @param application The application whose status you wish to monitor.
111 public static void initialize(Application app) {
114 ApplicationStatusManager.registerWindowFocusChangedListener(
115 new ApplicationStatusManager.WindowFocusChangedListener() {
117 public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
118 if (!hasFocus || activity == sActivity) return;
120 int state = getStateForActivity(activity);
122 if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) {
123 sActivity = activity;
126 // TODO(dtrainor): Notify of active activity change?
130 sApplication.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
132 public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
133 onStateChange(activity, ActivityState.CREATED);
137 public void onActivityDestroyed(Activity activity) {
138 onStateChange(activity, ActivityState.DESTROYED);
142 public void onActivityPaused(Activity activity) {
143 onStateChange(activity, ActivityState.PAUSED);
147 public void onActivityResumed(Activity activity) {
148 onStateChange(activity, ActivityState.RESUMED);
152 public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
155 public void onActivityStarted(Activity activity) {
156 onStateChange(activity, ActivityState.STARTED);
160 public void onActivityStopped(Activity activity) {
161 onStateChange(activity, ActivityState.STOPPED);
167 * Must be called by the main activity when it changes state.
169 * @param activity Current activity.
170 * @param newState New state value.
172 private static void onStateChange(Activity activity, int newState) {
173 if (activity == null) throw new IllegalArgumentException("null activity is not supported");
175 if (sActivity == null
176 || newState == ActivityState.CREATED
177 || newState == ActivityState.RESUMED
178 || newState == ActivityState.STARTED) {
179 sActivity = activity;
182 int oldApplicationState = getStateForApplication();
184 if (newState == ActivityState.CREATED) {
185 assert !sActivityInfo.containsKey(activity);
186 sActivityInfo.put(activity, new ActivityInfo());
189 // Invalidate the cached application state.
190 sCachedApplicationState = null;
192 ActivityInfo info = sActivityInfo.get(activity);
193 // Ignore status from none tracked activitys.
194 if (info == null) return;
196 info.setStatus(newState);
198 // Notify all state observers that are specifically listening to this activity.
199 for (ActivityStateListener listener : info.getListeners()) {
200 listener.onActivityStateChange(activity, newState);
203 // Notify all state observers that are listening globally for all activity state
205 for (ActivityStateListener listener : sGeneralActivityStateListeners) {
206 listener.onActivityStateChange(activity, newState);
209 int applicationState = getStateForApplication();
210 if (applicationState != oldApplicationState) {
211 for (ApplicationStateListener listener : sApplicationStateListeners) {
212 listener.onApplicationStateChange(applicationState);
216 if (newState == ActivityState.DESTROYED) {
217 sActivityInfo.remove(activity);
218 if (activity == sActivity) sActivity = null;
223 * Testing method to update the state of the specified activity.
225 public static void onStateChangeForTesting(Activity activity, int newState) {
226 onStateChange(activity, newState);
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.
233 public static Activity getLastTrackedFocusedActivity() {
238 * @return A {@link List} of all non-destroyed {@link Activity}s.
240 public static List<WeakReference<Activity>> getRunningActivities() {
241 ThreadUtils.assertOnUiThread();
242 List<WeakReference<Activity>> activities = new ArrayList<WeakReference<Activity>>();
243 for (Activity activity : sActivityInfo.keySet()) {
244 activities.add(new WeakReference<Activity>(activity));
250 * @return The {@link Context} for the {@link Application}.
252 public static Context getApplicationContext() {
253 return sApplication != null ? sApplication.getApplicationContext() : null;
257 * Query the state for a given activity. If the activity is not being tracked, this will
258 * return {@link ActivityState#DESTROYED}.
261 * Please note that Chrome can have multiple activities running simultaneously. Please also
262 * look at {@link #getStateForApplication()} for more details.
265 * When relying on this method, be familiar with the expected life cycle state
267 * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
272 * During activity transitions (activity B launching in front of activity A), A will completely
273 * paused before the creation of activity B begins.
276 * A basic flow for activity A starting, followed by activity B being opened and then closed:
278 * <li> -- Starting Activity A --
279 * <li> Activity A - ActivityState.CREATED
280 * <li> Activity A - ActivityState.STARTED
281 * <li> Activity A - ActivityState.RESUMED
282 * <li> -- Starting Activity B --
283 * <li> Activity A - ActivityState.PAUSED
284 * <li> Activity B - ActivityState.CREATED
285 * <li> Activity B - ActivityState.STARTED
286 * <li> Activity B - ActivityState.RESUMED
287 * <li> Activity A - ActivityState.STOPPED
288 * <li> -- Closing Activity B, Activity A regaining focus --
289 * <li> Activity B - ActivityState.PAUSED
290 * <li> Activity A - ActivityState.STARTED
291 * <li> Activity A - ActivityState.RESUMED
292 * <li> Activity B - ActivityState.STOPPED
293 * <li> Activity B - ActivityState.DESTROYED
296 * @param activity The activity whose state is to be returned.
297 * @return The state of the specified activity (see {@link ActivityState}).
299 public static int getStateForActivity(Activity activity) {
300 ActivityInfo info = sActivityInfo.get(activity);
301 return info != null ? info.getStatus() : ActivityState.DESTROYED;
305 * @return The state of the application (see {@link ApplicationState}).
307 public static int getStateForApplication() {
308 if (sCachedApplicationState == null) sCachedApplicationState = determineApplicationState();
310 return sCachedApplicationState.intValue();
314 * Checks whether or not any Activity in this Application is visible to the user. Note that
315 * this includes the PAUSED state, which can happen when the Activity is temporarily covered
316 * by another Activity's Fragment (e.g.).
317 * @return Whether any Activity under this Application is visible.
319 public static boolean hasVisibleActivities() {
320 int state = getStateForApplication();
321 return state == ApplicationState.HAS_RUNNING_ACTIVITIES
322 || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
326 * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
327 * @return True if all Activities have been destroyed.
329 public static boolean isEveryActivityDestroyed() {
330 return sActivityInfo.isEmpty();
334 * Registers the given listener to receive state changes for all activities.
335 * @param listener Listener to receive state changes.
337 public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
338 sGeneralActivityStateListeners.addObserver(listener);
342 * Registers the given listener to receive state changes for {@code activity}. After a call to
343 * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
344 * {@link ActivityState#DESTROYED} all listeners associated with that particular
345 * {@link Activity} are removed.
346 * @param listener Listener to receive state changes.
347 * @param activity Activity to track or {@code null} to track all activities.
349 public static void registerStateListenerForActivity(ActivityStateListener listener,
351 assert activity != null;
353 ActivityInfo info = sActivityInfo.get(activity);
354 assert info != null && info.getStatus() != ActivityState.DESTROYED;
355 info.getListeners().addObserver(listener);
359 * Unregisters the given listener from receiving activity state changes.
360 * @param listener Listener that doesn't want to receive state changes.
362 public static void unregisterActivityStateListener(ActivityStateListener listener) {
363 sGeneralActivityStateListeners.removeObserver(listener);
365 // Loop through all observer lists for all activities and remove the listener.
366 for (ActivityInfo info : sActivityInfo.values()) {
367 info.getListeners().removeObserver(listener);
372 * Registers the given listener to receive state changes for the application.
373 * @param listener Listener to receive state state changes.
375 public static void registerApplicationStateListener(ApplicationStateListener listener) {
376 sApplicationStateListeners.addObserver(listener);
380 * Unregisters the given listener from receiving state changes.
381 * @param listener Listener that doesn't want to receive state changes.
383 public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
384 sApplicationStateListeners.removeObserver(listener);
388 * When ApplicationStatus initialized after application started, the onActivityCreated(),
389 * onActivityStarted() and onActivityResumed() callbacks will be missed.
390 * This function will give the chance to simulate these three callbacks.
392 public static void informActivityStarted(Activity activity) {
393 onStateChange(activity, ActivityState.CREATED);
394 onStateChange(activity, ActivityState.STARTED);
395 onStateChange(activity, ActivityState.RESUMED);
399 * Registers the single thread-safe native activity status listener.
400 * This handles the case where the caller is not on the main thread.
401 * Note that this is used by a leaky singleton object from the native
402 * side, hence lifecycle management is greatly simplified.
405 private static void registerThreadSafeNativeApplicationStateListener() {
406 ThreadUtils.runOnUiThread(new Runnable () {
409 if (sNativeApplicationStateListener != null) return;
411 sNativeApplicationStateListener = new ApplicationStateListener() {
413 public void onApplicationStateChange(int newState) {
414 nativeOnApplicationStateChange(newState);
417 registerApplicationStateListener(sNativeApplicationStateListener);
423 * Determines the current application state as defined by {@link ApplicationState}. This will
424 * loop over all the activities and check their state to determine what the general application
426 * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
427 * HAS_PAUSED_ACTIVITIES if none are running and one is paused.
428 * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
429 * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
431 private static int determineApplicationState() {
432 boolean hasPausedActivity = false;
433 boolean hasStoppedActivity = false;
435 for (ActivityInfo info : sActivityInfo.values()) {
436 int state = info.getStatus();
437 if (state != ActivityState.PAUSED
438 && state != ActivityState.STOPPED
439 && state != ActivityState.DESTROYED) {
440 return ApplicationState.HAS_RUNNING_ACTIVITIES;
441 } else if (state == ActivityState.PAUSED) {
442 hasPausedActivity = true;
443 } else if (state == ActivityState.STOPPED) {
444 hasStoppedActivity = true;
448 if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
449 if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
450 return ApplicationState.HAS_DESTROYED_ACTIVITIES;
453 // Called to notify the native side of state changes.
454 // IMPORTANT: This is always called on the main thread!
455 private static native void nativeOnApplicationStateChange(int newState);