1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep; 17 18 import static android.view.MotionEvent.ACTION_CANCEL; 19 import static android.view.MotionEvent.ACTION_DOWN; 20 import static android.view.MotionEvent.ACTION_UP; 21 22 import static com.android.launcher3.Utilities.FLAG_NO_GESTURES; 23 24 import android.view.InputEvent; 25 import android.view.KeyEvent; 26 import android.view.MotionEvent; 27 28 import com.android.launcher3.util.Preconditions; 29 import com.android.quickstep.inputconsumers.InputConsumer; 30 import com.android.quickstep.util.SwipeAnimationTargetSet; 31 import com.android.systemui.shared.system.InputConsumerController; 32 33 import java.util.ArrayList; 34 import java.util.function.Supplier; 35 36 import androidx.annotation.UiThread; 37 38 /** 39 * Wrapper around RecentsAnimationController to help with some synchronization 40 */ 41 public class RecentsAnimationWrapper { 42 43 // A list of callbacks to run when we receive the recents animation target. There are different 44 // than the state callbacks as these run on the current worker thread. 45 private final ArrayList<Runnable> mCallbacks = new ArrayList<>(); 46 47 public SwipeAnimationTargetSet targetSet; 48 49 private boolean mWindowThresholdCrossed = false; 50 51 private final InputConsumerController mInputConsumerController; 52 private final Supplier<InputConsumer> mInputProxySupplier; 53 54 private InputConsumer mInputConsumer; 55 private boolean mTouchInProgress; 56 57 private boolean mFinishPending; 58 RecentsAnimationWrapper(InputConsumerController inputConsumerController, Supplier<InputConsumer> inputProxySupplier)59 public RecentsAnimationWrapper(InputConsumerController inputConsumerController, 60 Supplier<InputConsumer> inputProxySupplier) { 61 mInputConsumerController = inputConsumerController; 62 mInputProxySupplier = inputProxySupplier; 63 } 64 hasTargets()65 public boolean hasTargets() { 66 return targetSet != null && targetSet.hasTargets(); 67 } 68 69 @UiThread setController(SwipeAnimationTargetSet targetSet)70 public synchronized void setController(SwipeAnimationTargetSet targetSet) { 71 Preconditions.assertUIThread(); 72 this.targetSet = targetSet; 73 74 if (targetSet == null) { 75 return; 76 } 77 targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed); 78 79 if (!mCallbacks.isEmpty()) { 80 for (Runnable action : new ArrayList<>(mCallbacks)) { 81 action.run(); 82 } 83 mCallbacks.clear(); 84 } 85 } 86 runOnInit(Runnable action)87 public synchronized void runOnInit(Runnable action) { 88 if (targetSet == null) { 89 mCallbacks.add(action); 90 } else { 91 action.run(); 92 } 93 } 94 95 /** See {@link #finish(boolean, Runnable, boolean)} */ 96 @UiThread finish(boolean toRecents, Runnable onFinishComplete)97 public void finish(boolean toRecents, Runnable onFinishComplete) { 98 finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */); 99 } 100 101 /** 102 * @param onFinishComplete A callback that runs on the main thread after the animation 103 * controller has finished on the background thread. 104 * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing 105 * activity. If userLeaveHint is true, the activity will enter into 106 * picture-in-picture mode upon being paused. 107 */ 108 @UiThread finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)109 public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) { 110 Preconditions.assertUIThread(); 111 if (!toRecents) { 112 finishAndClear(false, onFinishComplete, sendUserLeaveHint); 113 } else { 114 if (mTouchInProgress) { 115 mFinishPending = true; 116 // Execute the callback 117 if (onFinishComplete != null) { 118 onFinishComplete.run(); 119 } 120 } else { 121 finishAndClear(true, onFinishComplete, sendUserLeaveHint); 122 } 123 } 124 } 125 finishAndClear(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)126 private void finishAndClear(boolean toRecents, Runnable onFinishComplete, 127 boolean sendUserLeaveHint) { 128 SwipeAnimationTargetSet controller = targetSet; 129 targetSet = null; 130 if (controller != null) { 131 controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint); 132 } 133 } 134 enableInputConsumer()135 public void enableInputConsumer() { 136 if (targetSet != null) { 137 targetSet.enableInputConsumer(); 138 } 139 } 140 141 /** 142 * Indicates that the gesture has crossed the window boundary threshold and system UI can be 143 * update the represent the window behind 144 */ setWindowThresholdCrossed(boolean windowThresholdCrossed)145 public void setWindowThresholdCrossed(boolean windowThresholdCrossed) { 146 if (mWindowThresholdCrossed != windowThresholdCrossed) { 147 mWindowThresholdCrossed = windowThresholdCrossed; 148 if (targetSet != null) { 149 targetSet.setWindowThresholdCrossed(windowThresholdCrossed); 150 } 151 } 152 } 153 enableInputProxy()154 public void enableInputProxy() { 155 mInputConsumerController.setInputListener(this::onInputConsumerEvent); 156 } 157 onInputConsumerEvent(InputEvent ev)158 private boolean onInputConsumerEvent(InputEvent ev) { 159 if (ev instanceof MotionEvent) { 160 onInputConsumerMotionEvent((MotionEvent) ev); 161 } else if (ev instanceof KeyEvent) { 162 if (mInputConsumer == null) { 163 mInputConsumer = mInputProxySupplier.get(); 164 } 165 mInputConsumer.onKeyEvent((KeyEvent) ev); 166 return true; 167 } 168 return false; 169 } 170 onInputConsumerMotionEvent(MotionEvent ev)171 private boolean onInputConsumerMotionEvent(MotionEvent ev) { 172 int action = ev.getAction(); 173 if (action == ACTION_DOWN) { 174 mTouchInProgress = true; 175 if (mInputConsumer == null) { 176 mInputConsumer = mInputProxySupplier.get(); 177 } 178 } else if (action == ACTION_CANCEL || action == ACTION_UP) { 179 // Finish any pending actions 180 mTouchInProgress = false; 181 if (mFinishPending) { 182 mFinishPending = false; 183 finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */); 184 } 185 } 186 if (mInputConsumer != null) { 187 int flags = ev.getEdgeFlags(); 188 ev.setEdgeFlags(flags | FLAG_NO_GESTURES); 189 mInputConsumer.onMotionEvent(ev); 190 ev.setEdgeFlags(flags); 191 } 192 193 return true; 194 } 195 setCancelWithDeferredScreenshot(boolean deferredWithScreenshot)196 public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) { 197 if (targetSet != null) { 198 targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot); 199 } 200 } 201 getController()202 public SwipeAnimationTargetSet getController() { 203 return targetSet; 204 } 205 } 206