diff options
| author | 2022-12-05 17:15:34 -0800 | |
|---|---|---|
| committer | 2023-01-10 18:44:38 -0800 | |
| commit | 23a9bc9919f9afd45a120e9eed7aa7ed133b3c52 (patch) | |
| tree | 3763b609582ae06c98409ccff7f6ebfb4e91447e | |
| parent | 8486154984d2035f1f0343b1e8a3588ff32d3550 (diff) | |
Add full-width caption on Shell.
This CL adds a default full-width caption implementation on Shell to act
as a stable caption implementation in freeform windowing environment,
since the desktop mode caption is not yet finalized.
Currently, this caption is used unless the desktop mode flag is turned
on.
Bug: 260276028
Test: atest WMShellUnitTests
Change-Id: I354330cfb959b453e382fed44d87ca7f0308a4e2
9 files changed, 712 insertions, 43 deletions
diff --git a/libs/WindowManager/Shell/res/drawable/caption_decor_title.xml b/libs/WindowManager/Shell/res/drawable/caption_decor_title.xml new file mode 100644 index 000000000000..6114ad6e277a --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/caption_decor_title.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> +<shape android:shape="rectangle" + android:tintMode="multiply" + android:tint="@color/decor_title_color" + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@android:color/white" /> +</shape> diff --git a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml index b7ff96e64eec..91edbf1a7bd4 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml @@ -14,10 +14,10 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" android:tint="@color/decor_button_dark_color"> <path android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/> diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decor.xml b/libs/WindowManager/Shell/res/layout/caption_window_decor.xml new file mode 100644 index 000000000000..f3d219872001 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/caption_window_decor.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> +<com.android.wm.shell.windowdecor.WindowDecorLinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="end" + android:background="@drawable/caption_decor_title"> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/back_button" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/back_button_text" + android:background="@drawable/decor_back_button_dark" + android:duplicateParentState="true"/> + <Space + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:elevation="2dp"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/minimize_window" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/minimize_button_text" + android:background="@drawable/decor_minimize_button_dark" + android:duplicateParentState="true"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/maximize_window" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/maximize_button_text" + android:background="@drawable/decor_maximize_button_dark" + android:duplicateParentState="true"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/close_window" + android:contentDescription="@string/close_button_text" + android:background="@drawable/decor_close_button_dark" + android:duplicateParentState="true"/> +</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index d3b9fa5e628d..512a4ef386bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -49,6 +49,7 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; +import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; @@ -93,6 +94,7 @@ import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition; import com.android.wm.shell.unfold.qualifier.UnfoldTransition; +import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -192,7 +194,8 @@ public abstract class WMShellModule { SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { - return new DesktopModeWindowDecorViewModel( + if (DesktopModeStatus.isAnyEnabled()) { + return new DesktopModeWindowDecorViewModel( context, mainHandler, mainChoreographer, @@ -201,6 +204,14 @@ public abstract class WMShellModule { syncQueue, desktopModeController, desktopTasksController); + } + return new CaptionWindowDecorViewModel( + context, + mainHandler, + mainChoreographer, + taskOrganizer, + displayController, + syncQueue); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java new file mode 100644 index 000000000000..1e72c565157a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2023 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.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.os.Handler; +import android.util.SparseArray; +import android.view.Choreographer; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.View; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; + +/** + * View model for the window decoration with a caption and shadows. Works with + * {@link CaptionWindowDecoration}. + */ +public class CaptionWindowDecorViewModel implements WindowDecorViewModel { + private final ShellTaskOrganizer mTaskOrganizer; + private final Context mContext; + private final Handler mMainHandler; + private final Choreographer mMainChoreographer; + private final DisplayController mDisplayController; + private final SyncTransactionQueue mSyncQueue; + private TaskOperations mTaskOperations; + + private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); + + public CaptionWindowDecorViewModel( + Context context, + Handler mainHandler, + Choreographer mainChoreographer, + ShellTaskOrganizer taskOrganizer, + DisplayController displayController, + SyncTransactionQueue syncQueue) { + mContext = context; + mMainHandler = mainHandler; + mMainChoreographer = mainChoreographer; + mTaskOrganizer = taskOrganizer; + mDisplayController = displayController; + mSyncQueue = syncQueue; + } + + @Override + public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) { + mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue); + } + + @Override + public boolean onTaskOpening( + RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + if (!shouldShowWindowDecor(taskInfo)) return false; + createWindowDecoration(taskInfo, taskSurface, startT, finishT); + return true; + } + + @Override + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); + + if (decoration == null) return; + + decoration.relayout(taskInfo); + setupCaptionColor(taskInfo, decoration); + } + + @Override + public void onTaskChanging( + RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); + + if (!shouldShowWindowDecor(taskInfo)) { + if (decoration != null) { + destroyWindowDecoration(taskInfo); + } + return; + } + + if (decoration == null) { + createWindowDecoration(taskInfo, taskSurface, startT, finishT); + } else { + decoration.relayout(taskInfo, startT, finishT); + } + } + + @Override + public void onTaskClosing( + RunningTaskInfo taskInfo, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); + if (decoration == null) return; + + decoration.relayout(taskInfo, startT, finishT); + } + + @Override + public void destroyWindowDecoration(RunningTaskInfo taskInfo) { + final CaptionWindowDecoration decoration = + mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId); + if (decoration == null) return; + + decoration.close(); + } + + private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) { + int statusBarColor = taskInfo.taskDescription.getStatusBarColor(); + decoration.setCaptionColor(statusBarColor); + } + + private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { + return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM + || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD + && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() + == WINDOWING_MODE_FREEFORM); + } + + private void createWindowDecoration( + RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); + if (oldDecoration != null) { + // close the old decoration if it exists to avoid two window decorations being added + oldDecoration.close(); + } + final CaptionWindowDecoration windowDecoration = + new CaptionWindowDecoration( + mContext, + mDisplayController, + mTaskOrganizer, + taskInfo, + taskSurface, + mMainHandler, + mMainChoreographer, + mSyncQueue); + mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); + + TaskPositioner taskPositioner = + new TaskPositioner(mTaskOrganizer, windowDecoration); + CaptionTouchEventListener touchEventListener = + new CaptionTouchEventListener(taskInfo, taskPositioner); + windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); + windowDecoration.setDragResizeCallback(taskPositioner); + windowDecoration.relayout(taskInfo, startT, finishT); + setupCaptionColor(taskInfo, windowDecoration); + } + + private class CaptionTouchEventListener implements + View.OnClickListener, View.OnTouchListener { + + private final int mTaskId; + private final WindowContainerToken mTaskToken; + private final DragResizeCallback mDragResizeCallback; + + private int mDragPointerId = -1; + + private CaptionTouchEventListener( + RunningTaskInfo taskInfo, + DragResizeCallback dragResizeCallback) { + mTaskId = taskInfo.taskId; + mTaskToken = taskInfo.token; + mDragResizeCallback = dragResizeCallback; + } + + @Override + public void onClick(View v) { + final int id = v.getId(); + if (id == R.id.close_window) { + mTaskOperations.closeTask(mTaskToken); + } else if (id == R.id.back_button) { + mTaskOperations.injectBackKey(); + } else if (id == R.id.minimize_window) { + mTaskOperations.minimizeTask(mTaskToken); + } else if (id == R.id.maximize_window) { + RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + mTaskOperations.maximizeTask(taskInfo); + } + } + + @Override + public boolean onTouch(View v, MotionEvent e) { + if (v.getId() != R.id.caption) { + return false; + } + handleEventForMove(e); + + if (e.getAction() != MotionEvent.ACTION_DOWN) { + return false; + } + RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + if (taskInfo.isFocused) { + return false; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(mTaskToken, true /* onTop */); + mSyncQueue.queue(wct); + return true; + } + + /** + * @param e {@link MotionEvent} to process + * @return {@code true} if a drag is happening; or {@code false} if it is not + */ + private void handleEventForMove(MotionEvent e) { + RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + return; + } + switch (e.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mDragPointerId = e.getPointerId(0); + mDragResizeCallback.onDragResizeStart( + 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); + break; + } + case MotionEvent.ACTION_MOVE: { + int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDragResizeCallback.onDragResizeMove( + e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDragResizeCallback.onDragResizeEnd( + e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + break; + } + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java new file mode 100644 index 000000000000..8609c6b0302a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2023 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 android.app.ActivityManager.RunningTaskInfo; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.VectorDrawable; +import android.os.Handler; +import android.view.Choreographer; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewConfiguration; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with + * {@link CaptionWindowDecorViewModel}. The caption bar contains a back button, minimize button, + * maximize button and close button. + */ +public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { + private final Handler mHandler; + private final Choreographer mChoreographer; + private final SyncTransactionQueue mSyncQueue; + + private View.OnClickListener mOnCaptionButtonClickListener; + private View.OnTouchListener mOnCaptionTouchListener; + private DragResizeCallback mDragResizeCallback; + + private DragResizeInputListener mDragResizeListener; + + private RelayoutParams mRelayoutParams = new RelayoutParams(); + private final RelayoutResult<WindowDecorLinearLayout> mResult = + new RelayoutResult<>(); + + private DragDetector mDragDetector; + + CaptionWindowDecoration( + Context context, + DisplayController displayController, + ShellTaskOrganizer taskOrganizer, + RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + Handler handler, + Choreographer choreographer, + SyncTransactionQueue syncQueue) { + super(context, displayController, taskOrganizer, taskInfo, taskSurface); + + mHandler = handler; + mChoreographer = choreographer; + mSyncQueue = syncQueue; + mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); + } + + void setCaptionListeners( + View.OnClickListener onCaptionButtonClickListener, + View.OnTouchListener onCaptionTouchListener) { + mOnCaptionButtonClickListener = onCaptionButtonClickListener; + mOnCaptionTouchListener = onCaptionTouchListener; + } + + void setDragResizeCallback(DragResizeCallback dragResizeCallback) { + mDragResizeCallback = dragResizeCallback; + } + + @Override + void relayout(RunningTaskInfo taskInfo) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + relayout(taskInfo, t, t); + mSyncQueue.runInSync(transaction -> { + transaction.merge(t); + t.close(); + }); + } + + void relayout(RunningTaskInfo taskInfo, + SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + final int shadowRadiusID = taskInfo.isFocused + ? R.dimen.freeform_decor_shadow_focused_thickness + : R.dimen.freeform_decor_shadow_unfocused_thickness; + final boolean isFreeform = + taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; + final boolean isDragResizeable = isFreeform && taskInfo.isResizeable; + + WindowDecorLinearLayout oldRootView = mResult.mRootView; + final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; + final WindowContainerTransaction wct = new WindowContainerTransaction(); + + int outsetLeftId = R.dimen.freeform_resize_handle; + int outsetTopId = R.dimen.freeform_resize_handle; + int outsetRightId = R.dimen.freeform_resize_handle; + int outsetBottomId = R.dimen.freeform_resize_handle; + + mRelayoutParams.reset(); + mRelayoutParams.mRunningTaskInfo = taskInfo; + mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; + mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; + mRelayoutParams.mShadowRadiusId = shadowRadiusID; + if (isDragResizeable) { + mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); + } + + relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); + taskInfo = null; // Clear it just in case we use it accidentally + + 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) { + setupRootView(); + } + + if (!isDragResizeable) { + closeDragResizeListener(); + return; + } + + if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) { + closeDragResizeListener(); + mDragResizeListener = new DragResizeInputListener( + mContext, + mHandler, + mChoreographer, + mDisplay.getDisplayId(), + mDecorationContainerSurface, + mDragResizeCallback); + } + + int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop(); + mDragDetector.setTouchSlop(touchSlop); + + int resize_handle = mResult.mRootView.getResources() + .getDimensionPixelSize(R.dimen.freeform_resize_handle); + int resize_corner = mResult.mRootView.getResources() + .getDimensionPixelSize(R.dimen.freeform_resize_corner); + mDragResizeListener.setGeometry( + mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop); + } + + /** + * Sets up listeners when a new root view is created. + */ + private void setupRootView() { + View caption = mResult.mRootView.findViewById(R.id.caption); + caption.setOnTouchListener(mOnCaptionTouchListener); + View close = caption.findViewById(R.id.close_window); + close.setOnClickListener(mOnCaptionButtonClickListener); + View back = caption.findViewById(R.id.back_button); + back.setOnClickListener(mOnCaptionButtonClickListener); + View minimize = caption.findViewById(R.id.minimize_window); + minimize.setOnClickListener(mOnCaptionButtonClickListener); + View maximize = caption.findViewById(R.id.maximize_window); + maximize.setOnClickListener(mOnCaptionButtonClickListener); + } + + void setCaptionColor(int captionColor) { + if (mResult.mRootView == null) { + return; + } + + View caption = mResult.mRootView.findViewById(R.id.caption); + GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground(); + captionDrawable.setColor(captionColor); + + int buttonTintColorRes = + Color.valueOf(captionColor).luminance() < 0.5 + ? R.color.decor_button_light_color + : R.color.decor_button_dark_color; + ColorStateList buttonTintColor = + caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */); + + View back = caption.findViewById(R.id.back_button); + VectorDrawable backBackground = (VectorDrawable) back.getBackground(); + backBackground.setTintList(buttonTintColor); + + View minimize = caption.findViewById(R.id.minimize_window); + VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground(); + minimizeBackground.setTintList(buttonTintColor); + + View maximize = caption.findViewById(R.id.maximize_window); + VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground(); + maximizeBackground.setTintList(buttonTintColor); + + View close = caption.findViewById(R.id.close_window); + VectorDrawable closeBackground = (VectorDrawable) close.getBackground(); + closeBackground.setTintList(buttonTintColor); + } + + private void closeDragResizeListener() { + if (mDragResizeListener == null) { + return; + } + mDragResizeListener.close(); + mDragResizeListener = null; + } + + @Override + public void close() { + closeDragResizeListener(); + super.close(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 00aab6733369..13b4a956e4e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -27,17 +27,12 @@ import android.content.Context; import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; -import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; import android.view.InputChannel; -import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; @@ -55,7 +50,6 @@ import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; -import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -85,6 +79,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new SparseArray<>(); private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); private InputMonitorFactory mInputMonitorFactory; + private TaskOperations mTaskOperations; public DesktopModeWindowDecorViewModel( Context context, @@ -136,7 +131,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) { - mTransitionStarter = transitionStarter; + mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue); } @Override @@ -210,7 +205,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - private class CaptionTouchEventListener implements + private class DesktopModeTouchEventListener implements View.OnClickListener, View.OnTouchListener { private final int mTaskId; @@ -220,7 +215,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private int mDragPointerId = -1; - private CaptionTouchEventListener( + private DesktopModeTouchEventListener( RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback, DragDetector dragDetector) { @@ -235,15 +230,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); if (id == R.id.close_window) { - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.removeTask(mTaskToken); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mTransitionStarter.startRemoveTransition(wct); - } else { - mSyncQueue.queue(wct); - } + mTaskOperations.closeTask(mTaskToken); } else if (id == R.id.back_button) { - injectBackKey(); + mTaskOperations.injectBackKey(); } else if (id == R.id.caption_handle) { decoration.createHandleMenu(); } else if (id == R.id.desktop_button) { @@ -258,25 +247,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - private void injectBackKey() { - sendBackEvent(KeyEvent.ACTION_DOWN); - sendBackEvent(KeyEvent.ACTION_UP); - } - - private void sendBackEvent(int action) { - final long when = SystemClock.uptimeMillis(); - final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, - 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, - 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, - InputDevice.SOURCE_KEYBOARD); - - ev.setDisplayId(mContext.getDisplay().getDisplayId()); - if (!InputManager.getInstance() - .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { - Log.e(TAG, "Inject input event fail"); - } - } - @Override public boolean onTouch(View v, MotionEvent e) { boolean isDrag = false; @@ -590,8 +560,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener); - CaptionTouchEventListener touchEventListener = - new CaptionTouchEventListener( + DesktopModeTouchEventListener touchEventListener = + new DesktopModeTouchEventListener( taskInfo, taskPositioner, windowDecoration.getDragDetector()); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragResizeCallback(taskPositioner); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java new file mode 100644 index 000000000000..aea340464304 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 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.WINDOWING_MODE_UNDEFINED; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.SystemClock; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.transition.Transitions; + +/** + * Utility class to handle task operations performed on a window decoration. + */ +class TaskOperations { + private static final String TAG = "TaskOperations"; + + private final FreeformTaskTransitionStarter mTransitionStarter; + private final Context mContext; + private final SyncTransactionQueue mSyncQueue; + + TaskOperations(FreeformTaskTransitionStarter transitionStarter, Context context, + SyncTransactionQueue syncQueue) { + mTransitionStarter = transitionStarter; + mContext = context; + mSyncQueue = syncQueue; + } + + void injectBackKey() { + sendBackEvent(KeyEvent.ACTION_DOWN); + sendBackEvent(KeyEvent.ACTION_UP); + } + + private void sendBackEvent(int action) { + final long when = SystemClock.uptimeMillis(); + final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, + 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, + 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + + ev.setDisplayId(mContext.getDisplay().getDisplayId()); + if (!InputManager.getInstance() + .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { + Log.e(TAG, "Inject input event fail"); + } + } + + void closeTask(WindowContainerToken taskToken) { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.removeTask(taskToken); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitionStarter.startRemoveTransition(wct); + } else { + mSyncQueue.queue(wct); + } + } + + void minimizeTask(WindowContainerToken taskToken) { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(taskToken, false); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitionStarter.startMinimizedModeTransition(wct); + } else { + mSyncQueue.queue(wct); + } + } + + void maximizeTask(RunningTaskInfo taskInfo) { + WindowContainerTransaction wct = new WindowContainerTransaction(); + int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN + ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM; + int displayWindowingMode = + taskInfo.configuration.windowConfiguration.getDisplayWindowingMode(); + wct.setWindowingMode(taskInfo.token, + targetWindowingMode == displayWindowingMode + ? WINDOWING_MODE_UNDEFINED : targetWindowingMode); + if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) { + wct.setBounds(taskInfo.token, null); + } + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitionStarter.startWindowingModeTransition(targetWindowingMode, wct); + } else { + mSyncQueue.queue(wct); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java index a49a300995e6..20631f85453f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -47,6 +47,10 @@ class TaskPositioner implements DragResizeCallback { private int mCtrlType; private DragStartListener mDragStartListener; + TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) { + this(taskOrganizer, windowDecoration, dragStartListener -> {}); + } + TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, DragStartListener dragStartListener) { mTaskOrganizer = taskOrganizer; |