blob: df43dbebfbb9bf95df55fbb72607ecd9a1f85ff2 [file] [log] [blame]
// 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.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 ActivityStatus {
// Constants matching activity states reported to StateListener.onStateChange
// As an implementation detail, these are now defined in the auto-generated
// ActivityState interface, to be shared with C++.
public static final int CREATED = ActivityState.CREATED;
public static final int STARTED = ActivityState.STARTED;
public static final int RESUMED = ActivityState.RESUMED;
public static final int PAUSED = ActivityState.PAUSED;
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).
private static Activity sActivity;
private static final Map<Activity, Integer> sActivityStates =
new HashMap<Activity, Integer>();
private static final ObserverList<StateListener> sStateListeners =
new ObserverList<StateListener>();
/**
* Interface to be implemented by listeners.
*/
public interface StateListener {
/**
* Called when the activity's state changes.
* @param newState New activity state.
*/
public void onActivityStateChange(int newState);
}
private ActivityStatus() {}
/**
* Initializes the activity status for a specified application.
*
* @param application The application whose status you wish to monitor.
*/
public static void initialize(Application application) {
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
onStateChange(activity, CREATED);
}
@Override
public void onActivityDestroyed(Activity activity) {
onStateChange(activity, DESTROYED);
}
@Override
public void onActivityPaused(Activity activity) {
onStateChange(activity, PAUSED);
}
@Override
public void onActivityResumed(Activity activity) {
onStateChange(activity, RESUMED);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public void onActivityStarted(Activity activity) {
onStateChange(activity, STARTED);
}
@Override
public void onActivityStopped(Activity activity) {
onStateChange(activity, 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 != 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 (newState != DESTROYED) {
sActivityStates.put(activity, newState);
} else {
sActivityStates.remove(activity);
}
if (sActivity == activity) {
for (StateListener listener : sStateListeners) {
listener.onActivityStateChange(newState);
}
if (newState == DESTROYED) {
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 current activity.
*/
public static Activity getActivity() {
return sActivity;
}
/**
* @return The current activity's state (if no activity is registered, then DESTROYED will
* be returned).
*/
public static int getState() {
return getStateForActivity(sActivity);
}
/**
* Query the state for a given activity. If the activity is not being tracked, this will
* return {@link #DESTROYED}.
*
* <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 - CREATED
* <li> Activity A - STARTED
* <li> Activity A - RESUMED
* <li> -- Starting Activity B --
* <li> Activity A - PAUSED
* <li> Activity B - CREATED
* <li> Activity B - STARTED
* <li> Activity B - RESUMED
* <li> Activity A - STOPPED
* <li> -- Closing Activity B, Activity A regaining focus --
* <li> Activity B - PAUSED
* <li> Activity A - STARTED
* <li> Activity A - RESUMED
* <li> Activity B - STOPPED
* <li> Activity B - DESTROYED
* </ul>
*
* @param activity The activity whose state is to be returned.
* @return The state of the specified activity.
*/
public static int getStateForActivity(Activity activity) {
Integer currentStatus = sActivityStates.get(activity);
return currentStatus != null ? currentStatus.intValue() : DESTROYED;
}
/**
* Registers the given listener to receive activity state changes.
* @param listener Listener to receive state changes.
*/
public static void registerStateListener(StateListener listener) {
sStateListeners.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);
}
/**
* 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 registerThreadSafeNativeStateListener() {
ThreadUtils.runOnUiThread(new Runnable () {
@Override
public void run() {
// Register a new listener that calls nativeOnActivityStateChange.
sStateListeners.addObserver(new StateListener() {
@Override
public void onActivityStateChange(int newState) {
nativeOnActivityStateChange(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.
*/
public static boolean isApplicationVisible() {
int state = getState();
return state != STOPPED && state != DESTROYED;
}
/**
* 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();
}
}