From f64f5dab262c82f3bf05916b07a836c5070ad876 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Mon, 16 Mar 2020 13:47:48 -0700 Subject: [PATCH] Restrict ime adjustment to maintain primary split visibility This does a few things. 1. Fixes the logic around restricting the amount of adjustment. Its supposed to make sure that at-least 1/3 of the primary split is visible. 2. Fixes an issue where windowcontainertransactions weren't applying configuration updates to tiles specifically 3. Includes configuration changes among the things that will send onTaskInfoChanged updates to task-organizers. Previously, the changes were actually applied late. - The reported changes are restricted to only configs that task organizers can set. 4. To adapt to config changes being reported, divider code needed to be changed. Bug: 151181674 Test: Using various snaps of split, open ime in secondary and verify that primary remains visible. Change-Id: I7001bd29872a15950f91ab2407848fde8c3f1d02 --- .../systemui/stackdivider/Divider.java | 24 +++++++--- .../stackdivider/SplitDisplayLayout.java | 21 +++++++-- .../SplitScreenTaskOrganizer.java | 13 +++-- .../core/java/com/android/server/wm/Task.java | 22 ++++++--- .../server/wm/TaskOrganizerController.java | 47 ++++++++++++++++--- .../java/com/android/server/wm/TaskTile.java | 7 +++ 6 files changed, 105 insertions(+), 29 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index d7eab3a6eac44..d58cc730e7490 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -273,10 +273,12 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } // Update all the adjusted-for-ime states - mView.setAdjustedForIme(mTargetShown, mTargetShown - ? DisplayImeController.ANIMATION_DURATION_SHOW_MS - : DisplayImeController.ANIMATION_DURATION_HIDE_MS); - setAdjustedForIme(mTargetShown); + if (!mPaused) { + mView.setAdjustedForIme(mTargetShown, mTargetShown + ? DisplayImeController.ANIMATION_DURATION_SHOW_MS + : DisplayImeController.ANIMATION_DURATION_HIDE_MS); + } + setAdjustedForIme(mTargetShown && !mPaused); } @Override @@ -390,6 +392,9 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, mTargetPrimaryDim = mTargetSecondaryDim = 0.f; updateImeAdjustState(); startAsyncAnimation(); + if (mAnimation != null) { + mAnimation.end(); + } }); } @@ -605,15 +610,17 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, + mHomeStackResizable + "->" + homeStackResizable + " split:" + inSplitMode()); } WindowContainerTransaction wct = new WindowContainerTransaction(); + final boolean minimizedChanged = mMinimized != minimized; // Update minimized state - if (mMinimized != minimized) { + if (minimizedChanged) { mMinimized = minimized; } // Always set this because we could be entering split when mMinimized is already true wct.setFocusable(mSplits.mPrimary.token, !mMinimized); // Update home-stack resizability - if (mHomeStackResizable != homeStackResizable) { + final boolean homeResizableChanged = mHomeStackResizable != homeStackResizable; + if (homeResizableChanged) { mHomeStackResizable = homeStackResizable; if (inSplitMode()) { WindowManagerProxy.applyHomeTasksMinimized( @@ -629,7 +636,10 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, if (mMinimized) { mImePositionProcessor.pause(displayId); } - mView.setMinimizedDockStack(minimized, getAnimDuration(), homeStackResizable); + if (minimizedChanged || homeResizableChanged) { + // This conflicts with IME adjustment, so only call it when things change. + mView.setMinimizedDockStack(minimized, getAnimDuration(), homeStackResizable); + } if (!mMinimized) { // afterwards so it can end any animations started in view mImePositionProcessor.resume(displayId); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java index 271faed54bcab..deead34b86bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java @@ -191,13 +191,24 @@ public class SplitDisplayLayout { final int currDividerWidth = (int) (dividerWidthInactive * shownFraction + dividerWidth * (1.f - shownFraction)); + // Calculate the highest we can move the bottom of the top stack to keep 30% visible. final int minTopStackBottom = displayStableRect.top + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN); - final int minImeTop = minTopStackBottom + currDividerWidth; - - // Calculate an offset which shifts the stacks up by the height of the IME, but still - // leaves at least 30% of the top stack visible. - final int yOffset = Math.max(0, dl.height() - Math.max(currImeTop, minImeTop)); + // Based on that, calculate the maximum amount we'll allow the ime to shift things. + final int maxOffset = mPrimary.bottom - minTopStackBottom; + // Calculate how much we would shift things without limits (basically the height of ime). + final int desiredOffset = hiddenTop - shownTop; + // Calculate an "adjustedTop" which is the currImeTop but restricted by our constraints. + // We want an effect where the adjustment only occurs during the "highest" portion of the + // ime animation. This is done by shifting the adjustment values by the difference in + // offsets (effectively playing the whole adjustment animation some fixed amount of pixels + // below the ime top). + final int topCorrection = Math.max(0, desiredOffset - maxOffset); + final int adjustedTop = currImeTop + topCorrection; + // The actual yOffset is the distance between adjustedTop and the bottom of the display. + // Since our adjustedTop values are playing "below" the ime, we clamp at 0 so we only + // see adjustment upward. + final int yOffset = Math.max(0, dl.height() - adjustedTop); // TOP // Reduce the offset by an additional small amount to squish the divider bar. diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java index 48ea4aef9968b..f1bb27af776bb 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java @@ -30,7 +30,6 @@ import android.os.RemoteException; import android.util.Log; import android.view.Display; import android.view.ITaskOrganizer; -import android.view.IWindowContainer; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -108,6 +107,8 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { * presentations based on the contents of the split regions. */ private void handleTaskInfoChanged(RunningTaskInfo info) { + final boolean secondaryWasHomeOrRecents = mSecondary.topActivityType == ACTIVITY_TYPE_HOME + || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS; final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; if (info.token.asBinder() == mPrimary.token.asBinder()) { @@ -117,9 +118,16 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { } final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; + final boolean secondaryIsHomeOrRecents = mSecondary.topActivityType == ACTIVITY_TYPE_HOME + || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS; if (DEBUG) { Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); } + if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty + && secondaryWasHomeOrRecents == secondaryIsHomeOrRecents) { + // No relevant changes + return; + } if (primaryIsEmpty || secondaryIsEmpty) { // At-least one of the splits is empty which means we are currently transitioning // into or out-of split-screen mode. @@ -146,8 +154,7 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { } mDivider.startEnterSplit(); } - } else if (mSecondary.topActivityType == ACTIVITY_TYPE_HOME - || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS) { + } else if (secondaryIsHomeOrRecents) { // Both splits are populated but the secondary split has a home/recents stack on top, // so enter minimized mode. mDivider.ensureMinimizedSplit(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f826deb6926a6..3c0db0961c15b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1938,7 +1938,12 @@ class Task extends WindowContainer { // TODO: Should also take care of Pip mode changes here. saveLaunchingStateIfNeeded(); - updateTaskOrganizerState(false /* forceUpdate */); + final boolean taskOrgChanged = updateTaskOrganizerState(false /* forceUpdate */); + // If the task organizer has changed, then it will already be receiving taskAppeared with + // the latest task-info thus the task-info won't have changed. + if (!taskOrgChanged && mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */); + } } /** @@ -4043,15 +4048,16 @@ class Task extends WindowContainer { } } - void setTaskOrganizer(ITaskOrganizer organizer) { + boolean setTaskOrganizer(ITaskOrganizer organizer) { if (mTaskOrganizer == organizer) { - return; + return false; } // Let the old organizer know it has lost control. sendTaskVanished(); mTaskOrganizer = organizer; sendTaskAppeared(); onTaskOrganizerChanged(); + return true; } // Called on Binder death. @@ -4068,10 +4074,11 @@ class Task extends WindowContainer { * @param forceUpdate Updates the task organizer to the one currently specified in the task * org controller for the task's windowing mode, ignoring the cached * windowing mode checks. + * @return {@code true} if task organizer changed. */ - void updateTaskOrganizerState(boolean forceUpdate) { + boolean updateTaskOrganizerState(boolean forceUpdate) { if (!isRootTask()) { - return; + return false; } final int windowingMode = getWindowingMode(); @@ -4080,7 +4087,7 @@ class Task extends WindowContainer { // with our old organizer. This lets us implement the semantic // where SysUI can continue to manage it's old tasks // while CTS temporarily takes over the registration. - return; + return false; } /* * Different windowing modes may be managed by different task organizers. If @@ -4089,8 +4096,9 @@ class Task extends WindowContainer { */ final ITaskOrganizer org = mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); - setTaskOrganizer(org); + final boolean result = setTaskOrganizer(org); mLastTaskOrganizerWindowingMode = windowingMode; + return result; } private void onTaskOrganizerChanged() { diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 8f09f3faae29c..b0dbc8275b025 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -67,6 +67,21 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; + /** + * Masks specifying which configurations task-organizers can control. Incoming transactions + * will be filtered to only include these. + */ + private static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION + | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE; + private static final int CONTROLLABLE_WINDOW_CONFIGS = WindowConfiguration.WINDOW_CONFIG_BOUNDS + | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; + /** + * Masks specifying which configurations are important to report back to an organizer when + * changed. + */ + private static final int REPORT_CONFIGS = CONTROLLABLE_CONFIGS; + private static final int REPORT_WINDOW_CONFIGS = CONTROLLABLE_WINDOW_CONFIGS; + private final WindowManagerGlobalLock mGlobalLock; private class DeathRecipient implements IBinder.DeathRecipient { @@ -320,11 +335,23 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub if (mTmpTaskInfo == null) { mTmpTaskInfo = new RunningTaskInfo(); } + mTmpTaskInfo.configuration.unset(); task.fillTaskInfo(mTmpTaskInfo); boolean changed = lastInfo == null || mTmpTaskInfo.topActivityType != lastInfo.topActivityType || mTmpTaskInfo.isResizable() != lastInfo.isResizable() || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams; + if (!changed) { + int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration); + final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + ? (int) mTmpTaskInfo.configuration.windowConfiguration.diff( + lastInfo.configuration.windowConfiguration, + true /* compareUndefined */) : 0; + if ((winCfgChanges & REPORT_WINDOW_CONFIGS) == 0) { + cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + } + changed = (cfgChanges & REPORT_CONFIGS) != 0; + } if (!(changed || force)) { return; } @@ -479,12 +506,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub final Task task = (Task) container; // The "client"-facing API should prevent bad changes; however, just in case, sanitize // masks here. - int configMask = change.getConfigSetMask(); - int windowMask = change.getWindowSetMask(); - configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION - | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE; - windowMask &= (WindowConfiguration.WINDOW_CONFIG_BOUNDS - | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS); + final int configMask = change.getConfigSetMask() & CONTROLLABLE_CONFIGS; + final int windowMask = change.getWindowSetMask() & CONTROLLABLE_WINDOW_CONFIGS; int effects = 0; if (configMask != 0) { Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); @@ -671,7 +694,17 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub false /* preserveWindow */); try { for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { - haveConfigChanges.valueAt(i).forAllActivities(f); + final WindowContainer wc = haveConfigChanges.valueAt(i); + final Task task = wc.asTask(); + final TaskTile tile = task != null ? task.asTile() : null; + if (tile != null) { + // Special case for tile. Can't override normal forAllActivities + // because it generates duplicate calls and messes up existing + // code-paths. + tile.forAllTileActivities(f); + } else { + wc.forAllActivities(f); + } } } finally { f.recycle(); diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java index 205b423a38dd7..822f840005b79 100644 --- a/services/core/java/com/android/server/wm/TaskTile.java +++ b/services/core/java/com/android/server/wm/TaskTile.java @@ -34,6 +34,7 @@ import android.util.Slog; import java.util.ArrayList; import java.util.Comparator; +import java.util.function.Consumer; /** * A Tile. Right now this acts as a proxy for manipulating non-child stacks. Eventually, this @@ -141,6 +142,12 @@ public class TaskTile extends ActivityStack { } } + void forAllTileActivities(Consumer callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + mChildren.get(i).forAllActivities(callback, true /* traverseTopToBottom */); + } + } + /** * Until this can be part of the hierarchy, the Stack level can use this utility during * resolveOverrideConfig to simulate inheritance.