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:
committed by
Merissa Mitchell
parent
8486154984
commit
23a9bc9919
@@ -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>
|
||||
@@ -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"/>
|
||||
|
||||
56
libs/WindowManager/Shell/res/layout/caption_window_decor.xml
Normal file
56
libs/WindowManager/Shell/res/layout/caption_window_decor.xml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user