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
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index d7eab3a..d58cc73 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 @@
}
// 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 @@
mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
updateImeAdjustState();
startAsyncAnimation();
+ if (mAnimation != null) {
+ mAnimation.end();
+ }
});
}
@@ -605,15 +610,17 @@
+ 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 @@
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 271faed..deead34 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 @@
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 48ea4ae..f1bb27a 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.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 @@
* 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 @@
}
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 @@
}
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 f826deb..3c0db09 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 @@
// 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 @@
}
}
- 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 @@
* @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 @@
// 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 @@
*/
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 8f09f3f..b0dbc82 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 @@
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 @@
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 @@
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 @@
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 205b423..822f840 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 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 @@
}
}
+ void forAllTileActivities(Consumer<ActivityRecord> 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.