diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index db2c9d1a67d44..49247d3729ace 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1273,11 +1273,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void computeConfigurationAfterMultiWindowModeChange() { final Configuration newConfig = new Configuration(); - newConfig.setTo(task.getRequestedOverrideConfiguration()); + final TaskFragment taskFrag = getTaskFragment(); + newConfig.setTo(taskFrag.getRequestedOverrideConfiguration()); Rect outBounds = newConfig.windowConfiguration.getBounds(); - final Configuration parentConfig = task.getParent().getConfiguration(); - task.adjustForMinimalTaskDimensions(outBounds, outBounds, parentConfig); - task.computeConfigResourceOverrides(newConfig, parentConfig); + // TODO(b/189384393): which parent does this Activity really needed? + // I guess we should either call getRootTask or getDisplayArea for this case. + final Configuration parentConfig = taskFrag.getParent().getConfiguration(); + taskFrag.adjustForMinimalTaskDimensions(outBounds, outBounds, parentConfig); + taskFrag.computeConfigResourceOverrides(newConfig, parentConfig); } Task getTask() { @@ -7064,7 +7067,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If the activity has requested override bounds, the configuration needs to be // computed accordingly. if (!matchParentBounds()) { - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, + newParentConfiguration); } // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. @@ -7179,7 +7183,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Since bounds has changed, the configuration needs to be computed accordingly. - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } /** @@ -7364,7 +7368,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. - task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), + newParentConfig); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } @@ -7394,7 +7399,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { // Compute the configuration based on the resolved bounds. If aspect ratio doesn't // restrict, the bounds should be the requested override bounds. - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, getFixedRotationTransformDisplayInfo()); } } @@ -7461,10 +7466,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Use resolvedBounds to compute other override configurations such as appBounds. The bounds // are calculated in compat container space. The actual position on screen will be applied // later, so the calculation is simpler that doesn't need to involve offset from parent. - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, + getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, mCompatDisplayInsets); // Use current screen layout as source because the size of app is independent to parent. - resolvedConfig.screenLayout = Task.computeScreenLayoutOverride( + resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, resolvedConfig.screenHeightDp); @@ -8784,7 +8789,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A outAppBounds.offset(insets.left, insets.top); } else if (rotation != ROTATION_UNDEFINED) { // Ensure the app bounds won't overlap with insets. - Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]); + TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds, + mNonDecorInsets[rotation]); } } } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 8fbe1775fd19f..2eeabf2b17efd 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -34,6 +34,7 @@ import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_ import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION; import android.annotation.CallSuper; +import android.annotation.NonNull; import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Point; @@ -111,6 +112,7 @@ public abstract class ConfigurationContainer { * This method should be used for getting settings applied in each particular level of the * hierarchy. */ + @NonNull public Configuration getConfiguration() { return mFullConfiguration; } @@ -170,11 +172,13 @@ public abstract class ConfigurationContainer { } /** Returns requested override configuration applied to this configuration container. */ + @NonNull public Configuration getRequestedOverrideConfiguration() { return mRequestedOverrideConfiguration; } /** Returns the resolved override configuration. */ + @NonNull Configuration getResolvedOverrideConfiguration() { return mResolvedOverrideConfiguration; } @@ -203,6 +207,7 @@ public abstract class ConfigurationContainer { * Get merged override configuration from the top of the hierarchy down to this particular * instance. This should be reported to client as override config. */ + @NonNull public Configuration getMergedOverrideConfiguration() { return mMergedOverrideConfiguration; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0ebd494a58099..d2065d9224af5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -27,7 +27,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; -import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -50,9 +49,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; @@ -156,7 +152,6 @@ import android.app.IActivityController; import android.app.PictureInPictureParams; import android.app.RemoteAction; import android.app.TaskInfo; -import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -291,7 +286,6 @@ class Task extends TaskFragment { // code. static final int PERSIST_TASK_VERSION = 1; - static final int INVALID_MIN_SIZE = -1; private float mShadowRadius = 0; /** @@ -417,21 +411,12 @@ class Task extends TaskFragment { String mCallingPackage; String mCallingFeatureId; - private final Rect mTmpStableBounds = new Rect(); - private final Rect mTmpNonDecorBounds = new Rect(); - private final Rect mTmpBounds = new Rect(); - private final Rect mTmpInsets = new Rect(); - private final Rect mTmpFullBounds = new Rect(); private static final Rect sTmpBounds = new Rect(); // Last non-fullscreen bounds the task was launched in or resized to. // The information is persisted and used to determine the appropriate root task to launch the // task into on restore. Rect mLastNonFullscreenBounds = null; - // Minimal width and height of this task when it's resizeable. -1 means it should use the - // default minimal width/height. - int mMinWidth; - int mMinHeight; // The surface transition of the target when recents animation is finished. // This is originally introduced to carry out the current surface control position and window @@ -573,119 +558,6 @@ class Task extends TaskFragment { } private static final ResetTargetTaskHelper sResetTargetTaskHelper = new ResetTargetTaskHelper(); - private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper = - new EnsureVisibleActivitiesConfigHelper(); - private class EnsureVisibleActivitiesConfigHelper { - private boolean mUpdateConfig; - private boolean mPreserveWindow; - private boolean mBehindFullscreen; - - void reset(boolean preserveWindow) { - mPreserveWindow = preserveWindow; - mUpdateConfig = false; - mBehindFullscreen = false; - } - - void process(ActivityRecord start, boolean preserveWindow) { - if (start == null || !start.mVisibleRequested) { - return; - } - reset(preserveWindow); - - final PooledFunction f = PooledLambda.obtainFunction( - EnsureVisibleActivitiesConfigHelper::processActivity, this, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/); - f.recycle(); - - if (mUpdateConfig) { - // Ensure the resumed state of the focus activity if we updated the configuration of - // any activity. - mRootWindowContainer.resumeFocusedTasksTopActivities(); - } - } - - boolean processActivity(ActivityRecord r) { - mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow); - mBehindFullscreen |= r.occludesParent(); - return mBehindFullscreen; - } - } - - private final CheckBehindFullscreenActivityHelper mCheckBehindFullscreenActivityHelper = - new CheckBehindFullscreenActivityHelper(); - private class CheckBehindFullscreenActivityHelper { - private boolean mAboveTop; - private boolean mBehindFullscreenActivity; - private ActivityRecord mToCheck; - private Consumer mHandleBehindFullscreenActivity; - private boolean mHandlingOccluded; - - private void reset(ActivityRecord toCheck, - Consumer handleBehindFullscreenActivity) { - mToCheck = toCheck; - mHandleBehindFullscreenActivity = handleBehindFullscreenActivity; - mAboveTop = true; - mBehindFullscreenActivity = false; - - if (!shouldBeVisible(null)) { - // The root task is not visible, so no activity in it should be displaying a - // starting window. Mark all activities below top and behind fullscreen. - mAboveTop = false; - mBehindFullscreenActivity = true; - } - - mHandlingOccluded = mToCheck == null && mHandleBehindFullscreenActivity != null; - } - - boolean process(ActivityRecord toCheck, - Consumer handleBehindFullscreenActivity) { - reset(toCheck, handleBehindFullscreenActivity); - - if (!mHandlingOccluded && mBehindFullscreenActivity) { - return true; - } - - final ActivityRecord topActivity = topRunningActivity(); - final PooledFunction f = PooledLambda.obtainFunction( - CheckBehindFullscreenActivityHelper::processActivity, this, - PooledLambda.__(ActivityRecord.class), topActivity); - forAllActivities(f); - f.recycle(); - - return mBehindFullscreenActivity; - } - - /** Returns {@code true} to stop the outer loop and indicate the result is computed. */ - private boolean processActivity(ActivityRecord r, ActivityRecord topActivity) { - if (mAboveTop) { - if (r == topActivity) { - if (r == mToCheck) { - // It is the top activity in a visible root task. - mBehindFullscreenActivity = false; - return true; - } - mAboveTop = false; - } - mBehindFullscreenActivity |= r.occludesParent(); - return false; - } - - if (mHandlingOccluded) { - // Iterating through all occluded activities. - if (mBehindFullscreenActivity) { - mHandleBehindFullscreenActivity.accept(r); - } - } else if (r == mToCheck) { - return true; - } else if (mBehindFullscreenActivity) { - // It is occluded before {@param toCheck} is found. - return true; - } - mBehindFullscreenActivity |= r.occludesParent(); - return false; - } - } private final FindRootHelper mFindRootHelper = new FindRootHelper(); private class FindRootHelper { @@ -1595,15 +1467,6 @@ class Task extends TaskFragment { updateEffectiveIntent(); } - @Override - public int getActivityType() { - final int applicationType = super.getActivityType(); - if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { - return applicationType; - } - return getTopChild().getActivityType(); - } - @Override void addChild(WindowContainer child, int index) { // If this task had any child before we added this one. @@ -1906,32 +1769,6 @@ class Task extends TaskFragment { && supportsMultiWindowInDisplayArea(tda); } - boolean supportsMultiWindow() { - return supportsMultiWindowInDisplayArea(getDisplayArea()); - } - - /** - * @return whether this task supports multi-window if it is in the given - * {@link TaskDisplayArea}. - */ - boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) { - if (!mAtmService.mSupportsMultiWindow) { - return false; - } - if (tda == null) { - Slog.w(TAG_TASKS, "Can't find TaskDisplayArea to determine support for multi" - + " window. Task id=" + mTaskId + " attached=" + isAttached()); - return false; - } - - if (!isResizeable() && !tda.supportsNonResizableMultiWindow()) { - // Not support non-resizable in multi window. - return false; - } - - return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight); - } - /** * Check whether this task can be launched on the specified display. * @@ -2067,60 +1904,6 @@ class Task extends TaskFragment { } } - void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds, - @NonNull Configuration parentConfig) { - int minWidth = mMinWidth; - int minHeight = mMinHeight; - // If the task has no requested minimal size, we'd like to enforce a minimal size - // so that the user can not render the task too small to manipulate. We don't need - // to do this for the root pinned task as the bounds are controlled by the system. - if (!inPinnedWindowingMode()) { - final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; - final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT; - final int defaultMinSize = (int) (defaultMinSizeDp * density); - - if (minWidth == INVALID_MIN_SIZE) { - minWidth = defaultMinSize; - } - if (minHeight == INVALID_MIN_SIZE) { - minHeight = defaultMinSize; - } - } - if (bounds.isEmpty()) { - // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they - // do, we can just skip. - final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); - if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) { - return; - } - bounds.set(parentBounds); - } - final boolean adjustWidth = minWidth > bounds.width(); - final boolean adjustHeight = minHeight > bounds.height(); - if (!(adjustWidth || adjustHeight)) { - return; - } - - if (adjustWidth) { - if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) { - bounds.left = bounds.right - minWidth; - } else { - // Either left bounds match, or neither match, or the previous bounds were - // fullscreen and we default to keeping left. - bounds.right = bounds.left + minWidth; - } - } - if (adjustHeight) { - if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) { - bounds.top = bounds.bottom - minHeight; - } else { - // Either top bounds match, or neither match, or the previous bounds were - // fullscreen and we default to keeping top. - bounds.bottom = bounds.top + minHeight; - } - } - } - void setLastNonFullscreenBounds(Rect bounds) { if (mLastNonFullscreenBounds == null) { mLastNonFullscreenBounds = new Rect(bounds); @@ -2421,400 +2204,6 @@ class Task extends TaskFragment { mTaskSupervisor.mLaunchParamsPersister.saveTask(this, display); } - /** - * Adjust bounds to stay within root task bounds. - * - * Since bounds might be outside of root task bounds, this method tries to move the bounds in - * a way that keep them unchanged, but be contained within the root task bounds. - * - * @param bounds Bounds to be adjusted. - * @param rootTaskBounds Bounds within which the other bounds should remain. - * @param overlapPxX The amount of px required to be visible in the X dimension. - * @param overlapPxY The amount of px required to be visible in the Y dimension. - */ - private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX, - int overlapPxY) { - if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) { - return; - } - - // For each side of the parent (eg. left), check if the opposing side of the window (eg. - // right) is at least overlap pixels away. If less, offset the window by that difference. - int horizontalDiff = 0; - // If window is smaller than overlap, use it's smallest dimension instead - int overlapLR = Math.min(overlapPxX, bounds.width()); - if (bounds.right < (rootTaskBounds.left + overlapLR)) { - horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left); - } else if (bounds.left > (rootTaskBounds.right - overlapLR)) { - horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left)); - } - int verticalDiff = 0; - int overlapTB = Math.min(overlapPxY, bounds.width()); - if (bounds.bottom < (rootTaskBounds.top + overlapTB)) { - verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top); - } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) { - verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top)); - } - bounds.offset(horizontalDiff, verticalDiff); - } - - /** - * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than - * intersectBounds on a side, then the respective side will not be intersected. - * - * The assumption is that if inOutBounds is initially larger than intersectBounds, then the - * inset on that side is no-longer applicable. This scenario happens when a task's minimal - * bounds are larger than the provided parent/display bounds. - * - * @param inOutBounds the bounds to intersect. - * @param intersectBounds the bounds to intersect with. - * @param intersectInsets insets to apply to intersectBounds before intersecting. - */ - static void intersectWithInsetsIfFits( - Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) { - if (inOutBounds.right <= intersectBounds.right) { - inOutBounds.right = - Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right); - } - if (inOutBounds.bottom <= intersectBounds.bottom) { - inOutBounds.bottom = - Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom); - } - if (inOutBounds.left >= intersectBounds.left) { - inOutBounds.left = - Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left); - } - if (inOutBounds.top >= intersectBounds.top) { - inOutBounds.top = - Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top); - } - } - - /** - * Gets bounds with non-decor and stable insets applied respectively. - * - * If bounds overhangs the display, those edges will not get insets. See - * {@link #intersectWithInsetsIfFits} - * - * @param outNonDecorBounds where to place bounds with non-decor insets applied. - * @param outStableBounds where to place bounds with stable insets applied. - * @param bounds the bounds to inset. - */ - void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds, - DisplayInfo displayInfo) { - outNonDecorBounds.set(bounds); - outStableBounds.set(bounds); - final Task rootTask = getRootTask(); - if (rootTask == null || rootTask.mDisplayContent == null) { - return; - } - mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); - - final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy(); - policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, - displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets); - intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); - - policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); - intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); - } - - /** - * Forces the app bounds related configuration can be computed by - * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo, - * ActivityRecord.CompatDisplayInsets)}. - */ - private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) { - final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (appBounds != null) { - appBounds.setEmpty(); - } - inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) { - if (overrideDisplayInfo != null) { - // Make sure the screen related configs can be computed by the provided display info. - inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; - invalidateAppBoundsConfig(inOutConfig); - } - computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo, - null /* compatInsets */); - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig) { - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - null /* compatInsets */); - } - - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { - if (compatInsets != null) { - // Make sure the app bounds can be computed by the compat insets. - invalidateAppBoundsConfig(inOutConfig); - } - computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, - compatInsets); - } - - /** - * Calculates configuration values used by the client to get resources. This should be run - * using app-facing bounds (bounds unmodified by animations or transient interactions). - * - * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely - * configuring an "inherit-bounds" window which means that all configuration settings would - * just be inherited from the parent configuration. - **/ - void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, - @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { - int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - windowingMode = parentConfig.windowConfiguration.getWindowingMode(); - } - - float density = inOutConfig.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = parentConfig.densityDpi; - } - density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; - - // The bounds may have been overridden at this level. If the parent cannot cover these - // bounds, the configuration is still computed according to the override bounds. - final boolean insideParentBounds; - - final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); - final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds(); - if (resolvedBounds == null || resolvedBounds.isEmpty()) { - mTmpFullBounds.set(parentBounds); - insideParentBounds = true; - } else { - mTmpFullBounds.set(resolvedBounds); - insideParentBounds = parentBounds.contains(resolvedBounds); - } - - // Non-null compatibility insets means the activity prefers to keep its original size, so - // out bounds doesn't need to be restricted by the parent or current display - final boolean customContainerPolicy = compatInsets != null; - - Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (outAppBounds == null || outAppBounds.isEmpty()) { - // App-bounds hasn't been overridden, so calculate a value for it. - inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds); - outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - - if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) { - final Rect containingAppBounds; - if (insideParentBounds) { - containingAppBounds = parentConfig.windowConfiguration.getAppBounds(); - } else { - // Restrict appBounds to display non-decor rather than parent because the - // override bounds are beyond the parent. Otherwise, it won't match the - // overridden bounds. - final TaskDisplayArea displayArea = getDisplayArea(); - containingAppBounds = displayArea != null - ? displayArea.getWindowConfiguration().getAppBounds() : null; - } - if (containingAppBounds != null && !containingAppBounds.isEmpty()) { - outAppBounds.intersect(containingAppBounds); - } - } - } - - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED - || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) { - mTmpNonDecorBounds.set(mTmpFullBounds); - mTmpStableBounds.set(mTmpFullBounds); - } else if (!customContainerPolicy - && (overrideDisplayInfo != null || getDisplayContent() != null)) { - final DisplayInfo di = overrideDisplayInfo != null - ? overrideDisplayInfo - : getDisplayContent().getDisplayInfo(); - - // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen - // area, i.e. the screen area without the system bars. - // The non decor inset are areas that could never be removed in Honeycomb. See - // {@link WindowManagerPolicy#getNonDecorInsetsLw}. - calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di); - } else { - // Apply the given non-decor and stable insets to calculate the corresponding bounds - // for screen size of configuration. - int rotation = inOutConfig.windowConfiguration.getRotation(); - if (rotation == ROTATION_UNDEFINED) { - rotation = parentConfig.windowConfiguration.getRotation(); - } - if (rotation != ROTATION_UNDEFINED && customContainerPolicy) { - mTmpNonDecorBounds.set(mTmpFullBounds); - mTmpStableBounds.set(mTmpFullBounds); - compatInsets.getBoundsByRotation(mTmpBounds, rotation); - intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds, - compatInsets.mNonDecorInsets[rotation]); - intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds, - compatInsets.mStableInsets[rotation]); - outAppBounds.set(mTmpNonDecorBounds); - } else { - // Set to app bounds because it excludes decor insets. - mTmpNonDecorBounds.set(outAppBounds); - mTmpStableBounds.set(outAppBounds); - } - } - - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density); - inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy) - ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp) - : overrideScreenWidthDp; - } - if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density); - inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy) - ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp) - : overrideScreenHeightDp; - } - - if (inOutConfig.smallestScreenWidthDp - == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { - if (WindowConfiguration.isFloating(windowingMode)) { - // For floating tasks, calculate the smallest width from the bounds of the task - inOutConfig.smallestScreenWidthDp = (int) ( - Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); - } - // otherwise, it will just inherit - } - } - - if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { - inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; - } - if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { - // For calculating screen layout, we need to use the non-decor inset screen area for the - // calculation for compatibility reasons, i.e. screen area without system bars that - // could never go away in Honeycomb. - int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density); - int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density); - // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is - // undefined so it can't be used. - if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - compatScreenWidthDp = inOutConfig.screenWidthDp; - } - if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - compatScreenHeightDp = inOutConfig.screenHeightDp; - } - // Reducing the screen layout starting from its parent config. - inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout, - compatScreenWidthDp, compatScreenHeightDp); - } - } - - /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */ - static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp, - int screenHeightDp) { - sourceScreenLayout = sourceScreenLayout - & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); - final int longSize = Math.max(screenWidthDp, screenHeightDp); - final int shortSize = Math.min(screenWidthDp, screenHeightDp); - return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize); - } - - @Override - void resolveOverrideConfiguration(Configuration newParentConfig) { - mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); - super.resolveOverrideConfiguration(newParentConfig); - - int windowingMode = - getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); - final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode(); - - // Resolve override windowing mode to fullscreen for home task (even on freeform - // display), or split-screen if in split-screen mode. - if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) { - windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode) - ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN; - getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); - } - - // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in - // pinned windowing mode. - if (!supportsMultiWindow()) { - final int candidateWindowingMode = - windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode; - if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode) - && candidateWindowingMode != WINDOWING_MODE_PINNED) { - getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode( - WINDOWING_MODE_FULLSCREEN); - } - } - - if (isLeafTask()) { - resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */); - } - computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); - } - - private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig, - Rect previousBounds) { - - int windowingMode = - getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); - } - // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old - // mode that may cause the bounds to be miscalculated, e.g. letterboxed. - getConfiguration().windowConfiguration.setWindowingMode(windowingMode); - Rect outOverrideBounds = - getResolvedOverrideConfiguration().windowConfiguration.getBounds(); - - if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - // Use empty bounds to indicate "fill parent". - outOverrideBounds.setEmpty(); - // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if - // the parent or display is smaller than the size, the content may be cropped. - return; - } - - adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig); - if (windowingMode == WINDOWING_MODE_FREEFORM) { - computeFreeformBounds(outOverrideBounds, newParentConfig); - return; - } - } - - /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ - private void computeFreeformBounds(@NonNull Rect outBounds, - @NonNull Configuration newParentConfig) { - // by policy, make sure the window remains within parent somewhere - final float density = - ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT; - final Rect parentBounds = - new Rect(newParentConfig.windowConfiguration.getBounds()); - final DisplayContent display = getDisplayContent(); - if (display != null) { - // If a freeform window moves below system bar, there is no way to move it again - // by touch. Because its caption is covered by system bar. So we exclude them - // from root task bounds. and then caption will be shown inside stable area. - final Rect stableBounds = new Rect(); - display.getStableRect(stableBounds); - parentBounds.intersect(stableBounds); - } - - fitWithinBounds(outBounds, parentBounds, - (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP), - (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP)); - - // Prevent to overlap caption with stable insets. - final int offsetTop = parentBounds.top - outBounds.top; - if (offsetTop > 0) { - outBounds.offset(0, offsetTop); - } - } - Rect updateOverrideConfigurationFromLaunchBounds() { // If the task is controlled by another organized task, do not set override // configurations and let its parent (organized task) to control it; @@ -2873,12 +2262,9 @@ class Task extends TaskFragment { return getRootTask().mTaskId; } + @Nullable Task getRootTask() { - final WindowContainer parent = getParent(); - if (parent == null) return this; - - final Task parentTask = parent.asTask(); - return parentTask == null ? this : parentTask.getRootTask(); + return getRootTaskFragment().asTask(); } /** @return the first organized task. */ @@ -3449,18 +2835,6 @@ class Task extends TaskFragment { mForceShowForAllUsers = forceShowForAllUsers; } - @Override - public boolean isAttached() { - final TaskDisplayArea taskDisplayArea = getDisplayArea(); - return taskDisplayArea != null && !taskDisplayArea.isRemoved(); - } - - @Override - @Nullable - TaskDisplayArea getDisplayArea() { - return (TaskDisplayArea) super.getDisplayArea(); - } - /** * When we are in a floating root task (Freeform, Pinned, ...) we calculate * insets differently. However if we are animating to the fullscreen root task @@ -5400,25 +4774,6 @@ class Task extends TaskFragment { } } - /** @see ActivityRecord#cancelInitializing() */ - void cancelInitializingActivities() { - // We don't want to clear starting window for activities that aren't behind fullscreen - // activities as we need to display their starting window until they are done initializing. - checkBehindFullscreenActivity(null /* toCheck */, ActivityRecord::cancelInitializing); - } - - /** - * If an activity {@param toCheck} is given, this method returns {@code true} if the activity - * is occluded by any fullscreen activity. If there is no {@param toCheck} and the handling - * function {@param handleBehindFullscreenActivity} is given, this method will pass all occluded - * activities to the function. - */ - boolean checkBehindFullscreenActivity(ActivityRecord toCheck, - Consumer handleBehindFullscreenActivity) { - return mCheckBehindFullscreenActivityHelper.process( - toCheck, handleBehindFullscreenActivity); - } - /** * Ensure that the top activity in the root task is resumed. * @@ -6197,13 +5552,6 @@ class Task extends TaskFragment { return true; } - /** - * Ensures all visible activities at or below the input activity have the right configuration. - */ - void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) { - mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow); - } - // TODO: Can only be called from special methods in ActivityTaskSupervisor. // Need to consolidate those calls points into this resize method so anyone can call directly. void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 845c2482561fc..97bce00fa416f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -16,11 +16,20 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; @@ -44,19 +53,26 @@ import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.Task.ActivityState.STOPPING; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ResultInfo; +import android.app.WindowConfiguration; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.ResumeActivityItem; +import android.content.res.Configuration; +import android.graphics.Rect; import android.os.RemoteException; +import android.util.DisplayMetrics; import android.util.Slog; +import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.function.pooled.PooledFunction; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; @@ -97,10 +113,33 @@ class TaskFragment extends WindowContainer { /** Set to false to disable the preview that is shown while a new activity is being started. */ static final boolean SHOW_APP_STARTING_PREVIEW = true; + /** + * Indicate that the minimal width/height should use the default value. + * + * @see #mMinWidth + * @see #mMinHeight + */ + static final int INVALID_MIN_SIZE = -1; + final ActivityTaskManagerService mAtmService; final ActivityTaskSupervisor mTaskSupervisor; final RootWindowContainer mRootWindowContainer; + // TODO(b/189384393): this is not set in TaskFragment so far. It should be passed from the + // parent task when adding to the hierarchy + /** + * Minimal width of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it + * should use the default minimal width. + */ + int mMinWidth; + // TODO(b/189384393): this is not set in TaskFragment so far. It should be passed from the + // parent task when adding to the hierarchy + /** + * Minimal height of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it + * should use the default minimal height. + */ + int mMinHeight; + // The TaskFragment that adjacent to this one. private TaskFragment mAdjacentTaskFragment; @@ -128,8 +167,53 @@ class TaskFragment extends WindowContainer { @Nullable private ActivityRecord mResumedActivity = null; + private final Rect mTmpInsets = new Rect(); + private final Rect mTmpBounds = new Rect(); + private final Rect mTmpFullBounds = new Rect(); + private final Rect mTmpStableBounds = new Rect(); + private final Rect mTmpNonDecorBounds = new Rect(); + private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper = new EnsureActivitiesVisibleHelper(this); + private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper = + new EnsureVisibleActivitiesConfigHelper(); + private class EnsureVisibleActivitiesConfigHelper { + private boolean mUpdateConfig; + private boolean mPreserveWindow; + private boolean mBehindFullscreen; + + void reset(boolean preserveWindow) { + mPreserveWindow = preserveWindow; + mUpdateConfig = false; + mBehindFullscreen = false; + } + + void process(ActivityRecord start, boolean preserveWindow) { + if (start == null || !start.mVisibleRequested) { + return; + } + reset(preserveWindow); + + final PooledFunction f = PooledLambda.obtainFunction( + EnsureVisibleActivitiesConfigHelper::processActivity, this, + PooledLambda.__(ActivityRecord.class)); + forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/); + f.recycle(); + + if (mUpdateConfig) { + // TODO(b/189384393): rename to resumeFocusedTaskFragmentsTopActivities + // Ensure the resumed state of the focus activity if we updated the configuration of + // any activity. + mRootWindowContainer.resumeFocusedTasksTopActivities(); + } + } + + boolean processActivity(ActivityRecord r) { + mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow); + mBehindFullscreen |= r.occludesParent(); + return mBehindFullscreen; + } + } TaskFragment(ActivityTaskManagerService atmService) { super(atmService.mWindowManager); @@ -197,9 +281,16 @@ class TaskFragment extends WindowContainer { return (TaskDisplayArea) super.getDisplayArea(); } + @Override + public boolean isAttached() { + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + return taskDisplayArea != null && !taskDisplayArea.isRemoved(); + } + /** * Returns the root {@link TaskFragment}, which is usually also a {@link Task}. */ + @NonNull TaskFragment getRootTaskFragment() { final WindowContainer parent = getParent(); if (parent == null) return this; @@ -1297,4 +1388,500 @@ class TaskFragment extends WindowContainer { boolean shouldSleepActivities() { return false; } + + @Override + void resolveOverrideConfiguration(Configuration newParentConfig) { + mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); + super.resolveOverrideConfiguration(newParentConfig); + + int windowingMode = + getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode(); + + // Resolve override windowing mode to fullscreen for home task (even on freeform + // display), or split-screen if in split-screen mode. + if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode) + ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN; + getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); + } + + // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in + // pinned windowing mode. + if (!supportsMultiWindow()) { + final int candidateWindowingMode = + windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode; + if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode) + && candidateWindowingMode != WINDOWING_MODE_PINNED) { + getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode( + WINDOWING_MODE_FULLSCREEN); + } + } + + if (isLeafTaskFragment()) { + resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */); + } + computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + } + + boolean supportsMultiWindow() { + return supportsMultiWindowInDisplayArea(getDisplayArea()); + } + + /** + * @return whether this task supports multi-window if it is in the given + * {@link TaskDisplayArea}. + */ + boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) { + if (!mAtmService.mSupportsMultiWindow) { + return false; + } + final Task task = getTask(); + if (task == null) { + return false; + } + if (tda == null) { + Slog.w(TAG, "Can't find TaskDisplayArea to determine support for multi" + + " window. Task id=" + getTaskId() + " attached=" + isAttached()); + return false; + } + if (!getTask().isResizeable() && !tda.supportsNonResizableMultiWindow()) { + // Not support non-resizable in multi window. + return false; + } + + return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight); + } + + private int getTaskId() { + return getTask().mTaskId; + } + + private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig, + Rect previousBounds) { + + int windowingMode = + getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); + } + // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old + // mode that may cause the bounds to be miscalculated, e.g. letterboxed. + getConfiguration().windowConfiguration.setWindowingMode(windowingMode); + Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); + + if (windowingMode == WINDOWING_MODE_FULLSCREEN) { + // Use empty bounds to indicate "fill parent". + outOverrideBounds.setEmpty(); + // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if + // the parent or display is smaller than the size, the content may be cropped. + return; + } + + adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig); + if (windowingMode == WINDOWING_MODE_FREEFORM) { + computeFreeformBounds(outOverrideBounds, newParentConfig); + return; + } + } + + /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ + private void computeFreeformBounds(@NonNull Rect outBounds, + @NonNull Configuration newParentConfig) { + // by policy, make sure the window remains within parent somewhere + final float density = + ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT; + final Rect parentBounds = + new Rect(newParentConfig.windowConfiguration.getBounds()); + final DisplayContent display = getDisplayContent(); + if (display != null) { + // If a freeform window moves below system bar, there is no way to move it again + // by touch. Because its caption is covered by system bar. So we exclude them + // from root task bounds. and then caption will be shown inside stable area. + final Rect stableBounds = new Rect(); + display.getStableRect(stableBounds); + parentBounds.intersect(stableBounds); + } + + fitWithinBounds(outBounds, parentBounds, + (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP), + (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP)); + + // Prevent to overlap caption with stable insets. + final int offsetTop = parentBounds.top - outBounds.top; + if (offsetTop > 0) { + outBounds.offset(0, offsetTop); + } + } + + /** + * Adjusts bounds to stay within root task bounds. + * + * Since bounds might be outside of root task bounds, this method tries to move the bounds in + * a way that keep them unchanged, but be contained within the root task bounds. + * + * @param bounds Bounds to be adjusted. + * @param rootTaskBounds Bounds within which the other bounds should remain. + * @param overlapPxX The amount of px required to be visible in the X dimension. + * @param overlapPxY The amount of px required to be visible in the Y dimension. + */ + private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX, + int overlapPxY) { + if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) { + return; + } + + // For each side of the parent (eg. left), check if the opposing side of the window (eg. + // right) is at least overlap pixels away. If less, offset the window by that difference. + int horizontalDiff = 0; + // If window is smaller than overlap, use it's smallest dimension instead + int overlapLR = Math.min(overlapPxX, bounds.width()); + if (bounds.right < (rootTaskBounds.left + overlapLR)) { + horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left); + } else if (bounds.left > (rootTaskBounds.right - overlapLR)) { + horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left)); + } + int verticalDiff = 0; + int overlapTB = Math.min(overlapPxY, bounds.width()); + if (bounds.bottom < (rootTaskBounds.top + overlapTB)) { + verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top); + } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) { + verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top)); + } + bounds.offset(horizontalDiff, verticalDiff); + } + + /** + * Ensures all visible activities at or below the input activity have the right configuration. + */ + void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) { + mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow); + } + + void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds, + @NonNull Configuration parentConfig) { + int minWidth = mMinWidth; + int minHeight = mMinHeight; + // If the task has no requested minimal size, we'd like to enforce a minimal size + // so that the user can not render the task fragment too small to manipulate. We don't need + // to do this for the root pinned task as the bounds are controlled by the system. + if (!inPinnedWindowingMode()) { + final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; + final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT; + final int defaultMinSize = (int) (defaultMinSizeDp * density); + + if (minWidth == INVALID_MIN_SIZE) { + minWidth = defaultMinSize; + } + if (minHeight == INVALID_MIN_SIZE) { + minHeight = defaultMinSize; + } + } + if (bounds.isEmpty()) { + // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they + // do, we can just skip. + final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); + if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) { + return; + } + bounds.set(parentBounds); + } + final boolean adjustWidth = minWidth > bounds.width(); + final boolean adjustHeight = minHeight > bounds.height(); + if (!(adjustWidth || adjustHeight)) { + return; + } + + if (adjustWidth) { + if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) { + bounds.left = bounds.right - minWidth; + } else { + // Either left bounds match, or neither match, or the previous bounds were + // fullscreen and we default to keeping left. + bounds.right = bounds.left + minWidth; + } + } + if (adjustHeight) { + if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) { + bounds.top = bounds.bottom - minHeight; + } else { + // Either top bounds match, or neither match, or the previous bounds were + // fullscreen and we default to keeping top. + bounds.bottom = bounds.top + minHeight; + } + } + } + + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig) { + computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, + null /* compatInsets */); + } + + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) { + if (overrideDisplayInfo != null) { + // Make sure the screen related configs can be computed by the provided display info. + inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; + invalidateAppBoundsConfig(inOutConfig); + } + computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo, + null /* compatInsets */); + } + + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig, + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { + if (compatInsets != null) { + // Make sure the app bounds can be computed by the compat insets. + invalidateAppBoundsConfig(inOutConfig); + } + computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */, + compatInsets); + } + + /** + * Forces the app bounds related configuration can be computed by + * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo, + * ActivityRecord.CompatDisplayInsets)}. + */ + private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) { + final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (appBounds != null) { + appBounds.setEmpty(); + } + inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + } + + /** + * Calculates configuration values used by the client to get resources. This should be run + * using app-facing bounds (bounds unmodified by animations or transient interactions). + * + * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely + * configuring an "inherit-bounds" window which means that all configuration settings would + * just be inherited from the parent configuration. + **/ + void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, + @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo, + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { + int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + windowingMode = parentConfig.windowConfiguration.getWindowingMode(); + } + + float density = inOutConfig.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = parentConfig.densityDpi; + } + density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; + + // The bounds may have been overridden at this level. If the parent cannot cover these + // bounds, the configuration is still computed according to the override bounds. + final boolean insideParentBounds; + + final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); + final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds(); + if (resolvedBounds == null || resolvedBounds.isEmpty()) { + mTmpFullBounds.set(parentBounds); + insideParentBounds = true; + } else { + mTmpFullBounds.set(resolvedBounds); + insideParentBounds = parentBounds.contains(resolvedBounds); + } + + // Non-null compatibility insets means the activity prefers to keep its original size, so + // out bounds doesn't need to be restricted by the parent or current display + final boolean customContainerPolicy = compatInsets != null; + + Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds == null || outAppBounds.isEmpty()) { + // App-bounds hasn't been overridden, so calculate a value for it. + inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds); + outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + + if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) { + final Rect containingAppBounds; + if (insideParentBounds) { + containingAppBounds = parentConfig.windowConfiguration.getAppBounds(); + } else { + // Restrict appBounds to display non-decor rather than parent because the + // override bounds are beyond the parent. Otherwise, it won't match the + // overridden bounds. + final TaskDisplayArea displayArea = getDisplayArea(); + containingAppBounds = displayArea != null + ? displayArea.getWindowConfiguration().getAppBounds() : null; + } + if (containingAppBounds != null && !containingAppBounds.isEmpty()) { + outAppBounds.intersect(containingAppBounds); + } + } + } + + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED + || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) { + mTmpNonDecorBounds.set(mTmpFullBounds); + mTmpStableBounds.set(mTmpFullBounds); + } else if (!customContainerPolicy + && (overrideDisplayInfo != null || getDisplayContent() != null)) { + final DisplayInfo di = overrideDisplayInfo != null + ? overrideDisplayInfo + : getDisplayContent().getDisplayInfo(); + + // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen + // area, i.e. the screen area without the system bars. + // The non decor inset are areas that could never be removed in Honeycomb. See + // {@link WindowManagerPolicy#getNonDecorInsetsLw}. + calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di); + } else { + // Apply the given non-decor and stable insets to calculate the corresponding bounds + // for screen size of configuration. + int rotation = inOutConfig.windowConfiguration.getRotation(); + if (rotation == ROTATION_UNDEFINED) { + rotation = parentConfig.windowConfiguration.getRotation(); + } + if (rotation != ROTATION_UNDEFINED && customContainerPolicy) { + mTmpNonDecorBounds.set(mTmpFullBounds); + mTmpStableBounds.set(mTmpFullBounds); + compatInsets.getBoundsByRotation(mTmpBounds, rotation); + intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds, + compatInsets.mNonDecorInsets[rotation]); + intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds, + compatInsets.mStableInsets[rotation]); + outAppBounds.set(mTmpNonDecorBounds); + } else { + // Set to app bounds because it excludes decor insets. + mTmpNonDecorBounds.set(outAppBounds); + mTmpStableBounds.set(outAppBounds); + } + } + + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density); + inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy) + ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp) + : overrideScreenWidthDp; + } + if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density); + inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy) + ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp) + : overrideScreenHeightDp; + } + + if (inOutConfig.smallestScreenWidthDp + == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + if (WindowConfiguration.isFloating(windowingMode)) { + // For floating tasks, calculate the smallest width from the bounds of the task + inOutConfig.smallestScreenWidthDp = (int) ( + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density); + } + // otherwise, it will just inherit + } + } + + if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { + inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) + ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + } + if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { + // For calculating screen layout, we need to use the non-decor inset screen area for the + // calculation for compatibility reasons, i.e. screen area without system bars that + // could never go away in Honeycomb. + int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density); + int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density); + // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is + // undefined so it can't be used. + if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + compatScreenWidthDp = inOutConfig.screenWidthDp; + } + if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + compatScreenHeightDp = inOutConfig.screenHeightDp; + } + // Reducing the screen layout starting from its parent config. + inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout, + compatScreenWidthDp, compatScreenHeightDp); + } + } + + /** + * Gets bounds with non-decor and stable insets applied respectively. + * + * If bounds overhangs the display, those edges will not get insets. See + * {@link #intersectWithInsetsIfFits} + * + * @param outNonDecorBounds where to place bounds with non-decor insets applied. + * @param outStableBounds where to place bounds with stable insets applied. + * @param bounds the bounds to inset. + */ + void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds, + DisplayInfo displayInfo) { + outNonDecorBounds.set(bounds); + outStableBounds.set(bounds); + final Task rootTask = getRootTaskFragment().asTask(); + if (rootTask == null || rootTask.mDisplayContent == null) { + return; + } + mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); + + final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy(); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets); + intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); + + policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); + intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); + } + + /** + * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than + * intersectBounds on a side, then the respective side will not be intersected. + * + * The assumption is that if inOutBounds is initially larger than intersectBounds, then the + * inset on that side is no-longer applicable. This scenario happens when a task's minimal + * bounds are larger than the provided parent/display bounds. + * + * @param inOutBounds the bounds to intersect. + * @param intersectBounds the bounds to intersect with. + * @param intersectInsets insets to apply to intersectBounds before intersecting. + */ + static void intersectWithInsetsIfFits( + Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) { + if (inOutBounds.right <= intersectBounds.right) { + inOutBounds.right = + Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right); + } + if (inOutBounds.bottom <= intersectBounds.bottom) { + inOutBounds.bottom = + Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom); + } + if (inOutBounds.left >= intersectBounds.left) { + inOutBounds.left = + Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left); + } + if (inOutBounds.top >= intersectBounds.top) { + inOutBounds.top = + Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top); + } + } + + /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */ + static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp, + int screenHeightDp) { + sourceScreenLayout = sourceScreenLayout + & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); + final int longSize = Math.max(screenWidthDp, screenHeightDp); + final int shortSize = Math.min(screenWidthDp, screenHeightDp); + return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize); + } + + @Override + public int getActivityType() { + final int applicationType = super.getActivityType(); + if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) { + return applicationType; + } + return getTopChild().getActivityType(); + } }