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
This commit is contained in:
Merissa Tan
2022-12-05 17:15:34 -08:00
committed by Merissa Mitchell
parent 8486154984
commit 23a9bc9919
9 changed files with 712 additions and 43 deletions

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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>

View File

@@ -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);
}
//

View File

@@ -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;
}
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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;