Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Unified Diff: base/android/java/src/org/chromium/base/ApplicationStatus.java

Issue 159173002: Refactor ActivityStatus to not store current activity (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld 408576698