Merge "Extend recents button hitbox on tablet" into tm-dev

This commit is contained in:
Vinit Nayak
2022-05-09 18:46:01 +00:00
committed by Android (Google) Code Review
5 changed files with 293 additions and 3 deletions

View File

@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
@@ -51,6 +52,7 @@ import android.graphics.Region.Op;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.PaintDrawable;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
import android.util.Property;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -158,6 +160,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
private final ViewTreeObserverWrapper.OnComputeInsetsListener mSeparateWindowInsetsComputer =
this::onComputeInsetsForSeparateWindow;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
mContext = context;
@@ -388,8 +391,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
// home and recents buttons
// home button
mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
navButtonController, R.id.home);
mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
@@ -399,8 +401,21 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
ALPHA_INDEX_KEYGUARD_OR_DISABLE),
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
(flags & FLAG_DISABLE_HOME) == 0));
// Recents button
View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
navContainer, navButtonController, R.id.recent_apps);
mHitboxExtender.init(recentsButton, mNavButtonsView, mContext.getDeviceProfile(),
() -> {
float[] recentsCoords = new float[2];
getDescendantCoordRelativeToAncestor(recentsButton, mNavButtonsView,
recentsCoords, false);
return recentsCoords;
}, new Handler());
recentsButton.setOnClickListener(v -> {
navButtonController.onButtonClick(BUTTON_RECENTS);
mHitboxExtender.onRecentsButtonClicked();
});
mPropertyHolders.add(new StatePropertyHolder(recentsButton,
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
&& !mContext.isNavBarKidsModeActive()));
@@ -504,6 +519,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
View button = mAllButtons.get(i);
if (button.getVisibility() == View.VISIBLE) {
parent.getDescendantRectRelativeToSelf(button, mTempRect);
if (mHitboxExtender.extendedHitboxEnabled()) {
mTempRect.bottom += mContext.mDeviceProfile.getTaskbarOffsetY();
}
outRegion.op(mTempRect, Op.UNION);
}
}
@@ -733,6 +751,17 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
return str.toString();
}
public TouchController getTouchController() {
return mHitboxExtender;
}
/**
* @param alignment 0 -> Taskbar, 1 -> Workspace
*/
public void updateTaskbarAlignment(float alignment) {
mHitboxExtender.onAnimationProgressToOverview(alignment);
}
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
@Override
public void onVisibilityChanged(boolean isVisible) {

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2022 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.launcher3.taskbar;
import android.graphics.Rect;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.util.TouchController;
import java.util.function.Supplier;
/**
* Extends the Recents touch area during the taskbar to overview animation
* to give user some error room when trying to quickly double tap recents button since it moves.
*
* Listens for icon alignment as our indication for the animation.
*/
public class RecentsHitboxExtender implements TouchController {
private static final int RECENTS_HITBOX_TIMEOUT_MS = 500;
private View mRecentsButton;
private View mRecentsParent;
private DeviceProfile mDeviceProfile;
private Supplier<float[]> mParentCoordSupplier;
private TouchDelegate mRecentsTouchDelegate;
/**
* Will be true while the animation from taskbar to overview is occurring.
* Lifecycle of this variable slightly extends past the animation by
* {@link #RECENTS_HITBOX_TIMEOUT_MS}, so can use this variable as a proxy for if
* the current hitbox is extended or not.
*/
private boolean mAnimatingFromTaskbarToOverview;
private float mLastIconAlignment;
private final Rect mRecentsHitBox = new Rect();
private boolean mRecentsButtonClicked;
private Handler mHandler;
private final Runnable mRecentsHitboxResetRunnable = this::reset;
public void init(View recentsButton, View recentsParent, DeviceProfile deviceProfile,
Supplier<float[]> parentCoordSupplier, Handler handler) {
mRecentsButton = recentsButton;
mRecentsParent = recentsParent;
mDeviceProfile = deviceProfile;
mParentCoordSupplier = parentCoordSupplier;
mHandler = handler;
}
public void onRecentsButtonClicked() {
mRecentsButtonClicked = true;
}
/**
* @param progress 0 -> Taskbar, 1 -> Overview
*/
public void onAnimationProgressToOverview(float progress) {
if (progress == 1 || progress == 0) {
// Done w/ animation
mLastIconAlignment = progress;
if (mAnimatingFromTaskbarToOverview) {
if (progress == 1) {
// Finished animation to workspace, remove the touch delegate shortly
mHandler.postDelayed(mRecentsHitboxResetRunnable, RECENTS_HITBOX_TIMEOUT_MS);
return;
} else {
// Went back to taskbar, reset immediately
mHandler.removeCallbacks(mRecentsHitboxResetRunnable);
reset();
}
}
}
if (mAnimatingFromTaskbarToOverview) {
return;
}
if (progress > 0 && mLastIconAlignment == 0 && mRecentsButtonClicked) {
// Starting animation, previously we were showing taskbar
mAnimatingFromTaskbarToOverview = true;
float[] recentsCoords = mParentCoordSupplier.get();
int x = (int) recentsCoords[0];
int y = (int) (recentsCoords[1]);
// Extend hitbox vertically by the offset amount from mDeviceProfile.getTaskbarOffsetY()
mRecentsHitBox.set(x, y,
x + mRecentsButton.getWidth(),
y + mRecentsButton.getHeight() + mDeviceProfile.getTaskbarOffsetY()
);
mRecentsTouchDelegate = new TouchDelegate(mRecentsHitBox, mRecentsButton);
mRecentsParent.setTouchDelegate(mRecentsTouchDelegate);
}
}
private void reset() {
mAnimatingFromTaskbarToOverview = false;
mRecentsButton.setTouchDelegate(null);
mRecentsHitBox.setEmpty();
mRecentsButtonClicked = false;
}
/**
* @return {@code true} if the bounds for recents touches are currently extended
*/
public boolean extendedHitboxEnabled() {
return mAnimatingFromTaskbarToOverview;
}
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
return mRecentsTouchDelegate.onTouchEvent(ev);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
return mRecentsHitBox.contains((int)ev.getX(), (int)ev.getY());
}
}

View File

@@ -182,7 +182,8 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
*/
public TouchController[] getTouchControllers() {
return new TouchController[]{mActivity.getDragController(),
mControllers.taskbarForceVisibleImmersiveController};
mControllers.taskbarForceVisibleImmersiveController,
mControllers.navbarButtonsViewController.getTouchController()};
}
}
}

View File

@@ -428,6 +428,7 @@ import java.util.function.Supplier;
// Switch taskbar and hotseat in last frame
setTaskbarViewVisible(alignment < 1);
mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment);
}
private float getCurrentIconAlignmentRatioBetweenAppAndHome() {

View File

@@ -0,0 +1,125 @@
package com.android.launcher3.taskbar;
import static android.view.MotionEvent.ACTION_DOWN;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.DeviceProfile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
public class RecentsHitboxExtenderTest {
private static final int TASKBAR_OFFSET_Y = 35;
private static final int BUTTON_WIDTH = 10;
private static final int BUTTON_HEIGHT = 10;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
@Mock
View mMockRecentsButton;
@Mock
View mMockRecentsParent;
@Mock
DeviceProfile mMockDeviceProfile;
@Mock
Handler mMockHandler;
Context mContext;
float[] mRecentsCoords = new float[]{0,0};
private final Supplier<float[]> mSupplier = () -> mRecentsCoords;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mContext = instrumentation.getContext();
mHitboxExtender.init(mMockRecentsButton, mMockRecentsParent, mMockDeviceProfile, mSupplier,
mMockHandler);
when(mMockDeviceProfile.getTaskbarOffsetY()).thenReturn(TASKBAR_OFFSET_Y);
when(mMockRecentsButton.getContext()).thenReturn(mContext);
when(mMockRecentsButton.getWidth()).thenReturn(BUTTON_WIDTH);
when(mMockRecentsButton.getHeight()).thenReturn(BUTTON_HEIGHT);
}
@Test
public void noRecentsButtonClick_notActive() {
mHitboxExtender.onAnimationProgressToOverview(0);
mHitboxExtender.onAnimationProgressToOverview(0.5f);
assertFalse(mHitboxExtender.extendedHitboxEnabled());
}
@Test
public void recentsButtonClick_active() {
mHitboxExtender.onRecentsButtonClicked();
mHitboxExtender.onAnimationProgressToOverview(0);
mHitboxExtender.onAnimationProgressToOverview(0.5f);
assertTrue(mHitboxExtender.extendedHitboxEnabled());
}
@Test
public void homeToTaskbar_notActive() {
mHitboxExtender.onAnimationProgressToOverview(1);
mHitboxExtender.onAnimationProgressToOverview(0.5f);
assertFalse(mHitboxExtender.extendedHitboxEnabled());
}
@Test
public void animationEndReset() {
mHitboxExtender.onRecentsButtonClicked();
mHitboxExtender.onAnimationProgressToOverview(0);
mHitboxExtender.onAnimationProgressToOverview(0.5f);
assertTrue(mHitboxExtender.extendedHitboxEnabled());
mHitboxExtender.onAnimationProgressToOverview(1);
verify(mMockHandler, times(1)).postDelayed(any(), anyLong());
}
@Test
public void motionWithinHitbox() {
mHitboxExtender.onRecentsButtonClicked();
mHitboxExtender.onAnimationProgressToOverview(0);
mHitboxExtender.onAnimationProgressToOverview(0.5f);
assertTrue(mHitboxExtender.extendedHitboxEnabled());
// Center width, past height but w/in offset bounds
MotionEvent motionEvent = getMotionEvent(ACTION_DOWN,
BUTTON_WIDTH / 2, BUTTON_HEIGHT + TASKBAR_OFFSET_Y / 2);
assertTrue(mHitboxExtender.onControllerInterceptTouchEvent(motionEvent));
}
@Test
public void motionOutsideHitbox() {
mHitboxExtender.onRecentsButtonClicked();
mHitboxExtender.onAnimationProgressToOverview(0);
mHitboxExtender.onAnimationProgressToOverview(0.5f);
assertTrue(mHitboxExtender.extendedHitboxEnabled());
// Center width, past height and offset
MotionEvent motionEvent = getMotionEvent(ACTION_DOWN,
BUTTON_WIDTH / 2, BUTTON_HEIGHT + TASKBAR_OFFSET_Y * 2);
assertFalse(mHitboxExtender.onControllerInterceptTouchEvent(motionEvent));
}
private MotionEvent getMotionEvent(int action, int x, int y) {
return MotionEvent.obtain(0, 0, action, x, y, 0);
}
}