From 829829ca2c1804147dd42b106e5d7f1efe99a1f9 Mon Sep 17 00:00:00 2001 From: Andrii Kulian Date: Mon, 19 Mar 2018 18:19:05 -0700 Subject: [PATCH] Don't throw exception for duplicate stop A double stop request is possible when display is being locked. An activity may receive a "sleep" message followed by "stop", both of which try to move it to stopped state. An example when this happens: a keyguard is set up and the screen is being locked. The keyguard will occlude the activity, which causes a transition to paused state and, eventually, to stopped state. A "sleep" message can be sent sometime before "stop" message and will ignore that activity is in the process of becoming stopped. Change-Id: I09e2c26004664b6e73ac5c2b6fe88bdf8271cf34 Fixes: 74967786 Test: FrameworksCoreTests:ActivityThreadTest --- core/java/android/app/ActivityThread.java | 33 ++++++++++----- .../android/app/ClientTransactionHandler.java | 14 ++++++- .../servertransaction/StopActivityItem.java | 2 +- .../TransactionExecutor.java | 3 +- .../app/activity/ActivityThreadTest.java | 41 +++++++++++++++---- 5 files changed, 71 insertions(+), 22 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 99bcdb9fe1d90..9f3c242f69471 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4033,9 +4033,11 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_PAUSE); } + /** Called from {@link LocalActivityManager}. */ final void performStopActivity(IBinder token, boolean saveState, String reason) { ActivityClientRecord r = mActivities.get(token); - performStopActivityInner(r, null, false, saveState, reason); + performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState, + false /* finalStateRequest */, reason); } private static final class ProviderRefCount { @@ -4067,9 +4069,16 @@ public final class ActivityThread extends ClientTransactionHandler { * it the result when it is done, but the window may still be visible. * For the client, we want to call onStop()/onStart() to indicate when * the activity's UI visibility changes. + * @param r Target activity client record. + * @param info Action that will report activity stop to server. + * @param keepShown Flag indicating whether the activity is still shown. + * @param saveState Flag indicating whether the activity state should be saved. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param reason Reason for performing this operation. */ private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown, - boolean saveState, String reason) { + boolean saveState, boolean finalStateRequest, String reason) { if (localLOGV) Slog.v(TAG, "Performing stop of " + r); if (r != null) { if (!keepShown && r.stopped) { @@ -4079,11 +4088,13 @@ public final class ActivityThread extends ClientTransactionHandler { // if the activity isn't resumed. return; } - RuntimeException e = new RuntimeException( - "Performing stop of activity that is already stopped: " - + r.intent.getComponent().toShortString()); - Slog.e(TAG, e.getMessage(), e); - Slog.e(TAG, r.getStateString()); + if (!finalStateRequest) { + final RuntimeException e = new RuntimeException( + "Performing stop of activity that is already stopped: " + + r.intent.getComponent().toShortString()); + Slog.e(TAG, e.getMessage(), e); + Slog.e(TAG, r.getStateString()); + } } // One must first be paused before stopped... @@ -4176,12 +4187,13 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleStopActivity(IBinder token, boolean show, int configChanges, - PendingTransactionActions pendingActions, String reason) { + PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { final ActivityClientRecord r = mActivities.get(token); r.activity.mConfigChangeFlags |= configChanges; final StopInfo stopInfo = new StopInfo(); - performStopActivityInner(r, stopInfo, show, true, reason); + performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest, + reason); if (localLOGV) Slog.v( TAG, "Finishing stop of " + r + ": show=" + show @@ -4233,7 +4245,8 @@ public final class ActivityThread extends ClientTransactionHandler { } if (!show && !r.stopped) { - performStopActivityInner(r, null, show, false, "handleWindowVisibility"); + performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */, + false /* finalStateRequest */, "handleWindowVisibility"); } else if (show && r.stopped) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 206495d0db6cc..961bca23c91af 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -78,9 +78,19 @@ public abstract class ClientTransactionHandler { public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason); - /** Stop the activity. */ + /** + * Stop the activity. + * @param token Target activity token. + * @param show Flag indicating whether activity is still shown. + * @param configChanges Activity configuration changes. + * @param pendingActions Pending actions to be used on this or later stages of activity + * transaction. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param reason Reason for performing this operation. + */ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, - PendingTransactionActions pendingActions, String reason); + PendingTransactionActions pendingActions, boolean finalStateRequest, String reason); /** Report that activity was stopped to server. */ public abstract void reportStop(PendingTransactionActions pendingActions); diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index 0a61fab2a8ea1..8db38d36c57ea 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -39,7 +39,7 @@ public class StopActivityItem extends ActivityLifecycleItem { PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions, - "STOP_ACTIVITY_ITEM"); + true /* finalStateRequest */, "STOP_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 0d995e866512d..553c3ae15387a 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -204,7 +204,8 @@ public class TransactionExecutor { break; case ON_STOP: mTransactionHandler.handleStopActivity(r.token, false /* show */, - 0 /* configChanges */, mPendingActions, "LIFECYCLER_STOP_ACTIVITY"); + 0 /* configChanges */, mPendingActions, false /* finalStateRequest */, + "LIFECYCLER_STOP_ACTIVITY"); break; case ON_DESTROY: mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */, diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 4628aa9e0f1a8..a99e1398a7bf5 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -22,6 +22,7 @@ import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.ResumeActivityItem; +import android.app.servertransaction.StopActivityItem; import android.content.Intent; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; @@ -34,6 +35,8 @@ import org.junit.runner.RunWith; /** * Test for verifying {@link android.app.ActivityThread} class. + * Build/Install/Run: + * atest FrameworksCoreTests:android.app.activity.ActivityThreadTest */ @RunWith(AndroidJUnit4.class) @MediumTest @@ -63,15 +66,23 @@ public class ActivityThreadTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } + @Test + public void testSleepAndStop() throws Exception { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + + appThread.scheduleSleeping(activity.getActivityToken(), true /* sleeping */); + appThread.scheduleTransaction(newStopTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, - null, 0, new MergedConfiguration(), - false /* preserveWindow */); + null, 0, new MergedConfiguration(), false /* preserveWindow */); final ResumeActivityItem resumeStateRequest = ResumeActivityItem.obtain(true /* isForward */); - final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); - final ClientTransaction transaction = - ClientTransaction.obtain(appThread, activity.getActivityToken()); + + final ClientTransaction transaction = newTransaction(activity); transaction.addCallback(callbackItem); transaction.setLifecycleStateRequest(resumeStateRequest); @@ -81,14 +92,28 @@ public class ActivityThreadTest { private static ClientTransaction newResumeTransaction(Activity activity) { final ResumeActivityItem resumeStateRequest = ResumeActivityItem.obtain(true /* isForward */); - final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); - final ClientTransaction transaction = - ClientTransaction.obtain(appThread, activity.getActivityToken()); + + final ClientTransaction transaction = newTransaction(activity); transaction.setLifecycleStateRequest(resumeStateRequest); return transaction; } + private static ClientTransaction newStopTransaction(Activity activity) { + final StopActivityItem stopStateRequest = + StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */); + + final ClientTransaction transaction = newTransaction(activity); + transaction.setLifecycleStateRequest(stopStateRequest); + + return transaction; + } + + private static ClientTransaction newTransaction(Activity activity) { + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + return ClientTransaction.obtain(appThread, activity.getActivityToken()); + } + // Test activity public static class TestActivity extends Activity { }