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