blob: 9fd57d7e12013f634b6f6abdd3bfc1b417bb4302 [file] [log] [blame]
/*
* Copyright (C) 2022 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 static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
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.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
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.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
import java.util.Optional;
import java.util.function.Supplier;
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link DesktopModeWindowDecoration}.
*/
public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "DesktopModeWindowDecorViewModel";
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
private final ActivityTaskManager mActivityTaskManager;
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 final Optional<DesktopModeController> mDesktopModeController;
private final Optional<DesktopTasksController> mDesktopTasksController;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
private final TaskCornersListener mCornersListener = new TaskCornersListenerImpl();
private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
private final Transitions mTransitions;
private Optional<SplitScreenController> mSplitScreenController;
private ValueAnimator mDragToDesktopValueAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
private boolean mDragToDesktopAnimationStarted;
public DesktopModeWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController) {
this(
context,
mainHandler,
mainChoreographer,
taskOrganizer,
displayController,
syncQueue,
transitions,
desktopModeController,
desktopTasksController,
splitScreenController,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new);
}
@VisibleForTesting
DesktopModeWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSplitScreenController = splitScreenController;
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
}
@Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
@Override
public boolean onTaskOpening(
ActivityManager.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 onTransitionReady(
@NonNull IBinder transition,
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
&& (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition);
}
}
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
decor.mergeTransitionPausingRelayout(merged, playing);
}
}
@Override
public void onTransitionFinished(@NonNull IBinder transition) {
for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
decor.removeTransitionPausingRelayout(transition);
}
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
if (taskInfo.displayId != oldTaskInfo.displayId) {
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
decoration.relayout(taskInfo);
}
@Override
public void onTaskChanging(
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final DesktopModeWindowDecoration 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, false /* applyStartTransactionOnDraw */);
}
}
@Override
public void onTaskClosing(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
}
@Override
public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
if (decoration == null) return;
decoration.close();
final int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) {
removeTaskFromEventReceiver(displayId);
}
}
private class DesktopModeTouchEventListener implements
View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
private boolean mIsDragging;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
DragPositioningCallback dragPositioningCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
}
@Override
public void onClick(View v) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window || id == R.id.close_button) {
mTaskOperations.closeTask(mTaskToken);
if (mSplitScreenController.isPresent()
&& mSplitScreenController.get().isSplitScreenVisible()) {
int remainingTaskPosition = mTaskId == mSplitScreenController.get()
.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
.getTaskInfo(remainingTaskPosition);
mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
if (!decoration.isHandleMenuActive()) {
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
decoration.createHandleMenu();
} else {
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
// TODO(b/278084491): dev option to enable display switching
// remove when select is implemented
mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
decoration.closeHandleMenu();
}
}
}
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
&& id != R.id.open_menu_button && id != R.id.close_window) {
return false;
}
moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
return mDragDetector.onMotionEvent(e);
}
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo));
}
}
/**
* @param e {@link MotionEvent} to process
* @return {@code true} if the motion event is handled.
*/
@Override
public boolean handleMotionEvent(MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
}
if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
&& mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) {
return false;
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
mIsDragging = false;
return false;
}
case MotionEvent.ACTION_MOVE: {
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mIsDragging = true;
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
// Position of the task is calculated by subtracting the raw location of the
// motion event (the location of the motion relative to the display) by the
// location of the motion event relative to the task's bounds
final Point position = new Point(
(int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)),
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
position));
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
}
}
return true;
}
}
// InputEventReceiver to listen for touch input outside of caption bounds
class EventReceiver extends InputEventReceiver {
private InputMonitor mInputMonitor;
private int mTasksOnDisplay;
EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
super(channel, looper);
mInputMonitor = inputMonitor;
mTasksOnDisplay = 1;
}
@Override
public void onInputEvent(InputEvent event) {
boolean handled = false;
if (event instanceof MotionEvent) {
handled = true;
DesktopModeWindowDecorViewModel.this
.handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
}
finishInputEvent(event, handled);
}
@Override
public void dispose() {
if (mInputMonitor != null) {
mInputMonitor.dispose();
mInputMonitor = null;
}
super.dispose();
}
private void incrementTaskNumber() {
mTasksOnDisplay++;
}
private void decrementTaskNumber() {
mTasksOnDisplay--;
}
private int getTasksOnDisplay() {
return mTasksOnDisplay;
}
}
/**
* Check if an EventReceiver exists on a particular display.
* If it does, increment its task count. Otherwise, create one for that display.
* @param displayId the display to check against
*/
private void incrementEventReceiverTasks(int displayId) {
if (mEventReceiversByDisplay.contains(displayId)) {
final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
eventReceiver.incrementTaskNumber();
} else {
createInputChannel(displayId);
}
}
// If all tasks on this display are gone, we don't need to monitor its input.
private void removeTaskFromEventReceiver(int displayId) {
if (!mEventReceiversByDisplay.contains(displayId)) return;
final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
if (eventReceiver == null) return;
eventReceiver.decrementTaskNumber();
if (eventReceiver.getTasksOnDisplay() == 0) {
disposeInputChannel(displayId);
}
}
/**
* Handle MotionEvents relevant to focused task's caption that don't directly touch it
*
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
if (DesktopModeStatus.isProto2Enabled()) {
if (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
|| mTransitionDragActive) {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
handleEventOutsideFocusedCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
if (DesktopModeStatus.isProto2Enabled()) {
if (mTransitionDragActive) {
inputMonitor.pilferPointers();
}
} else if (DesktopModeStatus.isProto1Enabled()) {
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
inputMonitor.pilferPointers();
}
}
}
// If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
private void handleEventOutsideFocusedCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (relevantDecor == null) {
return;
}
if (!mTransitionDragActive) {
relevantDecor.closeHandleMenuIfNeeded(ev);
}
}
}
/**
* Perform caption actions if not able to through normal means.
* Turn on desktop mode if handle is dragged below status bar.
*/
private void handleCaptionThroughStatusBar(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
// Begin drag through status bar if applicable.
if (relevantDecor != null) {
mDragToDesktopAnimationStartBounds.set(
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
if (DesktopModeStatus.isProto2Enabled()) {
// In proto2 any full screen task can be dragged to freeform
dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN;
}
if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
mTransitionDragActive = true;
}
}
break;
}
case MotionEvent.ACTION_UP: {
if (relevantDecor == null) {
mDragToDesktopAnimationStarted = false;
mTransitionDragActive = false;
return;
}
if (mTransitionDragActive) {
mTransitionDragActive = false;
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > 2 * statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
animateToDesktop(relevantDecor, ev);
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
mDragToDesktopAnimationStarted = false;
return;
} else if (mDragToDesktopAnimationStarted) {
Point position = new Point((int) ev.getX(), (int) ev.getY());
mDesktopTasksController.ifPresent(
c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
position));
mDragToDesktopAnimationStarted = false;
return;
}
}
relevantDecor.checkClickEvent(ev);
break;
}
case MotionEvent.ACTION_MOVE: {
if (relevantDecor == null) {
return;
}
if (mTransitionDragActive) {
mDesktopTasksController.ifPresent(
c -> c.onDragPositioningMoveThroughStatusBar(
relevantDecor.mTaskInfo,
relevantDecor.mTaskSurface, ev.getY()));
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
if (!mDragToDesktopAnimationStarted) {
mDragToDesktopAnimationStarted = true;
mDesktopTasksController.ifPresent(
c -> c.moveToFreeform(relevantDecor.mTaskInfo,
mDragToDesktopAnimationStartBounds));
startAnimation(relevantDecor);
}
}
if (mDragToDesktopAnimationStarted) {
Transaction t = mTransactionFactory.get();
float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
* mDragToDesktopAnimationStartBounds.width();
float x = ev.getX() - (width / 2);
t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
t.apply();
}
}
break;
}
case MotionEvent.ACTION_CANCEL: {
mTransitionDragActive = false;
mDragToDesktopAnimationStarted = false;
}
}
}
/**
* Gets bounds of a scaled window centered relative to the screen bounds
* @param scale the amount to scale to relative to the Screen Bounds
*/
private Rect calculateFreeformBounds(int displayId, float scale) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
final int screenWidth = displayLayout.width();
final int screenHeight = displayLayout.height();
final float adjustmentPercentage = (1f - scale) / 2;
final Rect endBounds = new Rect((int) (screenWidth * adjustmentPercentage),
(int) (screenHeight * adjustmentPercentage),
(int) (screenWidth * (adjustmentPercentage + scale)),
(int) (screenHeight * (adjustmentPercentage + scale)));
return endBounds;
}
/**
* Blocks relayout until transition is finished and transitions to Desktop
*/
private void animateToDesktop(DesktopModeWindowDecoration relevantDecor,
MotionEvent ev) {
relevantDecor.incrementRelayoutBlock();
centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
}
/**
* Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
* @param relevantDecor the window decor of the task to be animated
* @param ev the motion event that triggers the animation
*/
private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
MotionEvent ev) {
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(FREEFORM_ANIMATION_DURATION);
final SurfaceControl sc = relevantDecor.mTaskSurface;
final Rect endBounds = calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE);
final Transaction t = mTransactionFactory.get();
final float diffX = endBounds.centerX() - ev.getX();
final float diffY = endBounds.top - ev.getY();
final float startingX = ev.getX() - DRAG_FREEFORM_SCALE
* mDragToDesktopAnimationStartBounds.width() / 2;
animator.addUpdateListener(animation -> {
final float animatorValue = (float) animation.getAnimatedValue();
final float x = startingX + diffX * animatorValue;
final float y = ev.getY() + diffY * animatorValue;
t.setPosition(sc, x, y);
t.apply();
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDesktopTasksController.ifPresent(
c -> c.onDragPositioningEndThroughStatusBar(
relevantDecor.mTaskInfo,
calculateFreeformBounds(ev.getDisplayId(), FINAL_FREEFORM_SCALE)));
}
});
animator.start();
}
private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
final Transaction t = mTransactionFactory.get();
mDragToDesktopValueAnimator.addUpdateListener(animation -> {
final float animatorValue = (float) animation.getAnimatedValue();
SurfaceControl sc = focusedDecor.mTaskSurface;
t.setScale(sc, animatorValue, animatorValue);
t.apply();
});
mDragToDesktopValueAnimator.start();
}
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController.isPresent()
&& mSplitScreenController.get().isSplitScreenVisible()) {
// We can't look at focused task here as only one task will have focus.
return getSplitScreenDecor(ev);
} else {
return getFocusedDecor();
}
}
@Nullable
private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
ActivityManager.RunningTaskInfo topOrLeftTask =
mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
ActivityManager.RunningTaskInfo bottomOrRightTask =
mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
.windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
} else if (bottomOrRightTask != null && bottomOrRightTask.getConfiguration()
.windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
Rect bottomOrRightBounds = bottomOrRightTask.getConfiguration().windowConfiguration
.getBounds();
ev.offsetLocation(-bottomOrRightBounds.left, -bottomOrRightBounds.top);
return mWindowDecorByTaskId.get(bottomOrRightTask.taskId);
} else {
return null;
}
}
@Nullable
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
for (int i = 0; i < size; i++) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
break;
}
}
return focusedDecor;
}
private int getStatusBarHeight(int displayId) {
return mDisplayController.getDisplayLayout(displayId).stableInsets().top;
}
private void createInputChannel(int displayId) {
final InputManager inputManager = mContext.getSystemService(InputManager.class);
final InputMonitor inputMonitor =
mInputMonitorFactory.create(inputManager, mContext);
final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
}
private void disposeInputChannel(int displayId) {
final EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
if (eventReceiver != null) {
eventReceiver.dispose();
}
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
if (mSplitScreenController.isPresent()
&& mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) {
return false;
}
return DesktopModeStatus.isProto2Enabled()
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
private void createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final DesktopModeWindowDecoration 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 DesktopModeWindowDecoration windowDecoration =
mDesktopModeWindowDecorFactory.create(
mContext,
mDisplayController,
mTaskOrganizer,
taskInfo,
taskSurface,
mMainHandler,
mMainChoreographer,
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
windowDecoration, taskInfo);
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setCornersListener(mCornersListener);
windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */);
incrementEventReceiverTasks(taskInfo.displayId);
}
private DragPositioningCallback createDragPositioningCallback(
@NonNull DesktopModeWindowDecoration windowDecoration,
@NonNull RunningTaskInfo taskInfo) {
final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
getStatusBarHeight(taskInfo.displayId));
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
mTransactionFactory);
} else {
windowDecoration.createResizeVeil();
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
mTransitions);
}
}
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
public void onDragStart(int taskId) {
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
}
}
static class InputMonitorFactory {
InputMonitor create(InputManager inputManager, Context context) {
return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
}
}
private class TaskCornersListenerImpl
implements DesktopModeWindowDecoration.TaskCornersListener {
@Override
public void onTaskCornersChanged(int taskId, Region corner) {
mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
}
@Override
public void onTaskCornersRemoved(int taskId) {
mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId));
mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
}
}
}