| Index: base/android/java/src/org/chromium/base/ApplicationStatus.java
|
| diff --git a/base/android/java/src/org/chromium/base/ApplicationStatus.java b/base/android/java/src/org/chromium/base/ApplicationStatus.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9930682f15e9fc3ceabc7e8847caffa2b5f0d962
|
| --- /dev/null
|
| +++ b/base/android/java/src/org/chromium/base/ApplicationStatus.java
|
| @@ -0,0 +1,421 @@
|
| +// Copyright 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.base;
|
| +
|
| +import android.app.Activity;
|
| +import android.app.Application;
|
| +import android.app.Application.ActivityLifecycleCallbacks;
|
| +import android.content.Context;
|
| +import android.os.Bundle;
|
| +
|
| +import java.util.HashMap;
|
| +import java.util.Map;
|
| +
|
| +/**
|
| + * Provides information about the current activity's status, and a way
|
| + * to register / unregister listeners for state changes.
|
| + */
|
| +@JNINamespace("base::android")
|
| +public class ApplicationStatus {
|
| + private static class ActivityInfo {
|
| + private int mStatus = ActivityState.DESTROYED;
|
| + private ObserverList<ActivityStateListener> mListeners =
|
| + new ObserverList<ActivityStateListener>();
|
| +
|
| + /**
|
| + * @return The current {@link ActivityState} of the activity.
|
| + */
|
| + public int getStatus() {
|
| + return mStatus;
|
| + }
|
| +
|
| + /**
|
| + * @param status The new {@link ActivityState} of the activity.
|
| + */
|
| + public void setStatus(int status) {
|
| + mStatus = status;
|
| + }
|
| +
|
| + /**
|
| + * @return A list of {@link ActivityStateListener}s listening to this activity.
|
| + */
|
| + public ObserverList<ActivityStateListener> getListeners() {
|
| + return mListeners;
|
| + }
|
| + }
|
| +
|
| + private static Application sApplication;
|
| +
|
| + private static Integer sCachedApplicationState;
|
| +
|
| + /** Last activity that was shown (or null if none or it was destroyed). */
|
| + private static Activity sActivity;
|
| +
|
| + /**
|
| + *
|
| + */
|
| + private static final Map<Activity, ActivityInfo> sActivityInfo =
|
| + new HashMap<Activity, ActivityInfo>();
|
| +
|
| + /**
|
| + *
|
| + */
|
| + private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
|
| + new ObserverList<ActivityStateListener>();
|
| +
|
| + /**
|
| + * A list of observers to be notified when the visibility state of this {@link Application}
|
| + * changes. See {@link #getStateForApplication()}.
|
| + */
|
| + private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
|
| + new ObserverList<ApplicationStateListener>();
|
| +
|
| + /**
|
| + * Interface to be implemented by listeners.
|
| + */
|
| + public interface ApplicationStateListener {
|
| + /**
|
| + * Called when the application's state changes.
|
| + * @param newState The application state.
|
| + */
|
| + public void onApplicationStateChange(int newState);
|
| + }
|
| +
|
| + /**
|
| + * Interface to be implemented by listeners.
|
| + */
|
| + public interface ActivityStateListener {
|
| + /**
|
| + * Called when the activity's state changes.
|
| + * @param activity The activity that had a state change.
|
| + * @param newState New activity state.
|
| + */
|
| + public void onActivityStateChange(Activity activity, int newState);
|
| + }
|
| +
|
| + private ApplicationStatus() {}
|
| +
|
| + /**
|
| + * Initializes the activity status for a specified application.
|
| + *
|
| + * @param application The application whose status you wish to monitor.
|
| + */
|
| + public static void initialize(BaseChromiumApplication application) {
|
| + sApplication = application;
|
| +
|
| + application.registerWindowFocusChangedListener(
|
| + new BaseChromiumApplication.WindowFocusChangedListener() {
|
| + @Override
|
| + public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
|
| + if (!hasFocus || activity == sActivity) return;
|
| +
|
| + int state = getStateForActivity(activity);
|
| +
|
| + if (state != ActivityState.DESTROYED && state != ActivityState.STOPPED) {
|
| + sActivity = activity;
|
| + }
|
| +
|
| + // TODO(dtrainor): Notify of active activity change?
|
| + }
|
| + });
|
| +
|
| + application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
|
| + @Override
|
| + public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
|
| + onStateChange(activity, ActivityState.CREATED);
|
| + }
|
| +
|
| + @Override
|
| + public void onActivityDestroyed(Activity activity) {
|
| + onStateChange(activity, ActivityState.DESTROYED);
|
| + }
|
| +
|
| + @Override
|
| + public void onActivityPaused(Activity activity) {
|
| + onStateChange(activity, ActivityState.PAUSED);
|
| + }
|
| +
|
| + @Override
|
| + public void onActivityResumed(Activity activity) {
|
| + onStateChange(activity, ActivityState.RESUMED);
|
| + }
|
| +
|
| + @Override
|
| + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
|
| +
|
| + @Override
|
| + public void onActivityStarted(Activity activity) {
|
| + onStateChange(activity, ActivityState.STARTED);
|
| + }
|
| +
|
| + @Override
|
| + public void onActivityStopped(Activity activity) {
|
| + onStateChange(activity, ActivityState.STOPPED);
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Must be called by the main activity when it changes state.
|
| + *
|
| + * @param activity Current activity.
|
| + * @param newState New state value.
|
| + */
|
| + private static void onStateChange(Activity activity, int newState) {
|
| + if (activity == null) throw new IllegalArgumentException("null activity is not supported");
|
| +
|
| + if (sActivity == null
|
| + || newState == ActivityState.CREATED
|
| + || newState == ActivityState.RESUMED
|
| + || newState == ActivityState.STARTED) {
|
| + sActivity = activity;
|
| + }
|
| +
|
| + int oldApplicationState = getStateForApplication();
|
| +
|
| + if (newState == ActivityState.CREATED) {
|
| + assert !sActivityInfo.containsKey(activity);
|
| + sActivityInfo.put(activity, new ActivityInfo());
|
| + }
|
| +
|
| + // Invalidate the cached application state.
|
| + sCachedApplicationState = null;
|
| +
|
| + ActivityInfo info = sActivityInfo.get(activity);
|
| + info.setStatus(newState);
|
| +
|
| + // Notify all state observers that are specifically listening to this activity.
|
| + for (ActivityStateListener listener : info.getListeners()) {
|
| + listener.onActivityStateChange(activity, newState);
|
| + }
|
| +
|
| + // Notify all state observers that are listening globally for all activity state
|
| + // changes.
|
| + for (ActivityStateListener listener : sGeneralActivityStateListeners) {
|
| + listener.onActivityStateChange(activity, newState);
|
| + }
|
| +
|
| + int applicationState = getStateForApplication();
|
| + if (applicationState != oldApplicationState) {
|
| + for (ApplicationStateListener listener : sApplicationStateListeners) {
|
| + listener.onApplicationStateChange(applicationState);
|
| + }
|
| + }
|
| +
|
| + if (newState == ActivityState.DESTROYED) {
|
| + sActivityInfo.remove(activity);
|
| + if (activity == sActivity) sActivity = null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Testing method to update the state of the specified activity.
|
| + */
|
| + public static void onStateChangeForTesting(Activity activity, int newState) {
|
| + onStateChange(activity, newState);
|
| + }
|
| +
|
| + /**
|
| + * @return The most recent focused {@link Activity} tracked by this class. Being focused means
|
| + * out of all the activities tracked here, it has most recently gained window focus.
|
| + */
|
| + public static Activity getLastTrackedFocusedActivity() {
|
| + return sActivity;
|
| + }
|
| +
|
| + /**
|
| + * @return The {@link Context} for the {@link Application}.
|
| + */
|
| + public static Context getApplicationContext() {
|
| + return sApplication != null ? sApplication.getApplicationContext() : null;
|
| + }
|
| +
|
| + /**
|
| + * Query the state for a given activity. If the activity is not being tracked, this will
|
| + * return {@link ActivityState#DESTROYED}.
|
| + *
|
| + * <p>
|
| + * Please note that Chrome can have multiple activities running simultaneously. Please also
|
| + * look at {@link #getStateForApplication()} for more details.
|
| + *
|
| + * <p>
|
| + * When relying on this method, be familiar with the expected life cycle state
|
| + * transitions:
|
| + * <a href="http://developer.android.com/guide/components/activities.html#Lifecycle">
|
| + * Activity Lifecycle
|
| + * </a>
|
| + *
|
| + * <p>
|
| + * During activity transitions (activity B launching in front of activity A), A will completely
|
| + * paused before the creation of activity B begins.
|
| + *
|
| + * <p>
|
| + * A basic flow for activity A starting, followed by activity B being opened and then closed:
|
| + * <ul>
|
| + * <li> -- Starting Activity A --
|
| + * <li> Activity A - ActivityState.CREATED
|
| + * <li> Activity A - ActivityState.STARTED
|
| + * <li> Activity A - ActivityState.RESUMED
|
| + * <li> -- Starting Activity B --
|
| + * <li> Activity A - ActivityState.PAUSED
|
| + * <li> Activity B - ActivityState.CREATED
|
| + * <li> Activity B - ActivityState.STARTED
|
| + * <li> Activity B - ActivityState.RESUMED
|
| + * <li> Activity A - ActivityState.STOPPED
|
| + * <li> -- Closing Activity B, Activity A regaining focus --
|
| + * <li> Activity B - ActivityState.PAUSED
|
| + * <li> Activity A - ActivityState.STARTED
|
| + * <li> Activity A - ActivityState.RESUMED
|
| + * <li> Activity B - ActivityState.STOPPED
|
| + * <li> Activity B - ActivityState.DESTROYED
|
| + * </ul>
|
| + *
|
| + * @param activity The activity whose state is to be returned.
|
| + * @return The state of the specified activity (see {@link ActivityState}).
|
| + */
|
| + public static int getStateForActivity(Activity activity) {
|
| + ActivityInfo info = sActivityInfo.get(activity);
|
| + return info != null ? info.getStatus() : ActivityState.DESTROYED;
|
| + }
|
| +
|
| + /**
|
| + * @return The state of the application (see {@link ApplicationState}).
|
| + */
|
| + public static int getStateForApplication() {
|
| + if (sCachedApplicationState == null) sCachedApplicationState = determineApplicationState();
|
| +
|
| + return sCachedApplicationState.intValue();
|
| + }
|
| +
|
| + /**
|
| + * Checks whether or not any Activity in this Application is visible to the user. Note that
|
| + * this includes the PAUSED state, which can happen when the Activity is temporarily covered
|
| + * by another Activity's Fragment (e.g.).
|
| + * @return Whether any Activity under this Application is visible.
|
| + */
|
| + public static boolean hasVisibleActivities() {
|
| + int state = getStateForApplication();
|
| + return state == ApplicationState.HAS_RUNNING_ACTIVITIES
|
| + || state == ApplicationState.HAS_PAUSED_ACTIVITIES;
|
| + }
|
| +
|
| + /**
|
| + * Checks to see if there are any active Activity instances being watched by ApplicationStatus.
|
| + * @return True if all Activities have been destroyed.
|
| + */
|
| + public static boolean isEveryActivityDestroyed() {
|
| + return sActivityInfo.isEmpty();
|
| + }
|
| +
|
| + /**
|
| + * Registers the given listener to receive state changes for all activities.
|
| + * @param listener Listener to receive state changes.
|
| + */
|
| + public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
|
| + sGeneralActivityStateListeners.addObserver(listener);
|
| + }
|
| +
|
| + /**
|
| + * Registers the given listener to receive state changes for {@code activity}. After a call to
|
| + * {@link ActivityStateListener#onActivityStateChange(Activity, int)} with
|
| + * {@link ActivityState#DESTROYED} all listeners associated with that particular
|
| + * {@link Activity} are removed.
|
| + * @param listener Listener to receive state changes.
|
| + * @param activity Activity to track or {@code null} to track all activities.
|
| + */
|
| + public static void registerStateListenerForActivity(ActivityStateListener listener,
|
| + Activity activity) {
|
| + assert activity != null;
|
| +
|
| + ActivityInfo info = sActivityInfo.get(activity);
|
| + assert info != null && info.getStatus() != ActivityState.DESTROYED;
|
| + info.getListeners().addObserver(listener);
|
| + }
|
| +
|
| + /**
|
| + * Unregisters the given listener from receiving activity state changes.
|
| + * @param listener Listener that doesn't want to receive state changes.
|
| + */
|
| + public static void unregisterActivityStateListener(ActivityStateListener listener) {
|
| + sGeneralActivityStateListeners.removeObserver(listener);
|
| +
|
| + // Loop through all observer lists for all activities and remove the listener.
|
| + for (ActivityInfo info : sActivityInfo.values()) {
|
| + info.getListeners().removeObserver(listener);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Registers the given listener to receive state changes for the application.
|
| + * @param listener Listener to receive state state changes.
|
| + */
|
| + public static void registerApplicationStateListener(ApplicationStateListener listener) {
|
| + sApplicationStateListeners.addObserver(listener);
|
| + }
|
| +
|
| + /**
|
| + * Unregisters the given listener from receiving state changes.
|
| + * @param listener Listener that doesn't want to receive state changes.
|
| + */
|
| + public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
|
| + sApplicationStateListeners.removeObserver(listener);
|
| + }
|
| +
|
| + /**
|
| + * Registers the single thread-safe native activity status listener.
|
| + * This handles the case where the caller is not on the main thread.
|
| + * Note that this is used by a leaky singleton object from the native
|
| + * side, hence lifecycle management is greatly simplified.
|
| + */
|
| + @CalledByNative
|
| + private static void registerThreadSafeNativeApplicationStateListener() {
|
| + ThreadUtils.runOnUiThread(new Runnable () {
|
| + @Override
|
| + public void run() {
|
| + registerApplicationStateListener(new ApplicationStateListener() {
|
| + @Override
|
| + public void onApplicationStateChange(int newState) {
|
| + nativeOnApplicationStateChange(newState);
|
| + }
|
| + });
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Determines the current application state as defined by {@link ApplicationState}. This will
|
| + * loop over all the activities and check their state to determine what the general application
|
| + * state should be.
|
| + * @return HAS_RUNNING_ACTIVITIES if any activity is not paused, stopped, or destroyed.
|
| + * HAS_PAUSED_ACTIVITIES if none are running and one is paused.
|
| + * HAS_STOPPED_ACTIVITIES if none are running/paused and one is stopped.
|
| + * HAS_DESTROYED_ACTIVITIES if none are running/paused/stopped.
|
| + */
|
| + private static int determineApplicationState() {
|
| + boolean hasPausedActivity = false;
|
| + boolean hasStoppedActivity = false;
|
| +
|
| + for (ActivityInfo info : sActivityInfo.values()) {
|
| + int state = info.getStatus();
|
| + if (state != ActivityState.PAUSED
|
| + && state != ActivityState.STOPPED
|
| + && state != ActivityState.DESTROYED) {
|
| + return ApplicationState.HAS_RUNNING_ACTIVITIES;
|
| + } else if (state == ActivityState.PAUSED) {
|
| + hasPausedActivity = true;
|
| + } else if (state == ActivityState.STOPPED) {
|
| + hasStoppedActivity = true;
|
| + }
|
| + }
|
| +
|
| + if (hasPausedActivity) return ApplicationState.HAS_PAUSED_ACTIVITIES;
|
| + if (hasStoppedActivity) return ApplicationState.HAS_STOPPED_ACTIVITIES;
|
| + return ApplicationState.HAS_DESTROYED_ACTIVITIES;
|
| + }
|
| +
|
| + // Called to notify the native side of state changes.
|
| + // IMPORTANT: This is always called on the main thread!
|
| + private static native void nativeOnApplicationStateChange(int newState);
|
| +}
|
|
|