From 64975ae9922ed4b6344eb85226c5b9d2e264e8cf Mon Sep 17 00:00:00 2001 From: helencheuk Date: Tue, 2 May 2023 14:37:28 +0100 Subject: [PATCH] Add hover state border line to overview task item by using the same approach used by keyboard focus state Reviewed TAPL DD: https://docs.google.com/document/d/1OmCLgTDw3gFOMXjEvH0W1XoAe0n2J5pPdyMcAHtflMA/edit?resourcekey=0-bXle-rOnQqOR_RJBffRybQ Fix: 249859410 Test: OverviewTaskImageTest Change-Id: Ic9ce4e9fe90f38a4bf4be6c7deed302a12a3192a --- .../QuickstepTestInformationHandler.java | 7 ++ .../quickstep/util/BorderAnimator.java | 7 ++ .../com/android/quickstep/views/TaskView.java | 114 +++++++++++++++--- .../testing/shared/TestProtocol.java | 4 + .../tapl/LauncherInstrumentation.java | 5 + .../android/launcher3/tapl/OverviewTask.java | 37 ++++++ 6 files changed, 155 insertions(+), 19 deletions(-) diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java index 031d409ece..211aeb9a56 100644 --- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -79,6 +79,13 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { return response; } + case TestProtocol.REQUEST_GET_OVERVIEW_TASK_BORDER_WIDTH: { + Resources res = mContext.getResources(); + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, + res.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width)); + return response; + } + case TestProtocol.REQUEST_HAS_TIS: { response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true); return response; diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java index 011d45c8e7..5964a30be9 100644 --- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java +++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java @@ -28,6 +28,7 @@ import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.Px; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.AnimatorListeners; @@ -175,6 +176,12 @@ public final class BorderAnimator { } } + @NonNull + @VisibleForTesting + public AnimatedFloat getBorderAnimationProgress() { + return mBorderAnimationProgress; + } + /** * Callback to update the border bounds when building this animation. */ diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index fabc3fb3b6..607d83a759 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -70,6 +70,7 @@ import android.widget.Toast; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherSettings; @@ -411,7 +412,11 @@ public class TaskView extends FrameLayout implements Reusable { private boolean mIsClickableAsLiveTile = true; - @Nullable private final BorderAnimator mBorderAnimator; + @Nullable private BorderAnimator mBorderAnimator; + + private final boolean mCursorHoverStatesEnabled; + + private final boolean mKeyboardFocusHighlightEnabled; public TaskView(Context context) { this(context, null); @@ -434,26 +439,29 @@ public class TaskView extends FrameLayout implements Reusable { mCurrentFullscreenParams = new FullscreenDrawParams(context); mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this); - boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() + mKeyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() || DesktopTaskView.DESKTOP_MODE_SUPPORTED; + mCursorHoverStatesEnabled = FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get(); + if (mCursorHoverStatesEnabled) { + setOnHoverListener(this::onHover); + } - setWillNotDraw(!keyboardFocusHighlightEnabled); + setWillNotDraw(!mKeyboardFocusHighlightEnabled && !mCursorHoverStatesEnabled); - TypedArray ta = context.obtainStyledAttributes( - attrs, R.styleable.TaskView, defStyleAttr, defStyleRes); - - mBorderAnimator = !keyboardFocusHighlightEnabled - ? null - : new BorderAnimator( - /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius, - /* borderColor= */ ta.getColor( - R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR), - /* borderAnimationParams= */ new BorderAnimator.SimpleParams( - /* borderWidthPx= */ context.getResources().getDimensionPixelSize( - R.dimen.keyboard_quick_switch_border_width), - /* boundsBuilder= */ this::updateBorderBounds, - /* targetView= */ this)); - ta.recycle(); + if (mKeyboardFocusHighlightEnabled || mCursorHoverStatesEnabled) { + TypedArray ta = context.obtainStyledAttributes( + attrs, R.styleable.TaskView, defStyleAttr, defStyleRes); + mBorderAnimator = new BorderAnimator( + /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius, + /* borderColor= */ ta.getColor( + R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR), + /* borderAnimationParams= */ new BorderAnimator.SimpleParams( + /* borderWidthPx= */ context.getResources().getDimensionPixelSize( + R.dimen.keyboard_quick_switch_border_width), + /* boundsBuilder= */ this::updateBorderBounds, + /* targetView= */ this)); + ta.recycle(); + } } protected void updateBorderBounds(Rect bounds) { @@ -496,6 +504,12 @@ public class TaskView extends FrameLayout implements Reusable { return stubInfo; } + @Nullable + @VisibleForTesting + public BorderAnimator getBorderAnimator() { + return mBorderAnimator; + } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -507,11 +521,22 @@ public class TaskView extends FrameLayout implements Reusable { @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - if (mBorderAnimator != null) { + if (mKeyboardFocusHighlightEnabled) { mBorderAnimator.buildAnimator(gainFocus).start(); } } + @Override + public boolean onInterceptHoverEvent(MotionEvent event) { + if (mCursorHoverStatesEnabled) { + // avoid triggering hover event on child elements which would cause HOVER_EXIT for this + // task view + return true; + } else { + return super.onInterceptHoverEvent(event); + } + } + @Override public void draw(Canvas canvas) { super.draw(canvas); @@ -750,6 +775,57 @@ public class TaskView extends FrameLayout implements Reusable { .log(LAUNCHER_TASK_LAUNCH_TAP); } + private boolean onHover(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_MOVE: + if (mKeyboardFocusHighlightEnabled && !isFocused()) { + // existing focus is on another task selected by keyboard, + // cursor then moves inside this task thumbnail and steals the focus + requestFocusAndExitTouchMode(v); + } + return true; + case MotionEvent.ACTION_HOVER_ENTER: + if (mKeyboardFocusHighlightEnabled) { + if (isFocused()) { + // the task is already focused with border, no action is needed + return true; + } else { + requestFocusAndExitTouchMode(v); + } + } else { + // mKeyboardFocusHighlightEnabled is turned off so it only shows hover + // state animation but not steals the focus + mBorderAnimator.buildAnimator(true).start(); + } + return true; + case MotionEvent.ACTION_HOVER_EXIT: + if (mKeyboardFocusHighlightEnabled) { + // clearFocus() does not work here because parent element is not focusable + // so it changes to touch mode to clear focus + v.getViewRootImpl().touchModeChanged(true); + } else { + // just show the disappearing animation but not change the focus when + // mKeyboardFocusHighlightEnabled is off + mBorderAnimator.buildAnimator(false).start(); + } + return true; + default: + return false; + } + } + + private void requestFocusAndExitTouchMode(View v) { + if (isInTouchMode()) { + // Tasks are not focusable in touch mode by default. As hover state would steal focus + // when both mKeyboardFocusHighlightEnabled and mCursorHoverStatesEnabled are on, + // touch mode needs to be set to false when hovering so it can steal focus to current + // task and show border animation as hover state + v.getViewRootImpl().touchModeChanged(false); + } + + requestFocus(); + } + /** * @return {@code true} if user is already in split select mode and this tap was to choose the * second app. {@code false} otherwise diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java index 788e7de0a3..b636d3bc75 100644 --- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java +++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java @@ -140,6 +140,10 @@ public final class TestProtocol { public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET = "get-grid-task-size-rect-for-tablet"; public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing"; + + public static final String REQUEST_GET_OVERVIEW_TASK_BORDER_WIDTH = + "get-overview-task-border-width"; + public static final String REQUEST_ENABLE_ROTATION = "enable_rotation"; public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion"; public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared"; diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 0aefd68550..a92b1f3835 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -375,6 +375,11 @@ public final class LauncherInstrumentation { .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); } + public int getOverviewTaskBorderWidth() { + return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_TASK_BORDER_WIDTH) + .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + int getFocusedTaskHeightForTablet() { return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt( TestProtocol.TEST_INFO_RESPONSE_FIELD); diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 39b93b4b59..1d25614cdd 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -18,7 +18,13 @@ package com.android.launcher3.tapl; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; +import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_ENTER; +import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_EXIT; + +import android.graphics.Point; import android.graphics.Rect; +import android.os.SystemClock; +import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.test.uiautomator.By; @@ -187,4 +193,35 @@ public final class OverviewTask { boolean isTaskSplit() { return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null; } + + /** + * Returns this task's visible bounds. + */ + public Rect getVisibleBounds() { + return mTask.getVisibleBounds(); + } + + /** + * Emulate the cursor entering and exiting a hover over this task. + */ + public void hoverCursor() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "cursor hover entering task")) { + long downTime = SystemClock.uptimeMillis(); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER, + new Point(mTask.getVisibleCenter().x, mTask.getVisibleCenter().y), + null); + mLauncher.runCallbackIfActive(CALLBACK_HOVER_ENTER); + + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "cursor hover exiting task")) { + downTime = SystemClock.uptimeMillis(); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT, + new Point(mTask.getVisibleCenter().x, mTask.getVisibleCenter().y), + null); + mLauncher.runCallbackIfActive(CALLBACK_HOVER_EXIT); + } + } + } }