blob: e1d177af2331264629141ef4494cbd062e834e5f [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 static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.windowingModeToString;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ImageButton;
import android.window.WindowContainerTransaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
* {@link DesktopModeWindowDecorViewModel}.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
private final Point mPositionInParent = new Point();
private HandleMenu mHandleMenu;
private MaximizeMenu mMaximizeMenu;
private ResizeVeil mResizeVeil;
private Drawable mAppIconDrawable;
private Bitmap mAppIconBitmap;
private CharSequence mAppName;
private ExclusionRegionListener mExclusionRegionListener;
private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
private int mRelayoutBlock;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Configuration windowDecorConfig,
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
new SurfaceControlViewHostFactory() {});
}
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Configuration windowDecorConfig,
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
surfaceControlViewHostFactory);
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
loadAppInfo();
}
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
View.OnLongClickListener onLongClickListener) {
mOnCaptionButtonClickListener = onCaptionButtonClickListener;
mOnCaptionTouchListener = onCaptionTouchListener;
mOnCaptionLongClickListener = onLongClickListener;
}
void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
mExclusionRegionListener = exclusionRegionListener;
}
void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
mDragPositioningCallback = dragPositioningCallback;
}
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
// TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
// transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
// and interferes with the transition animation that is playing at the same time.
if (mRelayoutBlock > 0) {
return;
}
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw) {
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
final boolean isFreeform =
taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
taskInfo.getWindowingMode());
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
// The configuration used to lay out the window decoration. The system context's config is
// used when the task density has been overridden to a custom density so that the resources
// and views of the decoration aren't affected and match the rest of the System UI, if not
// then just use the task's configuration. A copy is made instead of using the original
// reference so that the configuration isn't mutated on config changes and diff checks can
// be made in WindowDecoration#relayout using the pre/post-relayout configuration.
// See b/301119301.
// TODO(b/301119301): consider moving the config data needed for diffs to relayout params
// instead of using a whole Configuration as a parameter.
final Configuration windowDecorConfig = new Configuration();
windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
? mContext.getResources().getConfiguration() // Use system context.
: mTaskInfo.configuration); // Use task configuration.
mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
mRelayoutParams.mCornerRadius =
(int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
mTaskOrganizer.applyTransaction(wct);
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
return;
}
if (oldRootView != mResult.mRootView) {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) {
mWindowDecorViewHolder = new DesktopModeFocusedWindowDecorationViewHolder(
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener
);
} else if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
mWindowDecorViewHolder = new DesktopModeAppControlsWindowDecorationViewHolder(
mResult.mRootView,
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
mOnCaptionLongClickListener,
mAppName,
mAppIconBitmap
);
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
}
}
mWindowDecorViewHolder.bindData(mTaskInfo);
if (!mTaskInfo.isFocused) {
closeHandleMenu();
closeMaximizeMenu();
}
if (!isDragResizeable) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
// We still want to track caption bar's exclusion region on a non-resizeable task.
updateExclusionRegion();
}
closeDragResizeListener();
return;
}
if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
closeDragResizeListener();
mDragResizeListener = new DragResizeInputListener(
mContext,
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
mRelayoutParams.mCornerRadius,
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
mSurfaceControlTransactionSupplier,
mDisplayController);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
.getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
final int resize_handle = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_handle);
final int resize_corner = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_corner);
// If either task geometry or position have changed, update this task's
// exclusion region listener
if (mDragResizeListener.setGeometry(
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
updateExclusionRegion();
}
if (isMaximizeMenuActive()) {
if (!mTaskInfo.isVisible()) {
closeMaximizeMenu();
} else {
mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT);
}
}
}
private PointF calculateMaximizeMenuPosition() {
final PointF position = new PointF();
final Resources resources = mContext.getResources();
final DisplayLayout displayLayout =
mDisplayController.getDisplayLayout(mTaskInfo.displayId);
if (displayLayout == null) return position;
final int displayWidth = displayLayout.width();
final int displayHeight = displayLayout.height();
final int captionHeight = getCaptionHeight(mTaskInfo.getWindowingMode());
final ImageButton maximizeWindowButton =
mResult.mRootView.findViewById(R.id.maximize_window);
final int[] maximizeButtonLocation = new int[2];
maximizeWindowButton.getLocationInWindow(maximizeButtonLocation);
final int menuWidth = loadDimensionPixelSize(
resources, R.dimen.desktop_mode_maximize_menu_width);
final int menuHeight = loadDimensionPixelSize(
resources, R.dimen.desktop_mode_maximize_menu_height);
float menuLeft = (mPositionInParent.x + maximizeButtonLocation[0]);
float menuTop = (mPositionInParent.y + captionHeight);
final float menuRight = menuLeft + menuWidth;
final float menuBottom = menuTop + menuHeight;
// If the menu is out of screen bounds, shift it up/left as needed
if (menuRight > displayWidth) {
menuLeft = (displayWidth - menuWidth);
}
if (menuBottom > displayHeight) {
menuTop = (displayHeight - menuHeight);
}
return new PointF(menuLeft, menuTop);
}
boolean isHandleMenuActive() {
return mHandleMenu != null;
}
private void loadAppInfo() {
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
try {
final IconProvider provider = new IconProvider(mContext);
mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
PackageManager.ComponentInfoFlags.of(0)));
final Resources resources = mContext.getResources();
final BaseIconFactory factory = new BaseIconFactory(mContext,
resources.getDisplayMetrics().densityDpi,
resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(0));
mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package not found: " + packageName, e);
}
}
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
}
mDragResizeListener.close();
mDragResizeListener = null;
}
/**
* Create the resize veil for this task. Note the veil's visibility is View.GONE by default
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo,
mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
}
/**
* Show the resize veil.
*/
public void showResizeVeil(Rect taskBounds) {
mResizeVeil.showVeil(mTaskSurface, taskBounds);
}
/**
* Show the resize veil.
*/
public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
}
/**
* Set new bounds for the resize veil
*/
public void updateResizeVeil(Rect newBounds) {
mResizeVeil.updateResizeVeil(newBounds);
}
/**
* Set new bounds for the resize veil
*/
public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
mResizeVeil.updateResizeVeil(tx, newBounds);
}
/**
* Fade the resize veil out.
*/
public void hideResizeVeil() {
mResizeVeil.hideVeil();
}
private void disposeResizeVeil() {
if (mResizeVeil == null) return;
mResizeVeil.dispose();
mResizeVeil = null;
}
/**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext,
calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
mMaximizeMenu.show();
}
/**
* Close the maximize menu window
*/
void closeMaximizeMenu() {
if (!isMaximizeMenuActive()) return;
mMaximizeMenu.close();
mMaximizeMenu = null;
}
boolean isMaximizeMenuActive() {
return mMaximizeMenu != null;
}
/**
* Create and display handle menu window.
*/
void createHandleMenu() {
mHandleMenu = new HandleMenu.Builder(this)
.setAppIcon(mAppIconBitmap)
.setAppName(mAppName)
.setOnClickListener(mOnCaptionButtonClickListener)
.setOnTouchListener(mOnCaptionTouchListener)
.setLayoutId(mRelayoutParams.mLayoutResId)
.setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
.setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
.setCaptionHeight(mResult.mCaptionHeight)
.build();
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
/**
* Close the handle menu window.
*/
void closeHandleMenu() {
if (!isHandleMenuActive()) return;
mWindowDecorViewHolder.onHandleMenuClosed();
mHandleMenu.close();
mHandleMenu = null;
}
@Override
void releaseViews() {
closeHandleMenu();
closeMaximizeMenu();
super.releaseViews();
}
/**
* Close an open handle menu if input is outside of menu coordinates
*
* @param ev the tapped point to compare against
*/
void closeHandleMenuIfNeeded(MotionEvent ev) {
if (!isHandleMenuActive()) return;
PointF inputPoint = offsetCaptionLocation(ev);
// If this is called before open_menu_button's onClick, we don't want to close
// the menu since it will just reopen in onClick.
final boolean pointInOpenMenuButton = pointInView(
mResult.mRootView.findViewById(R.id.open_menu_button),
inputPoint.x,
inputPoint.y);
if (!mHandleMenu.isValidMenuInput(inputPoint) && !pointInOpenMenuButton) {
closeHandleMenu();
}
}
/**
* Close an open maximize menu if input is outside of menu coordinates
*
* @param ev the tapped point to compare against
*/
void closeMaximizeMenuIfNeeded(MotionEvent ev) {
if (!isMaximizeMenuActive()) return;
final PointF inputPoint = offsetCaptionLocation(ev);
if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
closeMaximizeMenu();
}
}
boolean isFocused() {
return mTaskInfo.isFocused;
}
/**
* Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
*
* @param ev the {@link MotionEvent} to offset
* @return the point of the input in local space
*/
private PointF offsetCaptionLocation(MotionEvent ev) {
final PointF result = new PointF(ev.getX(), ev.getY());
final Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
.positionInParent;
result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
result.offset(-positionInParent.x, -positionInParent.y);
return result;
}
/**
* Determine if a passed MotionEvent is in a view in caption
*
* @param ev the {@link MotionEvent} to check
* @param layoutId the id of the view
* @return {@code true} if event is inside the specified view, {@code false} if not
*/
private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
if (mResult.mRootView == null) return false;
final PointF inputPoint = offsetCaptionLocation(ev);
final View view = mResult.mRootView.findViewById(layoutId);
return view != null && pointInView(view, inputPoint.x, inputPoint.y);
}
boolean checkTouchEventInHandle(MotionEvent ev) {
if (isHandleMenuActive()) return false;
return checkEventInCaptionView(ev, R.id.caption_handle);
}
/**
* Returns true if motion event is within the caption's root view's bounds.
*/
boolean checkTouchEventInCaption(MotionEvent ev) {
return checkEventInCaptionView(ev, getCaptionViewId());
}
/**
* Check a passed MotionEvent if a click has occurred on any button on this caption
* Note this should only be called when a regular onClick is not possible
* (i.e. the button was clicked through status bar layer)
*
* @param ev the MotionEvent to compare
*/
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
if (!isHandleMenuActive()) {
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
} else {
mHandleMenu.checkClickEvent(ev);
closeHandleMenuIfNeeded(ev);
}
}
private boolean clickIfPointInView(PointF inputPoint, View v) {
if (pointInView(v, inputPoint.x, inputPoint.y)) {
mOnCaptionButtonClickListener.onClick(v);
return true;
}
return false;
}
boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
}
@Override
public void close() {
closeDragResizeListener();
closeHandleMenu();
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
super.close();
}
private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
}
private void updatePositionInParent() {
mPositionInParent.set(mTaskInfo.positionInParent);
}
private void updateExclusionRegion() {
// An outdated position in parent is one reason for this to be called; update it here.
updatePositionInParent();
mExclusionRegionListener
.onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion());
}
/**
* Create a new exclusion region from the corner rects (if resizeable) and caption bounds
* of this task.
*/
private Region getGlobalExclusionRegion() {
Region exclusionRegion;
if (mTaskInfo.isResizeable) {
exclusionRegion = mDragResizeListener.getCornersRegion();
} else {
exclusionRegion = new Region();
}
exclusionRegion.union(new Rect(0, 0, mResult.mWidth,
getCaptionHeight(mTaskInfo.getWindowingMode())));
exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y);
return exclusionRegion;
}
/**
* If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
* mRelayoutBlock
*/
void removeTransitionPausingRelayout(IBinder transition) {
if (mTransitionsPausingRelayout.remove(transition)) {
mRelayoutBlock--;
}
}
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FULLSCREEN
? R.dimen.desktop_mode_fullscreen_decor_caption_height
: R.dimen.desktop_mode_freeform_decor_caption_height;
}
private int getCaptionHeight(@WindowingMode int windowingMode) {
return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
}
@Override
int getCaptionViewId() {
return R.id.desktop_mode_caption;
}
/**
* Add transition to mTransitionsPausingRelayout
*/
void addTransitionPausingRelayout(IBinder transition) {
mTransitionsPausingRelayout.add(transition);
}
/**
* If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
* remove the merged transition from the set and add the transition it was merged into.
*/
public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
if (mTransitionsPausingRelayout.remove(merged)) {
mTransitionsPausingRelayout.add(playing);
}
}
/**
* Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
*/
public void incrementRelayoutBlock() {
mRelayoutBlock++;
}
@Override
public String toString() {
return "{"
+ "mPositionInParent=" + mPositionInParent + ", "
+ "mRelayoutBlock=" + mRelayoutBlock + ", "
+ "taskId=" + mTaskInfo.taskId + ", "
+ "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ "isFocused=" + isFocused()
+ "}";
}
static class Factory {
DesktopModeWindowDecoration create(
Context context,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Handler handler,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
final Configuration windowDecorConfig =
DesktopTasksController.isDesktopDensityOverrideSet()
? context.getResources().getConfiguration() // Use system context
: taskInfo.configuration; // Use task configuration
return new DesktopModeWindowDecoration(
context,
displayController,
taskOrganizer,
taskInfo,
taskSurface,
windowDecorConfig,
handler,
choreographer,
syncQueue,
rootTaskDisplayAreaOrganizer);
}
}
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
/**
* Inform the implementing class that this task no longer needs an exclusion region,
* likely due to it closing.
*/
void onExclusionRegionDismissed(int taskId);
}
}