Merge "Add double-tap to quick resize desktop tasks" into udc-qpr-dev
This commit is contained in:
@@ -57,6 +57,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
|
||||
import com.android.wm.shell.desktopmode.DesktopTasksController;
|
||||
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
|
||||
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
|
||||
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
|
||||
import com.android.wm.shell.draganddrop.DragAndDropController;
|
||||
import com.android.wm.shell.freeform.FreeformComponents;
|
||||
import com.android.wm.shell.freeform.FreeformTaskListener;
|
||||
@@ -690,6 +691,7 @@ public abstract class WMShellModule {
|
||||
Transitions transitions,
|
||||
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
|
||||
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
|
||||
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
|
||||
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
|
||||
LaunchAdjacentController launchAdjacentController,
|
||||
@ShellMainThread ShellExecutor mainExecutor
|
||||
@@ -697,7 +699,8 @@ public abstract class WMShellModule {
|
||||
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
|
||||
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
|
||||
transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
|
||||
desktopModeTaskRepository, launchAdjacentController, mainExecutor);
|
||||
toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
|
||||
launchAdjacentController, mainExecutor);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@@ -707,6 +710,13 @@ public abstract class WMShellModule {
|
||||
return new EnterDesktopTaskTransitionHandler(transitions);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
|
||||
Transitions transitions) {
|
||||
return new ToggleResizeDesktopTaskTransitionHandler(transitions);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.graphics.Rect
|
||||
import android.graphics.Region
|
||||
import android.os.IBinder
|
||||
import android.os.SystemProperties
|
||||
import android.util.DisplayMetrics.DENSITY_DEFAULT
|
||||
import android.view.SurfaceControl
|
||||
import android.view.WindowManager.TRANSIT_CHANGE
|
||||
import android.view.WindowManager.TRANSIT_NONE
|
||||
@@ -78,6 +79,8 @@ class DesktopTasksController(
|
||||
private val transitions: Transitions,
|
||||
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
|
||||
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
|
||||
private val toggleResizeDesktopTaskTransitionHandler:
|
||||
ToggleResizeDesktopTaskTransitionHandler,
|
||||
private val desktopModeTaskRepository: DesktopModeTaskRepository,
|
||||
private val launchAdjacentController: LaunchAdjacentController,
|
||||
@ShellMainThread private val mainExecutor: ShellExecutor
|
||||
@@ -382,6 +385,49 @@ class DesktopTasksController(
|
||||
}
|
||||
}
|
||||
|
||||
/** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
|
||||
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
|
||||
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
|
||||
|
||||
val stableBounds = Rect()
|
||||
displayLayout.getStableBounds(stableBounds)
|
||||
val destinationBounds = Rect()
|
||||
if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
|
||||
// The desktop task is currently occupying the whole stable bounds, toggle to the
|
||||
// default bounds.
|
||||
getDefaultDesktopTaskBounds(
|
||||
density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
|
||||
stableBounds = stableBounds,
|
||||
outBounds = destinationBounds
|
||||
)
|
||||
} else {
|
||||
// Toggle to the stable bounds.
|
||||
destinationBounds.set(stableBounds)
|
||||
}
|
||||
|
||||
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
|
||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
|
||||
toggleResizeDesktopTaskTransitionHandler.startTransition(
|
||||
wct,
|
||||
taskInfo.taskId,
|
||||
windowDecor
|
||||
)
|
||||
} else {
|
||||
shellTaskOrganizer.applyTransaction(wct)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
|
||||
val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
|
||||
val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
|
||||
outBounds.set(0, 0, width, height)
|
||||
// Center the task in stable bounds
|
||||
outBounds.offset(
|
||||
stableBounds.centerX() - outBounds.centerX(),
|
||||
stableBounds.centerY() - outBounds.centerY()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get windowing move for a given `taskId`
|
||||
*
|
||||
@@ -883,6 +929,14 @@ class DesktopTasksController(
|
||||
SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
|
||||
private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
|
||||
|
||||
// Override default freeform task width when desktop mode is enabled. In dips.
|
||||
private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
|
||||
SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
|
||||
|
||||
// Override default freeform task height when desktop mode is enabled. In dips.
|
||||
private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
|
||||
SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
|
||||
|
||||
/**
|
||||
* Check if desktop density override is enabled
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.desktopmode
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.RectEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.Rect
|
||||
import android.os.IBinder
|
||||
import android.util.SparseArray
|
||||
import android.view.SurfaceControl
|
||||
import android.view.WindowManager.TRANSIT_CHANGE
|
||||
import android.window.TransitionInfo
|
||||
import android.window.TransitionRequestInfo
|
||||
import android.window.WindowContainerTransaction
|
||||
import androidx.core.animation.addListener
|
||||
import com.android.wm.shell.transition.Transitions
|
||||
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
|
||||
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
|
||||
import java.util.function.Supplier
|
||||
|
||||
/** Handles the animation of quick resizing of desktop tasks. */
|
||||
class ToggleResizeDesktopTaskTransitionHandler(
|
||||
private val transitions: Transitions,
|
||||
private val transactionSupplier: Supplier<SurfaceControl.Transaction>
|
||||
) : Transitions.TransitionHandler {
|
||||
|
||||
private val rectEvaluator = RectEvaluator(Rect())
|
||||
private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
|
||||
|
||||
private var boundsAnimator: Animator? = null
|
||||
|
||||
constructor(
|
||||
transitions: Transitions
|
||||
) : this(transitions, Supplier { SurfaceControl.Transaction() })
|
||||
|
||||
/** Starts a quick resize transition. */
|
||||
fun startTransition(
|
||||
wct: WindowContainerTransaction,
|
||||
taskId: Int,
|
||||
windowDecoration: DesktopModeWindowDecoration
|
||||
) {
|
||||
// Pause relayout until the transition animation finishes.
|
||||
windowDecoration.incrementRelayoutBlock()
|
||||
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
|
||||
taskToDecorationMap.put(taskId, windowDecoration)
|
||||
}
|
||||
|
||||
override fun startAnimation(
|
||||
transition: IBinder,
|
||||
info: TransitionInfo,
|
||||
startTransaction: SurfaceControl.Transaction,
|
||||
finishTransaction: SurfaceControl.Transaction,
|
||||
finishCallback: Transitions.TransitionFinishCallback
|
||||
): Boolean {
|
||||
val change = findRelevantChange(info)
|
||||
val leash = change.leash
|
||||
val taskId = change.taskInfo.taskId
|
||||
val startBounds = change.startAbsBounds
|
||||
val endBounds = change.endAbsBounds
|
||||
val windowDecor =
|
||||
taskToDecorationMap.removeReturnOld(taskId)
|
||||
?: throw IllegalStateException("Window decoration not found for task $taskId")
|
||||
|
||||
val tx = transactionSupplier.get()
|
||||
boundsAnimator?.cancel()
|
||||
boundsAnimator =
|
||||
ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds)
|
||||
.setDuration(RESIZE_DURATION_MS)
|
||||
.apply {
|
||||
addListener(
|
||||
onStart = {
|
||||
startTransaction
|
||||
.setPosition(
|
||||
leash,
|
||||
startBounds.left.toFloat(),
|
||||
startBounds.top.toFloat()
|
||||
)
|
||||
.setWindowCrop(leash, startBounds.width(), startBounds.height())
|
||||
.show(leash)
|
||||
windowDecor.showResizeVeil(startTransaction, startBounds)
|
||||
},
|
||||
onEnd = {
|
||||
finishTransaction
|
||||
.setPosition(
|
||||
leash,
|
||||
endBounds.left.toFloat(),
|
||||
endBounds.top.toFloat()
|
||||
)
|
||||
.setWindowCrop(leash, endBounds.width(), endBounds.height())
|
||||
.show(leash)
|
||||
windowDecor.hideResizeVeil()
|
||||
finishCallback.onTransitionFinished(null, null)
|
||||
boundsAnimator = null
|
||||
}
|
||||
)
|
||||
addUpdateListener { anim ->
|
||||
val rect = anim.animatedValue as Rect
|
||||
tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
|
||||
.setWindowCrop(leash, rect.width(), rect.height())
|
||||
.show(leash)
|
||||
windowDecor.updateResizeVeil(tx, rect)
|
||||
}
|
||||
start()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun handleRequest(
|
||||
transition: IBinder,
|
||||
request: TransitionRequestInfo
|
||||
): WindowContainerTransaction? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
|
||||
val matchingChanges =
|
||||
info.changes.filter { c ->
|
||||
!isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE
|
||||
}
|
||||
if (matchingChanges.size != 1) {
|
||||
throw IllegalStateException(
|
||||
"Expected 1 relevant change but found: ${matchingChanges.size}"
|
||||
)
|
||||
}
|
||||
return matchingChanges.first()
|
||||
}
|
||||
|
||||
private fun isWallpaper(change: TransitionInfo.Change): Boolean {
|
||||
return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0
|
||||
}
|
||||
|
||||
private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
|
||||
return change.taskInfo != null && change.taskInfo?.taskId != -1
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RESIZE_DURATION_MS = 300L
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,10 @@ public class Transitions implements RemoteCallable<Transitions>,
|
||||
public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
|
||||
WindowManager.TRANSIT_FIRST_CUSTOM + 13;
|
||||
|
||||
/** Transition type to animate the toggle resize between the max and default desktop sizes. */
|
||||
public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
|
||||
WindowManager.TRANSIT_FIRST_CUSTOM + 14;
|
||||
|
||||
private final WindowOrganizer mOrganizer;
|
||||
private final Context mContext;
|
||||
private final ShellExecutor mMainExecutor;
|
||||
|
||||
@@ -33,6 +33,8 @@ import android.window.TransitionInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
@@ -258,7 +260,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
|
||||
* @return {@code true} if a drag is happening; or {@code false} if it is not
|
||||
*/
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent e) {
|
||||
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
|
||||
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
|
||||
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
|
||||
return false;
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Choreographer;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.InputChannel;
|
||||
import android.view.InputEvent;
|
||||
import android.view.InputEventReceiver;
|
||||
@@ -198,7 +199,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
if (change.getMode() == WindowManager.TRANSIT_CHANGE
|
||||
&& (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
|
||||
|| info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
|
||||
|| info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE)) {
|
||||
|| info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
|
||||
|| info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
|
||||
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
|
||||
.addTransitionPausingRelayout(transition);
|
||||
}
|
||||
@@ -279,15 +281,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
private class DesktopModeTouchEventListener implements
|
||||
View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
|
||||
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
|
||||
implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
|
||||
|
||||
private final int mTaskId;
|
||||
private final WindowContainerToken mTaskToken;
|
||||
private final DragPositioningCallback mDragPositioningCallback;
|
||||
private final DragDetector mDragDetector;
|
||||
private final GestureDetector mGestureDetector;
|
||||
|
||||
private boolean mIsDragging;
|
||||
private boolean mShouldClick;
|
||||
private int mDragPointerId = -1;
|
||||
|
||||
private DesktopModeTouchEventListener(
|
||||
@@ -297,6 +301,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
mTaskToken = taskInfo.token;
|
||||
mDragPositioningCallback = dragPositioningCallback;
|
||||
mDragDetector = new DragDetector(this);
|
||||
mGestureDetector = new GestureDetector(mContext, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -357,7 +362,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
return false;
|
||||
}
|
||||
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
|
||||
return mDragDetector.onMotionEvent(e);
|
||||
return mDragDetector.onMotionEvent(v, e);
|
||||
}
|
||||
|
||||
private void moveTaskToFront(RunningTaskInfo taskInfo) {
|
||||
@@ -372,7 +377,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
* @return {@code true} if the motion event is handled.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent e) {
|
||||
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
|
||||
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
|
||||
if (DesktopModeStatus.isProto2Enabled()
|
||||
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
|
||||
@@ -383,6 +388,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
== WINDOWING_MODE_FULLSCREEN) {
|
||||
return false;
|
||||
}
|
||||
if (mGestureDetector.onTouchEvent(e)) {
|
||||
return true;
|
||||
}
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mDragPointerId = e.getPointerId(0);
|
||||
@@ -390,7 +398,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
0 /* ctrlType */, e.getRawX(0),
|
||||
e.getRawY(0));
|
||||
mIsDragging = false;
|
||||
return false;
|
||||
mShouldClick = true;
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final DesktopModeWindowDecoration decoration =
|
||||
@@ -404,10 +413,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
mDragPositioningCallback.onDragPositioningMove(
|
||||
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
|
||||
mIsDragging = true;
|
||||
mShouldClick = false;
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
final boolean wasDragging = mIsDragging;
|
||||
if (!wasDragging) {
|
||||
if (mShouldClick && v != null) {
|
||||
v.performClick();
|
||||
mShouldClick = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (e.findPointerIndex(mDragPointerId) == -1) {
|
||||
mDragPointerId = e.getPointerId(0);
|
||||
}
|
||||
@@ -422,13 +441,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
|
||||
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
|
||||
position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
|
||||
final boolean wasDragging = mIsDragging;
|
||||
mIsDragging = false;
|
||||
return wasDragging;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(@NonNull MotionEvent e) {
|
||||
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
|
||||
mDesktopTasksController.ifPresent(c -> {
|
||||
c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// InputEventReceiver to listen for touch input outside of caption bounds
|
||||
@@ -813,6 +840,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
mMainChoreographer,
|
||||
mSyncQueue);
|
||||
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
|
||||
windowDecoration.createResizeVeil();
|
||||
|
||||
final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
|
||||
windowDecoration, taskInfo);
|
||||
@@ -843,7 +871,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
|
||||
mTransactionFactory);
|
||||
} else {
|
||||
windowDecoration.createResizeVeil();
|
||||
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
|
||||
mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
|
||||
mTransitions);
|
||||
|
||||
@@ -294,23 +294,37 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade in the resize veil
|
||||
* Show the resize veil.
|
||||
*/
|
||||
void showResizeVeil(Rect taskBounds) {
|
||||
public void showResizeVeil(Rect taskBounds) {
|
||||
mResizeVeil.showVeil(mTaskSurface, taskBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the resize veil.
|
||||
*/
|
||||
public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
|
||||
mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new bounds for the resize veil
|
||||
*/
|
||||
void updateResizeVeil(Rect newBounds) {
|
||||
public void updateResizeVeil(Rect newBounds) {
|
||||
mResizeVeil.updateResizeVeil(newBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new bounds for the resize veil
|
||||
*/
|
||||
public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
|
||||
mResizeVeil.updateResizeVeil(tx, newBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade the resize veil out.
|
||||
*/
|
||||
void hideResizeVeil() {
|
||||
public void hideResizeVeil() {
|
||||
mResizeVeil.hideVeil();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
|
||||
@@ -54,14 +57,24 @@ class DragDetector {
|
||||
*
|
||||
* @return the result returned by {@link #mEventHandler}, or the result when
|
||||
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
|
||||
*/
|
||||
*/
|
||||
boolean onMotionEvent(MotionEvent ev) {
|
||||
return onMotionEvent(null /* view */, ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* The receiver of the {@link MotionEvent} flow.
|
||||
*
|
||||
* @return the result returned by {@link #mEventHandler}, or the result when
|
||||
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
|
||||
*/
|
||||
boolean onMotionEvent(View v, MotionEvent ev) {
|
||||
final boolean isTouchScreen =
|
||||
(ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
|
||||
if (!isTouchScreen) {
|
||||
// Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
|
||||
// to take the slop threshold into consideration.
|
||||
return mEventHandler.handleMotionEvent(ev);
|
||||
return mEventHandler.handleMotionEvent(v, ev);
|
||||
}
|
||||
switch (ev.getActionMasked()) {
|
||||
case ACTION_DOWN: {
|
||||
@@ -69,7 +82,7 @@ class DragDetector {
|
||||
float rawX = ev.getRawX(0);
|
||||
float rawY = ev.getRawY(0);
|
||||
mInputDownPoint.set(rawX, rawY);
|
||||
mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
|
||||
mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev);
|
||||
return mResultOfDownAction;
|
||||
}
|
||||
case ACTION_MOVE: {
|
||||
@@ -87,7 +100,7 @@ class DragDetector {
|
||||
// The event handler should only be notified about 'move' events if a drag has been
|
||||
// detected.
|
||||
if (mIsDragEvent) {
|
||||
return mEventHandler.handleMotionEvent(ev);
|
||||
return mEventHandler.handleMotionEvent(v, ev);
|
||||
} else {
|
||||
return mResultOfDownAction;
|
||||
}
|
||||
@@ -95,10 +108,10 @@ class DragDetector {
|
||||
case ACTION_UP:
|
||||
case ACTION_CANCEL: {
|
||||
resetState();
|
||||
return mEventHandler.handleMotionEvent(ev);
|
||||
return mEventHandler.handleMotionEvent(v, ev);
|
||||
}
|
||||
default:
|
||||
return mEventHandler.handleMotionEvent(ev);
|
||||
return mEventHandler.handleMotionEvent(v, ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +127,6 @@ class DragDetector {
|
||||
}
|
||||
|
||||
interface MotionEventHandler {
|
||||
boolean handleMotionEvent(MotionEvent ev);
|
||||
boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import android.view.InputEventReceiver;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
@@ -303,7 +304,7 @@ class DragResizeInputListener implements AutoCloseable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent e) {
|
||||
public boolean handleMotionEvent(View v, MotionEvent e) {
|
||||
boolean result = false;
|
||||
// Check if this is a touch event vs mouse event.
|
||||
// Touch events are tracked in four corners. Other events are tracked in resize edges.
|
||||
|
||||
@@ -102,11 +102,17 @@ public class ResizeVeil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate veil's alpha to 1, fading it in.
|
||||
* Shows the veil surface/view.
|
||||
*
|
||||
* @param t the transaction to apply in sync with the veil draw
|
||||
* @param parentSurface the surface that the veil should be a child of
|
||||
* @param taskBounds the bounds of the task that owns the veil
|
||||
* @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
|
||||
* immediately
|
||||
*/
|
||||
public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
|
||||
public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
|
||||
Rect taskBounds, boolean fadeIn) {
|
||||
// Parent surface can change, ensure it is up to date.
|
||||
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
|
||||
if (!parentSurface.equals(mParentSurface)) {
|
||||
t.reparent(mVeilSurface, parentSurface);
|
||||
mParentSurface = parentSurface;
|
||||
@@ -115,21 +121,35 @@ public class ResizeVeil {
|
||||
int backgroundColorId = getBackgroundColorId();
|
||||
mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
|
||||
|
||||
final ValueAnimator animator = new ValueAnimator();
|
||||
animator.setFloatValues(0f, 1f);
|
||||
animator.setDuration(RESIZE_ALPHA_DURATION);
|
||||
animator.addUpdateListener(animation -> {
|
||||
t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
|
||||
t.apply();
|
||||
});
|
||||
|
||||
relayout(taskBounds, t);
|
||||
t.show(mVeilSurface)
|
||||
.addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start())
|
||||
.setAlpha(mVeilSurface, 0);
|
||||
if (fadeIn) {
|
||||
final ValueAnimator animator = new ValueAnimator();
|
||||
animator.setFloatValues(0f, 1f);
|
||||
animator.setDuration(RESIZE_ALPHA_DURATION);
|
||||
animator.addUpdateListener(animation -> {
|
||||
t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
|
||||
t.apply();
|
||||
});
|
||||
|
||||
t.show(mVeilSurface)
|
||||
.addTransactionCommittedListener(
|
||||
mContext.getMainExecutor(), () -> animator.start())
|
||||
.setAlpha(mVeilSurface, 0);
|
||||
} else {
|
||||
// Show the veil immediately at full opacity.
|
||||
t.show(mVeilSurface).setAlpha(mVeilSurface, 1);
|
||||
}
|
||||
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate veil's alpha to 1, fading it in.
|
||||
*/
|
||||
public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
|
||||
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
|
||||
showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update veil bounds to match bounds changes.
|
||||
* @param newBounds bounds to update veil to.
|
||||
@@ -147,6 +167,16 @@ public class ResizeVeil {
|
||||
*/
|
||||
public void updateResizeVeil(Rect newBounds) {
|
||||
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
|
||||
updateResizeVeil(t, newBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls relayout to update task and veil bounds.
|
||||
*
|
||||
* @param t a transaction to be applied in sync with the veil draw.
|
||||
* @param newBounds bounds to update veil to.
|
||||
*/
|
||||
public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
|
||||
relayout(newBounds, t);
|
||||
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
@Mock lateinit var transitions: Transitions
|
||||
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
|
||||
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
|
||||
@Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
|
||||
ToggleResizeDesktopTaskTransitionHandler
|
||||
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
|
||||
|
||||
private lateinit var mockitoSession: StaticMockitoSession
|
||||
@@ -129,6 +131,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
transitions,
|
||||
enterDesktopTransitionHandler,
|
||||
exitDesktopTransitionHandler,
|
||||
mToggleResizeDesktopTaskTransitionHandler,
|
||||
desktopModeTaskRepository,
|
||||
launchAdjacentController,
|
||||
shellExecutor
|
||||
|
||||
@@ -55,7 +55,7 @@ class DragDetectorTest {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
|
||||
`when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
|
||||
|
||||
dragDetector = DragDetector(eventHandler)
|
||||
dragDetector.setTouchSlop(SLOP)
|
||||
@@ -72,13 +72,13 @@ class DragDetectorTest {
|
||||
@Test
|
||||
fun testNoMove_passesDownAndUp() {
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
@@ -86,12 +86,12 @@ class DragDetectorTest {
|
||||
|
||||
@Test
|
||||
fun testMoveInSlop_touch_passesDownAndUp() {
|
||||
`when`(eventHandler.handleMotionEvent(argThat {
|
||||
`when`(eventHandler.handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_DOWN
|
||||
})).thenReturn(false)
|
||||
|
||||
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
@@ -99,12 +99,12 @@ class DragDetectorTest {
|
||||
val newX = X + SLOP - 1
|
||||
assertFalse(
|
||||
dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
|
||||
verify(eventHandler, never()).handleMotionEvent(argThat {
|
||||
verify(eventHandler, never()).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_MOVE
|
||||
})
|
||||
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
@@ -112,13 +112,13 @@ class DragDetectorTest {
|
||||
|
||||
@Test
|
||||
fun testMoveInSlop_mouse_passesDownMoveAndUp() {
|
||||
`when`(eventHandler.handleMotionEvent(argThat {
|
||||
`when`(eventHandler.handleMotionEvent(any(), argThat {
|
||||
it.action == MotionEvent.ACTION_DOWN
|
||||
})).thenReturn(false)
|
||||
|
||||
assertFalse(dragDetector.onMotionEvent(
|
||||
createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_MOUSE
|
||||
})
|
||||
@@ -126,14 +126,14 @@ class DragDetectorTest {
|
||||
val newX = X + SLOP - 1
|
||||
assertTrue(dragDetector.onMotionEvent(
|
||||
createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_MOUSE
|
||||
})
|
||||
|
||||
assertTrue(dragDetector.onMotionEvent(
|
||||
createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_MOUSE
|
||||
})
|
||||
@@ -141,25 +141,25 @@ class DragDetectorTest {
|
||||
|
||||
@Test
|
||||
fun testMoveBeyondSlop_passesDownMoveAndUp() {
|
||||
`when`(eventHandler.handleMotionEvent(argThat {
|
||||
`when`(eventHandler.handleMotionEvent(any(), argThat {
|
||||
it.action == MotionEvent.ACTION_DOWN
|
||||
})).thenReturn(false)
|
||||
|
||||
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
|
||||
val newX = X + SLOP + 1
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
|
||||
it.source == InputDevice.SOURCE_TOUCHSCREEN
|
||||
})
|
||||
@@ -167,12 +167,12 @@ class DragDetectorTest {
|
||||
|
||||
@Test
|
||||
fun testPassesHoverEnter() {
|
||||
`when`(eventHandler.handleMotionEvent(argThat {
|
||||
`when`(eventHandler.handleMotionEvent(any(), argThat {
|
||||
it.action == MotionEvent.ACTION_HOVER_ENTER
|
||||
})).thenReturn(false)
|
||||
|
||||
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
|
||||
})
|
||||
}
|
||||
@@ -180,7 +180,7 @@ class DragDetectorTest {
|
||||
@Test
|
||||
fun testPassesHoverMove() {
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
|
||||
})
|
||||
}
|
||||
@@ -188,7 +188,7 @@ class DragDetectorTest {
|
||||
@Test
|
||||
fun testPassesHoverExit() {
|
||||
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
|
||||
verify(eventHandler).handleMotionEvent(argThat {
|
||||
verify(eventHandler).handleMotionEvent(any(), argThat {
|
||||
return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user