blob: b6b8ad14e1060444e96cb01c8a861185707ce4f6 [file] [log] [blame]
/*
* Copyright (C) 2021 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 android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
// SizeCompatTests and LetterboxTests but not all.
// TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the
// hierarchy in addition to ActivityRecord (Task, DisplayArea, ...).
final class LetterboxUiController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
private final Point mTmpPoint = new Point();
private final LetterboxConfiguration mLetterboxConfiguration;
private final ActivityRecord mActivityRecord;
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
private Letterbox mLetterbox;
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
}
/** Cleans up {@link Letterbox} if it exists.*/
void destroy() {
if (mLetterbox != null) {
mLetterbox.destroy();
mLetterbox = null;
}
}
void onMovedToDisplay(int displayId) {
if (mLetterbox != null) {
mLetterbox.onMovedToDisplay(displayId);
}
}
boolean hasWallpaperBackgroudForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
/** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
Rect getLetterboxInsets() {
if (mLetterbox != null) {
return mLetterbox.getInsets();
} else {
return new Rect();
}
}
/** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */
void getLetterboxInnerBounds(Rect outBounds) {
if (mLetterbox != null) {
outBounds.set(mLetterbox.getInnerFrame());
} else {
outBounds.setEmpty();
}
}
/**
* @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
* when the current activity is displayed.
*/
boolean isFullyTransparentBarAllowed(Rect rect) {
return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
}
void updateLetterboxSurface(WindowState winHint) {
final WindowState w = mActivityRecord.findMainWindow();
if (w != winHint && winHint != null && w != null) {
return;
}
layoutLetterbox(winHint);
if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction());
}
}
void layoutLetterbox(WindowState winHint) {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || winHint != null && w != winHint) {
return;
}
updateRoundedCorners(w);
updateWallpaperForLetterbox(w);
if (shouldShowLetterboxUi(w)) {
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
mLetterboxConfiguration::isLetterboxActivityCornersRounded,
this::getLetterboxBackgroundColor,
this::hasWallpaperBackgroudForLetterbox,
this::getLetterboxWallpaperBlurRadius,
this::getLetterboxWallpaperDarkScrimAlpha);
mLetterbox.attachInput(w);
}
mActivityRecord.getPosition(mTmpPoint);
// 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 task-level represents this. 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.getRootTask().getBounds()
: mActivityRecord.getRootTask().getParent().getBounds();
mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
}
}
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
// Check that an activity isn't transparent.
&& mActivityRecord.fillsParent()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox backgroud.
&& (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
}
@VisibleForTesting
boolean isSurfaceReadyAndVisible(WindowState mainWindow) {
boolean surfaceReady = mainWindow.isDrawn() // Regular case
// Waiting for relayoutWindow to call preserveSurface
|| mainWindow.isDragResizeChanged();
return surfaceReady && (mActivityRecord.isVisible()
|| mActivityRecord.isVisibleRequested());
}
private Color getLetterboxBackgroundColor() {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || w.isLetterboxedForDisplayCutout()) {
return Color.valueOf(Color.BLACK);
}
@LetterboxBackgroundType int letterboxBackgroundType =
mLetterboxConfiguration.getLetterboxBackgroundType();
TaskDescription taskDescription = mActivityRecord.taskDescription;
switch (letterboxBackgroundType) {
case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
return Color.valueOf(taskDescription.getBackgroundColorFloating());
}
break;
case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
return Color.valueOf(taskDescription.getBackgroundColor());
}
break;
case LETTERBOX_BACKGROUND_WALLPAPER:
if (hasWallpaperBackgroudForLetterbox()) {
// Color is used for translucent scrim that dims wallpaper.
return Color.valueOf(Color.BLACK);
}
Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
+ "blur is not supported by a device or not supported in the current "
+ "window configuration or both alpha scrim and blur radius aren't "
+ "provided so using solid color background");
break;
case LETTERBOX_BACKGROUND_SOLID_COLOR:
return mLetterboxConfiguration.getLetterboxBackgroundColor();
default:
throw new AssertionError(
"Unexpected letterbox background type: " + letterboxBackgroundType);
}
// If picked option configured incorrectly or not supported then default to a solid color
// background.
return mLetterboxConfiguration.getLetterboxBackgroundColor();
}
private void updateRoundedCorners(WindowState mainWindow) {
int cornersRadius =
// Don't round corners if letterboxed only for display cutout.
shouldShowLetterboxUi(mainWindow)
&& !mainWindow.isLetterboxedForDisplayCutout()
? Math.max(0, mLetterboxConfiguration.getLetterboxActivityCornersRadius())
: 0;
setCornersRadius(mainWindow, cornersRadius);
}
private void setCornersRadius(WindowState mainWindow, int cornersRadius) {
final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
if (windowSurface != null && windowSurface.isValid()) {
Transaction transaction = mActivityRecord.getSyncTransaction();
transaction.setCornerRadius(windowSurface, cornersRadius);
}
}
private void updateWallpaperForLetterbox(WindowState mainWindow) {
@LetterboxBackgroundType int letterboxBackgroundType =
mLetterboxConfiguration.getLetterboxBackgroundType();
boolean wallpaperShouldBeShown =
letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER
&& shouldShowLetterboxUi(mainWindow)
// Don't use wallpaper as a background if letterboxed for display cutout.
&& !mainWindow.isLetterboxedForDisplayCutout()
// Check that dark scrim alpha or blur radius are provided
&& (getLetterboxWallpaperBlurRadius() > 0
|| getLetterboxWallpaperDarkScrimAlpha() > 0)
// Check that blur is supported by a device if blur radius is provided.
&& (getLetterboxWallpaperBlurRadius() <= 0
|| isLetterboxWallpaperBlurSupported());
if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
mActivityRecord.requestUpdateWallpaperIfNeeded();
}
}
private int getLetterboxWallpaperBlurRadius() {
int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius();
return blurRadius < 0 ? 0 : blurRadius;
}
private float getLetterboxWallpaperDarkScrimAlpha() {
float alpha = mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha();
// No scrim by default.
return (alpha < 0 || alpha >= 1) ? 0.0f : alpha;
}
private boolean isLetterboxWallpaperBlurSupported() {
return mLetterboxConfiguration.mContext.getSystemService(WindowManager.class)
.isCrossWindowBlurEnabled();
}
void dump(PrintWriter pw, String prefix) {
final WindowState mainWin = mActivityRecord.findMainWindow();
if (mainWin == null) {
return;
}
boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed();
pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
if (!areBoundsLetterboxed) {
return;
}
pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin));
pw.println(prefix + " letterboxAspectRatio="
+ mActivityRecord.computeAspectRatio(mActivityRecord.getBounds()));
boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin);
pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi);
if (!shouldShowLetterboxUi) {
return;
}
pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString(
getLetterboxBackgroundColor().toArgb()));
pw.println(prefix + " letterboxBackgroundType="
+ letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
if (mLetterboxConfiguration.getLetterboxBackgroundType()
== LETTERBOX_BACKGROUND_WALLPAPER) {
pw.println(prefix + " isLetterboxWallpaperBlurSupported="
+ isLetterboxWallpaperBlurSupported());
pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha="
+ getLetterboxWallpaperDarkScrimAlpha());
pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius="
+ getLetterboxWallpaperBlurRadius());
}
pw.println(prefix + " letterboxHorizontalPositionMultiplier="
+ mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
}
/**
* Returns a string representing the reason for letterboxing. This method assumes the activity
* is letterboxed.
*/
private String getLetterboxReasonString(WindowState mainWin) {
if (mActivityRecord.inSizeCompatMode()) {
return "SIZE_COMPAT_MODE";
}
if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) {
return "FIXED_ORIENTATION";
}
if (mainWin.isLetterboxedForDisplayCutout()) {
return "DISPLAY_CUTOUT";
}
return "UNKNOWN_REASON";
}
}