• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.Rect;
22 import android.view.InsetsState;
23 import android.view.RoundedCorner;
24 import android.view.SurfaceControl;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.util.function.Predicate;
29 
30 /**
31  * Utility to unify rounded corners in app compat.
32  */
33 class AppCompatRoundedCorners {
34 
35     @NonNull
36     private final ActivityRecord mActivityRecord;
37     @NonNull
38     private final Predicate<WindowState> mRoundedCornersWindowCondition;
39 
AppCompatRoundedCorners(@onNull ActivityRecord activityRecord, @NonNull Predicate<WindowState> roundedCornersWindowCondition)40     AppCompatRoundedCorners(@NonNull ActivityRecord  activityRecord,
41             @NonNull Predicate<WindowState> roundedCornersWindowCondition) {
42         mActivityRecord = activityRecord;
43         mRoundedCornersWindowCondition = roundedCornersWindowCondition;
44     }
45 
updateRoundedCornersIfNeeded(@onNull final WindowState mainWindow)46     void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) {
47         final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
48         if (windowSurface == null || !windowSurface.isValid()) {
49             return;
50         }
51 
52         // cropBounds must be non-null for the cornerRadius to be ever applied.
53         mActivityRecord.getSyncTransaction()
54                 .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow))
55                 .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
56     }
57 
58     @VisibleForTesting
59     @Nullable
getCropBoundsIfNeeded(@onNull final WindowState mainWindow)60     Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) {
61         if (!requiresRoundedCorners(mainWindow)) {
62             // We don't want corner radius on the window.
63             // In the case the ActivityRecord requires a letterboxed animation we never want
64             // rounded corners on the window because rounded corners are applied at the
65             // animation-bounds surface level and rounded corners on the window would interfere
66             // with that leading to unexpected rounded corner positioning during the animation.
67             return null;
68         }
69 
70         final Rect cropBounds = new Rect(mActivityRecord.getBounds());
71 
72         // In case of translucent activities we check if the requested size is different from
73         // the size provided using inherited bounds. In that case we decide to not apply rounded
74         // corners because we assume the specific layout would. This is the case when the layout
75         // of the translucent activity uses only a part of all the bounds because of the use of
76         // LayoutParams.WRAP_CONTENT.
77         final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController
78                 .getTransparentPolicy();
79         if (transparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth
80                 || cropBounds.height() != mainWindow.mRequestedHeight)) {
81             return null;
82         }
83 
84         // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
85         // because taskbar bounds used in {@link #adjustBoundsIfNeeded}
86         // are in screen coordinates
87         AppCompatUtils.adjustBoundsForTaskbar(mainWindow, cropBounds);
88 
89         final float scale = mainWindow.mInvGlobalScale;
90         if (scale != 1f && scale > 0f) {
91             cropBounds.scale(scale);
92         }
93 
94         // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
95         // control is in the top left corner of an app window so offsetting bounds
96         // accordingly.
97         cropBounds.offsetTo(0, 0);
98         return cropBounds;
99     }
100 
101     /**
102      * Returns rounded corners radius the letterboxed activity should have based on override in
103      * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
104      * Device corners can be different on the right and left sides, but we use the same radius
105      * for all corners for consistency and pick a minimal bottom one for consistency with a
106      * taskbar rounded corners.
107      *
108      * @param mainWindow    The {@link WindowState} to consider for rounded corners calculation.
109      */
getRoundedCornersRadius(@onNull final WindowState mainWindow)110     int getRoundedCornersRadius(@NonNull final WindowState mainWindow) {
111         if (!requiresRoundedCorners(mainWindow)) {
112             return 0;
113         }
114         final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
115                 .mAppCompatController.getLetterboxOverrides();
116         final int radius;
117         if (letterboxOverrides.getLetterboxActivityCornersRadius() >= 0) {
118             radius = letterboxOverrides.getLetterboxActivityCornersRadius();
119         } else {
120             final InsetsState insetsState = mainWindow.getInsetsState();
121             radius = Math.min(
122                     getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
123                     getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
124         }
125 
126         final float scale = mainWindow.mInvGlobalScale;
127         return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius;
128     }
129 
getInsetsStateCornerRadius(@onNull InsetsState insetsState, @RoundedCorner.Position int position)130     private static int getInsetsStateCornerRadius(@NonNull InsetsState insetsState,
131             @RoundedCorner.Position int position) {
132         final RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
133         return corner == null ? 0 : corner.getRadius();
134     }
135 
requiresRoundedCorners(@onNull final WindowState mainWindow)136     private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) {
137         final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
138                 .mAppCompatController.getLetterboxOverrides();
139         return mRoundedCornersWindowCondition.test(mainWindow)
140                 && letterboxOverrides.isLetterboxActivityCornersRounded();
141     }
142 
143 }
144