Merge "Don't finish activity on duplicate resume request" into pi-dev
am: 38af02e75b
Change-Id: I8d7d56c9b6d7b1e987379ae26e883a8e1deb2ecf
This commit is contained in:
@@ -3736,54 +3736,64 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
//Slog.i(TAG, "Running services: " + mServices);
|
||||
}
|
||||
|
||||
ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
|
||||
/**
|
||||
* Resume the activity.
|
||||
* @param token Target activity token.
|
||||
* @param finalStateRequest Flag indicating if this is part of final state resolution for a
|
||||
* transaction.
|
||||
* @param reason Reason for performing the action.
|
||||
*
|
||||
* @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public 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) {
|
||||
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;
|
||||
final ActivityClientRecord r = mActivities.get(token);
|
||||
if (localLOGV) {
|
||||
Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
|
||||
}
|
||||
if (r == null || r.activity.mFinished) {
|
||||
return null;
|
||||
}
|
||||
if (r.getLifecycleState() == ON_RESUME) {
|
||||
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.
|
||||
}
|
||||
if (finalStateRequest) {
|
||||
r.hideForNow = false;
|
||||
r.activity.mStartedActivity = false;
|
||||
return null;
|
||||
}
|
||||
if (finalStateRequest) {
|
||||
r.hideForNow = false;
|
||||
r.activity.mStartedActivity = false;
|
||||
}
|
||||
try {
|
||||
r.activity.onStateNotSaved();
|
||||
r.activity.mFragments.noteStateNotSaved();
|
||||
checkAndBlockForNetworkAccess();
|
||||
if (r.pendingIntents != null) {
|
||||
deliverNewIntents(r, r.pendingIntents);
|
||||
r.pendingIntents = null;
|
||||
}
|
||||
try {
|
||||
r.activity.onStateNotSaved();
|
||||
r.activity.mFragments.noteStateNotSaved();
|
||||
checkAndBlockForNetworkAccess();
|
||||
if (r.pendingIntents != null) {
|
||||
deliverNewIntents(r, r.pendingIntents);
|
||||
r.pendingIntents = null;
|
||||
}
|
||||
if (r.pendingResults != null) {
|
||||
deliverResults(r, r.pendingResults);
|
||||
r.pendingResults = null;
|
||||
}
|
||||
r.activity.performResume(r.startsNotResumed, reason);
|
||||
if (r.pendingResults != null) {
|
||||
deliverResults(r, r.pendingResults);
|
||||
r.pendingResults = null;
|
||||
}
|
||||
r.activity.performResume(r.startsNotResumed, reason);
|
||||
|
||||
r.state = null;
|
||||
r.persistentState = null;
|
||||
r.setState(ON_RESUME);
|
||||
} catch (Exception e) {
|
||||
if (!mInstrumentation.onException(r.activity, e)) {
|
||||
throw new RuntimeException(
|
||||
"Unable to resume activity "
|
||||
+ r.intent.getComponent().toShortString()
|
||||
+ ": " + e.toString(), e);
|
||||
}
|
||||
r.state = null;
|
||||
r.persistentState = null;
|
||||
r.setState(ON_RESUME);
|
||||
} catch (Exception e) {
|
||||
if (!mInstrumentation.onException(r.activity, e)) {
|
||||
throw new RuntimeException("Unable to resume activity "
|
||||
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
@@ -3816,126 +3826,115 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
|
||||
// TODO Push resumeArgs into the activity for consideration
|
||||
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
|
||||
if (r == null) {
|
||||
// We didn't actually resume the activity, so skipping any follow-up actions.
|
||||
return;
|
||||
}
|
||||
|
||||
if (r != null) {
|
||||
final Activity a = r.activity;
|
||||
final Activity a = r.activity;
|
||||
|
||||
if (localLOGV) Slog.v(
|
||||
TAG, "Resume " + r + " started activity: " +
|
||||
a.mStartedActivity + ", hideForNow: " + r.hideForNow
|
||||
+ ", finished: " + a.mFinished);
|
||||
if (localLOGV) {
|
||||
Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
|
||||
+ ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
|
||||
}
|
||||
|
||||
final int forwardBit = isForward ?
|
||||
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
|
||||
final int forwardBit = isForward
|
||||
? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
|
||||
|
||||
// If the window hasn't yet been added to the window manager,
|
||||
// and this guy didn't finish itself or start another activity,
|
||||
// then go ahead and add the window.
|
||||
boolean willBeVisible = !a.mStartedActivity;
|
||||
if (!willBeVisible) {
|
||||
try {
|
||||
willBeVisible = ActivityManager.getService().willActivityBeVisible(
|
||||
a.getActivityToken());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
// If the window hasn't yet been added to the window manager,
|
||||
// and this guy didn't finish itself or start another activity,
|
||||
// then go ahead and add the window.
|
||||
boolean willBeVisible = !a.mStartedActivity;
|
||||
if (!willBeVisible) {
|
||||
try {
|
||||
willBeVisible = ActivityManager.getService().willActivityBeVisible(
|
||||
a.getActivityToken());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
if (r.window == null && !a.mFinished && willBeVisible) {
|
||||
r.window = r.activity.getWindow();
|
||||
View decor = r.window.getDecorView();
|
||||
decor.setVisibility(View.INVISIBLE);
|
||||
ViewManager wm = a.getWindowManager();
|
||||
WindowManager.LayoutParams l = r.window.getAttributes();
|
||||
a.mDecor = decor;
|
||||
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
|
||||
l.softInputMode |= forwardBit;
|
||||
if (r.mPreserveWindow) {
|
||||
a.mWindowAdded = true;
|
||||
r.mPreserveWindow = false;
|
||||
// Normally the ViewRoot sets up callbacks with the Activity
|
||||
// in addView->ViewRootImpl#setView. If we are instead reusing
|
||||
// the decor view we have to notify the view root that the
|
||||
// callbacks may have changed.
|
||||
ViewRootImpl impl = decor.getViewRootImpl();
|
||||
if (impl != null) {
|
||||
impl.notifyChildRebuilt();
|
||||
}
|
||||
}
|
||||
if (r.window == null && !a.mFinished && willBeVisible) {
|
||||
r.window = r.activity.getWindow();
|
||||
View decor = r.window.getDecorView();
|
||||
decor.setVisibility(View.INVISIBLE);
|
||||
ViewManager wm = a.getWindowManager();
|
||||
WindowManager.LayoutParams l = r.window.getAttributes();
|
||||
a.mDecor = decor;
|
||||
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
|
||||
l.softInputMode |= forwardBit;
|
||||
if (r.mPreserveWindow) {
|
||||
if (a.mVisibleFromClient) {
|
||||
if (!a.mWindowAdded) {
|
||||
a.mWindowAdded = true;
|
||||
r.mPreserveWindow = false;
|
||||
// Normally the ViewRoot sets up callbacks with the Activity
|
||||
// in addView->ViewRootImpl#setView. If we are instead reusing
|
||||
// the decor view we have to notify the view root that the
|
||||
// callbacks may have changed.
|
||||
ViewRootImpl impl = decor.getViewRootImpl();
|
||||
if (impl != null) {
|
||||
impl.notifyChildRebuilt();
|
||||
}
|
||||
}
|
||||
if (a.mVisibleFromClient) {
|
||||
if (!a.mWindowAdded) {
|
||||
a.mWindowAdded = true;
|
||||
wm.addView(decor, l);
|
||||
} else {
|
||||
// The activity will get a callback for this {@link LayoutParams} change
|
||||
// earlier. However, at that time the decor will not be set (this is set
|
||||
// in this method), so no action will be taken. This call ensures the
|
||||
// callback occurs with the decor set.
|
||||
a.onWindowAttributesChanged(l);
|
||||
}
|
||||
wm.addView(decor, l);
|
||||
} else {
|
||||
// The activity will get a callback for this {@link LayoutParams} change
|
||||
// earlier. However, at that time the decor will not be set (this is set
|
||||
// in this method), so no action will be taken. This call ensures the
|
||||
// callback occurs with the decor set.
|
||||
a.onWindowAttributesChanged(l);
|
||||
}
|
||||
}
|
||||
|
||||
// If the window has already been added, but during resume
|
||||
// we started another activity, then don't yet make the
|
||||
// window visible.
|
||||
} else if (!willBeVisible) {
|
||||
if (localLOGV) Slog.v(
|
||||
TAG, "Launch " + r + " mStartedActivity set");
|
||||
r.hideForNow = true;
|
||||
} else if (!willBeVisible) {
|
||||
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
|
||||
r.hideForNow = true;
|
||||
}
|
||||
|
||||
// Get rid of anything left hanging around.
|
||||
cleanUpPendingRemoveWindows(r, false /* force */);
|
||||
|
||||
// The window is now visible if it has been added, we are not
|
||||
// simply finishing, and we are not starting another activity.
|
||||
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
|
||||
if (r.newConfig != null) {
|
||||
performConfigurationChangedForActivity(r, r.newConfig);
|
||||
if (DEBUG_CONFIGURATION) {
|
||||
Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
|
||||
+ r.activity.mCurrentConfig);
|
||||
}
|
||||
r.newConfig = null;
|
||||
}
|
||||
|
||||
// Get rid of anything left hanging around.
|
||||
cleanUpPendingRemoveWindows(r, false /* force */);
|
||||
|
||||
// The window is now visible if it has been added, we are not
|
||||
// simply finishing, and we are not starting another activity.
|
||||
if (!r.activity.mFinished && willBeVisible
|
||||
&& r.activity.mDecor != null && !r.hideForNow) {
|
||||
if (r.newConfig != null) {
|
||||
performConfigurationChangedForActivity(r, r.newConfig);
|
||||
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
|
||||
+ r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
|
||||
r.newConfig = null;
|
||||
}
|
||||
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
|
||||
+ isForward);
|
||||
WindowManager.LayoutParams l = r.window.getAttributes();
|
||||
if ((l.softInputMode
|
||||
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
|
||||
!= forwardBit) {
|
||||
l.softInputMode = (l.softInputMode
|
||||
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
|
||||
| forwardBit;
|
||||
if (r.activity.mVisibleFromClient) {
|
||||
ViewManager wm = a.getWindowManager();
|
||||
View decor = r.window.getDecorView();
|
||||
wm.updateViewLayout(decor, l);
|
||||
}
|
||||
}
|
||||
|
||||
r.activity.mVisibleFromServer = true;
|
||||
mNumVisibleActivities++;
|
||||
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
|
||||
WindowManager.LayoutParams l = r.window.getAttributes();
|
||||
if ((l.softInputMode
|
||||
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
|
||||
!= forwardBit) {
|
||||
l.softInputMode = (l.softInputMode
|
||||
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
|
||||
| forwardBit;
|
||||
if (r.activity.mVisibleFromClient) {
|
||||
r.activity.makeVisible();
|
||||
ViewManager wm = a.getWindowManager();
|
||||
View decor = r.window.getDecorView();
|
||||
wm.updateViewLayout(decor, l);
|
||||
}
|
||||
}
|
||||
|
||||
r.nextIdle = mNewActivities;
|
||||
mNewActivities = r;
|
||||
if (localLOGV) {
|
||||
Slog.v(TAG, "Scheduling idle handler for " + r);
|
||||
}
|
||||
Looper.myQueue().addIdleHandler(new Idler());
|
||||
} else {
|
||||
// If an exception was thrown when trying to resume, then
|
||||
// just end this activity.
|
||||
try {
|
||||
ActivityManager.getService()
|
||||
.finishActivity(token, Activity.RESULT_CANCELED, null,
|
||||
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
|
||||
} catch (RemoteException ex) {
|
||||
throw ex.rethrowFromSystemServer();
|
||||
r.activity.mVisibleFromServer = true;
|
||||
mNumVisibleActivities++;
|
||||
if (r.activity.mVisibleFromClient) {
|
||||
r.activity.makeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
r.nextIdle = mNewActivities;
|
||||
mNewActivities = r;
|
||||
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
|
||||
Looper.myQueue().addIdleHandler(new Idler());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.res.Configuration;
|
||||
import android.os.IBinder;
|
||||
import android.util.MergedConfiguration;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.content.ReferrerIntent;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -48,7 +49,8 @@ public abstract class ClientTransactionHandler {
|
||||
* Execute transaction immediately without scheduling it. This is used for local requests, so
|
||||
* it will also recycle the transaction.
|
||||
*/
|
||||
void executeTransaction(ClientTransaction transaction) {
|
||||
@VisibleForTesting
|
||||
public void executeTransaction(ClientTransaction transaction) {
|
||||
transaction.preExecute(this);
|
||||
getTransactionExecutor().execute(transaction);
|
||||
transaction.recycle();
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package android.app.activity;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.IApplicationThread;
|
||||
import android.app.servertransaction.ActivityRelaunchItem;
|
||||
import android.app.servertransaction.ClientTransaction;
|
||||
@@ -76,6 +79,21 @@ public class ActivityThreadTest {
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
/** Verify that repeated resume requests to activity will be ignored. */
|
||||
@Test
|
||||
public void testRepeatedResume() throws Exception {
|
||||
final Activity activity = mActivityTestRule.launchActivity(new Intent());
|
||||
final ActivityThread activityThread = activity.getActivityThread();
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
|
||||
activityThread.executeTransaction(newResumeTransaction(activity));
|
||||
assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
|
||||
true /* finalStateRequest */, "test"));
|
||||
|
||||
assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
|
||||
false /* finalStateRequest */, "test"));
|
||||
});
|
||||
}
|
||||
|
||||
private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
|
||||
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
|
||||
null, 0, new MergedConfiguration(), false /* preserveWindow */);
|
||||
|
||||
Reference in New Issue
Block a user