blob: ac5ff20759016ab52a49b86f02fd70d0d4c1f8a8 [file] [log] [blame]
/*
* Copyright (C) 2022 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.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.window.SurfaceSyncGroup;
import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import java.util.function.Supplier;
/**
* Manages a container surface and a windowless window to show window decoration. Responsible to
* update window decoration window state and layout parameters on task info changes and so that
* window decoration is in correct state and bounds.
*
* The container surface is a child of the task display area in the same display, so that window
* decorations can be drawn out of the task bounds and receive input events from out of the task
* bounds to support drag resizing.
*
* The windowless window that hosts window decoration is positioned in front of all activities, to
* allow the foreground activity to draw its own background behind window decorations, such as
* the window captions.
*
* @param <T> The type of the root view
*/
public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
implements AutoCloseable {
/**
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
final DisplayController mDisplayController;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
public void onDisplayAdded(int displayId) {
if (mTaskInfo.displayId != displayId) {
return;
}
mDisplayController.removeDisplayWindowListener(this);
relayout(mTaskInfo);
}
};
RunningTaskInfo mTaskInfo;
int mLayoutResId;
final SurfaceControl mTaskSurface;
Display mDisplay;
Context mDecorWindowContext;
SurfaceControl mDecorationContainerSurface;
SurfaceControl mCaptionContainerSurface;
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
private final float[] mTmpColor = new float[3];
WindowDecoration(
Context context,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
SurfaceControl taskSurface) {
this(context, displayController, taskOrganizer, taskInfo, taskSurface,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
Context context,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
mTaskSurface = taskSurface;
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
mDecorWindowContext = mContext.createConfigurationContext(
getConfigurationWithOverrides(mTaskInfo));
}
/**
* Get {@link Configuration} from supplied {@link RunningTaskInfo}.
*
* Allows values to be overridden before returning the configuration.
*/
protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
return taskInfo.getConfiguration();
}
/**
* Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a
* relayout weren't satisfied are satisfied now.
*
* @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the
* constructor.
*/
abstract void relayout(RunningTaskInfo taskInfo);
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
final int oldLayoutResId = mLayoutResId;
mLayoutResId = params.mLayoutResId;
if (!mTaskInfo.isVisible) {
releaseViews();
finishT.hide(mTaskSurface);
return;
}
if (rootView == null && params.mLayoutResId == 0) {
throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
}
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId
|| oldLayoutResId != mLayoutResId) {
releaseViews();
if (!obtainDisplayOrRegisterListener()) {
outResult.mRootView = null;
return;
}
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
if (params.mLayoutResId != 0) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
.inflate(params.mLayoutResId, null);
}
}
if (outResult.mRootView == null) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
.inflate(params.mLayoutResId, null);
}
final Resources resources = mDecorWindowContext.getResources();
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
outResult.mWidth = taskBounds.width();
outResult.mHeight = taskBounds.height();
// DecorationContainerSurface
if (mDecorationContainerSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
mDecorationContainerSurface = builder
.setName("Decor container of Task=" + mTaskInfo.taskId)
.setContainerLayer()
.setParent(mTaskSurface)
.build();
startT.setTrustedOverlay(mDecorationContainerSurface, true)
.setLayer(mDecorationContainerSurface,
TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
}
startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
.show(mDecorationContainerSurface);
// CaptionContainerSurface, CaptionWindowManager
if (mCaptionContainerSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
mCaptionContainerSurface = builder
.setName("Caption container of Task=" + mTaskInfo.taskId)
.setContainerLayer()
.setParent(mDecorationContainerSurface)
.build();
}
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (ViewRootImpl.CAPTION_ON_SHELL) {
outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
// Caption insets
mCaptionInsetsRect.set(taskBounds);
mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
} else {
startT.hide(mCaptionContainerSurface);
}
// Task surface itself
float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
final Point taskPosition = mTaskInfo.positionInParent;
startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
.setShadowRadius(mTaskSurface, shadowRadius)
.setColor(mTaskSurface, mTmpColor)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
}
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
mCaptionWindowManager = new WindowlessWindowManager(
mTaskInfo.getConfiguration(), mCaptionContainerSurface,
null /* hostInputToken */);
}
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(captionWidth, captionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
mCaptionWindowManager);
if (params.mApplyStartTransactionOnDraw) {
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
}
mViewHost.setView(outResult.mRootView, lp);
} else {
if (params.mApplyStartTransactionOnDraw) {
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
}
mViewHost.relayout(lp);
}
}
/**
* Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
* registers {@link #mOnDisplaysChangedListener} if it doesn't.
*
* @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise
*/
private boolean obtainDisplayOrRegisterListener() {
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
if (mDisplay == null) {
mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
return false;
}
return true;
}
void releaseViews() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
}
mCaptionWindowManager = null;
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
boolean released = false;
if (mCaptionContainerSurface != null) {
t.remove(mCaptionContainerSurface);
mCaptionContainerSurface = null;
released = true;
}
if (mDecorationContainerSurface != null) {
t.remove(mDecorationContainerSurface);
mDecorationContainerSurface = null;
released = true;
}
if (released) {
t.apply();
}
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
wct.removeInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar());
mTaskOrganizer.applyTransaction(wct);
}
@Override
public void close() {
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
releaseViews();
}
static int loadDimensionPixelSize(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimensionPixelSize(resourceId);
}
static float loadDimension(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimension(resourceId);
}
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
*
* @param layoutId layout to make the window from
* @param t the transaction to apply
* @param xPos x position of new window
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
* @param shadowRadius radius of the shadow of the new window
* @param cornerRadius radius of the corners of the new window
* @return the {@link AdditionalWindow} that was added.
*/
AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
int cornerRadius) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
.setContainerLayer()
.setParent(mDecorationContainerSurface)
.build();
View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
t.setPosition(windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
.setShadowRadius(windowSurfaceControl, shadowRadius)
.setCornerRadius(windowSurfaceControl, cornerRadius)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
windowSurfaceControl, null /* hostInputToken */);
SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
.create(mDecorWindowContext, mDisplay, windowManager);
ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
return new AdditionalWindow(windowSurfaceControl, viewHost,
mSurfaceControlTransactionSupplier);
}
static class RelayoutParams {
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
int mCaptionHeightId;
int mCaptionWidthId;
int mShadowRadiusId;
int mCornerRadius;
int mCaptionX;
int mCaptionY;
boolean mApplyStartTransactionOnDraw;
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
mCaptionX = 0;
mCaptionY = 0;
mApplyStartTransactionOnDraw = false;
}
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mWidth;
int mHeight;
T mRootView;
void reset() {
mWidth = 0;
mHeight = 0;
mRootView = null;
}
}
interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
}
}
/**
* Subclass for additional windows associated with this WindowDecoration
*/
static class AdditionalWindow {
SurfaceControl mWindowSurface;
SurfaceControlViewHost mWindowViewHost;
Supplier<SurfaceControl.Transaction> mTransactionSupplier;
AdditionalWindow(SurfaceControl surfaceControl,
SurfaceControlViewHost surfaceControlViewHost,
Supplier<SurfaceControl.Transaction> transactionSupplier) {
mWindowSurface = surfaceControl;
mWindowViewHost = surfaceControlViewHost;
mTransactionSupplier = transactionSupplier;
}
void releaseView() {
WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
if (mWindowViewHost != null) {
mWindowViewHost.release();
mWindowViewHost = null;
}
windowManager = null;
final SurfaceControl.Transaction t = mTransactionSupplier.get();
boolean released = false;
if (mWindowSurface != null) {
t.remove(mWindowSurface);
mWindowSurface = null;
released = true;
}
if (released) {
t.apply();
}
}
}
}