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 cc52cb445a1aa..82c590d1920ab 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 { @@ -321,11 +336,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; } @@ -480,12 +507,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()); @@ -672,7 +695,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.