Merge "Add double-tap to quick resize desktop tasks" into udc-qpr-dev am: 6b8fc86902

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23595716

Change-Id: If095852acc3d77f0e11a1ecea31d7e2644c97d3f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Jorge Gil
2023-06-09 16:40:37 +00:00
committed by Automerger Merge Worker
12 changed files with 368 additions and 56 deletions

View File

@@ -57,6 +57,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; 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.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -690,6 +691,7 @@ public abstract class WMShellModule {
Transitions transitions, Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController, LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor @ShellMainThread ShellExecutor mainExecutor
@@ -697,7 +699,8 @@ public abstract class WMShellModule {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
desktopModeTaskRepository, launchAdjacentController, mainExecutor); toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
launchAdjacentController, mainExecutor);
} }
@WMSingleton @WMSingleton
@@ -707,6 +710,13 @@ public abstract class WMShellModule {
return new EnterDesktopTaskTransitionHandler(transitions); return new EnterDesktopTaskTransitionHandler(transitions);
} }
@WMSingleton
@Provides
static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
Transitions transitions) {
return new ToggleResizeDesktopTaskTransitionHandler(transitions);
}
@WMSingleton @WMSingleton
@Provides @Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(

View File

@@ -31,6 +31,7 @@ import android.graphics.Rect
import android.graphics.Region import android.graphics.Region
import android.os.IBinder import android.os.IBinder
import android.os.SystemProperties import android.os.SystemProperties
import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.view.SurfaceControl import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_NONE
@@ -78,6 +79,8 @@ class DesktopTasksController(
private val transitions: Transitions, private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository, private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController, private val launchAdjacentController: LaunchAdjacentController,
@ShellMainThread private val mainExecutor: ShellExecutor @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` * Get windowing move for a given `taskId`
* *
@@ -883,6 +929,14 @@ class DesktopTasksController(
SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0) SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000) 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 * Check if desktop density override is enabled
*/ */

View File

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

View File

@@ -161,6 +161,10 @@ public class Transitions implements RemoteCallable<Transitions>,
public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
WindowManager.TRANSIT_FIRST_CUSTOM + 13; 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 WindowOrganizer mOrganizer;
private final Context mContext; private final Context mContext;
private final ShellExecutor mMainExecutor; private final ShellExecutor mMainExecutor;

View File

@@ -33,6 +33,8 @@ import android.window.TransitionInfo;
import android.window.WindowContainerToken; import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController; 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 * @return {@code true} if a drag is happening; or {@code false} if it is not
*/ */
@Override @Override
public boolean handleMotionEvent(MotionEvent e) { public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false; return false;

View File

@@ -43,6 +43,7 @@ import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.InputChannel; import android.view.InputChannel;
import android.view.InputEvent; import android.view.InputEvent;
import android.view.InputEventReceiver; import android.view.InputEventReceiver;
@@ -198,7 +199,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (change.getMode() == WindowManager.TRANSIT_CHANGE if (change.getMode() == WindowManager.TRANSIT_CHANGE
&& (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_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) mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition); .addTransitionPausingRelayout(transition);
} }
@@ -279,15 +281,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} }
} }
private class DesktopModeTouchEventListener implements private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId; private final int mTaskId;
private final WindowContainerToken mTaskToken; private final WindowContainerToken mTaskToken;
private final DragPositioningCallback mDragPositioningCallback; private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector; private final DragDetector mDragDetector;
private final GestureDetector mGestureDetector;
private boolean mIsDragging; private boolean mIsDragging;
private boolean mShouldClick;
private int mDragPointerId = -1; private int mDragPointerId = -1;
private DesktopModeTouchEventListener( private DesktopModeTouchEventListener(
@@ -297,6 +301,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTaskToken = taskInfo.token; mTaskToken = taskInfo.token;
mDragPositioningCallback = dragPositioningCallback; mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this); mDragDetector = new DragDetector(this);
mGestureDetector = new GestureDetector(mContext, this);
} }
@Override @Override
@@ -357,7 +362,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false; return false;
} }
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
return mDragDetector.onMotionEvent(e); return mDragDetector.onMotionEvent(v, e);
} }
private void moveTaskToFront(RunningTaskInfo taskInfo) { private void moveTaskToFront(RunningTaskInfo taskInfo) {
@@ -372,7 +377,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* @return {@code true} if the motion event is handled. * @return {@code true} if the motion event is handled.
*/ */
@Override @Override
public boolean handleMotionEvent(MotionEvent e) { public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled() if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -383,6 +388,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
== WINDOWING_MODE_FULLSCREEN) { == WINDOWING_MODE_FULLSCREEN) {
return false; return false;
} }
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
switch (e.getActionMasked()) { switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: { case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0); mDragPointerId = e.getPointerId(0);
@@ -390,7 +398,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
0 /* ctrlType */, e.getRawX(0), 0 /* ctrlType */, e.getRawX(0),
e.getRawY(0)); e.getRawY(0));
mIsDragging = false; mIsDragging = false;
return false; mShouldClick = true;
return true;
} }
case MotionEvent.ACTION_MOVE: { case MotionEvent.ACTION_MOVE: {
final DesktopModeWindowDecoration decoration = final DesktopModeWindowDecoration decoration =
@@ -404,10 +413,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPositioningCallback.onDragPositioningMove( mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mIsDragging = true; mIsDragging = true;
mShouldClick = false;
return true; return true;
} }
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: { 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) { if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0); mDragPointerId = e.getPointerId(0);
} }
@@ -422,13 +441,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId))); position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
final boolean wasDragging = mIsDragging;
mIsDragging = false; mIsDragging = false;
return wasDragging; return true;
} }
} }
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 // InputEventReceiver to listen for touch input outside of caption bounds
@@ -813,6 +840,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mMainChoreographer, mMainChoreographer,
mSyncQueue); mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
windowDecoration, taskInfo); windowDecoration, taskInfo);
@@ -843,7 +871,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDisplayController, disallowedAreaForEndBounds, mDragStartListener, mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
mTransactionFactory); mTransactionFactory);
} else { } else {
windowDecoration.createResizeVeil();
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration, return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
mDisplayController, disallowedAreaForEndBounds, mDragStartListener, mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
mTransitions); mTransitions);

View File

@@ -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); 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 * Set new bounds for the resize veil
*/ */
void updateResizeVeil(Rect newBounds) { public void updateResizeVeil(Rect newBounds) {
mResizeVeil.updateResizeVeil(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. * Fade the resize veil out.
*/ */
void hideResizeVeil() { public void hideResizeVeil() {
mResizeVeil.hideVeil(); mResizeVeil.hideVeil();
} }

View File

@@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_UP;
import android.graphics.PointF; import android.graphics.PointF;
import android.view.MotionEvent; 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 * 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 * @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 * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
*/ */
boolean onMotionEvent(MotionEvent ev) { 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 = final boolean isTouchScreen =
(ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
if (!isTouchScreen) { if (!isTouchScreen) {
// Only touches generate noisy moves, so mouse/trackpad events don't need to filtered // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
// to take the slop threshold into consideration. // to take the slop threshold into consideration.
return mEventHandler.handleMotionEvent(ev); return mEventHandler.handleMotionEvent(v, ev);
} }
switch (ev.getActionMasked()) { switch (ev.getActionMasked()) {
case ACTION_DOWN: { case ACTION_DOWN: {
@@ -69,7 +82,7 @@ class DragDetector {
float rawX = ev.getRawX(0); float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0); float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY); mInputDownPoint.set(rawX, rawY);
mResultOfDownAction = mEventHandler.handleMotionEvent(ev); mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev);
return mResultOfDownAction; return mResultOfDownAction;
} }
case ACTION_MOVE: { case ACTION_MOVE: {
@@ -87,7 +100,7 @@ class DragDetector {
// The event handler should only be notified about 'move' events if a drag has been // The event handler should only be notified about 'move' events if a drag has been
// detected. // detected.
if (mIsDragEvent) { if (mIsDragEvent) {
return mEventHandler.handleMotionEvent(ev); return mEventHandler.handleMotionEvent(v, ev);
} else { } else {
return mResultOfDownAction; return mResultOfDownAction;
} }
@@ -95,10 +108,10 @@ class DragDetector {
case ACTION_UP: case ACTION_UP:
case ACTION_CANCEL: { case ACTION_CANCEL: {
resetState(); resetState();
return mEventHandler.handleMotionEvent(ev); return mEventHandler.handleMotionEvent(v, ev);
} }
default: default:
return mEventHandler.handleMotionEvent(ev); return mEventHandler.handleMotionEvent(v, ev);
} }
} }
@@ -114,6 +127,6 @@ class DragDetector {
} }
interface MotionEventHandler { interface MotionEventHandler {
boolean handleMotionEvent(MotionEvent ev); boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
} }
} }

View File

@@ -42,6 +42,7 @@ import android.view.InputEventReceiver;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.PointerIcon; import android.view.PointerIcon;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal; import android.view.WindowManagerGlobal;
@@ -303,7 +304,7 @@ class DragResizeInputListener implements AutoCloseable {
} }
@Override @Override
public boolean handleMotionEvent(MotionEvent e) { public boolean handleMotionEvent(View v, MotionEvent e) {
boolean result = false; boolean result = false;
// Check if this is a touch event vs mouse event. // Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges. // Touch events are tracked in four corners. Other events are tracked in resize edges.

View File

@@ -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. // Parent surface can change, ensure it is up to date.
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
if (!parentSurface.equals(mParentSurface)) { if (!parentSurface.equals(mParentSurface)) {
t.reparent(mVeilSurface, parentSurface); t.reparent(mVeilSurface, parentSurface);
mParentSurface = parentSurface; mParentSurface = parentSurface;
@@ -115,21 +121,35 @@ public class ResizeVeil {
int backgroundColorId = getBackgroundColorId(); int backgroundColorId = getBackgroundColorId();
mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId)); 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); relayout(taskBounds, t);
t.show(mVeilSurface) if (fadeIn) {
.addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start()) final ValueAnimator animator = new ValueAnimator();
.setAlpha(mVeilSurface, 0); 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); 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. * Update veil bounds to match bounds changes.
* @param newBounds bounds to update veil to. * @param newBounds bounds to update veil to.
@@ -147,6 +167,16 @@ public class ResizeVeil {
*/ */
public void updateResizeVeil(Rect newBounds) { public void updateResizeVeil(Rect newBounds) {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); 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); relayout(newBounds, t);
mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
} }

View File

@@ -89,6 +89,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var transitions: Transitions @Mock lateinit var transitions: Transitions
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController @Mock lateinit var launchAdjacentController: LaunchAdjacentController
private lateinit var mockitoSession: StaticMockitoSession private lateinit var mockitoSession: StaticMockitoSession
@@ -129,6 +131,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
transitions, transitions,
enterDesktopTransitionHandler, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, exitDesktopTransitionHandler,
mToggleResizeDesktopTaskTransitionHandler,
desktopModeTaskRepository, desktopModeTaskRepository,
launchAdjacentController, launchAdjacentController,
shellExecutor shellExecutor

View File

@@ -55,7 +55,7 @@ class DragDetectorTest {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
`when`(eventHandler.handleMotionEvent(any())).thenReturn(true) `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
dragDetector = DragDetector(eventHandler) dragDetector = DragDetector(eventHandler)
dragDetector.setTouchSlop(SLOP) dragDetector.setTouchSlop(SLOP)
@@ -72,13 +72,13 @@ class DragDetectorTest {
@Test @Test
fun testNoMove_passesDownAndUp() { fun testNoMove_passesDownAndUp() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) 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 && return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP))) 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 && return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
@@ -86,12 +86,12 @@ class DragDetectorTest {
@Test @Test
fun testMoveInSlop_touch_passesDownAndUp() { fun testMoveInSlop_touch_passesDownAndUp() {
`when`(eventHandler.handleMotionEvent(argThat { `when`(eventHandler.handleMotionEvent(any(), argThat {
return@argThat it.action == MotionEvent.ACTION_DOWN return@argThat it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false) })).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) 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 && return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
@@ -99,12 +99,12 @@ class DragDetectorTest {
val newX = X + SLOP - 1 val newX = X + SLOP - 1
assertFalse( assertFalse(
dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) 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 return@argThat it.action == MotionEvent.ACTION_MOVE
}) })
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) 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 && return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
@@ -112,13 +112,13 @@ class DragDetectorTest {
@Test @Test
fun testMoveInSlop_mouse_passesDownMoveAndUp() { fun testMoveInSlop_mouse_passesDownMoveAndUp() {
`when`(eventHandler.handleMotionEvent(argThat { `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_DOWN it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false) })).thenReturn(false)
assertFalse(dragDetector.onMotionEvent( assertFalse(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) 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 && return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE it.source == InputDevice.SOURCE_MOUSE
}) })
@@ -126,14 +126,14 @@ class DragDetectorTest {
val newX = X + SLOP - 1 val newX = X + SLOP - 1
assertTrue(dragDetector.onMotionEvent( assertTrue(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) 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 && return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE it.source == InputDevice.SOURCE_MOUSE
}) })
assertTrue(dragDetector.onMotionEvent( assertTrue(dragDetector.onMotionEvent(
createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false))) 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 && return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_MOUSE it.source == InputDevice.SOURCE_MOUSE
}) })
@@ -141,25 +141,25 @@ class DragDetectorTest {
@Test @Test
fun testMoveBeyondSlop_passesDownMoveAndUp() { fun testMoveBeyondSlop_passesDownMoveAndUp() {
`when`(eventHandler.handleMotionEvent(argThat { `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_DOWN it.action == MotionEvent.ACTION_DOWN
})).thenReturn(false) })).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) 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 && return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
val newX = X + SLOP + 1 val newX = X + SLOP + 1
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) 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 && return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) 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 && return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
it.source == InputDevice.SOURCE_TOUCHSCREEN it.source == InputDevice.SOURCE_TOUCHSCREEN
}) })
@@ -167,12 +167,12 @@ class DragDetectorTest {
@Test @Test
fun testPassesHoverEnter() { fun testPassesHoverEnter() {
`when`(eventHandler.handleMotionEvent(argThat { `when`(eventHandler.handleMotionEvent(any(), argThat {
it.action == MotionEvent.ACTION_HOVER_ENTER it.action == MotionEvent.ACTION_HOVER_ENTER
})).thenReturn(false) })).thenReturn(false)
assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER))) 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 return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
}) })
} }
@@ -180,7 +180,7 @@ class DragDetectorTest {
@Test @Test
fun testPassesHoverMove() { fun testPassesHoverMove() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE))) 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 return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
}) })
} }
@@ -188,7 +188,7 @@ class DragDetectorTest {
@Test @Test
fun testPassesHoverExit() { fun testPassesHoverExit() {
assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT))) 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 return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
}) })
} }