Merge "Not show hover border on select mode" into main
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 95ce406..35fa539 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -238,6 +238,12 @@
         if (toState == MODAL_TASK) {
             setOverviewSelectEnabled(true);
         }
+
+        // Set border after select mode changes to avoid showing border during state transition
+        if (!toState.overviewUi() || toState == MODAL_TASK) {
+            setTaskBorderEnabled(false);
+        }
+
         setFreezeViewVisibility(true);
     }
 
@@ -253,6 +259,11 @@
         if (finalState != MODAL_TASK) {
             setOverviewSelectEnabled(false);
         }
+
+        if (finalState.overviewUi() && finalState != MODAL_TASK) {
+            setTaskBorderEnabled(true);
+        }
+
         if (finalState != OVERVIEW_SPLIT_SELECT) {
             if (FeatureFlags.enableSplitContextually()) {
                 mSplitSelectStateController.resetState();
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index b542909..5b5d0de 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -145,6 +145,12 @@
         if (toState == OVERVIEW_MODAL_TASK) {
             setOverviewSelectEnabled(true);
         }
+
+        // Set border after select mode changes to avoid showing border during state transition
+        if (!toState.overviewUi || toState == OVERVIEW_MODAL_TASK) {
+            setTaskBorderEnabled(false);
+        }
+
         setFreezeViewVisibility(true);
         if (mActivity.getDesktopVisibilityController() != null) {
             mActivity.getDesktopVisibilityController().onLauncherStateChanged(toState);
@@ -165,6 +171,10 @@
             setOverviewSelectEnabled(false);
         }
 
+        if (finalState.overviewUi && finalState != OVERVIEW_MODAL_TASK) {
+            setTaskBorderEnabled(true);
+        }
+
         if (isOverlayEnabled) {
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f55dce9..22a5064 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1410,6 +1410,17 @@
     }
 
     /**
+     * Enable or disable showing border on hover and focus change on task views
+     */
+    public void setTaskBorderEnabled(boolean enabled) {
+        int taskCount = getTaskViewCount();
+        for (int i = 0; i < taskCount; i++) {
+            TaskView taskView = requireTaskViewAt(i);
+            taskView.setBorderEnabled(enabled);
+        }
+    }
+
+    /**
      * Whether the Clear All button is hidden or fully visible. Used to determine if center
      * displayed page is a task or the Clear All button.
      *
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 49eda8f..af4f402 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -408,6 +408,7 @@
             new TaskIdAttributeContainer[2];
 
     private boolean mShowScreenshot;
+    private boolean mBorderEnabled;
 
     // The current background requests to load the task thumbnail and icon
     @Nullable
@@ -439,8 +440,15 @@
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskView(
-            Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        this(context, attrs, defStyleAttr, defStyleRes, null, null);
+    }
+
+    @VisibleForTesting
+    public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes, BorderAnimator focusBorderAnimator,
+            BorderAnimator hoverBorderAnimator) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mActivity = StatefulActivity.fromContext(context);
         setOnClickListener(this::onClick);
@@ -457,28 +465,35 @@
         TypedArray styledAttrs = context.obtainStyledAttributes(
                 attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
 
-        mFocusBorderAnimator = keyboardFocusHighlightEnabled
-                ? BorderAnimator.createSimpleBorderAnimator(
-                        /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
-                        /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
-                                R.dimen.keyboard_quick_switch_border_width),
-                        /* boundsBuilder= */ this::updateBorderBounds,
-                        /* targetView= */ this,
-                        /* borderColor= */ styledAttrs.getColor(
-                                R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
-                : null;
+        if (focusBorderAnimator != null) {
+            mFocusBorderAnimator = focusBorderAnimator;
+        } else {
+            mFocusBorderAnimator = keyboardFocusHighlightEnabled
+                    ? BorderAnimator.createSimpleBorderAnimator(
+                    /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+                    /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+                            R.dimen.keyboard_quick_switch_border_width),
+                    /* boundsBuilder= */ this::updateBorderBounds,
+                    /* targetView= */ this,
+                    /* borderColor= */ styledAttrs.getColor(
+                            R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
+                    : null;
+        }
 
-        mHoverBorderAnimator = cursorHoverStatesEnabled
-                ? BorderAnimator.createSimpleBorderAnimator(
-                        /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
-                        /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
-                                R.dimen.task_hover_border_width),
-                        /* boundsBuilder= */ this::updateBorderBounds,
-                        /* targetView= */ this,
-                        /* borderColor= */ styledAttrs.getColor(
-                                R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
-                : null;
-
+        if (hoverBorderAnimator != null) {
+            mHoverBorderAnimator = hoverBorderAnimator;
+        } else {
+            mHoverBorderAnimator = cursorHoverStatesEnabled
+                    ? BorderAnimator.createSimpleBorderAnimator(
+                    /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+                    /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+                            R.dimen.task_hover_border_width),
+                    /* boundsBuilder= */ this::updateBorderBounds,
+                    /* targetView= */ this,
+                    /* borderColor= */ styledAttrs.getColor(
+                            R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
+                    : null;
+        }
         styledAttrs.recycle();
     }
 
@@ -538,24 +553,25 @@
     }
 
     @Override
-    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        if (mFocusBorderAnimator != null) {
+        if (mFocusBorderAnimator != null && mBorderEnabled) {
             mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
         }
     }
 
     @Override
     public boolean onHoverEvent(MotionEvent event) {
-        if (mHoverBorderAnimator != null) {
+        if (mHoverBorderAnimator != null && mBorderEnabled) {
             switch (event.getAction()) {
                 case MotionEvent.ACTION_HOVER_ENTER:
-                    mHoverBorderAnimator.setBorderVisibility(
-                            /* visible= */ true, /* animated= */ true);
+                    mHoverBorderAnimator.setBorderVisibility(/* visible= */ true, /* animated= */
+                            true);
                     break;
                 case MotionEvent.ACTION_HOVER_EXIT:
-                    mHoverBorderAnimator.setBorderVisibility(
-                            /* visible= */ false, /* animated= */ true);
+                    mHoverBorderAnimator.setBorderVisibility(/* visible= */ false, /* animated= */
+                            true);
                     break;
                 default:
                     break;
@@ -564,6 +580,24 @@
         return super.onHoverEvent(event);
     }
 
+    /**
+     * Enable or disable showing border on hover and focus change
+     */
+    public void setBorderEnabled(boolean enabled) {
+        mBorderEnabled = enabled;
+        // Set the animation correctly in case it misses the hover/focus event during state
+        // transition
+        if (mHoverBorderAnimator != null) {
+            mHoverBorderAnimator.setBorderVisibility(/* visible= */
+                    enabled && isHovered(), /* animated= */ true);
+        }
+
+        if (mFocusBorderAnimator != null) {
+            mFocusBorderAnimator.setBorderVisibility(/* visible= */
+                    enabled && isFocused(), /* animated= */true);
+        }
+    }
+
     @Override
     public boolean onInterceptHoverEvent(MotionEvent event) {
         if (enableCursorHoverStates()) {
@@ -1327,6 +1361,7 @@
         mSnapshotView.setThumbnail(mTask, null);
         setOverlayEnabled(false);
         onTaskListVisibilityChanged(false);
+        mBorderEnabled = false;
     }
 
     public float getTaskCornerRadius() {
diff --git a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
new file mode 100644
index 0000000..d744194
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.util.BorderAnimator;
+import com.android.quickstep.views.TaskView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class TaskViewTest {
+
+    @Mock
+    private StatefulActivity mContext;
+    @Mock
+    private Resources mResource;
+    @Mock
+    private BorderAnimator mHoverAnimator;
+    @Mock
+    private BorderAnimator mFocusAnimator;
+    private TaskView mTaskView;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mResource.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
+        when(mResource.getConfiguration()).thenReturn(new Configuration());
+
+        when(mContext.getResources()).thenReturn(mResource);
+        when(mContext.getTheme()).thenReturn(mock(Resources.Theme.class));
+        when(mContext.getApplicationInfo()).thenReturn(mock(ApplicationInfo.class));
+        when(mContext.obtainStyledAttributes(any(), any(), anyInt(), anyInt())).thenReturn(
+                mock(TypedArray.class));
+
+        mTaskView = new TaskView(mContext, null, 0, 0, mFocusAnimator, mHoverAnimator);
+    }
+
+    @Test
+    public void notShowBorderOnBorderDisabled() {
+        mTaskView.setBorderEnabled(/* enabled= */ false);
+        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
+        mTaskView.onHoverEvent(MotionEvent.obtain(event));
+        verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ true, /* animated= */
+                true);
+
+        mTaskView.onFocusChanged(false, 0, new Rect());
+        verify(mFocusAnimator, never()).setBorderVisibility(/* visible= */ true, /* animated= */
+                true);
+    }
+
+    @Test
+    public void showBorderOnBorderEnabled() {
+        mTaskView.setBorderEnabled(/* enabled= */ true);
+        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
+        mTaskView.onHoverEvent(MotionEvent.obtain(event));
+        verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
+                true);
+        mTaskView.onFocusChanged(true, 0, new Rect());
+        verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
+                true);
+    }
+
+    @Test
+    public void hideBorderOnBorderDisabled() {
+        mTaskView.setBorderEnabled(/* enabled= */ false);
+        verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ false, /* animated= */
+                true);
+        verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ false, /* animated= */
+                true);
+    }
+
+    @Test
+    public void notShowBorderByDefault() {
+        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
+        mTaskView.onHoverEvent(MotionEvent.obtain(event));
+        verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ false, /* animated= */
+                true);
+        mTaskView.onFocusChanged(true, 0, new Rect());
+        verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ false, /* animated= */
+                true);
+    }
+}