diff options
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 289 | ||||
| -rw-r--r-- | core/java/android/app/ClientTransactionHandler.java | 4 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/activity/ActivityThreadTest.java | 18 |
3 files changed, 165 insertions, 146 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1df724edeccb..50a439897ed6 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -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. + } + 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; } - if (finalStateRequest) { - r.hideForNow = false; - r.activity.mStartedActivity = false; + if (r.pendingResults != null) { + deliverResults(r, r.pendingResults); + r.pendingResults = 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); + 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); - - 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 (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; + + // 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 */); + // 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); - } + // 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.activity.mVisibleFromServer = true; - mNumVisibleActivities++; + 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) { - 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 diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 925080e44778..0639b00045c4 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -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(); diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index a99e1398a7bf..9ef58abba79a 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -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 */); |