diff options
| author | 2022-12-14 14:29:56 +0000 | |
|---|---|---|
| committer | 2022-12-14 14:29:56 +0000 | |
| commit | 86c6d900c60c0dba5c4c549ef5f62ec6643d37fe (patch) | |
| tree | 31011a8a2348facf9915891ded9e5f54c8b97d28 | |
| parent | bb848b653a1a401cd9dd80d4bbf42bf86d26766a (diff) | |
| parent | de531310145c462c7356dcaac378bda6aacaefbb (diff) | |
Merge "Fix Letterboxing for Transparent Activities" into tm-qpr-dev
8 files changed, 474 insertions, 25 deletions
| diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 18c29d196ada..bbbb79c79527 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5300,6 +5300,10 @@      <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->      <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool> +    <!-- Whether the specific behaviour for translucent activities letterboxing is enabled. +         TODO(b/255532890) Enable when ignoreOrientationRequest is set --> +    <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool> +      <!-- Whether a camera compat controller is enabled to allow the user to apply or revert           treatment for stretched issues in camera viewfinder. -->      <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b9259218c2d3..b0f6ae6085e3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4401,6 +4401,9 @@    <!-- Set to true to make assistant show in front of the dream/screensaver. -->    <java-symbol type="bool" name="config_assistantOnTopOfDream"/> +  <!-- Set to true to enable letterboxing on translucent activities. --> +  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" /> +    <java-symbol type="string" name="config_overrideComponentUiPackage" />    <java-symbol type="string" name="notification_channel_network_status" /> diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2c274827ba8e..e639866a6bab 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1239,8 +1239,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A                  pw.println(prefix + "supportsEnterPipOnTaskSwitch: "                          + supportsEnterPipOnTaskSwitch);              } -            if (info.getMaxAspectRatio() != 0) { -                pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio()); +            if (getMaxAspectRatio() != 0) { +                pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());              }              final float minAspectRatio = getMinAspectRatio();              if (minAspectRatio != 0) { @@ -1590,6 +1590,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A                  newParent.setResumedActivity(this, "onParentChanged");                  mImeInsetsFrozenUntilStartInput = false;              } +            mLetterboxUiController.onActivityParentChanged(newParent);          }          if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -7682,6 +7683,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      @Configuration.Orientation      @Override      int getRequestedConfigurationOrientation(boolean forDisplay) { +        if (mLetterboxUiController.hasInheritedOrientation()) { +            final RootDisplayArea root = getRootDisplayArea(); +            if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { +                return ActivityInfo.reverseOrientation( +                        mLetterboxUiController.getInheritedOrientation()); +            } else { +                return mLetterboxUiController.getInheritedOrientation(); +            } +        }          if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {              // We use Task here because we want to be consistent with what happens in              // multi-window mode where other tasks orientations are ignored. @@ -7809,6 +7819,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      @Nullable      CompatDisplayInsets getCompatDisplayInsets() { +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            return mLetterboxUiController.getInheritedCompatDisplayInsets(); +        }          return mCompatDisplayInsets;      } @@ -7891,6 +7904,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.      private void updateCompatDisplayInsets() { +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            mCompatDisplayInsets =  mLetterboxUiController.getInheritedCompatDisplayInsets(); +            return; +        }          if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {              // The override configuration is set only once in size compatibility mode.              return; @@ -7952,6 +7969,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      @Override      float getCompatScale() { +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            return mLetterboxUiController.getInheritedSizeCompatScale(); +        }          return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();      } @@ -8061,6 +8081,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      }      /** +     * @return The orientation to use to understand if reachability is enabled. +     */ +    @ActivityInfo.ScreenOrientation +    int getOrientationForReachability() { +        return mLetterboxUiController.hasInheritedLetterboxBehavior() +                ? mLetterboxUiController.getInheritedOrientation() +                : getRequestedConfigurationOrientation(); +    } + +    /**       * Returns whether activity bounds are letterboxed.       *       * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link @@ -8100,6 +8130,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A          if (!ignoreVisibility && !mVisibleRequested) {              return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;          } +        // TODO(b/256564921): Investigate if we need new metrics for translucent activities +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            return mLetterboxUiController.getInheritedAppCompatState(); +        }          if (mInSizeCompatModeForBounds) {              return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;          } @@ -8570,6 +8604,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      }      private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity +            // is letterboxed. +            return false; +        }          final int appWidth = appBounds.width();          final int appHeight = appBounds.height();          final int containerAppWidth = containerBounds.width(); @@ -8590,10 +8629,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A          // The rest of the condition is that only one side is smaller than the container, but it          // still needs to exclude the cases where the size is limited by the fixed aspect ratio. -        if (info.getMaxAspectRatio() > 0) { +        final float maxAspectRatio = getMaxAspectRatio(); +        if (maxAspectRatio > 0) {              final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))                      / Math.min(appWidth, appHeight); -            if (aspectRatio >= info.getMaxAspectRatio()) { +            if (aspectRatio >= maxAspectRatio) {                  // The current size has reached the max aspect ratio.                  return false;              } @@ -8815,7 +8855,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.      private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,              Rect containingBounds, float desiredAspectRatio) { -        final float maxAspectRatio = info.getMaxAspectRatio(); +        final float maxAspectRatio = getMaxAspectRatio();          final Task rootTask = getRootTask();          final float minAspectRatio = getMinAspectRatio();          final TaskFragment organizedTf = getOrganizedTaskFragment(); @@ -8922,6 +8962,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A       * Returns the min aspect ratio of this activity.       */      float getMinAspectRatio() { +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            return mLetterboxUiController.getInheritedMinAspectRatio(); +        }          if (info.applicationInfo == null) {              return info.getMinAspectRatio();          } @@ -8966,11 +9009,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A                  && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;      } +    float getMaxAspectRatio() { +        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { +            return mLetterboxUiController.getInheritedMaxAspectRatio(); +        } +        return info.getMaxAspectRatio(); +    } +      /**       * Returns true if the activity has maximum or minimum aspect ratio.       */      private boolean hasFixedAspectRatio() { -        return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; +        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;      }      /** diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index c19353cb2676..127a7bf1c9a5 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -21,6 +21,7 @@ import android.annotation.NonNull;  import android.annotation.Nullable;  import android.content.Context;  import android.graphics.Color; +import android.provider.DeviceConfig;  import com.android.internal.R;  import com.android.internal.annotations.VisibleForTesting; @@ -103,6 +104,10 @@ final class LetterboxConfiguration {      final Context mContext; +    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier +    @NonNull +    private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; +      // Aspect ratio of letterbox for fixed orientation, values <=      // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.      private float mFixedOrientationLetterboxAspectRatio; @@ -165,9 +170,12 @@ final class LetterboxConfiguration {      // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.      private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled; -    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier -    @NonNull -    private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; +    // Whether letterboxing strategy is enabled for translucent activities. If {@value false} +    // all the feature is disabled +    private boolean mTranslucentLetterboxingEnabled; + +    // Allows to enable letterboxing strategy for translucent activities ignoring flags. +    private boolean mTranslucentLetterboxingOverrideEnabled;      LetterboxConfiguration(Context systemUiContext) {          this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext, @@ -206,6 +214,8 @@ final class LetterboxConfiguration {                  R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));          mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(                  R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); +        mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean( +                R.bool.config_letterboxIsEnabledForTranslucentActivities);          mLetterboxConfigurationPersister = letterboxConfigurationPersister;          mLetterboxConfigurationPersister.start();      } @@ -817,6 +827,32 @@ final class LetterboxConfiguration {                  R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);      } +    boolean isTranslucentLetterboxingEnabled() { +        return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled +                && isTranslucentLetterboxingAllowed()); +    } + +    void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) { +        mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled; +    } + +    void setTranslucentLetterboxingOverrideEnabled( +            boolean translucentLetterboxingOverrideEnabled) { +        mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled; +        setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled); +    } + +    /** +     * Resets whether we use the constraints override strategy for letterboxing when dealing +     * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}. +     */ +    void resetTranslucentLetterboxingEnabled() { +        final boolean newValue = mContext.getResources().getBoolean( +                R.bool.config_letterboxIsEnabledForTranslucentActivities); +        setTranslucentLetterboxingEnabled(newValue); +        setTranslucentLetterboxingOverrideEnabled(false); +    } +      /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */      private void updatePositionForHorizontalReachability(              Function<Integer, Integer> newHorizonalPositionFun) { @@ -839,4 +875,9 @@ final class LetterboxConfiguration {                  nextVerticalPosition);      } +    // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener +    static boolean isTranslucentLetterboxingAllowed() { +        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, +                "enable_translucent_activity_letterbox", false); +    }  } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index bcea6f4db1dc..a53a5fc00b0c 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,6 +17,7 @@  package com.android.server.wm;  import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;  import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;  import static android.content.res.Configuration.ORIENTATION_PORTRAIT;  import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; @@ -27,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG  import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;  import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;  import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;  import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;  import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;  import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT; @@ -82,13 +84,44 @@ final class LetterboxUiController {      private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; +    private static final float UNDEFINED_ASPECT_RATIO = 0f; +      private final Point mTmpPoint = new Point();      private final LetterboxConfiguration mLetterboxConfiguration; +      private final ActivityRecord mActivityRecord; +    /* +     * WindowContainerListener responsible to make translucent activities inherit +     * constraints from the first opaque activity beneath them. It's null for not +     * translucent activities. +     */ +    @Nullable +    private WindowContainerListener mLetterboxConfigListener; +      private boolean mShowWallpaperForLetterboxBackground; +    // In case of transparent activities we might need to access the aspectRatio of the +    // first opaque activity beneath. +    private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; +    private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; + +    @Configuration.Orientation +    private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; + +    // The app compat state for the opaque activity if any +    private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; + +    // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. +    private boolean mIsInheritedInSizeCompatMode; + +    // This is the SizeCompatScale of the opaque activity beneath a translucent one +    private float mInheritedSizeCompatScale; + +    // The CompatDisplayInsets of the opaque activity beneath the translucent one. +    private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; +      @Nullable      private Letterbox mLetterbox; @@ -220,7 +253,9 @@ final class LetterboxUiController {                      : mActivityRecord.inMultiWindowMode()                              ? mActivityRecord.getTask().getBounds()                              : mActivityRecord.getRootTask().getParent().getBounds(); -            mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint); +            final Rect innerFrame = hasInheritedLetterboxBehavior() +                    ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); +            mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);          } else if (mLetterbox != null) {              mLetterbox.hide();          } @@ -305,7 +340,9 @@ final class LetterboxUiController {      }      private void handleHorizontalDoubleTap(int x) { -        if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { +        // TODO(b/260857308): Investigate if enabling reachability for translucent activity +        if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled() +                || mActivityRecord.isInTransition()) {              return;          } @@ -341,7 +378,9 @@ final class LetterboxUiController {      }      private void handleVerticalDoubleTap(int y) { -        if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { +        // TODO(b/260857308): Investigate if enabling reachability for translucent activity +        if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled() +                || mActivityRecord.isInTransition()) {              return;          } @@ -390,7 +429,7 @@ final class LetterboxUiController {                  && parentConfiguration.windowConfiguration.getWindowingMode()                          == WINDOWING_MODE_FULLSCREEN                  && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE -                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT); +                && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);      }      private boolean isHorizontalReachabilityEnabled() { @@ -412,7 +451,7 @@ final class LetterboxUiController {                  && parentConfiguration.windowConfiguration.getWindowingMode()                          == WINDOWING_MODE_FULLSCREEN                  && (parentConfiguration.orientation == ORIENTATION_PORTRAIT -                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE); +                && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);      }      private boolean isVerticalReachabilityEnabled() { @@ -576,9 +615,7 @@ final class LetterboxUiController {          // Rounded corners should be displayed above the taskbar.          bounds.bottom =                  Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top); -        if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) { -            bounds.scale(1.0f / mActivityRecord.getCompatScale()); -        } +        scaleIfNeeded(bounds);      }      private int getInsetsStateCornerRadius( @@ -788,4 +825,144 @@ final class LetterboxUiController {                  w.mAttrs.insetsFlags.appearance          );      } + +    /** +     * Handles translucent activities letterboxing inheriting constraints from the +     * first opaque activity beneath. +     * @param parent The parent container. +     */ +    void onActivityParentChanged(WindowContainer<?> parent) { +        if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { +            return; +        } +        if (mLetterboxConfigListener != null) { +            mLetterboxConfigListener.onRemoved(); +            clearInheritedConfig(); +        } +        // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the +        // opaque activity constraints because we're expecting the activity is already letterboxed. +        if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null +                || mActivityRecord.fillsParent()) { +            return; +        } +        final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( +                ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */, +                true /* traverseTopToBottom */); +        if (firstOpaqueActivityBeneath == null +                || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) { +            // We skip letterboxing if the translucent activity doesn't have any opaque +            // activities beneath of if it's launched from a different user (e.g. notification) +            return; +        } +        inheritConfiguration(firstOpaqueActivityBeneath); +        mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( +                mActivityRecord, firstOpaqueActivityBeneath, +                (opaqueConfig, transparentConfig) -> { +                    final Configuration mutatedConfiguration = new Configuration(); +                    final Rect parentBounds = parent.getWindowConfiguration().getBounds(); +                    final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); +                    final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); +                    // We cannot use letterboxBounds directly here because the position relies on +                    // letterboxing. Using letterboxBounds directly, would produce a double offset. +                    bounds.set(parentBounds.left, parentBounds.top, +                            parentBounds.left + letterboxBounds.width(), +                            parentBounds.top + letterboxBounds.height()); +                    // We need to initialize appBounds to avoid NPE. The actual value will +                    // be set ahead when resolving the Configuration for the activity. +                    mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); +                    return mutatedConfiguration; +                }); +    } + +    /** +     * @return {@code true} if the current activity is translucent with an opaque activity +     * beneath. In this case it will inherit bounds, orientation and aspect ratios from +     * the first opaque activity beneath. +     */ +    boolean hasInheritedLetterboxBehavior() { +        return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds(); +    } + +    /** +     * @return {@code true} if the current activity is translucent with an opaque activity +     * beneath and needs to inherit its orientation. +     */ +    boolean hasInheritedOrientation() { +        // To force a different orientation, the transparent one needs to have an explicit one +        // otherwise the existing one is fine and the actual orientation will depend on the +        // bounds. +        // To avoid wrong behaviour, we're not forcing orientation for activities with not +        // fixed orientation (e.g. permission dialogs). +        return hasInheritedLetterboxBehavior() +                && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED; +    } + +    float getInheritedMinAspectRatio() { +        return mInheritedMinAspectRatio; +    } + +    float getInheritedMaxAspectRatio() { +        return mInheritedMaxAspectRatio; +    } + +    int getInheritedAppCompatState() { +        return mInheritedAppCompatState; +    } + +    float getInheritedSizeCompatScale() { +        return mInheritedSizeCompatScale; +    } + +    @Configuration.Orientation +    int getInheritedOrientation() { +        return mInheritedOrientation; +    } + +    public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { +        return mInheritedCompatDisplayInsets; +    } + +    private void inheritConfiguration(ActivityRecord firstOpaque) { +        // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities +        // which are not already providing one (e.g. permission dialogs) and presumably also +        // not resizable. +        if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { +            mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); +        } +        if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { +            mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); +        } +        mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); +        mInheritedAppCompatState = firstOpaque.getAppCompatState(); +        mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); +        mInheritedSizeCompatScale = firstOpaque.getCompatScale(); +        mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); +    } + +    private void clearInheritedConfig() { +        mLetterboxConfigListener = null; +        mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; +        mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; +        mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; +        mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; +        mIsInheritedInSizeCompatMode = false; +        mInheritedSizeCompatScale = 1f; +        mInheritedCompatDisplayInsets = null; +    } + +    private void scaleIfNeeded(Rect bounds) { +        if (boundsNeedToScale()) { +            bounds.scale(1.0f / mActivityRecord.getCompatScale()); +        } +    } + +    private boolean boundsNeedToScale() { +        if (hasInheritedLetterboxBehavior()) { +            return mIsInheritedInSizeCompatMode +                    && mInheritedSizeCompatScale < 1.0f; +        } else { +            return mActivityRecord.inSizeCompatMode() +                    && mActivityRecord.getCompatScale() < 1.0f; +        } +    }  } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 80e7ddb432d3..5c893de6b920 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3938,27 +3938,54 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<          unregisterConfigurationChangeListener(listener);      } +    static void overrideConfigurationPropagation(WindowContainer<?> receiver, +            WindowContainer<?> supplier) { +        overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */); +    } +      /**       * Forces the receiver container to always use the configuration of the supplier container as       * its requested override configuration. It allows to propagate configuration without changing       * the relationship between child and parent. +     * +     * @param receiver            The {@link WindowContainer<?>} which will receive the {@link +     *                            Configuration} result of the merging operation. +     * @param supplier            The {@link WindowContainer<?>} which provides the initial {@link +     *                            Configuration}. +     * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link +     *                            Configuration} of the receiver and the supplier.       */ -    static void overrideConfigurationPropagation(WindowContainer<?> receiver, -            WindowContainer<?> supplier) { +    static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver, +            WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) {          final ConfigurationContainerListener listener = new ConfigurationContainerListener() {              @Override              public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { -                receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration()); +                final Configuration mergedConfiguration = +                        configurationMerger != null +                                ? configurationMerger.merge(mergedOverrideConfig, +                                receiver.getConfiguration()) +                                : supplier.getConfiguration(); +                receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);              }          };          supplier.registerConfigurationChangeListener(listener); -        receiver.registerWindowContainerListener(new WindowContainerListener() { +        final WindowContainerListener wcListener = new WindowContainerListener() {              @Override              public void onRemoved() {                  receiver.unregisterWindowContainerListener(this);                  supplier.unregisterConfigurationChangeListener(listener);              } -        }); +        }; +        receiver.registerWindowContainerListener(wcListener); +        return wcListener; +    } + +    /** +     * Abstraction for functions merging two {@link Configuration} objects into one. +     */ +    @FunctionalInterface +    interface ConfigurationMerger { +        Configuration merge(Configuration first, Configuration second);      }      /** diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index b8cf0ad2e774..4e692e2d212a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -963,6 +963,29 @@ public class WindowManagerShellCommand extends ShellCommand {          return 0;      } +    private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) { +        String arg = getNextArg(); +        final boolean enabled; +        switch (arg) { +            case "true": +            case "1": +                enabled = true; +                break; +            case "false": +            case "0": +                enabled = false; +                break; +            default: +                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg); +                return -1; +        } + +        synchronized (mInternal.mGlobalLock) { +            mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled); +        } +        return 0; +    } +      private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {          if (peekNextArg() == null) {              getErrPrintWriter().println("Error: No arguments provided."); @@ -1018,6 +1041,9 @@ public class WindowManagerShellCommand extends ShellCommand {                  case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":                      runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);                      break; +                case "--isTranslucentLetterboxingEnabled": +                    runSetTranslucentLetterboxingEnabled(pw); +                    break;                  default:                      getErrPrintWriter().println(                              "Error: Unrecognized letterbox style option: " + arg); @@ -1081,6 +1107,9 @@ public class WindowManagerShellCommand extends ShellCommand {                          mLetterboxConfiguration                                  .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();                          break; +                    case "isTranslucentLetterboxingEnabled": +                        mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); +                        break;                      default:                          getErrPrintWriter().println(                                  "Error: Unrecognized letterbox style option: " + arg); @@ -1181,6 +1210,7 @@ public class WindowManagerShellCommand extends ShellCommand {              mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();              mLetterboxConfiguration.resetIsEducationEnabled();              mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); +            mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();          }      } @@ -1217,7 +1247,6 @@ public class WindowManagerShellCommand extends ShellCommand {              pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "                      + mLetterboxConfiguration                              .getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); -              pw.println("Background type: "                      + LetterboxConfiguration.letterboxBackgroundTypeToString(                              mLetterboxConfiguration.getLetterboxBackgroundType())); @@ -1227,6 +1256,12 @@ public class WindowManagerShellCommand extends ShellCommand {                      + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());              pw.println("    Wallpaper dark scrim alpha: "                      + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); + +            if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { +                pw.println("Letterboxing for translucent activities: enabled"); +            } else { +                pw.println("Letterboxing for translucent activities: disabled"); +            }          }          return 0;      } @@ -1419,12 +1454,16 @@ public class WindowManagerShellCommand extends ShellCommand {          pw.println("      --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");          pw.println("        Whether using split screen aspect ratio as a default aspect ratio for");          pw.println("        unresizable apps."); +        pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]"); +        pw.println("        Whether letterboxing for translucent activities is enabled."); +          pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");          pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");          pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");          pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled"); -        pw.println("      isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); -        pw.println("      ||defaultPositionMultiplierForVerticalReachability]"); +        pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); +        pw.println("      |isTranslucentLetterboxingEnabled"); +        pw.println("      |defaultPositionMultiplierForVerticalReachability]");          pw.println("    Resets overrides to default values for specified properties separated");          pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");          pw.println("    If no arguments provided, all values will be reset."); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index babad4d4d744..94c33f27f651 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -164,6 +164,114 @@ public class SizeCompatTests extends WindowTestsBase {      }      @Test +    public void testApplyStrategyToTranslucentActivities() { +        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); +        setUpDisplaySizeWithApp(2000, 1000); +        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); +        mActivity.info.setMinAspectRatio(1.2f); +        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); +        // Translucent Activity +        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) +                .setLaunchedFromUid(mActivity.getUid()) +                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) +                .setMinAspectRatio(1.1f) +                .setMaxAspectRatio(3f) +                .build(); +        doReturn(false).when(translucentActivity).fillsParent(); +        mTask.addChild(translucentActivity); +        // We check bounds +        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); +        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); +        assertEquals(opaqueBounds, translucentRequestedBounds); +        // We check orientation +        final int translucentOrientation = +                translucentActivity.getRequestedConfigurationOrientation(); +        assertEquals(ORIENTATION_PORTRAIT, translucentOrientation); +        // We check aspect ratios +        assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f); +        assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f); +    } + +    @Test +    public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() { +        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); +        setUpDisplaySizeWithApp(2000, 1000); +        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); +        mActivity.info.setMinAspectRatio(1.2f); +        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); +        // Translucent Activity +        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) +                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) +                .setMinAspectRatio(1.1f) +                .setMaxAspectRatio(3f) +                .build(); +        doReturn(false).when(translucentActivity).fillsParent(); +        mTask.addChild(translucentActivity); +        // We check bounds +        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); +        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); +        assertNotEquals(opaqueBounds, translucentRequestedBounds); +    } + +    @Test +    public void testApplyStrategyToMultipleTranslucentActivities() { +        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); +        setUpDisplaySizeWithApp(2000, 1000); +        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); +        mActivity.info.setMinAspectRatio(1.2f); +        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); +        // Translucent Activity +        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) +                .setLaunchedFromUid(mActivity.getUid()) +                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) +                .setMinAspectRatio(1.1f) +                .setMaxAspectRatio(3f) +                .build(); +        doReturn(false).when(translucentActivity).fillsParent(); +        mTask.addChild(translucentActivity); +        // We check bounds +        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); +        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); +        assertEquals(opaqueBounds, translucentRequestedBounds); +        // Launch another translucent activity +        final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm) +                .setLaunchedFromUid(mActivity.getUid()) +                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) +                .build(); +        doReturn(false).when(translucentActivity2).fillsParent(); +        mTask.addChild(translucentActivity2); +        // We check bounds +        final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds(); +        assertEquals(opaqueBounds, translucent2RequestedBounds); +    } + +    @Test +    public void testTranslucentActivitiesDontGoInSizeCompactMode() { +        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); +        setUpDisplaySizeWithApp(2800, 1400); +        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); +        prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); +        // Rotate to put activity in size compat mode. +        rotateDisplay(mActivity.mDisplayContent, ROTATION_90); +        assertTrue(mActivity.inSizeCompatMode()); +        // Rotate back +        rotateDisplay(mActivity.mDisplayContent, ROTATION_0); +        assertFalse(mActivity.inSizeCompatMode()); +        // We launch a transparent activity +        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) +                .setLaunchedFromUid(mActivity.getUid()) +                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) +                .build(); +        doReturn(true).when(translucentActivity).fillsParent(); +        mTask.addChild(translucentActivity); +        // It should not be in SCM +        assertFalse(translucentActivity.inSizeCompatMode()); +        // We rotate again +        rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90); +        assertFalse(translucentActivity.inSizeCompatMode()); +    } + +    @Test      public void testRestartProcessIfVisible() {          setUpDisplaySizeWithApp(1000, 2500);          doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity); |