diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 99c5d2b9a9273..d957eaa52ae6f 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -124,6 +124,7 @@ import android.widget.Toast; import android.widget.Toolbar; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; @@ -7110,6 +7111,12 @@ public class Activity extends ContextThemeWrapper return mParent != null ? mParent.getActivityToken() : mToken; } + /** @hide */ + @VisibleForTesting + public final ActivityThread getActivityThread() { + return mMainThread; + } + final void performCreate(Bundle icicle) { performCreate(icicle, null); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 379944e2e2309..afcd51572d517 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3722,16 +3722,27 @@ public final class ActivityThread extends ClientTransactionHandler { //Slog.i(TAG, "Running services: " + mServices); } - ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide, String reason) { + ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, + String reason) { ActivityClientRecord r = mActivities.get(token); if (localLOGV) Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); if (r != null && !r.activity.mFinished) { if (r.getLifecycleState() == ON_RESUME) { - throw new IllegalStateException( - "Trying to resume activity which is already resumed"); + if (!finalStateRequest) { + final RuntimeException e = new IllegalStateException( + "Trying to resume activity which is already resumed"); + Slog.e(TAG, e.getMessage(), e); + Slog.e(TAG, r.getStateString()); + // TODO(lifecycler): A double resume request is possible when an activity + // receives two consequent transactions with relaunch requests and "resumed" + // final state requests and the second relaunch is omitted. We still try to + // handle two resume requests for the final state. For cases other than this + // one, we don't expect it to happen. + } + return null; } - if (clearHide) { + if (finalStateRequest) { r.hideForNow = false; r.activity.mStartedActivity = false; } @@ -3782,7 +3793,7 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, + public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. @@ -3790,7 +3801,7 @@ public final class ActivityThread extends ClientTransactionHandler { mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration - final ActivityClientRecord r = performResumeActivity(token, clearHide, reason); + final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); if (r != null) { final Activity a = r.activity; diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 6bc66ecdb2618..206495d0db6cc 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -67,9 +67,16 @@ public abstract class ClientTransactionHandler { public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, String reason); - /** Resume the activity. */ - public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, - String reason); + /** + * Resume the activity. + * @param token Target activity token. + * @param finalStateRequest Flag indicating if this call is handling final lifecycle state + * request for a transaction. + * @param isForward Flag indicating if next transition is forward. + * @param reason Reason for performing this operation. + */ + public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest, + boolean isForward, String reason); /** Stop the activity. */ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index af2fb713e1bc4..d16bc97cbc875 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -48,7 +48,8 @@ public class ResumeActivityItem extends ActivityLifecycleItem { public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - client.handleResumeActivity(token, true /* clearHide */, mIsForward, "RESUME_ACTIVITY"); + client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward, + "RESUME_ACTIVITY"); 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 0e52b34715046..9e9a9c57656bd 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -191,7 +191,7 @@ public class TransactionExecutor { mTransactionHandler.handleStartActivity(r, mPendingActions); break; case ON_RESUME: - mTransactionHandler.handleResumeActivity(r.token, false /* clearHide */, + mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */, r.isForward, "LIFECYCLER_RESUME_ACTIVITY"); break; case ON_PAUSE: diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 53c22f624e8cb..0f019e7119549 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1379,6 +1379,10 @@ android:theme="@style/Theme"> + + + diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java new file mode 100644 index 0000000000000..4628aa9e0f1a8 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.activity; + +import android.app.Activity; +import android.app.IApplicationThread; +import android.app.servertransaction.ActivityRelaunchItem; +import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.util.MergedConfiguration; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for verifying {@link android.app.ActivityThread} class. + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +public class ActivityThreadTest { + + private final ActivityTestRule mActivityTestRule = + new ActivityTestRule(TestActivity.class, true /* initialTouchMode */, + false /* launchActivity */); + + @Test + public void testDoubleRelaunch() throws Exception { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void testResumeAfterRelaunch() throws Exception { + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + appThread.scheduleTransaction(newResumeTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { + final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, + 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()); + transaction.addCallback(callbackItem); + transaction.setLifecycleStateRequest(resumeStateRequest); + + return transaction; + } + + 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()); + transaction.setLifecycleStateRequest(resumeStateRequest); + + return transaction; + } + + // Test activity + public static class TestActivity extends Activity { + } +}