Index: base/android/java/src/org/chromium/base/ActivityStatus.java |
diff --git a/base/android/java/src/org/chromium/base/ActivityStatus.java b/base/android/java/src/org/chromium/base/ActivityStatus.java |
index df43dbebfbb9bf95df55fbb72607ecd9a1f85ff2..300a60cb510eac54d126ca6753b50d2861924eb4 100644 |
--- a/base/android/java/src/org/chromium/base/ActivityStatus.java |
+++ b/base/android/java/src/org/chromium/base/ActivityStatus.java |
@@ -7,6 +7,7 @@ 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; |
@@ -29,24 +30,88 @@ public class ActivityStatus { |
public static final int STOPPED = ActivityState.STOPPED; |
public static final int DESTROYED = ActivityState.DESTROYED; |
- // Last activity that was shown (or null if none or it was destroyed). |
+ public static final int HAS_RUNNING_ACTIVITIES = ApplicationState.HAS_RUNNING_ACTIVITIES; |
+ public static final int HAS_ONLY_PAUSED_ACTIVITIES = |
+ ApplicationState.HAS_ONLY_PAUSED_ACTIVITIES; |
+ public static final int HAS_ONLY_STOPPED_ACTIVITIES = |
+ ApplicationState.HAS_ONLY_STOPPED_ACTIVITIES; |
+ public static final int HAS_ONLY_DESTROYED_ACTIVITIES = |
+ ApplicationState.HAS_ONLY_DESTROYED_ACTIVITIES; |
bulach
2014/02/17 12:04:01
are these needed? can't we use ApplicationState di
David Trainor- moved to gerrit
2014/02/18 19:54:15
Done.
|
+ |
+ private static class ActivityInfo { |
+ private int mStatus = 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, Integer> sActivityStates = |
- new HashMap<Activity, Integer>(); |
+ /** |
+ * |
+ */ |
+ 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>(); |
- private static final ObserverList<StateListener> sStateListeners = |
- new ObserverList<StateListener>(); |
+ /** |
+ * 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 StateListener { |
+ 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(int newState); |
+ public void onActivityStateChange(Activity activity, int newState); |
} |
private ActivityStatus() {} |
@@ -56,10 +121,28 @@ public class ActivityStatus { |
* |
* @param application The application whose status you wish to monitor. |
*/ |
- public static void initialize(Application application) { |
+ 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 != DESTROYED && state != STOPPED) { |
+ sActivity = activity; |
+ } |
+ |
+ // TODO(dtrainor): Notify of active activity change? |
+ } |
+ }); |
+ |
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { |
@Override |
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) { |
+ public void onActivityCreated(final Activity activity, Bundle savedInstanceState) { |
onStateChange(activity, CREATED); |
} |
@@ -102,32 +185,46 @@ public class ActivityStatus { |
private static void onStateChange(Activity activity, int newState) { |
if (activity == null) throw new IllegalArgumentException("null activity is not supported"); |
- if (sActivity != activity) { |
- // ActivityStatus is notified with the CREATED event very late during the main activity |
- // creation to avoid making startup performance worse than it is by notifying observers |
- // that could do some expensive work. This can lead to non-CREATED events being fired |
- // before the CREATED event which is problematic. |
- // TODO(pliard): fix http://crbug.com/176837. |
- if (sActivity == null |
- || newState == CREATED || newState == RESUMED || newState == STARTED) { |
- sActivity = activity; |
- } |
+ if (sActivity == null |
+ || newState == CREATED || newState == RESUMED || newState == STARTED) { |
+ sActivity = activity; |
} |
- if (newState != DESTROYED) { |
- sActivityStates.put(activity, newState); |
- } else { |
- sActivityStates.remove(activity); |
+ int oldApplicationState = getStateForApplication(); |
+ |
+ if (newState == CREATED) { |
+ assert !sActivityInfo.containsKey(activity); |
+ sActivityInfo.put(activity, new ActivityInfo()); |
} |
- if (sActivity == activity) { |
- for (StateListener listener : sStateListeners) { |
- listener.onActivityStateChange(newState); |
- } |
- if (newState == DESTROYED) { |
- sActivity = null; |
+ // 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 == DESTROYED) { |
+ sActivityInfo.remove(activity); |
+ if (activity == sActivity) sActivity = null; |
+ } |
} |
/** |
@@ -138,18 +235,18 @@ public class ActivityStatus { |
} |
/** |
- * @return The current activity. |
+ * @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 getActivity() { |
+ public static Activity getLastTrackedFocusedActivity() { |
return sActivity; |
} |
/** |
- * @return The current activity's state (if no activity is registered, then DESTROYED will |
- * be returned). |
+ * @return The {@link Context} for the {@link Application}. |
*/ |
- public static int getState() { |
- return getStateForActivity(sActivity); |
+ public static Context getApplicationContext() { |
+ return sApplication != null ? sApplication.getApplicationContext() : null; |
} |
/** |
@@ -189,27 +286,93 @@ public class ActivityStatus { |
* </ul> |
* |
* @param activity The activity whose state is to be returned. |
- * @return The state of the specified activity. |
+ * @return The state of the specified activity (see {@link ActivityState}). |
*/ |
public static int getStateForActivity(Activity activity) { |
- Integer currentStatus = sActivityStates.get(activity); |
- return currentStatus != null ? currentStatus.intValue() : DESTROYED; |
+ ActivityInfo info = sActivityInfo.get(activity); |
+ return info != null ? info.getStatus() : DESTROYED; |
} |
/** |
- * Registers the given listener to receive activity state changes. |
+ * @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 == HAS_RUNNING_ACTIVITIES || state == HAS_ONLY_PAUSED_ACTIVITIES; |
+ } |
+ |
+ /** |
+ * Checks to see if there are any active Activity instances being watched by ActivityStatus. |
+ * @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 registerStateListener(StateListener listener) { |
- sStateListeners.addObserver(listener); |
+ 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() != 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 unregisterStateListener(StateListener listener) { |
- sStateListeners.removeObserver(listener); |
+ 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); |
} |
/** |
@@ -219,41 +382,50 @@ public class ActivityStatus { |
* side, hence lifecycle management is greatly simplified. |
*/ |
@CalledByNative |
- private static void registerThreadSafeNativeStateListener() { |
+ private static void registerThreadSafeNativeApplicationStateListener() { |
ThreadUtils.runOnUiThread(new Runnable () { |
@Override |
public void run() { |
- // Register a new listener that calls nativeOnActivityStateChange. |
- sStateListeners.addObserver(new StateListener() { |
+ registerApplicationStateListener(new ApplicationStateListener() { |
@Override |
- public void onActivityStateChange(int newState) { |
- nativeOnActivityStateChange(newState); |
+ public void onApplicationStateChange(int newState) { |
+ nativeOnApplicationStateChange(newState); |
} |
}); |
} |
}); |
} |
- // Called to notify the native side of state changes. |
- // IMPORTANT: This is always called on the main thread! |
- private static native void nativeOnActivityStateChange(int newState); |
- |
/** |
- * Checks whether or not the Application's current Activity 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 True if the Activity is visible, false otherwise. |
+ * 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 {@link #HAS_RUNNING_ACTIVITIES} if any activity is not paused, stopped, or destroyed. |
+ * {@link #HAS_ONLY_PAUSED_ACTIVITIES} if none are running and one is paused. |
+ * {@link #HAS_ONLY_STOPPED_ACTIVITIES} if none are running/paused and one is stopped. |
+ * {@link #HAS_ONLY_DESTROYED_ACTIVITIES} if none are running/paused/stopped. |
*/ |
- public static boolean isApplicationVisible() { |
- int state = getState(); |
- return state != STOPPED && state != DESTROYED; |
- } |
+ private static int determineApplicationState() { |
+ boolean hasPausedActivity = false; |
+ boolean hasStoppedActivity = false; |
- /** |
- * Checks to see if there are any active Activity instances being watched by ActivityStatus. |
- * @return True if all Activities have been destroyed. |
- */ |
- public static boolean isEveryActivityDestroyed() { |
- return sActivityStates.isEmpty(); |
+ for (ActivityInfo info : sActivityInfo.values()) { |
+ int state = info.getStatus(); |
+ if (state != PAUSED && state != STOPPED && state != DESTROYED) { |
+ return HAS_RUNNING_ACTIVITIES; |
+ } else if (state == PAUSED) { |
+ hasPausedActivity = true; |
+ } else if (state == STOPPED) { |
+ hasStoppedActivity = true; |
+ } |
+ } |
+ |
+ if (hasPausedActivity) return HAS_ONLY_PAUSED_ACTIVITIES; |
+ if (hasStoppedActivity) return HAS_ONLY_STOPPED_ACTIVITIES; |
+ return HAS_ONLY_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); |
} |