Don't throw exception for duplicate resume

A double resume request is possible when an activity receives two
consequent transactions with relaunch requests and the second is one
is omitted. We still get two resume requests for the final state.

Fixes: 74074327
Test: FrameworksCoreTests:ActivityThreadTest
Change-Id: I8bb8594948a17b7fbf595a49026ff33b54b66049
This commit is contained in:
Andrii Kulian
2018-03-08 15:42:24 -08:00
parent 1e36211a96
commit dfbf971688
7 changed files with 136 additions and 11 deletions

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -1379,6 +1379,10 @@
android:theme="@style/Theme">
</activity>
<activity android:name="android.app.activity.ActivityThreadTest$TestActivity"
android:exported="true">
</activity>
<activity
android:name="android.os.TestVrActivity"
android:enableVrMode="com.android.frameworks.coretests/android.os.TestVrActivity$TestVrListenerService">

View File

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