Chromium Code Reviews| 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..93144bba6b12b0a2af91de3a93d4bd2297235782 100644 |
| --- a/base/android/java/src/org/chromium/base/ActivityStatus.java |
| +++ b/base/android/java/src/org/chromium/base/ActivityStatus.java |
| @@ -7,9 +7,11 @@ 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.Iterator; |
| import java.util.Map; |
| /** |
| @@ -29,24 +31,59 @@ public class ActivityStatus { |
| public static final int STOPPED = ActivityState.STOPPED; |
| public static final int DESTROYED = ActivityState.DESTROYED; |
| + public static final int APP_RUNNING = ApplicationState.RUNNING; |
| + public static final int APP_PAUSED = ApplicationState.PAUSED; |
| + public static final int APP_STOPPED = ApplicationState.STOPPED; |
| + public static final int APP_DESTROYED = ApplicationState.DESTROYED; |
| + |
| + private static Application sApplication; |
| + |
| // Last activity that was shown (or null if none or it was destroyed). |
| + // TODO(dtrainor): Add window focus listener to update this. |
|
Ted C
2014/02/13 04:38:44
aren't you doing this down below?
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| private static Activity sActivity; |
| + /** |
| + * A map of {@link Activity} to {@link ActivityState}. If an {@link Activity} is not present it |
| + * can be assumed that it was destroyed. |
| + */ |
| private static final Map<Activity, Integer> sActivityStates = |
|
Ted C
2014/02/13 04:38:44
Hmm...I wonder if we should combine this and the s
David Trainor- moved to gerrit
2014/02/13 05:03:55
No it's a good idea. I thought about it but forgo
|
| new HashMap<Activity, Integer>(); |
| - private static final ObserverList<StateListener> sStateListeners = |
| - new ObserverList<StateListener>(); |
| + /** |
| + * A set of observers for each {@link Activity}. Observers on the {@code null} key in this map |
| + * will observer all changes for all {@link Activity}s. |
| + */ |
| + private static final Map<Activity, ObserverList<ActivityStateListener>> |
| + sActivityStateListeners = new HashMap<Activity, 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 StateListener { |
| + 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(int newState); |
| + public void onActivityStateChange(Activity activity, int newState); |
| } |
| private ActivityStatus() {} |
| @@ -56,10 +93,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 +157,44 @@ 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(); |
| + sActivityStates.put(activity, newState); |
| + |
| + // Notify all state observers that are specifically listening to this activity. |
| + ObserverList<ActivityStateListener> listeners = sActivityStateListeners.get(activity); |
| + if (listeners != null) { |
| + for (ActivityStateListener listener : listeners) { |
| + listener.onActivityStateChange(activity, newState); |
| + } |
| } |
| - if (sActivity == activity) { |
| - for (StateListener listener : sStateListeners) { |
| - listener.onActivityStateChange(newState); |
| + // Notify all state observers that are listening globally for all activity state |
| + // changes. |
| + ObserverList<ActivityStateListener> globalListeners = sActivityStateListeners.get(null); |
| + if (globalListeners != null) { |
| + for (ActivityStateListener listener : globalListeners) { |
| + listener.onActivityStateChange(activity, newState); |
| } |
| - if (newState == DESTROYED) { |
| - sActivity = null; |
| + } |
| + |
| + int applicationState = getStateForApplication(); |
| + if (applicationState != oldApplicationState) { |
| + for (ApplicationStateListener listener : sApplicationStateListeners) { |
| + listener.onApplicationStateChange(applicationState); |
| } |
| } |
| + |
| + if (newState == DESTROYED) { |
| + sActivityStateListeners.remove(activity); |
| + sActivityStates.remove(activity); |
| + |
| + if (activity == sActivity) sActivity = null; |
| + } |
| } |
| /** |
| @@ -138,18 +205,18 @@ public class ActivityStatus { |
| } |
| /** |
| - * @return The current activity. |
| + * @return The first visible {@link Activity} tracked by this class. Being visible is defined |
|
bulach
2014/02/13 11:17:48
hmm.. I wish we could call this "focused", but I s
David Trainor- moved to gerrit
2014/02/14 19:13:36
Yeah I used to return a list from this. But I fou
|
| + * as being in a state of created, started, or resumed. |
|
Ted C
2014/02/13 04:38:44
indent to be aligned with "The" above
Also since
David Trainor- moved to gerrit
2014/02/14 19:13:36
I updated the comment. I think I understand your
|
| */ |
| - public static Activity getActivity() { |
| + public static Activity getVisibleActivity() { |
| 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,7 +256,7 @@ 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); |
| @@ -197,19 +264,92 @@ public class ActivityStatus { |
| } |
| /** |
| - * Registers the given listener to receive activity state changes. |
| + * @return The state of the application (see {@link ApplicationState}). |
| + */ |
| + public static int getStateForApplication() { |
|
bulach
2014/02/13 11:17:48
hmm, I'm not sure, but I think it's bit unnatural
David Trainor- moved to gerrit
2014/02/14 19:13:36
Sounds reasonable. I changed the states to match
|
| + boolean running = false; |
| + boolean paused = false; |
| + boolean stopped = false; |
| + |
| + for (Integer state : sActivityStates.values()) { |
| + running |= state != PAUSED && state != STOPPED && state != DESTROYED; |
|
Ted C
2014/02/13 04:38:44
Seems like you can drop running and just return AP
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| + paused |= state != STOPPED && state != DESTROYED; |
| + stopped |= state != DESTROYED; |
| + } |
| + |
| + if (running) return APP_RUNNING; |
| + if (paused) return APP_PAUSED; |
| + if (stopped) return APP_STOPPED; |
| + return APP_DESTROYED; |
| + } |
| + |
| + /** |
| + * 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 True if the any Activity under this Application is visible, false otherwise. |
|
Ted C
2014/02/13 04:38:44
Whether any Activity ....
Then drop the "false ot
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| + */ |
| + public static boolean isApplicationVisible() { |
|
bulach
2014/02/13 11:17:48
how about "hasAnyVisibleActivities" ?
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| + int state = getStateForApplication(); |
| + return state == APP_RUNNING || state == APP_PAUSED; |
| + } |
| + |
| + /** |
| + * Registers the given listener to receive state changes for all activities. |
| + * @param listener Listener to receive state changes. |
| + */ |
| + public static void registerStateListenerForAllActivities(ActivityStateListener listener) { |
|
Ted C
2014/02/13 04:38:44
Does anyone actually need to listen for all activi
David Trainor- moved to gerrit
2014/02/13 05:03:55
There were one or two consumers as of when I moved
|
| + registerStateListenerForActivity(listener, null); |
| + } |
| + |
| + /** |
| + * 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 registerStateListener(StateListener listener) { |
| - sStateListeners.addObserver(listener); |
| + public static void registerStateListenerForActivity(ActivityStateListener listener, |
| + Activity activity) { |
| + assert getStateForActivity(activity) != DESTROYED; |
| + ObserverList<ActivityStateListener> list = sActivityStateListeners.get(activity); |
| + if (list == null) { |
|
Ted C
2014/02/13 04:38:44
Personally, I would change this to always initiali
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| + list = new ObserverList<ActivityStateListener>(); |
| + sActivityStateListeners.put(activity, list); |
| + } |
| + list.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) { |
| + // Loop through all observer lists for all activities and remove the listener. If the |
| + // list is empty after removing the listener remove the activity entry as well. |
| + for (Iterator<Map.Entry<Activity, ObserverList<ActivityStateListener>>> it = |
| + sActivityStateListeners.entrySet().iterator(); it.hasNext();) { |
| + Map.Entry<Activity, ObserverList<ActivityStateListener>> entry = it.next(); |
| + entry.getValue().removeObserver(listener); |
| + if (!entry.getValue().iterator().hasNext()) it.remove(); |
| + } |
| + } |
| + |
| + /** |
| + * 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,15 +359,14 @@ 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); |
| } |
| }); |
| } |
| @@ -236,17 +375,18 @@ public class ActivityStatus { |
| // 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); |
| + private static native void nativeOnApplicationStateChange(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. |
| + * Checks wehther or not the Application is active. This means Activity#onStart() has been |
| + * called but Activity#onStop() has not. |
| + * @return Whether or not the application is active. |
| */ |
| - public static boolean isApplicationVisible() { |
| - int state = getState(); |
| - return state != STOPPED && state != DESTROYED; |
| + public static boolean isApplicationActive() { |
|
Ted C
2014/02/13 04:38:44
Active is still an odd sounding name.
Maybe Foreg
David Trainor- moved to gerrit
2014/02/13 05:03:55
Oops this might be an old function I can erase...
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| + for (Integer state : sActivityStates.values()) { |
| + if (state == RESUMED || state == STARTED) return true; |
|
Ted C
2014/02/13 04:38:44
can you not use the application state here?
Also,
David Trainor- moved to gerrit
2014/02/14 19:13:36
Done.
|
| + } |
| + return false; |
| } |
| /** |