Loading services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java +172 −69 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxInnerBounds; import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxOuterBounds; import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxPosition; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -32,6 +35,7 @@ import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; import com.android.window.flags.Flags; import java.io.PrintWriter; Loading @@ -43,7 +47,7 @@ class AppCompatLetterboxPolicy { @NonNull private final ActivityRecord mActivityRecord; @NonNull private final LetterboxPolicyState mLetterboxPolicyState; private final AppCompatLetterboxPolicyState mLetterboxPolicyState; @NonNull private final AppCompatRoundedCorners mAppCompatRoundedCorners; @NonNull Loading @@ -54,7 +58,8 @@ class AppCompatLetterboxPolicy { AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord, @NonNull AppCompatConfiguration appCompatConfiguration) { mActivityRecord = activityRecord; mLetterboxPolicyState = new LetterboxPolicyState(); mLetterboxPolicyState = Flags.appCompatRefactoring() ? new ShellLetterboxPolicyState() : new LegacyLetterboxPolicyState(); // TODO (b/358334569) Improve cutout logic dependency on app compat. mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord, this::isLetterboxedNotForDisplayCutout); Loading Loading @@ -88,7 +93,24 @@ class AppCompatLetterboxPolicy { @Nullable LetterboxDetails getLetterboxDetails() { return mLetterboxPolicyState.getLetterboxDetails(); final WindowState w = mActivityRecord.findMainWindow(); if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) { return null; } final Rect letterboxInnerBounds = new Rect(); final Rect letterboxOuterBounds = new Rect(); mLetterboxPolicyState.getLetterboxInnerBounds(letterboxInnerBounds); mLetterboxPolicyState.getLetterboxOuterBounds(letterboxOuterBounds); if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { return null; } return new LetterboxDetails( letterboxInnerBounds, letterboxOuterBounds, w.mAttrs.insetsFlags.appearance ); } /** Loading @@ -99,6 +121,13 @@ class AppCompatLetterboxPolicy { return mLetterboxPolicyState.isFullyTransparentBarAllowed(rect); } /** * Updates the letterbox surfaces in case this is needed. * * @param winHint The WindowState for the letterboxed Activity. * @param t The current Transaction. * @param inputT The pending transaction used for the input surface. */ void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT) { Loading Loading @@ -232,12 +261,17 @@ class AppCompatLetterboxPolicy { || w.mAnimatingExit; } private class LetterboxPolicyState { /** * Existing {@link AppCompatLetterboxPolicyState} implementation. * TODO(b/375339716): Clean code for legacy implementation. */ private class LegacyLetterboxPolicyState implements AppCompatLetterboxPolicyState { @Nullable private Letterbox mLetterbox; void layoutLetterboxIfNeeded(@NonNull WindowState w) { @Override public void layoutLetterboxIfNeeded(@NonNull WindowState w) { if (!isRunning()) { final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord .mAppCompatController.getAppCompatLetterboxOverrides(); Loading @@ -252,41 +286,11 @@ class AppCompatLetterboxPolicy { .setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame); } final Point letterboxPosition = new Point(); if (mActivityRecord.isInLetterboxAnimation()) { // In this case we attach the letterbox to the task instead of the activity. mActivityRecord.getTask().getPosition(letterboxPosition); } else { mActivityRecord.getPosition(letterboxPosition); } // Get the bounds of the "space-to-fill". The transformed bounds have the highest // priority because the activity is launched in a rotated environment. In multi-window // mode, the taskFragment-level represents this for both split-screen // and activity-embedding. In fullscreen-mode, the task container does // (since the orientation letterbox is also applied to the task). final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); final Rect spaceToFill = transformedBounds != null ? transformedBounds : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTaskFragment().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct // information and in particular it might provide a value for a smaller area making // the letterbox overlap with the translucent activity's frame. // If we use WindowState#getFrame() for the translucent activity's letterbox inner // frame, the letterbox will then be overlapped with the translucent activity's frame. // Because the surface layer of letterbox is lower than an activity window, this // won't crop the content, but it may affect other features that rely on values stored // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController.getTransparentPolicy(); final Rect innerFrame = transparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame(); calculateLetterboxPosition(mActivityRecord, letterboxPosition); final Rect spaceToFill = new Rect(); calculateLetterboxOuterBounds(mActivityRecord, spaceToFill); final Rect innerFrame = new Rect(); calculateLetterboxInnerBounds(mActivityRecord, w, innerFrame); mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition); if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides() .isDoubleTapEvent()) { Loading @@ -299,18 +303,21 @@ class AppCompatLetterboxPolicy { * @return {@code true} if the policy is running and so if the current activity is * letterboxed. */ boolean isRunning() { @Override public boolean isRunning() { return mLetterbox != null; } void onMovedToDisplay(int displayId) { @Override public void onMovedToDisplay(int displayId) { if (isRunning()) { mLetterbox.onMovedToDisplay(displayId); } } /** Cleans up {@link Letterbox} if it exists.*/ void stop() { @Override public void stop() { if (isRunning()) { mLetterbox.destroy(); mLetterbox = null; Loading @@ -319,7 +326,8 @@ class AppCompatLetterboxPolicy { .setLetterboxInnerBoundsSupplier(null); } void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @Override public void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT) { if (shouldNotLayoutLetterbox(winHint)) { Loading @@ -331,15 +339,17 @@ class AppCompatLetterboxPolicy { } } void hide() { @Override public void hide() { if (isRunning()) { mLetterbox.hide(); } } /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ @Override @NonNull Rect getLetterboxInsets() { public Rect getLetterboxInsets() { if (isRunning()) { return mLetterbox.getInsets(); } else { Loading @@ -348,7 +358,8 @@ class AppCompatLetterboxPolicy { } /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */ void getLetterboxInnerBounds(@NonNull Rect outBounds) { @Override public void getLetterboxInnerBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mLetterbox.getInnerFrame()); final WindowState w = mActivityRecord.findMainWindow(); Loading @@ -361,7 +372,8 @@ class AppCompatLetterboxPolicy { } /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */ private void getLetterboxOuterBounds(@NonNull Rect outBounds) { @Override public void getLetterboxOuterBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mLetterbox.getOuterFrame()); } else { Loading @@ -373,39 +385,130 @@ class AppCompatLetterboxPolicy { * @return {@code true} if bar shown within a given rectangle is allowed to be fully * transparent when the current activity is displayed. */ boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { @Override public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect); } @Nullable LetterboxDetails getLetterboxDetails() { final WindowState w = mActivityRecord.findMainWindow(); if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) { return null; private SurfaceControl getLetterboxParentSurface() { if (mActivityRecord.isInLetterboxAnimation()) { return mActivityRecord.getTask().getSurfaceControl(); } return mActivityRecord.getSurfaceControl(); } final Rect letterboxInnerBounds = new Rect(); final Rect letterboxOuterBounds = new Rect(); getLetterboxInnerBounds(letterboxInnerBounds); getLetterboxOuterBounds(letterboxOuterBounds); if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { return null; } return new LetterboxDetails( letterboxInnerBounds, letterboxOuterBounds, w.mAttrs.insetsFlags.appearance /** * {@link AppCompatLetterboxPolicyState} implementation for the letterbox presentation on shell. */ private class ShellLetterboxPolicyState implements AppCompatLetterboxPolicyState { private final Rect mInnerBounds = new Rect(); private final Rect mOuterBounds = new Rect(); private final Point mLetterboxPosition = new Point(); private boolean mRunning; @Override public void layoutLetterboxIfNeeded(@NonNull WindowState w) { mRunning = true; calculateLetterboxPosition(mActivityRecord, mLetterboxPosition); calculateLetterboxOuterBounds(mActivityRecord, mOuterBounds); calculateLetterboxInnerBounds(mActivityRecord, w, mInnerBounds); mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy() .setLetterboxInnerBoundsSupplier(() -> mInnerBounds); } @Override public boolean isRunning() { return mRunning; } @Override public void onMovedToDisplay(int displayId) { // TODO(b/374918469): Handle Display Change for Letterbox in Shell } @Override public void stop() { if (!isRunning()) { return; } mRunning = false; mLetterboxPosition.set(0, 0); mInnerBounds.setEmpty(); mOuterBounds.setEmpty(); mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy() .setLetterboxInnerBoundsSupplier(null); } @Override public void hide() { if (!isRunning()) { return; } mLetterboxPosition.set(0, 0); mInnerBounds.setEmpty(); mOuterBounds.setEmpty(); } @NonNull @Override public Rect getLetterboxInsets() { if (isRunning()) { return new Rect( Math.max(0, mInnerBounds.left - mOuterBounds.left), Math.max(0, mOuterBounds.top - mInnerBounds.top), Math.max(0, mOuterBounds.right - mInnerBounds.right), Math.max(0, mInnerBounds.bottom - mOuterBounds.bottom) ); } return new Rect(); } @Nullable private SurfaceControl getLetterboxParentSurface() { if (mActivityRecord.isInLetterboxAnimation()) { return mActivityRecord.getTask().getSurfaceControl(); @Override public void getLetterboxInnerBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mInnerBounds); final WindowState w = mActivityRecord.findMainWindow(); if (w != null) { AppCompatUtils.adjustBoundsForTaskbar(w, outBounds); } } else { outBounds.setEmpty(); } return mActivityRecord.getSurfaceControl(); } @Override public void getLetterboxOuterBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mOuterBounds); } else { outBounds.setEmpty(); } } @Override public void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT) { if (shouldNotLayoutLetterbox(winHint)) { return; } start(winHint); } @Override public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { // TODO(b/374921442) Handle Transparent Activities Letterboxing in Shell. // At the moment Shell handles letterbox with a single surface. This would make // notIntersectsOrFullyContains() to return false in the existing Letterbox // implementation. // Note: Previous implementation is // !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect); return !isRunning(); } } } services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import android.annotation.NonNull; import android.graphics.Rect; import android.view.SurfaceControl; /** * Abstraction for different Letterbox state implementations. */ interface AppCompatLetterboxPolicyState { /** * Checks if a relayout is necessary for the letterbox implementations. * @param w The {@link WindowState} to use for defining Letterbox sizes. */ void layoutLetterboxIfNeeded(@NonNull WindowState w); /** * @return {@code true} if the policy is running and so if the current activity is * letterboxed. */ boolean isRunning(); /** * Called when the activity is moved to a new display. * @param displayId Id for the new display */ void onMovedToDisplay(int displayId); /** Cleans up {@link Letterbox} if it exists.*/ void stop(); /** Hides the letterbox surfaces implementation. */ void hide(); /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ @NonNull Rect getLetterboxInsets(); /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */ void getLetterboxInnerBounds(@NonNull Rect outBounds); /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */ void getLetterboxOuterBounds(@NonNull Rect outBounds); /** * Updates the letterbox surfaces in case this is needed. * * @param winHint The WindowState for the letterboxed Activity. * @param t The current Transaction. * @param inputT The pending transaction used for the input surface. */ void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT); /** * @return {@code true} if bar shown within a given rectangle is allowed to be fully * transparent when the current activity is displayed. */ boolean isFullyTransparentBarAllowed(@NonNull Rect rect); } services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; /** * Some utility methods used by different Letterbox implementations. */ class AppCompatLetterboxUtils { /** * Provides the position of the top left letterbox area in the display coordinate system. * * @param activity The Letterboxed activity. * @param outLetterboxPosition InOut parameter that will contain the desired letterbox position. */ static void calculateLetterboxPosition(@NonNull ActivityRecord activity, @NonNull Point outLetterboxPosition) { if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) { outLetterboxPosition.set(0, 0); return; } if (activity.isInLetterboxAnimation()) { // In this case we attach the letterbox to the task instead of the activity. activity.getTask().getPosition(outLetterboxPosition); } else { activity.getPosition(outLetterboxPosition); } } /** * Provides all the available space, in display coordinate, to fill with the letterboxed * activity and the letterbox areas. * * @param activity The Letterboxed activity. * @param outOuterBounds InOut parameter that will contain the outer bounds for the letterboxed * activity. */ static void calculateLetterboxOuterBounds(@NonNull ActivityRecord activity, @NonNull Rect outOuterBounds) { if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) { outOuterBounds.setEmpty(); return; } // Get the bounds of the "space-to-fill". The transformed bounds have the highest // priority because the activity is launched in a rotated environment. In multi-window // mode, the taskFragment-level represents this for both split-screen // and activity-embedding. In fullscreen-mode, the task container does // (since the orientation letterbox is also applied to the task). final Rect transformedBounds = activity.getFixedRotationTransformDisplayBounds(); outOuterBounds.set(transformedBounds != null ? transformedBounds : activity.inMultiWindowMode() ? activity.getTaskFragment().getBounds() : activity.getRootTask().getParent().getBounds()); } /** * Provides the inner bounds for the letterboxed activity in display coordinates. This is the * space the letterboxed activity will use. * * @param activity The Letterboxed activity. * @param outInnerBounds InOut parameter that will contain the inner bounds for the letterboxed * activity. */ static void calculateLetterboxInnerBounds(@NonNull ActivityRecord activity, @NonNull WindowState window, @NonNull Rect outInnerBounds) { if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) { outInnerBounds.setEmpty(); return; } // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct // information and in particular it might provide a value for a smaller area making // the letterbox overlap with the translucent activity's frame. // If we use WindowState#getFrame() for the translucent activity's letterbox inner // frame, the letterbox will then be overlapped with the translucent activity's frame. // Because the surface layer of letterbox is lower than an activity window, this // won't crop the content, but it may affect other features that rely on values stored // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final TransparentPolicy transparentPolicy = activity.mAppCompatController.getTransparentPolicy(); outInnerBounds.set( transparentPolicy.isRunning() ? activity.getBounds() : window.getFrame()); } } services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +8 −0 Original line number Diff line number Diff line Loading @@ -243,6 +243,10 @@ class AppCompatActivityRobot { doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask(); } void setIsInLetterboxAnimation(boolean inAnimation) { doReturn(inAnimation).when(mActivityStack.top()).isInLetterboxAnimation(); } void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) { doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode(); } Loading Loading @@ -284,6 +288,10 @@ class AppCompatActivityRobot { } } void setFixedRotationTransformDisplayBounds(@Nullable Rect bounds) { doReturn(bounds).when(mActivityStack.top()).getFixedRotationTransformDisplayBounds(); } void destroyTopActivity() { mActivityStack.top().removeImmediately(); } Loading services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java 0 → 100644 +254 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java +172 −69 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString; import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxInnerBounds; import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxOuterBounds; import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxPosition; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -32,6 +35,7 @@ import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType; import com.android.window.flags.Flags; import java.io.PrintWriter; Loading @@ -43,7 +47,7 @@ class AppCompatLetterboxPolicy { @NonNull private final ActivityRecord mActivityRecord; @NonNull private final LetterboxPolicyState mLetterboxPolicyState; private final AppCompatLetterboxPolicyState mLetterboxPolicyState; @NonNull private final AppCompatRoundedCorners mAppCompatRoundedCorners; @NonNull Loading @@ -54,7 +58,8 @@ class AppCompatLetterboxPolicy { AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord, @NonNull AppCompatConfiguration appCompatConfiguration) { mActivityRecord = activityRecord; mLetterboxPolicyState = new LetterboxPolicyState(); mLetterboxPolicyState = Flags.appCompatRefactoring() ? new ShellLetterboxPolicyState() : new LegacyLetterboxPolicyState(); // TODO (b/358334569) Improve cutout logic dependency on app compat. mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord, this::isLetterboxedNotForDisplayCutout); Loading Loading @@ -88,7 +93,24 @@ class AppCompatLetterboxPolicy { @Nullable LetterboxDetails getLetterboxDetails() { return mLetterboxPolicyState.getLetterboxDetails(); final WindowState w = mActivityRecord.findMainWindow(); if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) { return null; } final Rect letterboxInnerBounds = new Rect(); final Rect letterboxOuterBounds = new Rect(); mLetterboxPolicyState.getLetterboxInnerBounds(letterboxInnerBounds); mLetterboxPolicyState.getLetterboxOuterBounds(letterboxOuterBounds); if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { return null; } return new LetterboxDetails( letterboxInnerBounds, letterboxOuterBounds, w.mAttrs.insetsFlags.appearance ); } /** Loading @@ -99,6 +121,13 @@ class AppCompatLetterboxPolicy { return mLetterboxPolicyState.isFullyTransparentBarAllowed(rect); } /** * Updates the letterbox surfaces in case this is needed. * * @param winHint The WindowState for the letterboxed Activity. * @param t The current Transaction. * @param inputT The pending transaction used for the input surface. */ void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT) { Loading Loading @@ -232,12 +261,17 @@ class AppCompatLetterboxPolicy { || w.mAnimatingExit; } private class LetterboxPolicyState { /** * Existing {@link AppCompatLetterboxPolicyState} implementation. * TODO(b/375339716): Clean code for legacy implementation. */ private class LegacyLetterboxPolicyState implements AppCompatLetterboxPolicyState { @Nullable private Letterbox mLetterbox; void layoutLetterboxIfNeeded(@NonNull WindowState w) { @Override public void layoutLetterboxIfNeeded(@NonNull WindowState w) { if (!isRunning()) { final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord .mAppCompatController.getAppCompatLetterboxOverrides(); Loading @@ -252,41 +286,11 @@ class AppCompatLetterboxPolicy { .setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame); } final Point letterboxPosition = new Point(); if (mActivityRecord.isInLetterboxAnimation()) { // In this case we attach the letterbox to the task instead of the activity. mActivityRecord.getTask().getPosition(letterboxPosition); } else { mActivityRecord.getPosition(letterboxPosition); } // Get the bounds of the "space-to-fill". The transformed bounds have the highest // priority because the activity is launched in a rotated environment. In multi-window // mode, the taskFragment-level represents this for both split-screen // and activity-embedding. In fullscreen-mode, the task container does // (since the orientation letterbox is also applied to the task). final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); final Rect spaceToFill = transformedBounds != null ? transformedBounds : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTaskFragment().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct // information and in particular it might provide a value for a smaller area making // the letterbox overlap with the translucent activity's frame. // If we use WindowState#getFrame() for the translucent activity's letterbox inner // frame, the letterbox will then be overlapped with the translucent activity's frame. // Because the surface layer of letterbox is lower than an activity window, this // won't crop the content, but it may affect other features that rely on values stored // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController.getTransparentPolicy(); final Rect innerFrame = transparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame(); calculateLetterboxPosition(mActivityRecord, letterboxPosition); final Rect spaceToFill = new Rect(); calculateLetterboxOuterBounds(mActivityRecord, spaceToFill); final Rect innerFrame = new Rect(); calculateLetterboxInnerBounds(mActivityRecord, w, innerFrame); mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition); if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides() .isDoubleTapEvent()) { Loading @@ -299,18 +303,21 @@ class AppCompatLetterboxPolicy { * @return {@code true} if the policy is running and so if the current activity is * letterboxed. */ boolean isRunning() { @Override public boolean isRunning() { return mLetterbox != null; } void onMovedToDisplay(int displayId) { @Override public void onMovedToDisplay(int displayId) { if (isRunning()) { mLetterbox.onMovedToDisplay(displayId); } } /** Cleans up {@link Letterbox} if it exists.*/ void stop() { @Override public void stop() { if (isRunning()) { mLetterbox.destroy(); mLetterbox = null; Loading @@ -319,7 +326,8 @@ class AppCompatLetterboxPolicy { .setLetterboxInnerBoundsSupplier(null); } void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @Override public void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT) { if (shouldNotLayoutLetterbox(winHint)) { Loading @@ -331,15 +339,17 @@ class AppCompatLetterboxPolicy { } } void hide() { @Override public void hide() { if (isRunning()) { mLetterbox.hide(); } } /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ @Override @NonNull Rect getLetterboxInsets() { public Rect getLetterboxInsets() { if (isRunning()) { return mLetterbox.getInsets(); } else { Loading @@ -348,7 +358,8 @@ class AppCompatLetterboxPolicy { } /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */ void getLetterboxInnerBounds(@NonNull Rect outBounds) { @Override public void getLetterboxInnerBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mLetterbox.getInnerFrame()); final WindowState w = mActivityRecord.findMainWindow(); Loading @@ -361,7 +372,8 @@ class AppCompatLetterboxPolicy { } /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */ private void getLetterboxOuterBounds(@NonNull Rect outBounds) { @Override public void getLetterboxOuterBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mLetterbox.getOuterFrame()); } else { Loading @@ -373,39 +385,130 @@ class AppCompatLetterboxPolicy { * @return {@code true} if bar shown within a given rectangle is allowed to be fully * transparent when the current activity is displayed. */ boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { @Override public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect); } @Nullable LetterboxDetails getLetterboxDetails() { final WindowState w = mActivityRecord.findMainWindow(); if (!isRunning() || w == null || w.isLetterboxedForDisplayCutout()) { return null; private SurfaceControl getLetterboxParentSurface() { if (mActivityRecord.isInLetterboxAnimation()) { return mActivityRecord.getTask().getSurfaceControl(); } return mActivityRecord.getSurfaceControl(); } final Rect letterboxInnerBounds = new Rect(); final Rect letterboxOuterBounds = new Rect(); getLetterboxInnerBounds(letterboxInnerBounds); getLetterboxOuterBounds(letterboxOuterBounds); if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { return null; } return new LetterboxDetails( letterboxInnerBounds, letterboxOuterBounds, w.mAttrs.insetsFlags.appearance /** * {@link AppCompatLetterboxPolicyState} implementation for the letterbox presentation on shell. */ private class ShellLetterboxPolicyState implements AppCompatLetterboxPolicyState { private final Rect mInnerBounds = new Rect(); private final Rect mOuterBounds = new Rect(); private final Point mLetterboxPosition = new Point(); private boolean mRunning; @Override public void layoutLetterboxIfNeeded(@NonNull WindowState w) { mRunning = true; calculateLetterboxPosition(mActivityRecord, mLetterboxPosition); calculateLetterboxOuterBounds(mActivityRecord, mOuterBounds); calculateLetterboxInnerBounds(mActivityRecord, w, mInnerBounds); mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy() .setLetterboxInnerBoundsSupplier(() -> mInnerBounds); } @Override public boolean isRunning() { return mRunning; } @Override public void onMovedToDisplay(int displayId) { // TODO(b/374918469): Handle Display Change for Letterbox in Shell } @Override public void stop() { if (!isRunning()) { return; } mRunning = false; mLetterboxPosition.set(0, 0); mInnerBounds.setEmpty(); mOuterBounds.setEmpty(); mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy() .setLetterboxInnerBoundsSupplier(null); } @Override public void hide() { if (!isRunning()) { return; } mLetterboxPosition.set(0, 0); mInnerBounds.setEmpty(); mOuterBounds.setEmpty(); } @NonNull @Override public Rect getLetterboxInsets() { if (isRunning()) { return new Rect( Math.max(0, mInnerBounds.left - mOuterBounds.left), Math.max(0, mOuterBounds.top - mInnerBounds.top), Math.max(0, mOuterBounds.right - mInnerBounds.right), Math.max(0, mInnerBounds.bottom - mOuterBounds.bottom) ); } return new Rect(); } @Nullable private SurfaceControl getLetterboxParentSurface() { if (mActivityRecord.isInLetterboxAnimation()) { return mActivityRecord.getTask().getSurfaceControl(); @Override public void getLetterboxInnerBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mInnerBounds); final WindowState w = mActivityRecord.findMainWindow(); if (w != null) { AppCompatUtils.adjustBoundsForTaskbar(w, outBounds); } } else { outBounds.setEmpty(); } return mActivityRecord.getSurfaceControl(); } @Override public void getLetterboxOuterBounds(@NonNull Rect outBounds) { if (isRunning()) { outBounds.set(mOuterBounds); } else { outBounds.setEmpty(); } } @Override public void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT) { if (shouldNotLayoutLetterbox(winHint)) { return; } start(winHint); } @Override public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) { // TODO(b/374921442) Handle Transparent Activities Letterboxing in Shell. // At the moment Shell handles letterbox with a single surface. This would make // notIntersectsOrFullyContains() to return false in the existing Letterbox // implementation. // Note: Previous implementation is // !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect); return !isRunning(); } } }
services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import android.annotation.NonNull; import android.graphics.Rect; import android.view.SurfaceControl; /** * Abstraction for different Letterbox state implementations. */ interface AppCompatLetterboxPolicyState { /** * Checks if a relayout is necessary for the letterbox implementations. * @param w The {@link WindowState} to use for defining Letterbox sizes. */ void layoutLetterboxIfNeeded(@NonNull WindowState w); /** * @return {@code true} if the policy is running and so if the current activity is * letterboxed. */ boolean isRunning(); /** * Called when the activity is moved to a new display. * @param displayId Id for the new display */ void onMovedToDisplay(int displayId); /** Cleans up {@link Letterbox} if it exists.*/ void stop(); /** Hides the letterbox surfaces implementation. */ void hide(); /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ @NonNull Rect getLetterboxInsets(); /** Gets the inner bounds of letterbox. The bounds will be empty with no letterbox. */ void getLetterboxInnerBounds(@NonNull Rect outBounds); /** Gets the outer bounds of letterbox. The bounds will be empty with no letterbox. */ void getLetterboxOuterBounds(@NonNull Rect outBounds); /** * Updates the letterbox surfaces in case this is needed. * * @param winHint The WindowState for the letterboxed Activity. * @param t The current Transaction. * @param inputT The pending transaction used for the input surface. */ void updateLetterboxSurfaceIfNeeded(@NonNull WindowState winHint, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction inputT); /** * @return {@code true} if bar shown within a given rectangle is allowed to be fully * transparent when the current activity is displayed. */ boolean isFullyTransparentBarAllowed(@NonNull Rect rect); }
services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; /** * Some utility methods used by different Letterbox implementations. */ class AppCompatLetterboxUtils { /** * Provides the position of the top left letterbox area in the display coordinate system. * * @param activity The Letterboxed activity. * @param outLetterboxPosition InOut parameter that will contain the desired letterbox position. */ static void calculateLetterboxPosition(@NonNull ActivityRecord activity, @NonNull Point outLetterboxPosition) { if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) { outLetterboxPosition.set(0, 0); return; } if (activity.isInLetterboxAnimation()) { // In this case we attach the letterbox to the task instead of the activity. activity.getTask().getPosition(outLetterboxPosition); } else { activity.getPosition(outLetterboxPosition); } } /** * Provides all the available space, in display coordinate, to fill with the letterboxed * activity and the letterbox areas. * * @param activity The Letterboxed activity. * @param outOuterBounds InOut parameter that will contain the outer bounds for the letterboxed * activity. */ static void calculateLetterboxOuterBounds(@NonNull ActivityRecord activity, @NonNull Rect outOuterBounds) { if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) { outOuterBounds.setEmpty(); return; } // Get the bounds of the "space-to-fill". The transformed bounds have the highest // priority because the activity is launched in a rotated environment. In multi-window // mode, the taskFragment-level represents this for both split-screen // and activity-embedding. In fullscreen-mode, the task container does // (since the orientation letterbox is also applied to the task). final Rect transformedBounds = activity.getFixedRotationTransformDisplayBounds(); outOuterBounds.set(transformedBounds != null ? transformedBounds : activity.inMultiWindowMode() ? activity.getTaskFragment().getBounds() : activity.getRootTask().getParent().getBounds()); } /** * Provides the inner bounds for the letterboxed activity in display coordinates. This is the * space the letterboxed activity will use. * * @param activity The Letterboxed activity. * @param outInnerBounds InOut parameter that will contain the inner bounds for the letterboxed * activity. */ static void calculateLetterboxInnerBounds(@NonNull ActivityRecord activity, @NonNull WindowState window, @NonNull Rect outInnerBounds) { if (!activity.mAppCompatController.getAppCompatLetterboxPolicy().isRunning()) { outInnerBounds.setEmpty(); return; } // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct // information and in particular it might provide a value for a smaller area making // the letterbox overlap with the translucent activity's frame. // If we use WindowState#getFrame() for the translucent activity's letterbox inner // frame, the letterbox will then be overlapped with the translucent activity's frame. // Because the surface layer of letterbox is lower than an activity window, this // won't crop the content, but it may affect other features that rely on values stored // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final TransparentPolicy transparentPolicy = activity.mAppCompatController.getTransparentPolicy(); outInnerBounds.set( transparentPolicy.isRunning() ? activity.getBounds() : window.getFrame()); } }
services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +8 −0 Original line number Diff line number Diff line Loading @@ -243,6 +243,10 @@ class AppCompatActivityRobot { doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask(); } void setIsInLetterboxAnimation(boolean inAnimation) { doReturn(inAnimation).when(mActivityStack.top()).isInLetterboxAnimation(); } void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) { doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode(); } Loading Loading @@ -284,6 +288,10 @@ class AppCompatActivityRobot { } } void setFixedRotationTransformDisplayBounds(@Nullable Rect bounds) { doReturn(bounds).when(mActivityStack.top()).getFixedRotationTransformDisplayBounds(); } void destroyTopActivity() { mActivityStack.top().removeImmediately(); } Loading
services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java 0 → 100644 +254 −0 File added.Preview size limit exceeded, changes collapsed. Show changes