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 {
+ }
+}