mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
Use nearest region for all the nav buttons in 3-button folded mode
Bug: 230395757 Test: In 3-button folded mode, make sure that the touches that happen between nav buttons go to the nearest button. No regression in other modes. Change-Id: Icb776a9a4ed4fc31d33dc3267c7053f2b0da0bfc
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<FrameLayout
|
||||
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
|
||||
android:id="@+id/navbuttons_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -62,7 +62,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="end"/>
|
||||
</FrameLayout>
|
||||
</com.android.launcher3.taskbar.navbutton.NearestTouchFrame>
|
||||
|
||||
<com.android.launcher3.taskbar.StashedHandleView
|
||||
android:id="@+id/stashed_handle"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
android:elevation="@dimen/bubblebar_elevation"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
|
||||
android:id="@+id/navbuttons_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -83,7 +83,7 @@
|
||||
android:paddingTop="@dimen/taskbar_contextual_padding_top"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="end"/>
|
||||
</FrameLayout>
|
||||
</com.android.launcher3.taskbar.navbutton.NearestTouchFrame>
|
||||
|
||||
<com.android.launcher3.taskbar.StashedHandleView
|
||||
android:id="@+id/stashed_handle"
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.widget.FrameLayout;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
|
||||
|
||||
/**
|
||||
* Controller for managing buttons and status icons in taskbar in a desktop environment.
|
||||
@@ -43,7 +44,7 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
public DesktopNavbarButtonsViewController(TaskbarActivityContext context,
|
||||
@Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
|
||||
@Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
|
||||
super(context, navigationBarPanelContext, navButtonsView);
|
||||
mContext = context;
|
||||
mNavButtonsView = navButtonsView;
|
||||
|
||||
@@ -91,6 +91,7 @@ import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
|
||||
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
|
||||
import com.android.launcher3.util.DimensionUtils;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
@@ -151,7 +152,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private final TaskbarActivityContext mContext;
|
||||
private final @Nullable Context mNavigationBarPanelContext;
|
||||
private final WindowManagerProxy mWindowManagerProxy;
|
||||
private final FrameLayout mNavButtonsView;
|
||||
private final NearestTouchFrame mNavButtonsView;
|
||||
private final LinearLayout mNavButtonContainer;
|
||||
// Used for IME+A11Y buttons
|
||||
private final ViewGroup mEndContextualContainer;
|
||||
@@ -208,7 +209,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private ImageView mRecentsButton;
|
||||
|
||||
public NavbarButtonsViewController(TaskbarActivityContext context,
|
||||
@Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
|
||||
@Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
|
||||
mContext = context;
|
||||
mNavigationBarPanelContext = navigationBarPanelContext;
|
||||
mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
|
||||
@@ -517,6 +518,10 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
return (mState & FLAG_IME_VISIBLE) != 0;
|
||||
}
|
||||
|
||||
public boolean isImeRenderingNavButtons() {
|
||||
return mIsImeRenderingNavButtons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the home button is disabled
|
||||
*/
|
||||
@@ -1003,6 +1008,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
+ mOnTaskbarBackgroundNavButtonColorOverride.value);
|
||||
pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
|
||||
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
|
||||
|
||||
mNavButtonsView.dumpLogs(prefix + "\t", pw);
|
||||
}
|
||||
|
||||
private static String getStateString(int flags) {
|
||||
|
||||
@@ -104,6 +104,7 @@ import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
|
||||
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
@@ -236,7 +237,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
|
||||
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
|
||||
TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
|
||||
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
|
||||
NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
|
||||
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
|
||||
BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
|
||||
StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
|
||||
|
||||
@@ -309,7 +309,12 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
controllers.bubbleControllers.isPresent &&
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
|
||||
var insetsIsTouchableRegion = true
|
||||
if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
|
||||
if (context.isPhoneButtonNavMode &&
|
||||
(!controllers.navbarButtonsViewController.isImeVisible
|
||||
|| !controllers.navbarButtonsViewController.isImeRenderingNavButtons)) {
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
|
||||
insetsIsTouchableRegion = false
|
||||
} else if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
|
||||
debugTouchableRegion.lastSetTouchableReason = "Taskbar is invisible"
|
||||
|
||||
@@ -56,7 +56,7 @@ class NavButtonLayoutFactory {
|
||||
*/
|
||||
fun getUiLayoutter(
|
||||
deviceProfile: DeviceProfile,
|
||||
navButtonsView: FrameLayout,
|
||||
navButtonsView: NearestTouchFrame,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?,
|
||||
@@ -78,6 +78,7 @@ class NavButtonLayoutFactory {
|
||||
return when {
|
||||
isPhoneNavMode -> {
|
||||
if (!deviceProfile.isLandscape) {
|
||||
navButtonsView.setIsVertical(false)
|
||||
PhonePortraitNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
@@ -88,6 +89,7 @@ class NavButtonLayoutFactory {
|
||||
a11yButton
|
||||
)
|
||||
} else if (surfaceRotation == ROTATION_90) {
|
||||
navButtonsView.setIsVertical(true)
|
||||
PhoneLandscapeNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
@@ -98,6 +100,7 @@ class NavButtonLayoutFactory {
|
||||
a11yButton
|
||||
)
|
||||
} else {
|
||||
navButtonsView.setIsVertical(true)
|
||||
PhoneSeascapeNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.navbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Redirects touches that aren't handled by any child view to the nearest
|
||||
* clickable child. Only takes effect on <sw600dp.
|
||||
*/
|
||||
public class NearestTouchFrame extends FrameLayout {
|
||||
|
||||
private final List<View> mClickableChildren = new ArrayList<>();
|
||||
private final List<View> mAttachedChildren = new ArrayList<>();
|
||||
private final boolean mIsActive;
|
||||
|
||||
private boolean mIsVertical;
|
||||
private View mTouchingChild;
|
||||
private final Map<View, Rect> mTouchableRegions = new HashMap<>();
|
||||
/**
|
||||
* Used to sort all child views either by their left position or their top position,
|
||||
* depending on if this layout is used horizontally or vertically, respectively
|
||||
*/
|
||||
private final Comparator<View> mChildRegionComparator =
|
||||
(view1, view2) -> {
|
||||
int startingCoordView1 = mIsVertical ? view1.getTop() : view1.getLeft();
|
||||
int startingCoordView2 = mIsVertical ? view2.getTop() : view2.getLeft();
|
||||
|
||||
return startingCoordView1 - startingCoordView2;
|
||||
};
|
||||
|
||||
public NearestTouchFrame(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, context.getResources().getConfiguration());
|
||||
}
|
||||
|
||||
public NearestTouchFrame(Context context, AttributeSet attrs, Configuration c) {
|
||||
super(context, attrs);
|
||||
mIsActive = c.smallestScreenWidthDp < 600;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
mClickableChildren.clear();
|
||||
mAttachedChildren.clear();
|
||||
mTouchableRegions.clear();
|
||||
addClickableChildren(this);
|
||||
cacheClosestChildLocations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates {@link #mTouchableRegions} with the regions where each clickable child is the
|
||||
* closest for a given point on this layout.
|
||||
*/
|
||||
private void cacheClosestChildLocations() {
|
||||
if (getWidth() == 0 || getHeight() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by either top or left depending on mIsVertical, then take out all children
|
||||
// that are not attached to window
|
||||
mClickableChildren.sort(mChildRegionComparator);
|
||||
mClickableChildren.stream()
|
||||
.filter(View::isAttachedToWindow)
|
||||
.forEachOrdered(mAttachedChildren::add);
|
||||
|
||||
// Cache bounds of children
|
||||
// Mark coordinates where the actual child layout resides in this frame's window
|
||||
for (int i = 0; i < mAttachedChildren.size(); i++) {
|
||||
View child = mAttachedChildren.get(i);
|
||||
if (!child.isAttachedToWindow()) {
|
||||
continue;
|
||||
}
|
||||
Rect childRegion = getChildsBounds(child);
|
||||
|
||||
// We compute closest child from this child to the previous one
|
||||
if (i == 0) {
|
||||
// First child, nothing to the left/top of it
|
||||
if (mIsVertical) {
|
||||
childRegion.top = 0;
|
||||
} else {
|
||||
childRegion.left = 0;
|
||||
}
|
||||
mTouchableRegions.put(child, childRegion);
|
||||
continue;
|
||||
}
|
||||
|
||||
View previousChild = mAttachedChildren.get(i - 1);
|
||||
Rect previousChildBounds = mTouchableRegions.get(previousChild);
|
||||
int midPoint;
|
||||
if (mIsVertical) {
|
||||
int distance = childRegion.top - previousChildBounds.bottom;
|
||||
midPoint = distance / 2;
|
||||
childRegion.top -= midPoint;
|
||||
previousChildBounds.bottom += midPoint - ((distance % 2) == 0 ? 1 : 0);
|
||||
} else {
|
||||
int distance = childRegion.left - previousChildBounds.right;
|
||||
midPoint = distance / 2;
|
||||
childRegion.left -= midPoint;
|
||||
previousChildBounds.right += midPoint - ((distance % 2) == 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
if (i == mClickableChildren.size() - 1) {
|
||||
// Last child, nothing to right/bottom of it
|
||||
if (mIsVertical) {
|
||||
childRegion.bottom = getHeight();
|
||||
} else {
|
||||
childRegion.right = getWidth();
|
||||
}
|
||||
}
|
||||
|
||||
mTouchableRegions.put(child, childRegion);
|
||||
}
|
||||
}
|
||||
|
||||
void setIsVertical(boolean isVertical) {
|
||||
mIsVertical = isVertical;
|
||||
}
|
||||
|
||||
private Rect getChildsBounds(View child) {
|
||||
int left = child.getLeft();
|
||||
int top = child.getTop();
|
||||
int right = left + child.getWidth();
|
||||
int bottom = top + child.getHeight();
|
||||
return new Rect(left, top, right, bottom);
|
||||
}
|
||||
|
||||
private void addClickableChildren(ViewGroup group) {
|
||||
final int N = group.getChildCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
View child = group.getChildAt(i);
|
||||
if (child.isClickable()) {
|
||||
mClickableChildren.add(child);
|
||||
} else if (child instanceof ViewGroup) {
|
||||
addClickableChildren((ViewGroup) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mIsActive) {
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTouchingChild = mClickableChildren
|
||||
.stream()
|
||||
.filter(View::isAttachedToWindow)
|
||||
.filter(view -> mTouchableRegions.get(view).contains(x, y))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
}
|
||||
if (mTouchingChild != null) {
|
||||
// Translate the touch event to the view center of the touching child.
|
||||
event.offsetLocation(mTouchingChild.getWidth() / 2 - x,
|
||||
mTouchingChild.getHeight() / 2 - y);
|
||||
return mTouchingChild.getVisibility() == VISIBLE
|
||||
&& mTouchingChild.dispatchTouchEvent(event);
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "NearestTouchFrame:");
|
||||
|
||||
pw.println(String.format("%s\tmIsVertical=%s", prefix, mIsVertical));
|
||||
pw.println(String.format("%s\tmTouchingChild=%s", prefix, mTouchingChild));
|
||||
pw.println(String.format("%s\tmTouchableRegions=%s", prefix,
|
||||
mTouchableRegions.keySet().stream()
|
||||
.map(key -> key + "=" + mTouchableRegions.get(key))
|
||||
.collect(Collectors.joining(", ", "{", "}"))));
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import org.mockito.kotlin.whenever
|
||||
class NavButtonLayoutFactoryTest {
|
||||
|
||||
private val mockDeviceProfile: DeviceProfile = mock()
|
||||
private val mockParentButtonContainer: FrameLayout = mock()
|
||||
private val mockParentButtonContainer: NearestTouchFrame = mock()
|
||||
private val mockNavLayout: LinearLayout = mock()
|
||||
private val mockStartContextualLayout: ViewGroup = mock()
|
||||
private val mockEndContextualLayout: ViewGroup = mock()
|
||||
|
||||
Reference in New Issue
Block a user