From 16e165defe265d7cdc1ef5852393f586319eccce Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 24 Jul 2020 16:44:03 -0700 Subject: [PATCH] Fixing touches getting ignored just after swipe-up Moving the input proxy logic outside the recents controller, so that it is not lied to the controller lifecycle. > Fixing input consumer not getting registered if recentsController was not received until ACTION_UP > Fixing input events being ignored after finishing recentsAnimation, but before handler is invalidated Bug: 161750900 Change-Id: Ib06617caef77f18a71c5a231e781291c3a4ee57e (cherry picked from commit ff4b142789e8fa66d1b64a78e99c924d9c484867) --- .../android/quickstep/BaseSwipeUpHandler.java | 6 +- .../quickstep/BaseSwipeUpHandlerV2.java | 7 +- .../quickstep/util/InputConsumerProxy.java | 118 ++++++++++++++++++ .../quickstep/RecentsAnimationController.java | 103 +-------------- 4 files changed, 127 insertions(+), 107 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index a63f3a802f..aba5ab63e1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -39,6 +39,7 @@ import com.android.launcher3.util.WindowBounds; import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.InputConsumerProxy; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TransformParams; @@ -61,7 +62,7 @@ public abstract class BaseSwipeUpHandler, Q extend private static final String TAG = "BaseSwipeUpHandler"; protected final BaseActivityInterface mActivityInterface; - protected final InputConsumerController mInputConsumer; + protected final InputConsumerProxy mInputConsumerProxy; protected final ActivityInitListener mActivityInitListener; @@ -87,7 +88,8 @@ public abstract class BaseSwipeUpHandler, Q extend super(context, deviceState, gestureState, new TransformParams()); mActivityInterface = gestureState.getActivityInterface(); mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); - mInputConsumer = inputConsumer; + mInputConsumerProxy = + new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java index 6c4c5d3f4d..f0f3d0f1e3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java @@ -850,11 +850,9 @@ public abstract class BaseSwipeUpHandlerV2, Q exte } } - if (endTarget.isLauncher && mRecentsAnimationController != null) { - mRecentsAnimationController.enableInputProxy(mInputConsumer, - this::createNewInputProxyHandler); + if (endTarget.isLauncher) { + mInputConsumerProxy.enable(); } - if (endTarget == HOME) { setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); duration = Math.max(MIN_OVERSHOOT_DURATION, duration); @@ -1181,6 +1179,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte } private void invalidateHandler() { + mInputConsumerProxy.destroy(); endRunningWindowAnim(false /* cancel */); if (mGestureEndCallback != null) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java new file mode 100644 index 0000000000..3e87f48f02 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/InputConsumerProxy.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 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.util; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_UP; + +import android.util.Log; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import com.android.quickstep.InputConsumer; +import com.android.systemui.shared.system.InputConsumerController; + +import java.util.function.Supplier; + +/** + * Utility class which manages proxying input events from {@link InputConsumerController} + * to an {@link InputConsumer} + */ +public class InputConsumerProxy { + + private static final String TAG = "InputConsumerProxy"; + + private final InputConsumerController mInputConsumerController; + private final Supplier mConsumerSupplier; + + // The consumer is created lazily on demand. + private InputConsumer mInputConsumer; + + private boolean mDestroyed = false; + private boolean mTouchInProgress = false; + private boolean mDestroyPending = false; + + public InputConsumerProxy(InputConsumerController inputConsumerController, + Supplier consumerSupplier) { + mInputConsumerController = inputConsumerController; + mConsumerSupplier = consumerSupplier; + } + + public void enable() { + if (mDestroyed) { + return; + } + 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 = mConsumerSupplier.get(); + } + mInputConsumer.onKeyEvent((KeyEvent) ev); + return true; + } + return false; + } + + private boolean onInputConsumerMotionEvent(MotionEvent ev) { + int action = ev.getAction(); + + // Just to be safe, verify that ACTION_DOWN comes before any other action, + // and ignore any ACTION_DOWN after the first one (though that should not happen). + if (!mTouchInProgress && action != ACTION_DOWN) { + Log.w(TAG, "Received non-down motion before down motion: " + action); + return false; + } + if (mTouchInProgress && action == ACTION_DOWN) { + Log.w(TAG, "Received down motion while touch was already in progress"); + return false; + } + + if (action == ACTION_DOWN) { + mTouchInProgress = true; + if (mInputConsumer == null) { + mInputConsumer = mConsumerSupplier.get(); + } + } else if (action == ACTION_CANCEL || action == ACTION_UP) { + // Finish any pending actions + mTouchInProgress = false; + if (mDestroyPending) { + destroy(); + } + } + if (mInputConsumer != null) { + mInputConsumer.onMotionEvent(ev); + } + + return true; + } + + public void destroy() { + if (mTouchInProgress) { + mDestroyPending = true; + return; + } + mDestroyPending = false; + mDestroyed = true; + mInputConsumerController.setInputListener(null); + } +} diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index 4e9aa6135e..51f5e5d785 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -15,49 +15,30 @@ */ 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.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import android.os.SystemClock; -import android.util.Log; -import android.view.InputEvent; -import android.view.KeyEvent; -import android.view.MotionEvent; - import androidx.annotation.NonNull; import androidx.annotation.UiThread; import com.android.launcher3.util.Preconditions; import com.android.systemui.shared.recents.model.ThumbnailData; -import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.function.Consumer; -import java.util.function.Supplier; /** * Wrapper around RecentsAnimationControllerCompat to help with some synchronization */ public class RecentsAnimationController { - private static final String TAG = "RecentsAnimationController"; - private final RecentsAnimationControllerCompat mController; private final Consumer mOnFinishedListener; private final boolean mAllowMinimizeSplitScreen; - private InputConsumerController mInputConsumerController; - private Supplier mInputProxySupplier; - private InputConsumer mInputConsumer; private boolean mUseLauncherSysBarFlags = false; private boolean mSplitScreenMinimized = false; - private boolean mTouchInProgress; - private boolean mDisableInputProxyPending; public RecentsAnimationController(RecentsAnimationControllerCompat controller, boolean allowMinimizeSplitScreen, @@ -136,12 +117,12 @@ public class RecentsAnimationController { @UiThread public void finishAnimationToHome() { - finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */); + finishController(true /* toRecents */, null, false /* sendUserLeaveHint */); } @UiThread public void finishAnimationToApp() { - finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */); + finishController(false /* toRecents */, null, false /* sendUserLeaveHint */); } /** See {@link #finish(boolean, Runnable, boolean)} */ @@ -160,18 +141,6 @@ public class RecentsAnimationController { @UiThread public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) { Preconditions.assertUIThread(); - if (toRecents && mTouchInProgress) { - // Finish the controller as requested, but don't disable input proxy yet. - mDisableInputProxyPending = true; - finishController(toRecents, onFinishComplete, sendUserLeaveHint); - } else { - finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint); - } - } - - private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete, - boolean sendUserLeaveHint) { - disableInputProxy(); finishController(toRecents, onFinishComplete, sendUserLeaveHint); } @@ -179,7 +148,6 @@ public class RecentsAnimationController { public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) { mOnFinishedListener.accept(this); UI_HELPER_EXECUTOR.execute(() -> { - mController.setInputConsumerEnabled(false); mController.finish(toRecents, sendUserLeaveHint); if (callback != null) { MAIN_EXECUTOR.execute(callback); @@ -197,75 +165,8 @@ public class RecentsAnimationController { }); } - public void enableInputProxy(InputConsumerController inputConsumerController, - Supplier inputProxySupplier) { - mInputProxySupplier = inputProxySupplier; - mInputConsumerController = inputConsumerController; - mInputConsumerController.setInputListener(this::onInputConsumerEvent); - } - /** @return wrapper controller. */ public RecentsAnimationControllerCompat getController() { return mController; } - - private void disableInputProxy() { - if (mInputConsumer != null && mTouchInProgress) { - long now = SystemClock.uptimeMillis(); - MotionEvent dummyCancel = MotionEvent.obtain(now, now, ACTION_CANCEL, 0, 0, 0); - mInputConsumer.onMotionEvent(dummyCancel); - dummyCancel.recycle(); - } - if (mInputConsumerController != null) { - mInputConsumerController.setInputListener(null); - } - mInputProxySupplier = null; - } - - 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(); - - // Just to be safe, verify that ACTION_DOWN comes before any other action, - // and ignore any ACTION_DOWN after the first one (though that should not happen). - if (!mTouchInProgress && action != ACTION_DOWN) { - Log.w(TAG, "Received non-down motion before down motion: " + action); - return false; - } - if (mTouchInProgress && action == ACTION_DOWN) { - Log.w(TAG, "Received down motion while touch was already in progress"); - return false; - } - - 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 (mDisableInputProxyPending) { - mDisableInputProxyPending = false; - disableInputProxy(); - } - } - if (mInputConsumer != null) { - mInputConsumer.onMotionEvent(ev); - } - - return true; - } }