Merge "Add hover state border line to overview task item by using the same approach used by keyboard focus state" into udc-qpr-dev am: ed4c2acf58

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/23377578

Change-Id: I0dafecbe18763f5eb2b941d38d0f0d2720507cfd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Helen Cheuk
2023-06-01 16:24:23 +00:00
committed by Automerger Merge Worker
6 changed files with 155 additions and 19 deletions
@@ -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;
@@ -28,6 +28,7 @@ import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.launcher3.anim.AnimatedFloat;
@@ -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.
*/
@@ -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.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
@@ -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
@@ -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";
@@ -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);
@@ -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);
}
}
}
}