diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index caca05a9e3b34..763ce6c6fd170 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5418,13 +5418,12 @@ public final class ActivityThread extends ClientTransactionHandler { final int prevState = r.getLifecycleState(); - if (prevState < ON_RESUME || prevState > ON_STOP) { - Log.w(TAG, "Activity state must be in [ON_RESUME..ON_STOP] in order to be relaunched," + if (prevState < ON_START || prevState > ON_STOP) { + Log.w(TAG, "Activity state must be in [ON_START..ON_STOP] in order to be relaunched," + "current state is " + prevState); return; } - // Initialize a relaunch request. final MergedConfiguration mergedConfiguration = new MergedConfiguration( r.createdConfig != null ? r.createdConfig : mConfiguration, diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index a34be5c3edc77..56bf59b52f745 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -185,6 +185,9 @@ public class TransactionExecutorHelper { final ActivityLifecycleItem lifecycleItem; switch (prevState) { // TODO(lifecycler): Extend to support all possible states. + case ON_START: + lifecycleItem = StartActivityItem.obtain(); + break; case ON_PAUSE: lifecycleItem = PauseActivityItem.obtain(); break; diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 1cdc75aa1f40e..b63a19187b059 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -23,6 +23,8 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.app.servertransaction.ActivityLifecycleItem.ON_START; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -30,7 +32,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; @@ -50,6 +56,7 @@ import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.testing.PollingCheck; import android.view.WindowManagerGlobal; import androidx.test.annotation.UiThreadTest; @@ -63,6 +70,8 @@ import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import java.util.concurrent.TimeUnit; + /** * Test for verifying {@link android.app.ActivityThread} class. * @@ -76,6 +85,7 @@ import org.mockito.quality.Strictness; @MediumTest @Presubmit public class ActivityThreadClientTest { + private static final long WAIT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2); @Test @UiThreadTest @@ -152,6 +162,63 @@ public class ActivityThreadClientTest { } } + @Test + public void testLifecycleOfRelaunch() throws Exception { + try (ClientMockSession clientSession = new ClientMockSession()) { + ActivityThread activityThread = clientSession.mockThread(); + ActivityClientRecord r = clientSession.stubActivityRecord(); + final TestActivity[] activity = new TestActivity[1]; + + // Verify for ON_CREATE state. Activity should not be relaunched. + getInstrumentation().runOnMainSync(() -> { + activity[0] = (TestActivity) clientSession.launchActivity(r); + }); + recreateAndVerifyNoRelaunch(activityThread, activity[0]); + + // Verify for ON_START state. Activity should be relaunched. + getInstrumentation().runOnMainSync(() -> clientSession.startActivity(r)); + recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_START); + + // Verify for ON_RESUME state. Activity should be relaunched. + getInstrumentation().runOnMainSync(() -> clientSession.resumeActivity(r)); + recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_RESUME); + + // Verify for ON_PAUSE state. Activity should be relaunched. + getInstrumentation().runOnMainSync(() -> clientSession.pauseActivity(r)); + recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_PAUSE); + + // Verify for ON_STOP state. Activity should be relaunched. + getInstrumentation().runOnMainSync(() -> clientSession.stopActivity(r)); + recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_STOP); + + // Verify for ON_DESTROY state. Activity should not be relaunched. + getInstrumentation().runOnMainSync(() -> clientSession.destroyActivity(r)); + recreateAndVerifyNoRelaunch(activityThread, activity[0]); + } + } + + private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) { + clearInvocations(activityThread); + getInstrumentation().runOnMainSync(() -> activity.recreate()); + + verify(activityThread, after(WAIT_TIMEOUT_MS).never()) + .handleRelaunchActivity(any(), any()); + } + + private void recreateAndVerifyRelaunched(ActivityThread activityThread, TestActivity activity, + ActivityClientRecord r, int expectedState) throws Exception { + clearInvocations(activityThread); + getInstrumentation().runOnMainSync(() -> activity.recreate()); + + verify(activityThread, timeout(WAIT_TIMEOUT_MS)).handleRelaunchActivity(any(), any()); + + // Wait for the relaunch to complete. + PollingCheck.check("Waiting for the expected state " + expectedState + " timeout", + WAIT_TIMEOUT_MS, + () -> expectedState == r.getLifecycleState()); + assertEquals(expectedState, r.getLifecycleState()); + } + private class ClientMockSession implements AutoCloseable { private MockitoSession mMockSession; private ActivityThread mThread; @@ -200,6 +267,11 @@ public class ActivityThreadClientTest { false /* getNonConfigInstance */, "test"); } + private ActivityThread mockThread() { + spyOn(mThread); + return mThread; + } + private ActivityClientRecord stubActivityRecord() { ComponentName component = new ComponentName( InstrumentationRegistry.getInstrumentation().getContext(), TestActivity.class);