diff options
| author | 2024-10-24 15:59:57 +0000 | |
|---|---|---|
| committer | 2024-10-24 15:59:57 +0000 | |
| commit | 43ffc5cdf1ac26685060dabf3c56e6f5eb1ac77c (patch) | |
| tree | 5841e2b9270c9e66427ae44c780ca7a2934cdec2 | |
| parent | 3579b63c88c80cf3deeb0187bdf895c5ba2e8b97 (diff) | |
| parent | 42d5d2805ef0e831a1915ebe864c5ba47c50e6ee (diff) | |
Merge "[1/n] Create AppCompatLetterboxPolicyState abstraction" into main
5 files changed, 620 insertions, 69 deletions
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java index afc6506f9091..4e390df14131 100644 --- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java @@ -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; @@ -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; @@ -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 @@ -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); @@ -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 + ); } /** @@ -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) { @@ -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(); @@ -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()) { @@ -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; @@ -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)) { @@ -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 { @@ -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(); @@ -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 { @@ -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(); + } + + } + + /** + * {@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; } - final Rect letterboxInnerBounds = new Rect(); - final Rect letterboxOuterBounds = new Rect(); - getLetterboxInnerBounds(letterboxInnerBounds); - getLetterboxOuterBounds(letterboxOuterBounds); + mLetterboxPosition.set(0, 0); + mInnerBounds.setEmpty(); + mOuterBounds.setEmpty(); + } - if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { - return null; + @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(); + } - return new LetterboxDetails( - letterboxInnerBounds, - letterboxOuterBounds, - w.mAttrs.insetsFlags.appearance - ); + @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(); + } } - @Nullable - private SurfaceControl getLetterboxParentSurface() { - if (mActivityRecord.isInLetterboxAnimation()) { - return mActivityRecord.getTask().getSurfaceControl(); + @Override + public void getLetterboxOuterBounds(@NonNull Rect outBounds) { + if (isRunning()) { + outBounds.set(mOuterBounds); + } else { + outBounds.setEmpty(); } - return mActivityRecord.getSurfaceControl(); } + @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(); + } } } diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java new file mode 100644 index 000000000000..31ad536c2eb2 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicyState.java @@ -0,0 +1,79 @@ +/* + * 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); + +} diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java new file mode 100644 index 000000000000..79b3a55d0463 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java @@ -0,0 +1,107 @@ +/* + * 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()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index 08963f1c7647..3742249bdffe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -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(); } @@ -284,6 +288,10 @@ class AppCompatActivityRobot { } } + void setFixedRotationTransformDisplayBounds(@Nullable Rect bounds) { + doReturn(bounds).when(mActivityStack.top()).getFixedRotationTransformDisplayBounds(); + } + void destroyTopActivity() { mActivityStack.top().removeImmediately(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java new file mode 100644 index 000000000000..673d04166a7a --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java @@ -0,0 +1,254 @@ +/* + * Copyright 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +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 static org.mockito.Mockito.mock; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Tests for the {@link AppCompatLetterboxUtils} class. + * + * Build/Install/Run: + * atest WmTests:AppCompatLetterboxUtilsTest + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatLetterboxUtilsTest extends WindowTestsBase { + + @Test + public void allEmptyWhenIsAppNotLetterboxed() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(false); + robot.getLetterboxPosition(); + robot.assertPosition(/* x */ 0, /* y */0); + robot.getInnerBounds(); + robot.assertInnerBounds(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0); + robot.getOuterBounds(); + robot.assertOuterBounds(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0); + }); + } + + @Test + public void positionIsFromTaskWhenLetterboxAnimationIsRunning() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.activity().setIsInLetterboxAnimation(true); + robot.activity().configureTaskBounds( + new Rect(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ 400)); + robot.getLetterboxPosition(); + + robot.assertPosition(/* x */ 100, /* y */ 200); + }); + } + + @Test + public void positionIsFromActivityWhenLetterboxAnimationIsNotRunning() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.activity().setIsInLetterboxAnimation(false); + robot.activity().configureTopActivityBounds( + new Rect(/* left */ 200, /* top */ 400, /* right */ 300, /* bottom */ 400)); + robot.getLetterboxPosition(); + + robot.assertPosition(/* x */ 200, /* y */ 400); + }); + } + + @Test + public void outerBoundsWhenFixedRotationTransformDisplayBoundsIsAvailable() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.activity().setFixedRotationTransformDisplayBounds( + new Rect(/* left */ 1, /* top */ 2, /* right */ 3, /* bottom */ 4)); + robot.getOuterBounds(); + + robot.assertOuterBounds(/* left */ 1, /* top */ 2, /* right */ 3, /* bottom */ 4); + }); + } + + @Test + public void outerBoundsNoFixedRotationTransformDisplayBoundsInMultiWindow() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.activity().setFixedRotationTransformDisplayBounds(null); + robot.activity().setTopActivityInMultiWindowMode(true); + robot.getOuterBounds(); + + robot.checkOuterBoundsAreTaskFragmentBounds(); + }); + } + + @Test + public void outerBoundsNoFixedRotationTransformDisplayBoundsNotInMultiWindow() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.activity().setFixedRotationTransformDisplayBounds(null); + robot.activity().setTopActivityInMultiWindowMode(false); + robot.getOuterBounds(); + + robot.checkOuterBoundsAreRootTaskParentBounds(); + }); + } + + @Test + public void innerBoundsTransparencyPolicyIsRunning() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.setTopActivityTransparentPolicyRunning(true); + + robot.getInnerBounds(); + + robot.checkInnerBoundsAreActivityBounds(); + }); + } + + @Test + public void innerBoundsTransparencyPolicyIsNotRunning() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.setTopActivityLetterboxPolicyRunning(true); + robot.setTopActivityTransparentPolicyRunning(false); + robot.setWindowFrame( + new Rect(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ 400)); + + robot.getInnerBounds(); + + robot.assertInnerBounds(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ + 400); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<LetterboxUtilsRobotTest> consumer) { + final LetterboxUtilsRobotTest robot = new LetterboxUtilsRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class LetterboxUtilsRobotTest extends AppCompatRobotBase { + + private final Point mPosition = new Point(); + private final Rect mInnerBound = new Rect(); + private final Rect mOuterBound = new Rect(); + + @NonNull + private final WindowState mWindowState; + + LetterboxUtilsRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + super(wm, atm, supervisor); + mWindowState = mock(WindowState.class); + } + + @Override + void onPostActivityCreation(@NonNull ActivityRecord activity) { + super.onPostActivityCreation(activity); + spyOn(activity.mAppCompatController.getAppCompatLetterboxPolicy()); + spyOn(activity.mAppCompatController.getTransparentPolicy()); + } + + void setTopActivityLetterboxPolicyRunning(boolean running) { + doReturn(running).when(activity().top().mAppCompatController + .getAppCompatLetterboxPolicy()).isRunning(); + } + + void setTopActivityTransparentPolicyRunning(boolean running) { + doReturn(running).when(activity().top().mAppCompatController + .getTransparentPolicy()).isRunning(); + } + + void setWindowFrame(@NonNull Rect frame) { + doReturn(frame).when(mWindowState).getFrame(); + } + + void getLetterboxPosition() { + calculateLetterboxPosition(activity().top(), mPosition); + } + + void getInnerBounds() { + calculateLetterboxInnerBounds(activity().top(), mWindowState, mInnerBound); + } + + void getOuterBounds() { + calculateLetterboxOuterBounds(activity().top(), mOuterBound); + } + + void assertPosition(int expectedX, int expectedY) { + Assert.assertEquals(expectedX, mPosition.x); + Assert.assertEquals(expectedY, mPosition.y); + } + + void assertInnerBounds(int expectedLeft, int expectedTop, int expectedRight, + int expectedBottom) { + Assert.assertEquals(expectedLeft, mInnerBound.left); + Assert.assertEquals(expectedTop, mInnerBound.top); + Assert.assertEquals(expectedRight, mInnerBound.right); + Assert.assertEquals(expectedBottom, mInnerBound.bottom); + } + + void assertOuterBounds(int expectedLeft, int expectedTop, int expectedRight, + int expectedBottom) { + Assert.assertEquals(expectedLeft, mOuterBound.left); + Assert.assertEquals(expectedTop, mOuterBound.top); + Assert.assertEquals(expectedRight, mOuterBound.right); + Assert.assertEquals(expectedBottom, mOuterBound.bottom); + } + + void checkOuterBoundsAreRootTaskParentBounds() { + Assert.assertEquals(mOuterBound, + activity().top().getRootTask().getParent().getBounds()); + } + + void checkOuterBoundsAreTaskFragmentBounds() { + Assert.assertEquals(mOuterBound, + activity().top().getTaskFragment().getBounds()); + } + + void checkInnerBoundsAreActivityBounds() { + Assert.assertEquals(mInnerBound, activity().top().getBounds()); + } + + } +} |