Merge "Refine FloatingToolbarPopup to allow multiple implementations"

This commit is contained in:
Joanne Chung
2022-01-06 11:55:30 +00:00
committed by Android (Google) Code Review
10 changed files with 281 additions and 82 deletions

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import java.util.Objects;
@@ -40,6 +41,11 @@ public final class SelectionToolbarManager {
*/
public static final String LOG_TAG = "SelectionToolbar";
/**
* Whether system selection toolbar is enabled.
*/
private static final String REMOTE_SELECTION_TOOLBAR_ENABLED =
"remote_selection_toolbar_enabled";
@NonNull
private final Context mContext;
@@ -86,4 +92,21 @@ public final class SelectionToolbarManager {
throw e.rethrowFromSystemServer();
}
}
private boolean isRemoteSelectionToolbarEnabled() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR,
REMOTE_SELECTION_TOOLBAR_ENABLED, false);
}
/**
* Returns {@code true} if remote render selection toolbar enabled, otherwise
* returns {@code false}.
*/
public static boolean isRemoteSelectionToolbarEnabled(Context context) {
SelectionToolbarManager manager = context.getSystemService(SelectionToolbarManager.class);
if (manager != null) {
return manager.isRemoteSelectionToolbarEnabled();
}
return false;
}
}

View File

@@ -121,7 +121,7 @@ import com.android.internal.view.menu.MenuHelper;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.DecorCaptionView;
import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import java.util.List;
import java.util.function.Consumer;

View File

@@ -33,7 +33,7 @@ import android.widget.PopupWindow;
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import java.util.Arrays;
import java.util.Objects;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.internal.widget;
package com.android.internal.widget.floatingtoolbar;
import android.annotation.Nullable;
import android.graphics.Rect;
@@ -50,14 +50,10 @@ public final class FloatingToolbar {
private final FloatingToolbarPopup mPopup;
private final Rect mContentRect = new Rect();
private final Rect mPreviousContentRect = new Rect();
private Menu mMenu;
private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
private int mSuggestedWidth;
private boolean mWidthChanged = true;
private final OnLayoutChangeListener mOrientationChangeHandler = new OnLayoutChangeListener() {
private final Rect mNewRect = new Rect();
@@ -71,7 +67,7 @@ public final class FloatingToolbar {
mNewRect.set(newLeft, newRight, newTop, newBottom);
mOldRect.set(oldLeft, oldRight, oldTop, oldBottom);
if (mPopup.isShowing() && !mNewRect.equals(mOldRect)) {
mWidthChanged = true;
mPopup.setWidthChanged(true);
updateLayout();
}
}
@@ -114,7 +110,7 @@ public final class FloatingToolbar {
// TODO(b/65172902): Pass context in constructor when DecorView (and other callers)
// supports multi-display.
mWindow = Objects.requireNonNull(window);
mPopup = new FloatingToolbarPopup(window.getContext(), window.getDecorView());
mPopup = FloatingToolbarPopup.createInstance(window.getContext(), window.getDecorView());
}
/**
@@ -159,11 +155,7 @@ public final class FloatingToolbar {
* toolbar.
*/
public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
// Check if there's been a substantial width spec change.
int difference = Math.abs(suggestedWidth - mSuggestedWidth);
mWidthChanged = difference > (mSuggestedWidth * 0.2);
mSuggestedWidth = suggestedWidth;
mPopup.setSuggestedWidth(suggestedWidth);
return this;
}
@@ -232,19 +224,7 @@ public final class FloatingToolbar {
private void doShow() {
List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
menuItems.sort(mMenuItemComparator);
if (mPopup.isLayoutRequired(menuItems) || mWidthChanged) {
mPopup.dismiss();
mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
} else {
mPopup.updateMenuItems(menuItems, mMenuItemClickListener);
}
if (!mPopup.isShowing()) {
mPopup.show(mContentRect);
} else if (!mPreviousContentRect.equals(mContentRect)) {
mPopup.updateCoordinates(mContentRect);
}
mWidthChanged = false;
mPreviousContentRect.set(mContentRect);
mPopup.show(menuItems, mMenuItemClickListener, mContentRect);
}
/**

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2021 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.internal.widget.floatingtoolbar;
import android.content.Context;
import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
import android.view.selectiontoolbar.SelectionToolbarManager;
import android.widget.PopupWindow;
import java.util.List;
/**
* A popup window used by the {@link FloatingToolbar} to render menu items.
*
*/
public interface FloatingToolbarPopup {
/**
* Sets the suggested dp width of this floating toolbar.
* The actual width will be about this size but there are no guarantees that it will be exactly
* the suggested width.
*/
void setSuggestedWidth(int suggestedWidth);
/**
* Sets if the floating toolbar width changed.
*/
void setWidthChanged(boolean widthChanged);
/**
* Shows this popup at the specified coordinates.
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
*/
void show(List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener,
Rect contentRect);
/**
* Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
*/
void dismiss();
/**
* Hides this popup. This is a no-op if this popup is not showing.
* Use {@link #isHidden()} to distinguish between a hidden and a dismissed popup.
*/
void hide();
/**
* Returns {@code true} if this popup is currently showing. {@code false} otherwise.
*/
boolean isShowing();
/**
* Returns {@code true} if this popup is currently hidden. {@code false} otherwise.
*/
boolean isHidden();
/**
* Makes this toolbar "outside touchable" and sets the onDismissListener.
*
* @param outsideTouchable if true, the popup will be made "outside touchable" and
* "non focusable". The reverse will happen if false.
* @param onDismiss
*
* @return true if the "outsideTouchable" setting was modified. Otherwise returns false
*
* @see PopupWindow#setOutsideTouchable(boolean)
* @see PopupWindow#setFocusable(boolean)
* @see PopupWindow.OnDismissListener
*/
boolean setOutsideTouchable(boolean outsideTouchable, PopupWindow.OnDismissListener onDismiss);
/**
* Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar
* enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
*/
static FloatingToolbarPopup createInstance(Context context, View parent) {
boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
return enabled
? new RemoteFloatingToolbarPopup(context, parent)
: new LocalFloatingToolbarPopup(context, parent);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.internal.widget;
package com.android.internal.widget.floatingtoolbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -70,13 +70,13 @@ import java.util.Map;
import java.util.Objects;
/**
* A popup window used by the floating toolbar.
* A popup window used by the floating toolbar to render menu items in the local app process.
*
* This class is responsible for the rendering/animation of the floating toolbar.
* It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
* to transition between panels.
*/
public final class FloatingToolbarPopup {
public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup {
/* Minimum and maximum number of items allowed in the overflow. */
private static final int MIN_OVERFLOW_SIZE = 2;
@@ -94,7 +94,7 @@ public final class FloatingToolbarPopup {
private final ViewGroup mContentContainer; // holds all contents.
private final ViewGroup mMainPanel; // holds menu items that are initially displayed.
// holds menu items hidden in the overflow.
private final FloatingToolbarPopup.OverflowPanel mOverflowPanel;
private final OverflowPanel mOverflowPanel;
private final ImageButton mOverflowButton; // opens/closes the overflow.
/* overflow button drawables. */
private final Drawable mArrow;
@@ -102,8 +102,7 @@ public final class FloatingToolbarPopup {
private final AnimatedVectorDrawable mToArrow;
private final AnimatedVectorDrawable mToOverflow;
private final FloatingToolbarPopup.OverflowPanelViewHelper
mOverflowPanelViewHelper;
private final OverflowPanelViewHelper mOverflowPanelViewHelper;
/* Animation interpolators. */
private final Interpolator mLogAccelerateInterpolator;
@@ -138,7 +137,7 @@ public final class FloatingToolbarPopup {
private final int mIconTextSpacing;
/**
* @see FloatingToolbarPopup.OverflowPanelViewHelper#preparePopupContent().
* @see OverflowPanelViewHelper#preparePopupContent().
*/
private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
@Override
@@ -184,16 +183,20 @@ public final class FloatingToolbarPopup {
private int mTransitionDurationScale; // Used to scale the toolbar transition duration.
private final Rect mPreviousContentRect = new Rect();
private int mSuggestedWidth;
private boolean mWidthChanged = true;
/**
* Initializes a new floating toolbar popup.
*
* @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
* from.
*/
public FloatingToolbarPopup(Context context, View parent) {
public LocalFloatingToolbarPopup(Context context, View parent) {
mParent = Objects.requireNonNull(parent);
mContext = applyDefaultTheme(context);
mContentContainer = createContentContainer(context);
mContentContainer = createContentContainer(mContext);
mPopupWindow = createPopupWindow(mContentContainer);
mMarginHorizontal = parent.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
@@ -205,7 +208,7 @@ public final class FloatingToolbarPopup {
.getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
// Interpolators
mLogAccelerateInterpolator = new FloatingToolbarPopup.LogAccelerateInterpolator();
mLogAccelerateInterpolator = new LogAccelerateInterpolator();
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_slow_in);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
@@ -231,8 +234,7 @@ public final class FloatingToolbarPopup {
mOverflowButton = createOverflowButton();
mOverflowButtonSize = measure(mOverflowButton);
mMainPanel = createMainPanel();
mOverflowPanelViewHelper =
new FloatingToolbarPopup.OverflowPanelViewHelper(mContext, mIconTextSpacing);
mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing);
mOverflowPanel = createOverflowPanel();
// Animation. Need views.
@@ -263,19 +265,7 @@ public final class FloatingToolbarPopup {
});
}
/**
* Makes this toolbar "outside touchable" and sets the onDismissListener.
*
* @param outsideTouchable if true, the popup will be made "outside touchable" and
* "non focusable". The reverse will happen if false.
* @param onDismiss
*
* @return true if the "outsideTouchable" setting was modified. Otherwise returns false
*
* @see PopupWindow#setOutsideTouchable(boolean)
* @see PopupWindow#setFocusable(boolean)
* @see PopupWindow.OnDismissListener
*/
@Override
public boolean setOutsideTouchable(
boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
boolean ret = false;
@@ -293,7 +283,7 @@ public final class FloatingToolbarPopup {
* Lays out buttons for the specified menu items.
* Requires a subsequent call to {@link FloatingToolbar#show()} to show the items.
*/
public void layoutMenuItems(
private void layoutMenuItems(
List<MenuItem> menuItems,
MenuItem.OnMenuItemClickListener menuItemClickListener,
int suggestedWidth) {
@@ -314,7 +304,7 @@ public final class FloatingToolbarPopup {
*
* @see #isLayoutRequired(List<MenuItem>)
*/
public void updateMenuItems(
private void updateMenuItems(
List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) {
mMenuItems.clear();
for (MenuItem menuItem : menuItems) {
@@ -326,15 +316,42 @@ public final class FloatingToolbarPopup {
/**
* Returns true if this popup needs a relayout to properly render the specified menu items.
*/
public boolean isLayoutRequired(List<MenuItem> menuItems) {
private boolean isLayoutRequired(List<MenuItem> menuItems) {
return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values());
}
/**
* Shows this popup at the specified coordinates.
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
*/
public void show(Rect contentRectOnScreen) {
@Override
public void setWidthChanged(boolean widthChanged) {
mWidthChanged = widthChanged;
}
@Override
public void setSuggestedWidth(int suggestedWidth) {
// Check if there's been a substantial width spec change.
int difference = Math.abs(suggestedWidth - mSuggestedWidth);
mWidthChanged = difference > (mSuggestedWidth * 0.2);
mSuggestedWidth = suggestedWidth;
}
@Override
public void show(List<MenuItem> menuItems,
MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
if (isLayoutRequired(menuItems) || mWidthChanged) {
dismiss();
layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth);
} else {
updateMenuItems(menuItems, menuItemClickListener);
}
if (!isShowing()) {
show(contentRect);
} else if (!mPreviousContentRect.equals(contentRect)) {
updateCoordinates(contentRect);
}
mWidthChanged = false;
mPreviousContentRect.set(contentRect);
}
private void show(Rect contentRectOnScreen) {
Objects.requireNonNull(contentRectOnScreen);
if (isShowing()) {
@@ -357,9 +374,7 @@ public final class FloatingToolbarPopup {
runShowAnimation();
}
/**
* Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
*/
@Override
public void dismiss() {
if (mDismissed) {
return;
@@ -373,10 +388,7 @@ public final class FloatingToolbarPopup {
setZeroTouchableSurface();
}
/**
* Hides this popup. This is a no-op if this popup is not showing.
* Use {@link #isHidden()} to distinguish between a hidden and a dismissed popup.
*/
@Override
public void hide() {
if (!isShowing()) {
return;
@@ -387,16 +399,12 @@ public final class FloatingToolbarPopup {
setZeroTouchableSurface();
}
/**
* Returns {@code true} if this popup is currently showing. {@code false} otherwise.
*/
@Override
public boolean isShowing() {
return !mDismissed && !mHidden;
}
/**
* Returns {@code true} if this popup is currently hidden. {@code false} otherwise.
*/
@Override
public boolean isHidden() {
return mHidden;
}
@@ -406,7 +414,7 @@ public final class FloatingToolbarPopup {
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
* This is a no-op if this popup is not showing.
*/
public void updateCoordinates(Rect contentRectOnScreen) {
private void updateCoordinates(Rect contentRectOnScreen) {
Objects.requireNonNull(contentRectOnScreen);
if (!isShowing() || !mPopupWindow.isShowing()) {
@@ -1206,9 +1214,8 @@ public final class FloatingToolbarPopup {
return overflowButton;
}
private FloatingToolbarPopup.OverflowPanel createOverflowPanel() {
final FloatingToolbarPopup.OverflowPanel
overflowPanel = new FloatingToolbarPopup.OverflowPanel(this);
private OverflowPanel createOverflowPanel() {
final OverflowPanel overflowPanel = new OverflowPanel(this);
overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
overflowPanel.setDivider(null);
@@ -1307,9 +1314,9 @@ public final class FloatingToolbarPopup {
*/
private static final class OverflowPanel extends ListView {
private final FloatingToolbarPopup mPopup;
private final LocalFloatingToolbarPopup mPopup;
OverflowPanel(FloatingToolbarPopup popup) {
OverflowPanel(LocalFloatingToolbarPopup popup) {
super(Objects.requireNonNull(popup).mContext);
this.mPopup = popup;
setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);

View File

@@ -0,0 +1 @@
include /core/java/android/view/selectiontoolbar/OWNERS

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2021 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.internal.widget.floatingtoolbar;
import android.content.Context;
import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
import android.view.selectiontoolbar.SelectionToolbarManager;
import android.widget.PopupWindow;
import java.util.List;
import java.util.Objects;
/**
* A popup window used by the floating toolbar to render menu items in the remote system process.
*
* It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
* to transition between panels.
*/
public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
private final SelectionToolbarManager mSelectionToolbarManager;
// Parent for the popup window.
private final View mParent;
public RemoteFloatingToolbarPopup(Context context, View parent) {
// TODO: implement it
mParent = Objects.requireNonNull(parent);
mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
}
@Override
public void show(List<MenuItem> menuItems,
MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
// TODO: implement it
}
@Override
public void hide() {
// TODO: implement it
}
@Override
public void setSuggestedWidth(int suggestedWidth) {
// TODO: implement it
}
@Override
public void setWidthChanged(boolean widthChanged) {
// no-op
}
@Override
public void dismiss() {
// TODO: implement it
}
@Override
public boolean isHidden() {
return false;
}
@Override
public boolean isShowing() {
return false;
}
@Override
public boolean setOutsideTouchable(boolean outsideTouchable,
PopupWindow.OnDismissListener onDismiss) {
return false;
}
}

View File

@@ -28,7 +28,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.android.internal.widget.FloatingToolbarPopup.MenuItemRepr;
import static com.android.internal.widget.floatingtoolbar.LocalFloatingToolbarPopup.MenuItemRepr;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
@@ -42,7 +42,7 @@ import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

View File

@@ -50,7 +50,7 @@ import android.view.WindowInsetsController;
import android.widget.FrameLayout;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import com.android.systemui.R;
/**