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.
229 public static void onStateChangeForTesting(Activity activity, int newState) {
230 onStateChange(activity, newState);
234 * @return The most recent focused {@link Activity} tracked by this class. Being focused means
235 * out of all the activities tracked here, it has most recently gained window focus.
237 public static Activity getLastTrackedFocusedActivity() {
242 * @return A {@link List} of all non-destroyed {@link Activity}s.
244 public static List<WeakReference<Activity>> getRunningActivities() {
245 List<WeakReference<Activity>> activities = new ArrayList<WeakReference<Activity>>();
246 for (Activity activity : sActivityInfo.keySet()) {
247 activities.add(new WeakReference<Activity>(activity));
253 * @return The {@link Context} for the {@link Application}.
255 public static Context getApplicationContext() {
256 return sApplication != null ? sApplication.getApplicationContext() : null;
260 * Query the state for a given activity. If the activity is not being tracked, this will
261 * return {@link ActivityState#DESTROYED}.
264 * Please note that Chrome can have multiple activities running simultaneously. Please also
265 * look at {@link #getStateForApplication()} for more details.
268 * When relying on this method, be familiar with the expected life cycle state
270 * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
275 * During activity transitions (activity B launching in front of activity A), A will completely
276 * paused before the creation of activity B begins.
279 * A basic flow for activity A starting, followed by activity B being opened and then closed:
281 * <li> -- Starting Activity A --
282 * <li> Activity A - ActivityState.CREATED
283 * <li> Activity A - ActivityState.STARTED
284 * <li> Activity A - ActivityState.RESUMED
285 * <li> -- Starting Activity B --
286 * <li> Activity A - ActivityState.PAUSED
287 * <li> Activity B - ActivityState.CREATED
288 * <li> Activity B - ActivityState.STARTED
289 * <li> Activity B - ActivityState.RESUMED
290 * <li> Activity A - ActivityState.STOPPED
291 * <li> -- Closing Activity B, Activity A regaining focus --
292 * <li> Activity B - ActivityState.PAUSED
293 * <li> Activity A - ActivityState.STARTED
294 * <li> Activity A - ActivityState.RESUMED
295 * <li> Activity B - ActivityState.STOPPED
296 * <li> Activity B - ActivityState.DESTROYED
299 * @param activity The activity whose state is to be returned.
300 * @return The state of the specified activity (see {@link ActivityState}).
302 public static int getStateForActivity(Activity activity) {
303 ActivityInfo info = sActivityInfo.get(activity);
304 return info != null ? info.getStatus() : ActivityState.DESTROYED;
308 * @return The state of the application (see {@link ApplicationState}).
310 public static int getStateForApplication() {
311 synchronized (sCachedApplicationStateLock) {
312 if (sCachedApplicationState == null) {
313 sCachedApplicationState = determineApplicationState();
317 return sCachedApplicationState.intValue();
321 * Checks whether or not any Activity in this Application is visible to the user. Note that
322 * this includes the PAUSED state, which can happen when the Activity is temporarily covered
323 * by another Activity's Fragment (e.g.).
324 * @return Whether any Activity under this Application is visible.
326 public static boolean hasVisibleActivities() {
327 int state = getStateForApplication();
328 return state == ApplicationState.HAS_RUNNING_ACTIVITIES
329 || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
333 * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
334 * @return True if all Activities have been destroyed.
336 public static boolean isEveryActivityDestroyed() {
337 return sActivityInfo.isEmpty();
341 * Registers the given listener to receive state changes for all activities.
342 * @param listener Listener to receive state changes.
344 public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
345 sGeneralActivityStateListeners.addObserver(listener);
349 * Registers the given listener to receive state changes for {@code activity}. After a call to
350 * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
351 * {@link ActivityState#DESTROYED} all listeners associated with that particular
352 * {@link Activity} are removed.
353 * @param listener Listener to receive state changes.
354 * @param activity Activity to track or {@code null} to track all activities.
356 public static void registerStateListenerForActivity(ActivityStateListener listener,
358 assert activity != null;
360 ActivityInfo info = sActivityInfo.get(activity);
361 assert info != null && info.getStatus() != ActivityState.DESTROYED;
362 info.getListeners().addObserver(listener);
366 * Unregisters the given listener from receiving activity state changes.
367 * @param listener Listener that doesn't want to receive state changes.
369 public static void unregisterActivityStateListener(ActivityStateListener listener) {
370 sGeneralActivityStateListeners.removeObserver(listener);
372 // Loop through all observer lists for all activities and remove the listener.
373 for (ActivityInfo info : sActivityInfo.values()) {
374 info.getListeners().removeObserver(listener);
379 * Registers the given listener to receive state changes for the application.
380 * @param listener Listener to receive state state changes.
382 public static void registerApplicationStateListener(ApplicationStateListener listener) {
383 sApplicationStateListeners.addObserver(listener);
387 * Unregisters the given listener from receiving state changes.
388 * @param listener Listener that doesn't want to receive state changes.
390 public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
391 sApplicationStateListeners.removeObserver(listener);
395 * When ApplicationStatus initialized after application started, the onActivityCreated(),
396 * onActivityStarted() and onActivityResumed() callbacks will be missed.
397 * This function will give the chance to simulate these three callbacks.
399 public static void informActivityStarted(Activity activity) {
400 onStateChange(activity, ActivityState.CREATED);
401 onStateChange(activity, ActivityState.STARTED);
402 onStateChange(activity, ActivityState.RESUMED);
406 * Registers the single thread-safe native activity status listener.
407 * This handles the case where the caller is not on the main thread.
408 * Note that this is used by a leaky singleton object from the native
409 * side, hence lifecycle management is greatly simplified.
412 private static void registerThreadSafeNativeApplicationStateListener() {
413 ThreadUtils.runOnUiThread(new Runnable () {
416 if (sNativeApplicationStateListener != null) return;
418 sNativeApplicationStateListener = new ApplicationStateListener() {
420 public void onApplicationStateChange(int newState) {
421 nativeOnApplicationStateChange(newState);
424 registerApplicationStateListener(sNativeApplicationStateListener);
430 * Determines the current application state as defined by {@link ApplicationState}. This will
431 * loop over all the activities and check their state to determine what the general application
433 * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
434 * HAS_PAUSED_ACTIVITIES if none are running and one is paused.
435 * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
436 * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
438 private static int determineApplicationState() {
439 boolean hasPausedActivity = false;
440 boolean hasStoppedActivity = false;
442 for (ActivityInfo info : sActivityInfo.values()) {
443 int state = info.getStatus();
444 if (state != ActivityState.PAUSED
445 && state != ActivityState.STOPPED
446 && state != ActivityState.DESTROYED) {
447 return ApplicationState.HAS_RUNNING_ACTIVITIES;
448 } else if (state == ActivityState.PAUSED) {
449 hasPausedActivity = true;
450 } else if (state == ActivityState.STOPPED) {
451 hasStoppedActivity = true;
455 if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
456 if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
457 return ApplicationState.HAS_DESTROYED_ACTIVITIES;
460 // Called to notify the native side of state changes.
461 // IMPORTANT: This is always called on the main thread!
462 private static native void nativeOnApplicationStateChange(int newState);