diff options
| author | 2019-01-19 01:40:59 +0000 | |
|---|---|---|
| committer | 2019-01-19 01:40:59 +0000 | |
| commit | 26058dc2a00ce545631f543ee9f7df1b2fe41d0e (patch) | |
| tree | dbcc11e1a73c84ba80c0865acba63c41517ff5a8 | |
| parent | 4073dc0bd1ada94f7252e90f425f83b1b8860fde (diff) | |
| parent | 5bca8b8c25e8a944daab643b4fbf3c51662cd385 (diff) | |
Merge "Multiple resumed activities"
10 files changed, 166 insertions, 55 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index 65d66f44b5dd..e817dd47e756 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -558,22 +558,26 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } /** - * Pause all activities in either all of the stacks or just the back stacks. + * Pause all activities in either all of the stacks or just the back stacks. This is done before + * resuming a new activity and to make sure that previously active activities are + * paused in stacks that are no longer visible or in pinned windowing mode. This does not + * pause activities in visible stacks, so if an activity is launched within the same stack/task, + * then we should explicitly pause that stack's top activity. * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). * @param resuming The resuming activity. * @param dontWait The resuming activity isn't going to wait for all activities to be paused * before resuming. - * @return true if any activity was paused as a result of this call. + * @return {@code true} if any activity was paused as a result of this call. */ boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mStacks.get(stackNdx); - // TODO(b/111541062): Check if resumed activity on this display instead - if (!mRootActivityContainer.isTopDisplayFocusedStack(stack) - && stack.getResumedActivity() != null) { + final ActivityRecord resumedActivity = stack.getResumedActivity(); + if (resumedActivity != null + && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + - " mResumedActivity=" + stack.getResumedActivity()); + " mResumedActivity=" + resumedActivity); someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, dontWait); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b8634d88319a..6213fa02cb9f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1946,30 +1946,84 @@ final class ActivityRecord extends ConfigurationContainer { try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, WindowVisibilityItem.obtain(true /* showWindow */)); - if (shouldPauseWhenBecomingVisible()) { - // An activity must be in the {@link PAUSING} state for the system to validate - // the move to {@link PAUSED}. - setState(PAUSING, "makeVisibleIfNeeded"); + makeActiveIfNeeded(null /* activeActivity*/); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); + } + } + + /** + * Make activity resumed or paused if needed. + * @param activeActivity an activity that is resumed or just completed pause action. + * We won't change the state of this activity. + */ + boolean makeActiveIfNeeded(ActivityRecord activeActivity) { + if (shouldResumeActivity(activeActivity)) { + if (DEBUG_VISIBILITY) { + Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this); + } + return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */, + null /* options */); + } else if (shouldPauseActivity(activeActivity)) { + if (DEBUG_VISIBILITY) { + Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this); + } + // An activity must be in the {@link PAUSING} state for the system to validate + // the move to {@link PAUSED}. + setState(PAUSING, "makeVisibleIfNeeded"); + try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */)); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); } + return false; } - /** Check if activity should be moved to PAUSED state when it becomes visible. */ - private boolean shouldPauseWhenBecomingVisible() { - // If the activity is stopped or stopping, cycle to the paused state. We avoid doing + /** + * Check if activity should be moved to PAUSED state. The activity: + * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) + * - should be non-focusable + * - should not be currently pausing or paused + * @param activeActivity the activity that is active or just completed pause action. We won't + * resume if this activity is active. + */ + private boolean shouldPauseActivity(ActivityRecord activeActivity) { + return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED); + } + + /** + * Check if activity should be moved to RESUMED state. The activity: + * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) + * - should be focusable + * @param activeActivity the activity that is active or just completed pause action. We won't + * resume if this activity is active. + */ + private boolean shouldResumeActivity(ActivityRecord activeActivity) { + return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED); + } + + /** + * Check if activity is eligible to be made active (resumed of paused). The activity: + * - should be paused, stopped or stopping + * - should not be the currently active one + * - should be either the topmost in task, or right below the top activity that is finishing + * If all of these conditions are not met at the same time, the activity cannot be made active. + */ + private boolean shouldMakeActive(ActivityRecord activeActivity) { + // If the activity is stopped, stopping, cycle to an active state. We avoid doing // this when there is an activity waiting to become translucent as the extra binder // calls will lead to noticeable jank. A later call to - // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper - // paused state. We also avoid doing this for the activity the stack supervisor - // considers the resumed activity, as normal means will bring the activity from STOPPED - // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles. - if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null - || isResumedActivityOnDisplay()) { + // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper + // active state. + if (!isState(RESUMED, PAUSED, STOPPED, STOPPING) + || getActivityStack().mTranslucentActivityWaiting != null) { + return false; + } + + if (this == activeActivity) { return false; } @@ -1979,14 +2033,14 @@ final class ActivityRecord extends ConfigurationContainer { throw new IllegalStateException("Activity not found in its task"); } if (positionInTask == task.mActivities.size() - 1) { - // It's the topmost activity in the task - should become paused now + // It's the topmost activity in the task - should become resumed now return true; } // Check if activity above is finishing now and this one becomes the topmost in task. final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1); if (activityAbove.finishing && results == null) { - // We will only allow pausing if activity above wasn't launched for result. Otherwise it - // will cause this activity to resume before getting result. + // We will only allow making active if activity above wasn't launched for result. + // Otherwise it will cause this activity to resume before getting result. return true; } return false; diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 891c3da90b93..3aef8e1f84bf 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -357,6 +357,11 @@ class ActivityStack extends ConfigurationContainer { */ boolean mForceHidden = false; + /** + * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively + */ + boolean mInResumeTopActivity = false; + private boolean mUpdateBoundsDeferred; private boolean mUpdateBoundsDeferredCalled; private boolean mUpdateDisplayedBoundsDeferredCalled; @@ -1732,6 +1737,7 @@ class ActivityStack extends ConfigurationContainer { "Activity paused: token=" + token + ", timeout=" + timeout); final ActivityRecord r = isInStackLocked(token); + if (r != null) { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { @@ -2088,8 +2094,7 @@ class ActivityStack extends ConfigurationContainer { boolean aboveTop = top != null; final boolean stackShouldBeVisible = shouldBeVisible(starting); boolean behindFullscreenActivity = !stackShouldBeVisible; - boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this) - && (isInStackLocked(starting) == null); + boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null; final boolean isTopNotPinnedStack = isAttached() && getDisplay().isTopNotPinnedStack(this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -2150,6 +2155,10 @@ class ActivityStack extends ConfigurationContainer { if (r.handleAlreadyVisible()) { resumeNextActivity = false; } + + if (notifyClients) { + r.makeActiveIfNeeded(starting); + } } else { r.makeVisibleIfNeeded(starting, notifyClients); } @@ -2327,7 +2336,7 @@ class ActivityStack extends ConfigurationContainer { r.setVisible(true); } if (r != starting) { - mStackSupervisor.startSpecificActivityLocked(r, andResume, false); + mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */); return true; } } @@ -2505,7 +2514,7 @@ class ActivityStack extends ConfigurationContainer { */ @GuardedBy("mService") boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) { - if (mStackSupervisor.inResumeTopActivity) { + if (mInResumeTopActivity) { // Don't even start recursing. return false; } @@ -2513,7 +2522,7 @@ class ActivityStack extends ConfigurationContainer { boolean result = false; try { // Protect against recursion. - mStackSupervisor.inResumeTopActivity = true; + mInResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); // When resuming the top activity, it may be necessary to pause the top activity (for @@ -2528,7 +2537,7 @@ class ActivityStack extends ConfigurationContainer { checkReadyForSleep(); } } finally { - mStackSupervisor.inResumeTopActivity = false; + mInResumeTopActivity = false; } return result; @@ -2561,7 +2570,7 @@ class ActivityStack extends ConfigurationContainer { // Find the next top-most activity to resume in this stack that is not finishing and is // focusable. If it is not focusable, we will fall into the case below to resume the // top activity in the next focusable task. - final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); + ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); final boolean hasRunningActivity = next != null; @@ -2649,6 +2658,12 @@ class ActivityStack extends ConfigurationContainer { if (!mRootActivityContainer.allPausedActivitiesComplete()) { if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE, "resumeTopActivityLocked: Skip resume: some activity pausing."); + + // Adding previous activity to the waiting visible list, or it would be stopped + // before top activity being visible. + if (prev != null && !next.nowVisible) { + mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev); + } return false; } @@ -2858,7 +2873,9 @@ class ActivityStack extends ConfigurationContainer { // the screen based on the new activity order. boolean notUpdated = true; - if (isFocusedStackOnDisplay()) { + // Activity should also be visible if set mLaunchTaskBehind to true (see + // ActivityRecord#shouldBeVisibleIgnoringKeyguard()). + if (shouldBeVisible(next)) { // We have special rotation behavior when here is some active activity that // requests specific orientation or Keyguard is locked. Make sure all activity // visibilities are set correctly as well as the transition is updated if needed @@ -4087,6 +4104,12 @@ class ActivityStack extends ConfigurationContainer { mStackSupervisor.mFinishingActivities.add(r); r.resumeKeyDispatchingLocked(); mRootActivityContainer.resumeFocusedStacksTopActivities(); + // If activity was not paused at this point - explicitly pause it to start finishing + // process. Finishing will be completed once it reports pause back. + if (r.isState(RESUMED) && mPausingActivity != null) { + startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */, + false /* dontWait */); + } return r; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 3a288ca5560d..a83ef34f1cac 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -327,9 +327,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { */ PowerManager.WakeLock mGoingToSleep; - /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ - boolean inResumeTopActivity; - /** * Temporary rect used during docked stack resize calculation so we don't need to create a new * object each time. diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 08596836ed4d..37498cd6bf18 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -179,7 +179,10 @@ public class ActivityStartController { .setActivityOptions(options.toBundle()) .execute(); mLastHomeActivityStartRecord = tmpOutRecord[0]; - if (mSupervisor.inResumeTopActivity) { + final ActivityDisplay display = + mService.mRootActivityContainer.getActivityDisplay(displayId); + final ActivityStack homeStack = display != null ? display.getHomeStack() : null; + if (homeStack != null && homeStack.mInResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it // again. We need to schedule another resume. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 916baa08fcbc..8e9de31ffc56 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1622,7 +1622,7 @@ class ActivityStarter { // Also, we don't want to resume activities in a task that currently has an overlay // as the starting activity just needs to be in the visible paused state until the // over is removed. - mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); + mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS); // Go ahead and tell window manager to execute app transition for this activity // since the app transition will not be triggered through the resume channel. mTargetStack.getDisplay().mDisplayContent.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index 9b7214120aed..c4a853dc3483 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -1108,28 +1108,41 @@ class RootActivityContainer extends ConfigurationContainer return false; } + boolean result = false; if (targetStack != null && (targetStack.isTopStackOnDisplay() || getTopDisplayFocusedStack() == targetStack)) { - return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); + result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } - // Resume all top activities in focused stacks on all displays. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + boolean resumedOnDisplay = false; final ActivityDisplay display = mActivityDisplays.get(displayNdx); - final ActivityStack focusedStack = display.getFocusedStack(); - if (focusedStack == null) { - continue; + for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getChildAt(stackNdx); + final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); + if (!stack.isFocusableAndVisible() || topRunningActivity == null) { + continue; + } + if (topRunningActivity.isState(RESUMED)) { + // Kick off any lingering app transitions form the MoveTaskToFront operation. + stack.executeAppTransition(targetOptions); + } else { + resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target); + } } - final ActivityRecord r = focusedStack.topRunningActivityLocked(); - if (r == null || !r.isState(RESUMED)) { - focusedStack.resumeTopActivityUncheckedLocked(null, null); - } else if (r.isState(RESUMED)) { - // Kick off any lingering app transitions form the MoveTaskToFront operation. - focusedStack.executeAppTransition(targetOptions); + if (!resumedOnDisplay) { + // In cases when there are no valid activities (e.g. device just booted or launcher + // crashed) it's possible that nothing was resumed on a display. Requesting resume + // of top activity in focused stack explicitly will make sure that at least home + // activity is started and resumed, and no recursion occurs. + final ActivityStack focusedStack = display.getFocusedStack(); + if (focusedStack != null) { + focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions); + } } } - return false; + return result; } void applySleepTokens(boolean applyToStacks) { diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 69dcaf473b12..0529ed128130 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -698,6 +698,14 @@ class TaskRecord extends ConfigurationContainer { return false; } + final boolean toTopOfStack = position == MAX_VALUE; + if (toTopOfStack && toStack.getResumedActivity() != null + && toStack.topRunningActivityLocked() != null) { + // Pause the resumed activity on the target stack while re-parenting task on top of it. + toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, + null /* resuming */, false /* pauseImmediately */); + } + final int toStackWindowingMode = toStack.getWindowingMode(); final ActivityRecord topActivity = getTopActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 8be63fc43adb..319ffed3778c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -31,6 +31,7 @@ import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; +import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING; @@ -75,6 +76,9 @@ public class ActivityRecordTests extends ActivityTestsBase { mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build(); mTask = mStack.getChildAt(0); mActivity = mTask.getTopActivity(); + + doReturn(false).when(mService).isBooting(); + doReturn(true).when(mService).isBooted(); } @Test @@ -117,22 +121,23 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - // The activity is in the focused stack so it should not move to paused. + // The activity is in the focused stack so it should be resumed. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - assertTrue(mActivity.isState(STOPPED)); + assertTrue(mActivity.isState(RESUMED)); assertFalse(pauseFound.value); - // Clear focused stack - final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); - when(display.getFocusedStack()).thenReturn(null); + // Make the activity non focusable + mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); + doReturn(false).when(mActivity).isFocusable(); - // In the unfocused stack, the activity should move to paused. + // If the activity is not focusable, it should move to paused. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); assertTrue(mActivity.isState(PAUSING)); assertTrue(pauseFound.value); // Make sure that the state does not change for current non-stopping states. mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); + doReturn(true).when(mActivity).isFocusable(); mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 68df87e3e27d..ea8f33f0c630 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -55,6 +55,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.Looper; +import android.os.PowerManager; import android.os.Process; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; @@ -425,6 +426,7 @@ class ActivityTestsBase { doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); // allow background activity starts by default doReturn(true).when(this).isBackgroundActivityStartsEnabled(); + doNothing().when(this).updateCpuStats(); } void setup(IntentFirewall intentFirewall, PendingIntentController intentController, @@ -580,6 +582,8 @@ class ActivityTestsBase { doNothing().when(this).acquireLaunchWakelock(); doReturn(mKeyguardController).when(this).getKeyguardController(); + mLaunchingActivity = mock(PowerManager.WakeLock.class); + initialize(); } |