diff options
11 files changed, 205 insertions, 93 deletions
diff --git a/base/android/java/src/org/chromium/base/ActivityStatus.java b/base/android/java/src/org/chromium/base/ActivityStatus.java index 4747234..e449b38 100644 --- a/base/android/java/src/org/chromium/base/ActivityStatus.java +++ b/base/android/java/src/org/chromium/base/ActivityStatus.java @@ -5,8 +5,12 @@ package org.chromium.base; import android.app.Activity; -import android.os.Handler; -import android.os.Looper; +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 @@ -25,15 +29,14 @@ public class ActivityStatus { public static final int STOPPED = ActivityState.STOPPED; public static final int DESTROYED = ActivityState.DESTROYED; - // Current main activity, or null if none. + // Last activity that was shown (or null if none or it was destroyed). private static Activity sActivity; - // Current main activity's state. This can be set even if sActivity is null, to simplify unit - // testing. - private static int sActivityState; + private static final Map<Activity, Integer> sActivityStates + = new HashMap<Activity, Integer>(); - private static final ObserverList<StateListener> sStateListeners = - new ObserverList<StateListener>(); + private static final ObserverList<StateListener> sStateListeners + = new ObserverList<StateListener>(); /** * Interface to be implemented by listeners. @@ -49,47 +52,163 @@ public class ActivityStatus { 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. */ + // TODO(tedchoc): Make this method private (and remove @Deprecated) once all downstream usages + // move to the initialize method. + @Deprecated public 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. - sActivity = activity; + if (sActivity == null + || newState == CREATED || newState == RESUMED || newState == STARTED) { + sActivity = activity; + } } - sActivityState = newState; - for (StateListener listener : sStateListeners) { - listener.onActivityStateChange(newState); + + if (newState != DESTROYED) { + sActivityStates.put(activity, newState); + } else { + sActivityStates.remove(activity); } - if (newState == DESTROYED) { - sActivity = null; + + if (sActivity == activity) { + for (StateListener listener : sStateListeners) { + listener.onActivityStateChange(newState); + } + if (newState == DESTROYED) { + sActivity = null; + } } } /** - * Indicates that the parent activity is currently paused. + * Testing method to update the state of the specified activity. + */ + public static void onStateChangeForTesting(Activity activity, int newState) { + onStateChange(activity, newState); + } + + /** + * Indicates that the current activity is paused. + * + * Use explicit state checking instead. */ + @Deprecated public static boolean isPaused() { - return sActivityState == PAUSED; + if (sActivity == null) return false; + Integer currentStatus = sActivityStates.get(sActivity); + return currentStatus != null && currentStatus.intValue() == PAUSED; } /** - * Returns the current main application activity. + * @return The current activity. */ public static Activity getActivity() { return sActivity; } /** - * Returns the current main application activity's state. + * @return The current activity's state (if no activity is registered, then DESTROYED will + * be returned). */ public static int getState() { - return sActivityState; + 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> -- Staring 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; } /** diff --git a/base/android/java/src/org/chromium/base/ChromiumActivity.java b/base/android/java/src/org/chromium/base/ChromiumActivity.java deleted file mode 100644 index 65f5ce9..0000000 --- a/base/android/java/src/org/chromium/base/ChromiumActivity.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 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.os.Bundle; - -// All Chromium main activities should extend this class. This allows various sub-systems to -// properly react to activity state changes. -public class ChromiumActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstance) { - super.onCreate(savedInstance); - ActivityStatus.onStateChange(this, ActivityStatus.CREATED); - } - - @Override - protected void onStart() { - super.onStart(); - ActivityStatus.onStateChange(this, ActivityStatus.STARTED); - } - - @Override - protected void onResume() { - super.onResume(); - ActivityStatus.onStateChange(this, ActivityStatus.RESUMED); - } - - @Override - protected void onPause() { - ActivityStatus.onStateChange(this, ActivityStatus.PAUSED); - super.onPause(); - } - - @Override - protected void onStop() { - ActivityStatus.onStateChange(this, ActivityStatus.STOPPED); - super.onStop(); - } - - @Override - protected void onDestroy() { - ActivityStatus.onStateChange(this, ActivityStatus.DESTROYED); - super.onDestroy(); - } -} diff --git a/base/android/java/src/org/chromium/base/ChromiumApplication.java b/base/android/java/src/org/chromium/base/ChromiumApplication.java new file mode 100644 index 0000000..7c2b957 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ChromiumApplication.java @@ -0,0 +1,20 @@ +// Copyright 2013 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.Application; + +/** + * Basic application functionality that should be shared among all browser applications. + */ +public class ChromiumApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + ActivityStatus.initialize(this); + } + +} diff --git a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java index 8672ba9..f32fe46 100644 --- a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java +++ b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellActivity.java @@ -4,6 +4,7 @@ package org.chromium.chrome.testshell; +import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; @@ -14,7 +15,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.widget.Toast; -import org.chromium.base.ChromiumActivity; import org.chromium.base.MemoryPressureListener; import org.chromium.chrome.browser.DevToolsServer; import org.chromium.chrome.testshell.sync.SyncController; @@ -31,7 +31,7 @@ import org.chromium.ui.WindowAndroid; /** * The {@link android.app.Activity} component of a basic test shell to test Chrome features. */ -public class ChromiumTestShellActivity extends ChromiumActivity implements MenuHandler { +public class ChromiumTestShellActivity extends Activity implements MenuHandler { private static final String TAG = "ChromiumTestShellActivity"; /** * Sending an intent with this action will simulate a memory pressure signal diff --git a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellApplication.java b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellApplication.java index 8f0c1de..d0f0125 100644 --- a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellApplication.java +++ b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/ChromiumTestShellApplication.java @@ -4,9 +4,9 @@ package org.chromium.chrome.testshell; -import android.app.Application; import android.content.Intent; +import org.chromium.base.ChromiumApplication; import org.chromium.base.PathUtils; import org.chromium.chrome.browser.UmaUtils; import org.chromium.content.browser.ResourceExtractor; @@ -15,10 +15,10 @@ import org.chromium.content.common.CommandLine; import java.util.ArrayList; /** - * A basic test shell {@link Application}. Handles setting up the native library and + * A basic test shell {@link android.app.Application}. Handles setting up the native library and * loading the right resources. */ -public class ChromiumTestShellApplication extends Application { +public class ChromiumTestShellApplication extends ChromiumApplication { private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chromiumtestshell"; private static final String[] CHROME_MANDATORY_PAKS = { "en-US.pak", diff --git a/content/public/android/java/src/org/chromium/content/browser/LocationProvider.java b/content/public/android/java/src/org/chromium/content/browser/LocationProvider.java index 8b8eb29..300bfa5 100644 --- a/content/public/android/java/src/org/chromium/content/browser/LocationProvider.java +++ b/content/public/android/java/src/org/chromium/content/browser/LocationProvider.java @@ -73,7 +73,11 @@ class LocationProvider { ActivityStatus.registerStateListener(this); } mIsGpsEnabled = gpsEnabled; - if (ActivityStatus.isPaused()) { + + int activityState = ActivityStatus.getState(); + if (activityState == ActivityStatus.PAUSED + || activityState == ActivityStatus.STOPPED + || activityState == ActivityStatus.DESTROYED) { mShouldRunAfterActivityResume = true; } else { unregisterFromLocationUpdates(); diff --git a/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java b/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java index 5dde1f2..0e417f5 100644 --- a/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java +++ b/content/public/android/javatests/src/org/chromium/content/browser/LocationProviderTest.java @@ -4,6 +4,7 @@ package org.chromium.content.browser; +import android.app.Activity; import android.test.UiThreadTest; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.SmallTest; @@ -15,10 +16,12 @@ import org.chromium.base.test.util.Feature; * Test suite for LocationProvider. */ public class LocationProviderTest extends InstrumentationTestCase { + private Activity mActivity; private LocationProvider mLocationProvider; @Override public void setUp() { + mActivity = new Activity(); mLocationProvider = LocationProvider.create(getInstrumentation().getTargetContext()); } @@ -29,6 +32,7 @@ public class LocationProviderTest extends InstrumentationTestCase { @UiThreadTest @Feature({"Location"}) public void testStartStop() throws Exception { + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); mLocationProvider.start(false); assertTrue("Should be running", mLocationProvider.isRunning()); mLocationProvider.stop(); @@ -42,6 +46,7 @@ public class LocationProviderTest extends InstrumentationTestCase { @UiThreadTest @Feature({"Location"}) public void testStartUpgradeStop() throws Exception { + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); mLocationProvider.start(false); assertTrue("Should be running", mLocationProvider.isRunning()); mLocationProvider.start(true); @@ -58,11 +63,12 @@ public class LocationProviderTest extends InstrumentationTestCase { @UiThreadTest @Feature({"Location"}) public void testStartPauseResumeStop() throws Exception { + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); mLocationProvider.start(false); assertTrue("Should be running", mLocationProvider.isRunning()); - ActivityStatus.onStateChange(null, ActivityStatus.PAUSED); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.PAUSED); assertFalse("Should have paused", mLocationProvider.isRunning()); - ActivityStatus.onStateChange(null, ActivityStatus.RESUMED); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); assertTrue("Should have resumed", mLocationProvider.isRunning()); mLocationProvider.stop(); assertFalse("Should have stopped", mLocationProvider.isRunning()); @@ -76,10 +82,27 @@ public class LocationProviderTest extends InstrumentationTestCase { @UiThreadTest @Feature({"Location"}) public void testPauseStartResumeStop() throws Exception { - ActivityStatus.onStateChange(null, ActivityStatus.PAUSED); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.PAUSED); mLocationProvider.start(false); assertFalse("Should not be running", mLocationProvider.isRunning()); - ActivityStatus.onStateChange(null, ActivityStatus.RESUMED); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); + assertTrue("Should have resumed", mLocationProvider.isRunning()); + mLocationProvider.stop(); + assertFalse("Should have stopped", mLocationProvider.isRunning()); + } + + /** + * Verify that calling start when the activity is stopped doesn't start listening + * for location updates until activity resumes. + */ + @SmallTest + @UiThreadTest + @Feature({"Location"}) + public void testPauseStopStartResumeStop() throws Exception { + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.STOPPED); + mLocationProvider.start(false); + assertFalse("Should not be running", mLocationProvider.isRunning()); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); assertTrue("Should have resumed", mLocationProvider.isRunning()); mLocationProvider.stop(); assertFalse("Should have stopped", mLocationProvider.isRunning()); @@ -92,13 +115,14 @@ public class LocationProviderTest extends InstrumentationTestCase { @UiThreadTest @Feature({"Location"}) public void testStartPauseUpgradeResumeStop() throws Exception { + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); mLocationProvider.start(false); assertTrue("Should be running", mLocationProvider.isRunning()); - ActivityStatus.onStateChange(null, ActivityStatus.PAUSED); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.PAUSED); assertFalse("Should have paused", mLocationProvider.isRunning()); mLocationProvider.start(true); assertFalse("Should be paused", mLocationProvider.isRunning()); - ActivityStatus.onStateChange(null, ActivityStatus.RESUMED); + ActivityStatus.onStateChangeForTesting(mActivity, ActivityStatus.RESUMED); assertTrue("Should have resumed", mLocationProvider.isRunning()); mLocationProvider.stop(); assertFalse("Should have stopped", mLocationProvider.isRunning()); diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java index c98ba12..f6720b6 100644 --- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java +++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java @@ -4,6 +4,7 @@ package org.chromium.content_shell_apk; +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -14,7 +15,6 @@ import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; -import org.chromium.base.ChromiumActivity; import org.chromium.base.MemoryPressureListener; import org.chromium.content.app.LibraryLoader; import org.chromium.content.browser.ActivityContentVideoViewClient; @@ -33,7 +33,7 @@ import org.chromium.ui.WindowAndroid; /** * Activity for managing the Content Shell. */ -public class ContentShellActivity extends ChromiumActivity { +public class ContentShellActivity extends Activity { public static final String COMMAND_LINE_FILE = "/data/local/tmp/content-shell-command-line"; private static final String TAG = "ContentShellActivity"; diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java index ddbe76f..284f412 100644 --- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java +++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellApplication.java @@ -4,17 +4,15 @@ package org.chromium.content_shell_apk; -import android.app.Application; - +import org.chromium.base.ChromiumApplication; import org.chromium.base.PathUtils; -import org.chromium.content.app.LibraryLoader; import org.chromium.content.browser.ResourceExtractor; /** * Entry point for the content shell application. Handles initialization of information that needs * to be shared across the main activity and the child services created. */ -public class ContentShellApplication extends Application { +public class ContentShellApplication extends ChromiumApplication { private static final String[] MANDATORY_PAK_FILES = new String[] {"content_shell.pak"}; private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "content_shell"; diff --git a/testing/android/AndroidManifest.xml b/testing/android/AndroidManifest.xml index 73a0c14..a1afb56 100644 --- a/testing/android/AndroidManifest.xml +++ b/testing/android/AndroidManifest.xml @@ -12,7 +12,8 @@ found in the LICENSE file. <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" /> - <application android:label="ChromeNativeTests"> + <application android:label="ChromeNativeTests" + android:name="org.chromium.base.ChromiumApplication"> <activity android:name=".ChromeNativeTestActivity" android:label="ChromeNativeTest" android:configChanges="orientation|keyboardHidden"> diff --git a/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java b/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java index 6486a14..b8e41c83 100644 --- a/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java +++ b/testing/android/java/src/org/chromium/native_test/ChromeNativeTestActivity.java @@ -7,23 +7,18 @@ package org.chromium.native_test; import android.app.Activity; import android.content.Context; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.util.Log; -import org.chromium.base.ChromiumActivity; import org.chromium.base.PathUtils; import org.chromium.base.PowerMonitor; - // TODO(cjhopman): This should not refer to content. NativeLibraries should be moved to base. import org.chromium.content.app.NativeLibraries; -import java.io.File; - // Android's NativeActivity is mostly useful for pure-native code. // Our tests need to go up to our own java classes, which is not possible using // the native activity class loader. -public class ChromeNativeTestActivity extends ChromiumActivity { +public class ChromeNativeTestActivity extends Activity { private static final String TAG = "ChromeNativeTestActivity"; private static final String EXTRA_RUN_IN_SUB_THREAD = "RunInSubThread"; // We post a delayed task to run tests so that we do not block onCreate(). |