Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bdc646f5 authored by Winson Chung's avatar Winson Chung
Browse files

Reducing number of configuration changes during PiP transition.

- Fixes issue with the incorrect calculation of configuration smallest
  width for floating and always-fullscreen stacks.  Fullscreen stacks
  now have the smallest width matching the display, and floating stacks
  are set to the smallest size of their bounds.
- This CL also ensures that we test the combined global/override configs
  for changes in case changes when moving between stacks results in a
  shift in the configuration between the parent to the override of the
  child (ie. when going from fullscreen -> pinned)
- Also ensure that we are animating to the right fullscreen bounds for
  the PiP transition, accounting for the insets when calculating the config
  change the same way we would do for fullscreen tasks.

Bug: 33779483
Test: android.server.cts.ActivityManagerConfigChangeTests passes
Test: android.server.cts.ActivityManagerPinnedStackTests
Test: #testSingleConfigurationChangeDuringTransition
Change-Id: I2c2b695572cd17087d522cf6c8ebd105e57e08b8
parent c535d122
Loading
Loading
Loading
Loading
+15 −16
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.app.ActivityManager.StackId;
import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -47,7 +46,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
@@ -85,7 +83,6 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
@@ -290,8 +287,8 @@ final class ActivityRecord implements AppWindowContainerListener {
    /**
     * Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
     */
    private final Configuration mTmpGlobalConfig = new Configuration();
    private final Configuration mTmpTaskConfig = new Configuration();
    private final Configuration mTmpConfig1 = new Configuration();
    private final Configuration mTmpConfig2 = new Configuration();

    private static String startingWindowStateToString(int state) {
        switch (state) {
@@ -1975,13 +1972,13 @@ final class ActivityRecord implements AppWindowContainerListener {
        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                "Ensuring correct configuration: " + this);

        // Short circuit: if the two configurations are equal (the common case), then there is
        // nothing to do.
        final Configuration newGlobalConfig = service.getGlobalConfiguration();
        final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
        if (mLastReportedConfiguration.equals(newGlobalConfig)
                && mLastReportedOverrideConfiguration.equals(newTaskMergedOverrideConfig)
                && !forceNewConfig) {
        // Short circuit: if the two full configurations are equal (the common case), then there is
        // nothing to do.  We test the full configuration instead of the global and merged override
        // configurations because there are cases (like moving a task to the pinned stack) where
        // the combine configurations are equal, but would otherwise differ in the override config
        mTmpConfig1.setTo(mLastReportedConfiguration);
        mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
        if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig) {
            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                    "Configuration unchanged in " + this);
            return true;
@@ -1997,14 +1994,16 @@ final class ActivityRecord implements AppWindowContainerListener {

        // Okay we now are going to make this activity have the new config.
        // But then we need to figure out how it needs to deal with that.
        mTmpGlobalConfig.setTo(mLastReportedConfiguration);
        mTmpTaskConfig.setTo(mLastReportedOverrideConfiguration);
        final Configuration newGlobalConfig = service.getGlobalConfiguration();
        final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
        mTmpConfig1.setTo(mLastReportedConfiguration);
        mTmpConfig2.setTo(mLastReportedOverrideConfiguration);
        mLastReportedConfiguration.setTo(newGlobalConfig);
        mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);

        int taskChanges = getTaskConfigurationChanges(this, newTaskMergedOverrideConfig,
                mTmpTaskConfig);
        final int changes = mTmpGlobalConfig.diff(newGlobalConfig) | taskChanges;
                mTmpConfig2);
        final int changes = mTmpConfig1.diff(newGlobalConfig) | taskChanges;
        if (changes == 0 && !forceNewConfig) {
            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                    "Configuration no differences in " + this);
+16 −47
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
@@ -52,6 +51,7 @@ import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;

import com.android.server.wm.AppWindowContainerController;
import com.android.server.wm.StackWindowController;
import com.android.server.wm.TaskWindowContainerController;
import com.android.server.wm.TaskWindowContainerListener;

@@ -278,7 +278,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
    private final Rect mTmpStableBounds = new Rect();
    private final Rect mTmpNonDecorBounds = new Rect();
    private final Rect mTmpRect = new Rect();
    private final Rect mTmpRect2 = new Rect();

    // Last non-fullscreen bounds the task was launched in or resized to.
    // The information is persisted and used to determine the appropriate stack to launch the
@@ -1838,60 +1837,32 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
        return !mTmpConfig.equals(newConfig);
    }

    private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
                                        boolean overrideWidth, boolean overrideHeight) {
        mTmpRect2.set(inInsetBounds);
        mService.mWindowManager.subtractNonDecorInsets(mTmpRect2);
        int leftInset = mTmpRect2.left - inInsetBounds.left;
        int topInset = mTmpRect2.top - inInsetBounds.top;
        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
        inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
    }

    private void subtractStableInsets(Rect inOutBounds, Rect inInsetBounds,
                                      boolean overrideWidth, boolean overrideHeight) {
        mTmpRect2.set(inInsetBounds);
        mService.mWindowManager.subtractStableInsets(mTmpRect2);
        int leftInset = mTmpRect2.left - inInsetBounds.left;
        int topInset = mTmpRect2.top - inInsetBounds.top;
        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
        inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
    }

    /** Clears passed config and fills it with new override values. */
    private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
            boolean overrideWidth, boolean overrideHeight) {
        mTmpNonDecorBounds.set(bounds);
        mTmpStableBounds.set(bounds);

        final Configuration parentConfig = getParent().getConfiguration();
        config.unset();
        final Configuration parentConfig = getParent().getConfiguration();
        final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
        final boolean isFloatingTask = mStack != null && StackId.tasksAreFloating(mStack.mStackId);
        if (isFloatingTask) {
            // Floating tasks should not be resized to the screen's bounds.
            config.screenWidthDp = (int) (mTmpStableBounds.width() / density);
            config.screenHeightDp = (int) (mTmpStableBounds.height() / density);
        } else {
            // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
            // i.e. the screen area without the system bars.
            // Additionally task dimensions should not be bigger than its parents dimensions.
            subtractNonDecorInsets(mTmpNonDecorBounds, insetBounds != null ? insetBounds : bounds,
                    overrideWidth, overrideHeight);
            subtractStableInsets(mTmpStableBounds, insetBounds != null ? insetBounds : bounds,
                    overrideWidth, overrideHeight);
            config.screenWidthDp = Math.min(
                    (int) (mTmpStableBounds.width() / density), parentConfig.screenWidthDp);
            config.screenHeightDp = Math.min(
                    (int) (mTmpStableBounds.height() / density), parentConfig.screenHeightDp);
        }

        // TODO: Orientation?
        config.orientation = (config.screenWidthDp <= config.screenHeightDp)
                ? Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        if (mStack != null) {
            final StackWindowController stackController = mStack.getWindowContainerController();
            stackController.adjustConfigurationForBounds(bounds, insetBounds,
                    mTmpNonDecorBounds, mTmpStableBounds, overrideWidth, overrideHeight, density,
                    config, parentConfig);
        } else {
            // No stack, give some default values
            config.smallestScreenWidthDp =
                    mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
            config.screenWidthDp = config.screenHeightDp = config.smallestScreenWidthDp;
            Slog.wtf(TAG, "Expected stack when caclulating override config");
        }

        // 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
@@ -1905,8 +1876,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
        final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
        config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);

        config.smallestScreenWidthDp = mService.mWindowManager.getSmallestWidthForTaskBounds(
                insetBounds != null ? insetBounds : bounds);
    }

    /**
+1 −6
Original line number Diff line number Diff line
@@ -152,11 +152,6 @@ public class DockedStackDividerController implements DimLayerUser {
    int getSmallestWidthDpForBounds(Rect bounds) {
        final DisplayInfo di = mDisplayContent.getDisplayInfo();

        // If the bounds are fullscreen, return the value of the fullscreen configuration
        if (bounds == null || (bounds.left == 0 && bounds.top == 0
                && bounds.right == di.logicalWidth && bounds.bottom == di.logicalHeight)) {
            return mDisplayContent.getConfiguration().smallestScreenWidthDp;
        }
        final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
        final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
        int minWidth = Integer.MAX_VALUE;
@@ -185,7 +180,7 @@ public class DockedStackDividerController implements DimLayerUser {
                    mTmpRect2.width(), mTmpRect2.height(), getContentWidth());
            mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(),
                    mTmpRect3);
            mService.subtractInsets(mTmpRect2, mTmpRect3, mTmpRect);
            mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect);
            minWidth = Math.min(mTmpRect.width(), minWidth);
        }
        return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
+112 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server.wm;

import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;

import android.app.ActivityManager.StackId;
import android.app.RemoteAction;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -24,6 +27,8 @@ import android.os.Looper;
import android.os.Message;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayInfo;

import com.android.server.UiThread;
import com.android.internal.annotations.VisibleForTesting;

@@ -48,6 +53,12 @@ public class StackWindowController

    private final H mHandler;

    // Temp bounds only used in adjustConfigurationForBounds()
    private final Rect mTmpRect = new Rect();
    private final Rect mTmpStableInsets = new Rect();
    private final Rect mTmpNonDecorInsets = new Rect();
    private final Rect mTmpDisplayBounds = new Rect();

    public StackWindowController(int stackId, StackWindowListener listener,
            int displayId, boolean onTop, Rect outBounds) {
        this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
@@ -289,6 +300,107 @@ public class StackWindowController
        }
    }

    /**
     * Adjusts the screen size in dp's for the {@param config} for the given params.
     */
    public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds,
            Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
            boolean overrideHeight, float density, Configuration config,
            Configuration parentConfig) {
        synchronized (mWindowMap) {
            final TaskStack stack = mContainer;
            final DisplayContent displayContent = stack.getDisplayContent();
            final DisplayInfo di = displayContent.getDisplayInfo();

            // Get the insets and display bounds
            mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
                    mTmpStableInsets);
            mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
                    mTmpNonDecorInsets);
            mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);

            int width;
            int height;
            if (StackId.tasksAreFloating(mStackId)) {
                // Floating tasks should not be resized to the screen's bounds.

                if (bounds.width() == mTmpDisplayBounds.width() &&
                        bounds.height() == mTmpDisplayBounds.height()) {
                    // If the bounds we are animating is the same as the fullscreen stack
                    // dimensions, then apply the same inset calculations that we normally do for
                    // the fullscreen stack, without intersecting it with the display bounds
                    stableBounds.inset(mTmpStableInsets);
                    nonDecorBounds.inset(mTmpNonDecorInsets);
                }
                width = (int) (stableBounds.width() / density);
                height = (int) (stableBounds.height() / density);
            } else {
                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
                // area, i.e. the screen area without the system bars.
                // Additionally task dimensions should not be bigger than its parents dimensions.
                // The non decor inset are areas that could never be removed in Honeycomb. See
                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
                intersectDisplayBoundsExcludeInsets(nonDecorBounds,
                        insetBounds != null ? insetBounds : bounds, mTmpNonDecorInsets,
                        mTmpDisplayBounds, overrideWidth, overrideHeight);
                intersectDisplayBoundsExcludeInsets(stableBounds,
                        insetBounds != null ? insetBounds : bounds, mTmpStableInsets,
                        mTmpDisplayBounds, overrideWidth, overrideHeight);
                width = Math.min((int) (stableBounds.width() / density),
                        parentConfig.screenWidthDp);
                height = Math.min((int) (stableBounds.height() / density),
                        parentConfig.screenHeightDp);
            }

            config.screenWidthDp = width;
            config.screenHeightDp = height;
            config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
                    insetBounds != null ? insetBounds : bounds, density);
        }
    }

    /**
     * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
     * inset areas.
     *
     * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
     */
    private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds,
            Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) {
        mTmpRect.set(inInsetBounds);
        mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect);
        int leftInset = mTmpRect.left - inInsetBounds.left;
        int topInset = mTmpRect.top - inInsetBounds.top;
        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right;
        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom;
        inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
    }

    /**
     * Calculates the smallest width for a task given the {@param bounds}.
     *
     * @return the smallest width to be used in the Configuration, in dips
     */
    private int getSmallestWidthForTaskBounds(Rect bounds, float density) {
        final DisplayContent displayContent = mContainer.getDisplayContent();
        final DisplayInfo displayInfo = displayContent.getDisplayInfo();

        if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
                bounds.height() == displayInfo.logicalHeight)) {
            // If the bounds are fullscreen, return the value of the fullscreen configuration
            return displayContent.getConfiguration().smallestScreenWidthDp;
        } else if (StackId.tasksAreFloating(mStackId)) {
            // For floating tasks, calculate the smallest width from the bounds of the task
            return (int) (Math.min(bounds.width(), bounds.height()) / density);
        } else {
            // Iterating across all screen orientations, and return the minimum of the task
            // width taking into account that the bounds might change because the snap algorithm
            // snaps to a different value
            return displayContent.getDockedDividerController()
                    .getSmallestWidthDpForBounds(bounds);
        }
    }

    void requestResize(Rect bounds) {
        mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
    }
+4 −1
Original line number Diff line number Diff line
@@ -1476,7 +1476,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye

    @Override
    public void getFullScreenBounds(Rect bounds) {
        getDisplayContent().getContentRect(bounds);
        // This is currently only used for the pinned stack animation when leaving PiP
        // (see {@link BoundsAnimationController}), and in that case we need to animate this back
        // to the full bounds to match the fullscreen stack
        getDisplayContent().getLogicalDisplayRect(bounds);
    }

    public boolean hasMovementAnimations() {
Loading