blob: ddd28a35007f91c1b5f6c49f2bc094600823045c [file] [log] [blame]
/*
* Copyright (C) 2018 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.quickstep;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Utilities.FLAG_NO_GESTURES;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.systemui.shared.system.InputConsumerController;
import java.util.ArrayList;
import java.util.function.Supplier;
import androidx.annotation.UiThread;
/**
* Wrapper around RecentsAnimationController to help with some synchronization
*/
public class RecentsAnimationWrapper {
// A list of callbacks to run when we receive the recents animation target. There are different
// than the state callbacks as these run on the current worker thread.
private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
public SwipeAnimationTargetSet targetSet;
private boolean mWindowThresholdCrossed = false;
private final InputConsumerController mInputConsumerController;
private final Supplier<InputConsumer> mInputProxySupplier;
private InputConsumer mInputConsumer;
private boolean mTouchInProgress;
private boolean mFinishPending;
public RecentsAnimationWrapper(InputConsumerController inputConsumerController,
Supplier<InputConsumer> inputProxySupplier) {
mInputConsumerController = inputConsumerController;
mInputProxySupplier = inputProxySupplier;
}
public boolean hasTargets() {
return targetSet != null && targetSet.hasTargets();
}
@UiThread
public synchronized void setController(SwipeAnimationTargetSet targetSet) {
Preconditions.assertUIThread();
this.targetSet = targetSet;
if (targetSet == null) {
return;
}
targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed);
if (!mCallbacks.isEmpty()) {
for (Runnable action : new ArrayList<>(mCallbacks)) {
action.run();
}
mCallbacks.clear();
}
}
public synchronized void runOnInit(Runnable action) {
if (targetSet == null) {
mCallbacks.add(action);
} else {
action.run();
}
}
/** See {@link #finish(boolean, Runnable, boolean)} */
@UiThread
public void finish(boolean toRecents, Runnable onFinishComplete) {
finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
}
/**
* @param onFinishComplete A callback that runs on the main thread after the animation
* controller has finished on the background thread.
* @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
* activity. If userLeaveHint is true, the activity will enter into
* picture-in-picture mode upon being paused.
*/
@UiThread
public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
Preconditions.assertUIThread();
if (!toRecents) {
finishAndClear(false, onFinishComplete, sendUserLeaveHint);
} else {
if (mTouchInProgress) {
mFinishPending = true;
// Execute the callback
if (onFinishComplete != null) {
onFinishComplete.run();
}
} else {
finishAndClear(true, onFinishComplete, sendUserLeaveHint);
}
}
}
private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
boolean sendUserLeaveHint) {
SwipeAnimationTargetSet controller = targetSet;
targetSet = null;
if (controller != null) {
controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
}
}
public void enableInputConsumer() {
if (targetSet != null) {
targetSet.enableInputConsumer();
}
}
/**
* Indicates that the gesture has crossed the window boundary threshold and system UI can be
* update the represent the window behind
*/
public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
if (mWindowThresholdCrossed != windowThresholdCrossed) {
mWindowThresholdCrossed = windowThresholdCrossed;
if (targetSet != null) {
targetSet.setWindowThresholdCrossed(windowThresholdCrossed);
}
}
}
public void enableInputProxy() {
mInputConsumerController.setInputListener(this::onInputConsumerEvent);
}
private boolean onInputConsumerEvent(InputEvent ev) {
if (ev instanceof MotionEvent) {
onInputConsumerMotionEvent((MotionEvent) ev);
} else if (ev instanceof KeyEvent) {
if (mInputConsumer == null) {
mInputConsumer = mInputProxySupplier.get();
}
mInputConsumer.onKeyEvent((KeyEvent) ev);
return true;
}
return false;
}
private boolean onInputConsumerMotionEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == ACTION_DOWN) {
mTouchInProgress = true;
if (mInputConsumer == null) {
mInputConsumer = mInputProxySupplier.get();
}
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
if (mFinishPending) {
mFinishPending = false;
finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
}
}
if (mInputConsumer != null) {
int flags = ev.getEdgeFlags();
ev.setEdgeFlags(flags | FLAG_NO_GESTURES);
mInputConsumer.onMotionEvent(ev);
ev.setEdgeFlags(flags);
}
return true;
}
public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
if (targetSet != null) {
targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
}
}
public SwipeAnimationTargetSet getController() {
return targetSet;
}
}