From db78cc2cd8062bd60b68e7bde6ec1a86bf7062aa Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Tue, 28 Dec 2021 18:39:14 +0800 Subject: [PATCH 1/3] The service implementatoin of selection toolbar We clone most the render logic from LocalFloatingToolbarPopup to the DefaultSelectionToolbarRenderService to render the menu items in the system process. When the rendering is done, the system will notify the application process events to let the application to position the rendered content. The changes also removes unused file SelctionContext. We punt the TC part to the next release, the file is unnecessary now. This change is only for the service implementation, the client side implementation will be done on the next change. Bug: 190030331 Bug: 205823018 Test: manual. Merged-In: I3915e8165b4d371700cb5fb9e7eb23d46f39bd5c Change-Id: I2e704432b1e149ce8dd6853fcad6850cf02b60bb --- .../DefaultSelectionToolbarRenderService.java | 100 ++- .../selectiontoolbar/FloatingToolbarRoot.java | 61 ++ .../ISelectionToolbarRenderService.aidl | 5 +- ...electionToolbarRenderServiceCallback.aidl} | 12 +- .../RemoteSelectionToolbar.java | 632 +++++------------- .../SelectionToolbarRenderCallback.java | 12 +- .../SelectionToolbarRenderService.java | 119 +++- .../ISelectionToolbarCallback.aidl | 3 +- .../selectiontoolbar/SelectionContext.java | 248 ------- .../SelectionToolbarManager.java | 10 + .../view/selectiontoolbar/ShowInfo.java | 185 ++++- .../selectiontoolbar/ToolbarMenuItem.java | 350 +++++++++- .../view/selectiontoolbar/WidgetInfo.java | 78 ++- .../RemoteSelectionToolbarRenderService.java | 28 +- .../SelectionToolbarManagerServiceImpl.java | 29 +- 15 files changed, 1093 insertions(+), 779 deletions(-) create mode 100644 core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java rename core/java/android/{view/selectiontoolbar/SelectionContext.aidl => service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl} (64%) delete mode 100644 core/java/android/view/selectiontoolbar/SelectionContext.java diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java index c452e06156856..08a720555833f 100644 --- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java +++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java @@ -16,12 +16,21 @@ package android.service.selectiontoolbar; -import android.util.Log; +import static android.view.selectiontoolbar.SelectionToolbarManager.ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR; +import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID; + +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; import android.view.selectiontoolbar.ShowInfo; +import java.util.UUID; + /** * The default implementation of {@link SelectionToolbarRenderService}. * + *

NOTE: The requests are handled on the service main thread. + * * @hide */ // TODO(b/214122495): fix class not found then move to system service folder @@ -29,22 +38,97 @@ public final class DefaultSelectionToolbarRenderService extends SelectionToolbar private static final String TAG = "DefaultSelectionToolbarRenderService"; + // TODO(b/215497659): handle remove if the client process dies. + // Only show one toolbar, dismiss the old ones and remove from cache + private final SparseArray> mToolbarCache = + new SparseArray<>(); + + /** + * Only allow one package to create one toolbar. + */ + private boolean canShowToolbar(int uid, ShowInfo showInfo) { + if (showInfo.getWidgetToken() != NO_TOOLBAR_ID) { + return true; + } + return mToolbarCache.indexOfKey(uid) < 0; + } + @Override - public void onShow(ShowInfo showInfo, + public void onShow(int callingUid, ShowInfo showInfo, SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) { - // TODO: Add implementation - Log.w(TAG, "onShow()"); + if (!canShowToolbar(callingUid, showInfo)) { + Slog.e(TAG, "Do not allow multiple toolbar for the app."); + callbackWrapper.onError(ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR); + return; + } + long widgetToken = showInfo.getWidgetToken() == NO_TOOLBAR_ID + ? UUID.randomUUID().getMostSignificantBits() + : showInfo.getWidgetToken(); + + if (mToolbarCache.indexOfKey(callingUid) < 0) { + RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this, + widgetToken, showInfo.getHostInputToken(), + callbackWrapper, this::transferTouch); + mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar)); + } + Slog.v(TAG, "onShow() for " + widgetToken); + Pair toolbarPair = mToolbarCache.get(callingUid); + if (toolbarPair.first == widgetToken) { + toolbarPair.second.show(showInfo); + } else { + Slog.w(TAG, "onShow() for unknown " + widgetToken); + } } @Override public void onHide(long widgetToken) { - // TODO: Add implementation - Log.w(TAG, "onHide()"); + RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken); + if (toolbar != null) { + Slog.v(TAG, "onHide() for " + widgetToken); + toolbar.hide(widgetToken); + } } @Override public void onDismiss(long widgetToken) { - // TODO: Add implementation - Log.w(TAG, "onDismiss()"); + RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken); + if (toolbar != null) { + Slog.v(TAG, "onDismiss() for " + widgetToken); + toolbar.dismiss(widgetToken); + removeRemoteSelectionToolbarByTokenLocked(widgetToken); + } + } + + @Override + public void onToolbarShowTimeout(int callingUid) { + Slog.w(TAG, "onToolbarShowTimeout for callingUid = " + callingUid); + Pair toolbarPair = mToolbarCache.get(callingUid); + if (toolbarPair != null) { + RemoteSelectionToolbar remoteToolbar = toolbarPair.second; + remoteToolbar.dismiss(toolbarPair.first); + remoteToolbar.onToolbarShowTimeout(); + mToolbarCache.remove(callingUid); + } + } + + private RemoteSelectionToolbar getRemoteSelectionToolbarByTokenLocked(long widgetToken) { + for (int i = 0; i < mToolbarCache.size(); i++) { + Pair toolbarPair = mToolbarCache.valueAt(i); + if (toolbarPair.first == widgetToken) { + return toolbarPair.second; + } + } + return null; + } + + private void removeRemoteSelectionToolbarByTokenLocked(long widgetToken) { + for (int i = 0; i < mToolbarCache.size(); i++) { + Pair toolbarPair = mToolbarCache.valueAt(i); + if (toolbarPair.first == widgetToken) { + mToolbarCache.remove(mToolbarCache.keyAt(i)); + return; + } + } } } + diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java new file mode 100644 index 0000000000000..5e4262b970ebf --- /dev/null +++ b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java @@ -0,0 +1,61 @@ +/* + * 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 android.service.selectiontoolbar; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.IBinder; +import android.view.MotionEvent; +import android.widget.LinearLayout; + +/** + * This class is the root view for the selection toolbar. It is responsible for + * detecting the click on the item and to also transfer input focus to the application. + * + * @hide + */ +@SuppressLint("ViewConstructor") +public class FloatingToolbarRoot extends LinearLayout { + + private final IBinder mTargetInputToken; + private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener; + private float mDownX; + private float mDownY; + + public FloatingToolbarRoot(Context context, IBinder targetInputToken, + SelectionToolbarRenderService.TransferTouchListener transferTouchListener) { + super(context); + mTargetInputToken = targetInputToken; + mTransferTouchListener = transferTouchListener; + setFocusable(false); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean dispatchTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mDownX = event.getX(); + mDownY = event.getY(); + // TODO: Check x, y if we need to transfer touch focus to application + //mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(), + // mTargetInputToken); + } + } + return super.dispatchTouchEvent(event); + } +} diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl index 2bd99acbf24a1..79281b8b361dd 100644 --- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl +++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl @@ -25,7 +25,8 @@ import android.view.selectiontoolbar.ShowInfo; * @hide */ oneway interface ISelectionToolbarRenderService { - void onShow(in ShowInfo showInfo, in ISelectionToolbarCallback callback); + void onConnected(in IBinder callback); + void onShow(int callingUid, in ShowInfo showInfo, in ISelectionToolbarCallback callback); void onHide(long widgetToken); - void onDismiss(long widgetToken); + void onDismiss(int callingUid, long widgetToken); } diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl similarity index 64% rename from core/java/android/view/selectiontoolbar/SelectionContext.aidl rename to core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl index 52068312d4a1c..f6c47ddf1e007 100644 --- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl +++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,9 +14,15 @@ * limitations under the License. */ -package android.view.selectiontoolbar; +package android.service.selectiontoolbar; + +import android.os.IBinder; /** + * The interface from the SelectionToolbarRenderService to the system. + * * @hide */ -parcelable SelectionContext; +oneway interface ISelectionToolbarRenderServiceCallback { + void transferTouch(in IBinder source, in IBinder target); +} diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java index 95ecc4e16446b..cbe232b7b39aa 100644 --- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java +++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java @@ -21,72 +21,63 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; -import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.Region; import android.graphics.drawable.AnimatedVectorDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.IBinder; import android.text.TextUtils; +import android.util.Log; import android.util.Size; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.MotionEvent; +import android.view.SurfaceControlViewHost; import android.view.View; -import android.view.View.MeasureSpec; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.Transformation; +import android.view.selectiontoolbar.ShowInfo; +import android.view.selectiontoolbar.ToolbarMenuItem; +import android.view.selectiontoolbar.WidgetInfo; import android.widget.ArrayAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.PopupWindow; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.internal.widget.floatingtoolbar.FloatingToolbarPopup; +import com.android.internal.widget.floatingtoolbar.FloatingToolbar; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Objects; /** - * A popup window used by the floating toolbar to render menu items in the local app process. + * This class is responsible for rendering/animation of the selection toolbar in the remote + * system process. It holds 2 panels (i.e. main panel and overflow panel) and an overflow + * button to transition between panels. * - * 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. + * @hide */ - -final class RemoteSelectionToolbar implements FloatingToolbarPopup { +// TODO(b/215497659): share code with LocalFloatingToolbarPopup +final class RemoteSelectionToolbar { + private static final String TAG = "RemoteSelectionToolbar"; /* Minimum and maximum number of items allowed in the overflow. */ private static final int MIN_OVERFLOW_SIZE = 2; private static final int MAX_OVERFLOW_SIZE = 4; private final Context mContext; - private final View mParent; // Parent for the popup window. - private final PopupWindow mPopupWindow; /* Margins between the popup window and its content. */ private final int mMarginHorizontal; @@ -121,23 +112,22 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { private final Animation.AnimationListener mOverflowAnimationListener; private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in. - private final Point mCoordsOnWindow = new Point(); // popup window coordinates. - /* Temporary data holders. Reset values before using. */ - private final int[] mTmpCoords = new int[2]; - - private final Region mTouchableRegion = new Region(); - private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = - info -> { - info.contentInsets.setEmpty(); - info.visibleInsets.setEmpty(); - info.touchableRegion.set(mTouchableRegion); - info.setTouchableInsets( - ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - }; private final int mLineHeight; private final int mIconTextSpacing; + private final long mSelectionToolbarToken; + private IBinder mHostInputToken; + private final SelectionToolbarRenderService.RemoteCallbackWrapper mCallbackWrapper; + private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener; + private int mPopupWidth; + private int mPopupHeight; + // Coordinates to show the toolbar relative to the specified view port + private final Point mRelativeCoordsForToolbar = new Point(); + private List mMenuItems; + private SurfaceControlViewHost mSurfaceControlViewHost; + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; + /** * @see OverflowPanelViewHelper#preparePopupContent(). */ @@ -145,7 +135,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { @Override public void run() { setPanelsStatesAtRestingPosition(); - setContentAreaAsTouchableSurface(); mContentContainer.setAlpha(1); } }; @@ -159,26 +148,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { private Size mMainPanelSize; /* Menu items and click listeners */ - private final Map mMenuItems = new LinkedHashMap<>(); - private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener; - private final View.OnClickListener mMenuItemButtonOnClickListener = - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mOnMenuItemClickListener == null) { - return; - } - final Object tag = v.getTag(); - if (!(tag instanceof MenuItemRepr)) { - return; - } - final MenuItem menuItem = mMenuItems.get((MenuItemRepr) tag); - if (menuItem == null) { - return; - } - mOnMenuItemClickListener.onMenuItemClick(menuItem); - } - }; + private final View.OnClickListener mMenuItemButtonOnClickListener; private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards. private boolean mIsOverflowOpen; @@ -186,27 +156,26 @@ final class RemoteSelectionToolbar implements 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. - */ - RemoteSelectionToolbar(Context context, View parent) { - mParent = Objects.requireNonNull(parent); + private final Rect mTempContentRect = new Rect(); + + RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken, + SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper, + SelectionToolbarRenderService.TransferTouchListener transferTouchListener) { mContext = applyDefaultTheme(context); + mSelectionToolbarToken = selectionToolbarToken; + mCallbackWrapper = callbackWrapper; + mTransferTouchListener = transferTouchListener; + mHostInputToken = hostInputToken; + mContentContainer = createContentContainer(mContext); - mPopupWindow = createPopupWindow(mContentContainer); - mMarginHorizontal = parent.getResources() + mMarginHorizontal = mContext.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); - mMarginVertical = parent.getResources() + mMarginVertical = mContext.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin); - mLineHeight = context.getResources() + mLineHeight = mContext.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_height); - mIconTextSpacing = context.getResources() + mIconTextSpacing = mContext.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing); // Interpolators @@ -252,46 +221,54 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mPopupWindow.dismiss(); + // TODO(b/215497659): should dismiss window after animation mContentContainer.removeAllViews(); + mSurfaceControlViewHost.release(); + mSurfaceControlViewHost = null; + mSurfacePackage = null; } }); mHideAnimation = createExitAnimation( mContentContainer, 0, // startDelay - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mPopupWindow.dismiss(); - } - }); + null); // TODO(b/215497659): should handle hide after animation + mMenuItemButtonOnClickListener = v -> { + Object tag = v.getTag(); + if (!(tag instanceof ToolbarMenuItem)) { + return; + } + mCallbackWrapper.onMenuItemClicked((ToolbarMenuItem) tag); + }; } - @Override - public boolean setOutsideTouchable( - boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { - boolean ret = false; - if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) { - mPopupWindow.setOutsideTouchable(outsideTouchable); - mPopupWindow.setFocusable(!outsideTouchable); - mPopupWindow.update(); - ret = true; + private WidgetInfo createWidgetInfo() { + mTempContentRect.set(mRelativeCoordsForToolbar.x, mRelativeCoordsForToolbar.y, + mRelativeCoordsForToolbar.x + mPopupWidth, + mRelativeCoordsForToolbar.y + mPopupHeight); + return new WidgetInfo(mSelectionToolbarToken, mTempContentRect, getSurfacePackage()); + } + + private SurfaceControlViewHost.SurfacePackage getSurfacePackage() { + if (mSurfaceControlViewHost == null) { + final FloatingToolbarRoot contentHolder = new FloatingToolbarRoot(mContext, + mHostInputToken, mTransferTouchListener); + contentHolder.addView(mContentContainer); + mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), + mHostInputToken); + mSurfaceControlViewHost.setView(contentHolder, mPopupWidth, mPopupHeight); } - mPopupWindow.setOnDismissListener(onDismiss); - return ret; + if (mSurfacePackage == null) { + mSurfacePackage = mSurfaceControlViewHost.getSurfacePackage(); + } + return mSurfacePackage; } - /** - * Lays out buttons for the specified menu items. - * Requires a subsequent call to {@link FloatingToolbar#show()} to show the items. - */ private void layoutMenuItems( - List menuItems, - MenuItem.OnMenuItemClickListener menuItemClickListener, + List menuItems, int suggestedWidth) { cancelOverflowAnimations(); clearPanels(); - updateMenuItems(menuItems, menuItemClickListener); + menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth)); if (!menuItems.isEmpty()) { // Add remaining items to the overflow. @@ -300,148 +277,98 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { updatePopupSize(); } - /** - * Updates the popup's menu items without rebuilding the widget. - * Use in place of layoutMenuItems() when the popup's views need not be reconstructed. - * - * @see #isLayoutRequired(List) - */ - private void updateMenuItems( - List menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) { - mMenuItems.clear(); - for (MenuItem menuItem : menuItems) { - mMenuItems.put(MenuItemRepr.of(menuItem), menuItem); - } - mOnMenuItemClickListener = menuItemClickListener; + public void onToolbarShowTimeout() { + mCallbackWrapper.onToolbarShowTimeout(); } /** - * Returns true if this popup needs a relayout to properly render the specified menu items. + * Show the specified selection toolbar. */ - private boolean isLayoutRequired(List menuItems) { - return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values()); - } + public void show(ShowInfo showInfo) { + debugLog("show() for " + showInfo); - @Override - public void setWidthChanged(boolean widthChanged) { - mWidthChanged = widthChanged; - } + mMenuItems = showInfo.getMenuItems(); + mViewPortOnScreen.set(showInfo.getViewPortOnScreen()); - @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 menuItems, - MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) { - if (isLayoutRequired(menuItems) || mWidthChanged) { - dismiss(); - layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth); - } else { - updateMenuItems(menuItems, menuItemClickListener); + debugLog("show(): layoutRequired=" + showInfo.isLayoutRequired()); + if (showInfo.isLayoutRequired()) { + layoutMenuItems(mMenuItems, showInfo.getSuggestedWidth()); } + Rect contentRect = showInfo.getContentRect(); 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()) { - return; - } - mHidden = false; mDismissed = false; cancelDismissAndHideAnimations(); cancelOverflowAnimations(); - refreshCoordinatesAndOverflowDirection(contentRectOnScreen); preparePopupContent(); - // We need to specify the position in window coordinates. - // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can - // specify the popup position in screen coordinates. - mPopupWindow.showAtLocation( - mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y); - setTouchableSurfaceInsetsComputer(); - runShowAnimation(); + mCallbackWrapper.onShown(createWidgetInfo()); + // TODO(b/215681595): Use Choreographer to coordinate for show between different thread + mShowAnimation.start(); } - @Override - public void dismiss() { + /** + * Dismiss the specified selection toolbar. + */ + public void dismiss(long floatingToolbarToken) { + debugLog("dismiss for " + floatingToolbarToken); if (mDismissed) { return; } - mHidden = false; mDismissed = true; - mHideAnimation.cancel(); - runDismissAnimation(); - setZeroTouchableSurface(); + mHideAnimation.cancel(); + mDismissAnimation.start(); } - @Override - public void hide() { + /** + * Hide the specified selection toolbar. + */ + public void hide(long floatingToolbarToken) { + debugLog("hide for " + floatingToolbarToken); if (!isShowing()) { return; } mHidden = true; - runHideAnimation(); - setZeroTouchableSurface(); + mHideAnimation.start(); } - @Override public boolean isShowing() { return !mDismissed && !mHidden; } - @Override - public boolean isHidden() { - return mHidden; - } - - /** - * Updates the coordinates of this popup. - * 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. - */ private void updateCoordinates(Rect contentRectOnScreen) { Objects.requireNonNull(contentRectOnScreen); - if (!isShowing() || !mPopupWindow.isShowing()) { + if (!isShowing()) { return; } - cancelOverflowAnimations(); refreshCoordinatesAndOverflowDirection(contentRectOnScreen); preparePopupContent(); - // We need to specify the position in window coordinates. - // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can - // specify the popup position in screen coordinates. - mPopupWindow.update( - mCoordsOnWindow.x, mCoordsOnWindow.y, - mPopupWindow.getWidth(), mPopupWindow.getHeight()); + WidgetInfo widgetInfo = createWidgetInfo(); + mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight); + mCallbackWrapper.onWidgetUpdated(widgetInfo); } private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) { - refreshViewPort(); - // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in // landscape. final int x = Math.min( - contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2, - mViewPortOnScreen.right - mPopupWindow.getWidth()); + contentRectOnScreen.centerX() - mPopupWidth / 2, + mViewPortOnScreen.right - mPopupWidth); final int y; @@ -484,7 +411,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { // There is enough space at the top of the content rect for the overflow. // Position above and open upwards. updateOverflowHeight(availableHeightAboveContent - margin); - y = contentRectOnScreen.top - mPopupWindow.getHeight(); + y = contentRectOnScreen.top - mPopupHeight; mOpenOverflowUpwards = true; } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) { @@ -507,7 +434,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { // Position below but open upwards. updateOverflowHeight(availableHeightThroughContentUp - margin); y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin - - mPopupWindow.getHeight(); + - mPopupHeight; mOpenOverflowUpwards = true; } else { // Not enough space. @@ -517,45 +444,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { mOpenOverflowUpwards = false; } } - - // We later specify the location of PopupWindow relative to the attached window. - // The idea here is that 1) we can get the location of a View in both window coordinates - // and screen coordinates, where the offset between them should be equal to the window - // origin, and 2) we can use an arbitrary for this calculation while calculating the - // location of the rootview is supposed to be least expensive. - // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid - // the following calculation. - mParent.getRootView().getLocationOnScreen(mTmpCoords); - int rootViewLeftOnScreen = mTmpCoords[0]; - int rootViewTopOnScreen = mTmpCoords[1]; - mParent.getRootView().getLocationInWindow(mTmpCoords); - int rootViewLeftOnWindow = mTmpCoords[0]; - int rootViewTopOnWindow = mTmpCoords[1]; - int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow; - int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow; - mCoordsOnWindow.set( - Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); - } - - /** - * Performs the "show" animation on the floating popup. - */ - private void runShowAnimation() { - mShowAnimation.start(); - } - - /** - * Performs the "dismiss" animation on the floating popup. - */ - private void runDismissAnimation() { - mDismissAnimation.start(); - } - - /** - * Performs the "hide" animation on the floating popup. - */ - private void runHideAnimation() { - mHideAnimation.start(); + mRelativeCoordsForToolbar.set(x, y); } private void cancelDismissAndHideAnimations() { @@ -628,12 +517,11 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } }; widthAnimation.setInterpolator(mLogAccelerateInterpolator); - widthAnimation.setDuration(getAdjustedDuration(250)); + widthAnimation.setDuration(getAnimationDuration()); heightAnimation.setInterpolator(mFastOutSlowInInterpolator); - heightAnimation.setDuration(getAdjustedDuration(250)); + heightAnimation.setDuration(getAnimationDuration()); overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator); - overflowButtonAnimation.setDuration(getAdjustedDuration(250)); - mOpenOverflowAnimation.getAnimations().clear(); + overflowButtonAnimation.setDuration(getAnimationDuration()); mOpenOverflowAnimation.getAnimations().clear(); mOpenOverflowAnimation.addAnimation(widthAnimation); mOpenOverflowAnimation.addAnimation(heightAnimation); @@ -704,11 +592,11 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } }; widthAnimation.setInterpolator(mFastOutSlowInInterpolator); - widthAnimation.setDuration(getAdjustedDuration(250)); + widthAnimation.setDuration(getAnimationDuration()); heightAnimation.setInterpolator(mLogAccelerateInterpolator); - heightAnimation.setDuration(getAdjustedDuration(250)); + heightAnimation.setDuration(getAnimationDuration()); overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator); - overflowButtonAnimation.setDuration(getAdjustedDuration(250)); + overflowButtonAnimation.setDuration(getAnimationDuration()); mCloseOverflowAnimation.getAnimations().clear(); mCloseOverflowAnimation.addAnimation(widthAnimation); mCloseOverflowAnimation.addAnimation(heightAnimation); @@ -756,7 +644,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { mOverflowPanel.setX(0); // align left } else { mContentContainer.setX(// align right - mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal); + mPopupWidth - containerSize.getWidth() - mMarginHorizontal); mMainPanel.setX(-mContentContainer.getX()); // align right mOverflowButton.setX(0); // align left mOverflowPanel.setX(0); // align left @@ -798,7 +686,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { mOverflowPanel.setX(0); // align left } else { mContentContainer.setX(// align right - mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal); + mPopupWidth - containerSize.getWidth() - mMarginHorizontal); mMainPanel.setX(0); // align left mOverflowButton.setX(// align right containerSize.getWidth() - mOverflowButtonSize.getWidth()); @@ -866,70 +754,23 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { width = Math.max(width, mOverflowPanelSize.getWidth()); height = Math.max(height, mOverflowPanelSize.getHeight()); } - mPopupWindow.setWidth(width + mMarginHorizontal * 2); - mPopupWindow.setHeight(height + mMarginVertical * 2); - maybeComputeTransitionDurationScale(); - } - private void refreshViewPort() { - mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen); + mPopupWidth = width + mMarginHorizontal * 2; + mPopupHeight = height + mMarginVertical * 2; + maybeComputeTransitionDurationScale(); } private int getAdjustedToolbarWidth(int suggestedWidth) { int width = suggestedWidth; - refreshViewPort(); - int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources() + int maximumWidth = mViewPortOnScreen.width() - 2 * mContext.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); if (width <= 0) { - width = mParent.getResources() + width = mContext.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width); } return Math.min(width, maximumWidth); } - /** - * Sets the touchable region of this popup to be zero. This means that all touch events on - * this popup will go through to the surface behind it. - */ - private void setZeroTouchableSurface() { - mTouchableRegion.setEmpty(); - } - - /** - * Sets the touchable region of this popup to be the area occupied by its content. - */ - private void setContentAreaAsTouchableSurface() { - Objects.requireNonNull(mMainPanelSize); - final int width; - final int height; - if (mIsOverflowOpen) { - Objects.requireNonNull(mOverflowPanelSize); - width = mOverflowPanelSize.getWidth(); - height = mOverflowPanelSize.getHeight(); - } else { - width = mMainPanelSize.getWidth(); - height = mMainPanelSize.getHeight(); - } - mTouchableRegion.set( - (int) mContentContainer.getX(), - (int) mContentContainer.getY(), - (int) mContentContainer.getX() + width, - (int) mContentContainer.getY() + height); - } - - /** - * Make the touchable area of this popup be the area specified by mTouchableRegion. - * This should be called after the popup window has been dismissed (dismiss/hide) - * and is probably being re-shown with a new content root view. - */ - private void setTouchableSurfaceInsetsComputer() { - ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView() - .getRootView() - .getViewTreeObserver(); - viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer); - viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer); - } - private boolean isInRTLMode() { return mContext.getApplicationInfo().hasRtlSupport() && mContext.getResources().getConfiguration().getLayoutDirection() @@ -946,18 +787,14 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { * * @return The menu items that are not included in this main panel. */ - public List layoutMainPanelItems( - List menuItems, final int toolbarWidth) { - Objects.requireNonNull(menuItems); - - int availableWidth = toolbarWidth; - - final LinkedList remainingMenuItems = new LinkedList<>(); + private List layoutMainPanelItems(List menuItems, + int toolbarWidth) { + final LinkedList remainingMenuItems = new LinkedList<>(); // add the overflow menu items to the end of the remainingMenuItems list. - final LinkedList overflowMenuItems = new LinkedList(); - for (MenuItem menuItem : menuItems) { + final LinkedList overflowMenuItems = new LinkedList(); + for (ToolbarMenuItem menuItem : menuItems) { if (menuItem.getItemId() != android.R.id.textAssist - && menuItem.requiresOverflow()) { + && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) { overflowMenuItems.add(menuItem); } else { remainingMenuItems.add(menuItem); @@ -968,25 +805,22 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { mMainPanel.removeAllViews(); mMainPanel.setPaddingRelative(0, 0, 0, 0); - int lastGroupId = -1; + int availableWidth = toolbarWidth; boolean isFirstItem = true; while (!remainingMenuItems.isEmpty()) { - final MenuItem menuItem = remainingMenuItems.peek(); - + ToolbarMenuItem menuItem = remainingMenuItems.peek(); // if this is the first item, regardless of requiresOverflow(), it should be // displayed on the main panel. Otherwise all items including this one will be // overflow items, and should be displayed in overflow panel. - if (!isFirstItem && menuItem.requiresOverflow()) { + if (!isFirstItem && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) { break; } - final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist; final View menuItemButton = createMenuItemButton( mContext, menuItem, mIconTextSpacing, showIcon); if (!showIcon && menuItemButton instanceof LinearLayout) { ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER); } - // Adding additional start padding for the first button to even out button spacing. if (isFirstItem) { menuItemButton.setPaddingRelative( @@ -995,7 +829,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { menuItemButton.getPaddingEnd(), menuItemButton.getPaddingBottom()); } - // Adding additional end padding for the last button to even out button spacing. boolean isLastItem = remainingMenuItems.size() == 1; if (isLastItem) { @@ -1005,18 +838,17 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { (int) (1.5 * menuItemButton.getPaddingEnd()), menuItemButton.getPaddingBottom()); } - - menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + menuItemButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); final int menuItemButtonWidth = Math.min( menuItemButton.getMeasuredWidth(), toolbarWidth); - // Check if we can fit an item while reserving space for the overflowButton. final boolean canFitWithOverflow = menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth(); final boolean canFitNoOverflow = isLastItem && menuItemButtonWidth <= availableWidth; if (canFitWithOverflow || canFitNoOverflow) { - setButtonTagAndClickListener(menuItemButton, menuItem); + menuItemButton.setTag(menuItem); + menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener); // Set tooltips for main panel items, but not overflow items (b/35726766). menuItemButton.setTooltipText(menuItem.getTooltipText()); mMainPanel.addView(menuItemButton); @@ -1028,22 +860,20 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } else { break; } - lastGroupId = menuItem.getGroupId(); isFirstItem = false; } - if (!remainingMenuItems.isEmpty()) { // Reserve space for overflowButton. mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0); } - mMainPanelSize = measure(mMainPanel); + return remainingMenuItems; } - private void layoutOverflowPanelItems(List menuItems) { - ArrayAdapter overflowPanelAdapter = - (ArrayAdapter) mOverflowPanel.getAdapter(); + private void layoutOverflowPanelItems(List menuItems) { + ArrayAdapter overflowPanelAdapter = + (ArrayAdapter) mOverflowPanel.getAdapter(); overflowPanelAdapter.clear(); final int size = menuItems.size(); for (int i = 0; i < size; i++) { @@ -1055,7 +885,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } else { mOverflowPanel.setY(mOverflowButtonSize.getHeight()); } - int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth()); int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE); mOverflowPanelSize = new Size(width, height); @@ -1067,7 +896,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { */ private void preparePopupContent() { mContentContainer.removeAllViews(); - // Add views in the specified order so they stack up as expected. // Order: overflowPanel, mainPanel, overflowButton. if (hasOverflow()) { @@ -1078,7 +906,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { mContentContainer.addView(mOverflowButton); } setPanelsStatesAtRestingPosition(); - setContentAreaAsTouchableSurface(); // The positioning of contents in RTL is wrong when the view is first rendered. // Hide the view and post a runnable to recalculate positions and render the view. @@ -1093,12 +920,12 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { * Clears out the panels and their container. Resets their calculated sizes. */ private void clearPanels() { - mOverflowPanelSize = null; - mMainPanelSize = null; mIsOverflowOpen = false; + mMainPanelSize = null; mMainPanel.removeAllViews(); - ArrayAdapter overflowPanelAdapter = - (ArrayAdapter) mOverflowPanel.getAdapter(); + mOverflowPanelSize = null; + ArrayAdapter overflowPanelAdapter = + (ArrayAdapter) mOverflowPanel.getAdapter(); overflowPanelAdapter.clear(); mOverflowPanel.setAdapter(overflowPanelAdapter); mContentContainer.removeAllViews(); @@ -1116,7 +943,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { int overflowWidth = 0; final int count = mOverflowPanel.getAdapter().getCount(); for (int i = 0; i < count; i++) { - MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i); + ToolbarMenuItem menuItem = (ToolbarMenuItem) mOverflowPanel.getAdapter().getItem(i); overflowWidth = Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth); } @@ -1141,29 +968,24 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { + extension; } - private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) { - menuItemButton.setTag(MenuItemRepr.of(menuItem)); - menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener); - } - /** * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.* * animations. See comment about this in the code. */ - private int getAdjustedDuration(int originalDuration) { + private int getAnimationDuration() { if (mTransitionDurationScale < 150) { // For smaller transition, decrease the time. - return Math.max(originalDuration - 50, 0); + return 200; } else if (mTransitionDurationScale > 300) { // For bigger transition, increase the time. - return originalDuration + 50; + return 300; } // Scale the animation duration with getDurationScale(). This allows // android.view.animation.* animations to scale just like android.animation.* animations // when animator duration scale is adjusted in "Developer Options". // For this reason, do not use this method for android.animation.* animations. - return (int) (originalDuration * ValueAnimator.getDurationScale()); + return (int) (250 * ValueAnimator.getDurationScale()); } private void maybeComputeTransitionDurationScale() { @@ -1176,7 +998,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } private ViewGroup createMainPanel() { - ViewGroup mainPanel = new LinearLayout(mContext) { + return new LinearLayout(mContext) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (isOverflowAnimating()) { @@ -1195,7 +1017,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { return isOverflowAnimating(); } }; - return mainPanel; } private ImageButton createOverflowButton() { @@ -1203,6 +1024,12 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { .inflate(R.layout.floating_popup_overflow_button, null); overflowButton.setImageDrawable(mOverflow); overflowButton.setOnClickListener(v -> { + if (isShowing()) { + preparePopupContent(); + WidgetInfo widgetInfo = createWidgetInfo(); + mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight); + mCallbackWrapper.onWidgetUpdated(widgetInfo); + } if (mIsOverflowOpen) { overflowButton.setImageDrawable(mToOverflow); mToOverflow.start(); @@ -1224,7 +1051,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { overflowPanel.setDividerHeight(0); final ArrayAdapter adapter = - new ArrayAdapter(mContext, 0) { + new ArrayAdapter(mContext, 0) { @Override public View getView(int position, View convertView, ViewGroup parent) { return mOverflowPanelViewHelper.getView( @@ -1232,14 +1059,11 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } }; overflowPanel.setAdapter(adapter); - overflowPanel.setOnItemClickListener((parent, view, position, id) -> { - MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position); - if (mOnMenuItemClickListener != null) { - mOnMenuItemClickListener.onMenuItemClick(menuItem); - } + ToolbarMenuItem menuItem = + (ToolbarMenuItem) overflowPanel.getAdapter().getItem(position); + mCallbackWrapper.onMenuItemClicked(menuItem); }); - return overflowPanel; } @@ -1252,7 +1076,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { } private Animation.AnimationListener createOverflowAnimationListener() { - Animation.AnimationListener listener = new Animation.AnimationListener() { + return new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { // Disable the overflow button while it's animating. @@ -1270,7 +1094,6 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { // actually ends. mContentContainer.post(() -> { setPanelsStatesAtRestingPosition(); - setContentAreaAsTouchableSurface(); }); } @@ -1278,12 +1101,11 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { public void onAnimationRepeat(Animation animation) { } }; - return listener; } private static Size measure(View view) { Preconditions.checkState(view.getParent() == null); - view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); return new Size(view.getMeasuredWidth(), view.getMeasuredHeight()); } @@ -1372,13 +1194,11 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { * A helper for generating views for the overflow panel. */ private static final class OverflowPanelViewHelper { - + private final Context mContext; private final View mCalculator; private final int mIconTextSpacing; private final int mSidePadding; - private final Context mContext; - OverflowPanelViewHelper(Context context, int iconTextSpacing) { mContext = Objects.requireNonNull(context); mIconTextSpacing = iconTextSpacing; @@ -1387,7 +1207,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { mCalculator = createMenuButton(null); } - public View getView(MenuItem menuItem, int minimumWidth, View convertView) { + public View getView(ToolbarMenuItem menuItem, int minimumWidth, View convertView) { Objects.requireNonNull(menuItem); if (convertView != null) { updateMenuItemButton( @@ -1399,7 +1219,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { return convertView; } - public int calculateWidth(MenuItem menuItem) { + public int calculateWidth(ToolbarMenuItem menuItem) { updateMenuItemButton( mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); mCalculator.measure( @@ -1407,18 +1227,18 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { return mCalculator.getMeasuredWidth(); } - private View createMenuButton(MenuItem menuItem) { + private View createMenuButton(ToolbarMenuItem menuItem) { View button = createMenuItemButton( mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); button.setPadding(mSidePadding, 0, mSidePadding, 0); return button; } - private boolean shouldShowIcon(MenuItem menuItem) { + private boolean shouldShowIcon(ToolbarMenuItem menuItem) { if (menuItem != null) { return menuItem.getGroupId() == android.R.id.textAssist; } - return false; + return false; } } @@ -1426,7 +1246,7 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { * Creates and returns a menu button for the specified menu item. */ private static View createMenuItemButton( - Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) { + Context context, ToolbarMenuItem menuItem, int iconTextSpacing, boolean showIcon) { final View menuItemButton = LayoutInflater.from(context) .inflate(R.layout.floating_popup_menu_button, null); if (menuItem != null) { @@ -1438,8 +1258,8 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { /** * Updates the specified menu item button with the specified menu item data. */ - private static void updateMenuItemButton( - View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) { + private static void updateMenuItemButton(View menuItemButton, ToolbarMenuItem menuItem, + int iconTextSpacing, boolean showIcon) { final TextView buttonText = menuItemButton.findViewById( R.id.floating_toolbar_menu_item_text); buttonText.setEllipsize(null); @@ -1453,15 +1273,12 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { R.id.floating_toolbar_menu_item_image); if (menuItem.getIcon() == null || !showIcon) { buttonIcon.setVisibility(View.GONE); - if (buttonText != null) { - buttonText.setPaddingRelative(0, 0, 0, 0); - } + buttonText.setPaddingRelative(0, 0, 0, 0); } else { buttonIcon.setVisibility(View.VISIBLE); - buttonIcon.setImageDrawable(menuItem.getIcon()); - if (buttonText != null) { - buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0); - } + buttonIcon.setImageDrawable( + menuItem.getIcon().loadDrawable(menuItemButton.getContext())); + buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0); } final CharSequence contentDescription = menuItem.getContentDescription(); if (TextUtils.isEmpty(contentDescription)) { @@ -1476,27 +1293,11 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { .inflate(R.layout.floating_popup_container, null); contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - contentContainer.setTag("floating_toolbar"); + contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } - private static PopupWindow createPopupWindow(ViewGroup content) { - ViewGroup popupContentHolder = new LinearLayout(content.getContext()); - PopupWindow popupWindow = new PopupWindow(popupContentHolder); - // TODO: Use .setIsLaidOutInScreen(true) instead of .setClippingEnabled(false) - // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects. - popupWindow.setClippingEnabled(false); - popupWindow.setWindowLayoutType( - WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); - popupWindow.setAnimationStyle(0); - popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - content.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - popupContentHolder.addView(content); - return popupWindow; - } - /** * Creates an "appear" animation for the specified view. * @@ -1522,7 +1323,9 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { animation.playTogether( ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100)); animation.setStartDelay(startDelay); - animation.addListener(listener); + if (listener != null) { + animation.addListener(listener); + } return animation; } @@ -1538,82 +1341,9 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup { return new ContextThemeWrapper(originalContext, themeId); } - /** - * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup. - */ - @VisibleForTesting - public static final class MenuItemRepr { - - public final int itemId; - public final int groupId; - @Nullable public final String title; - @Nullable private final Drawable mIcon; - - private MenuItemRepr( - int itemId, int groupId, @Nullable CharSequence title, @Nullable Drawable icon) { - this.itemId = itemId; - this.groupId = groupId; - this.title = (title == null) ? null : title.toString(); - mIcon = icon; - } - - /** - * Creates an instance of MenuItemRepr for the specified menu item. - */ - public static MenuItemRepr of(MenuItem menuItem) { - return new MenuItemRepr( - menuItem.getItemId(), - menuItem.getGroupId(), - menuItem.getTitle(), - menuItem.getIcon()); - } - - /** - * Returns this object's hashcode. - */ - @Override - public int hashCode() { - return Objects.hash(itemId, groupId, title, mIcon); - } - - /** - * Returns true if this object is the same as the specified object. - */ - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof MenuItemRepr)) { - return false; - } - final MenuItemRepr other = (MenuItemRepr) o; - return itemId == other.itemId - && groupId == other.groupId - && TextUtils.equals(title, other.title) - // Many Drawables (icons) do not implement equals(). Using equals() here instead - // of reference comparisons in case a Drawable subclass implements equals(). - && Objects.equals(mIcon, other.mIcon); - } - - /** - * Returns true if the two menu item collections are the same based on MenuItemRepr. - */ - public static boolean reprEquals( - Collection menuItems1, Collection menuItems2) { - if (menuItems1.size() != menuItems2.size()) { - return false; - } - - final Iterator menuItems2Iter = menuItems2.iterator(); - for (MenuItem menuItem1 : menuItems1) { - final MenuItem menuItem2 = menuItems2Iter.next(); - if (!MenuItemRepr.of(menuItem1).equals(MenuItemRepr.of(menuItem2))) { - return false; - } - } - - return true; + private static void debugLog(String message) { + if (Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.DEBUG)) { + Log.v(TAG, message); } } } diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java index 6468183880f44..a10b6a8ac8cbb 100644 --- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java +++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java @@ -30,14 +30,6 @@ public interface SelectionToolbarRenderCallback { * The selection toolbar is shown. */ void onShown(WidgetInfo widgetInfo); - /** - * The selection toolbar is hidden. - */ - void onHidden(long widgetToken); - /** - * The selection toolbar is dismissed. - */ - void onDismissed(long widgetToken); /** * The selection toolbar has changed. */ @@ -46,6 +38,10 @@ public interface SelectionToolbarRenderCallback { * The menu item on the selection toolbar has been clicked. */ void onMenuItemClicked(ToolbarMenuItem item); + /** + * The toolbar doesn't be dismissed after showing on a given timeout. + */ + void onToolbarShowTimeout(); /** * The error occurred when operating on the selection toolbar. */ diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java index 6f66c9f1ed3ad..f33feaec6dde1 100644 --- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java +++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java @@ -28,6 +28,8 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; import android.view.selectiontoolbar.ISelectionToolbarCallback; import android.view.selectiontoolbar.ShowInfo; import android.view.selectiontoolbar.ToolbarMenuItem; @@ -42,6 +44,10 @@ public abstract class SelectionToolbarRenderService extends Service { private static final String TAG = "SelectionToolbarRenderService"; + // TODO(b/215497659): read from DeviceConfig + // The timeout to clean the cache if the client forgot to call dismiss() + private static final int CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS = 10 * 60 * 1000; // 10 minutes + /** * The {@link Intent} that must be declared as handled by the service. * @@ -53,6 +59,10 @@ public abstract class SelectionToolbarRenderService extends Service { "android.service.selectiontoolbar.SelectionToolbarRenderService"; private Handler mHandler; + private ISelectionToolbarRenderServiceCallback mServiceCallback; + + private final SparseArray> mCache = + new SparseArray<>(); /** * Binder to receive calls from system server. @@ -61,10 +71,18 @@ public abstract class SelectionToolbarRenderService extends Service { new ISelectionToolbarRenderService.Stub() { @Override - public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) { + public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) { + if (mCache.indexOfKey(callingUid) < 0) { + mCache.put(callingUid, new Pair<>(new RemoteCallbackWrapper(callback), + new CleanCacheRunnable(callingUid))); + } + Pair toolbarPair = mCache.get(callingUid); + CleanCacheRunnable cleanRunnable = toolbarPair.second; + mHandler.removeCallbacks(cleanRunnable); mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow, - SelectionToolbarRenderService.this, showInfo, - new RemoteCallbackWrapper(callback))); + SelectionToolbarRenderService.this, callingUid, showInfo, + toolbarPair.first)); + mHandler.postDelayed(cleanRunnable, CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS); } @Override @@ -74,9 +92,20 @@ public abstract class SelectionToolbarRenderService extends Service { } @Override - public void onDismiss(long widgetToken) { + public void onDismiss(int callingUid, long widgetToken) { mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss, SelectionToolbarRenderService.this, widgetToken)); + Pair toolbarPair = mCache.get(callingUid); + if (toolbarPair != null) { + mHandler.removeCallbacks(toolbarPair.second); + mCache.remove(callingUid); + } + } + + @Override + public void onConnected(IBinder callback) { + mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::handleOnConnected, + SelectionToolbarRenderService.this, callback)); } }; @@ -97,11 +126,28 @@ public abstract class SelectionToolbarRenderService extends Service { return null; } + private void handleOnConnected(@NonNull IBinder callback) { + mServiceCallback = ISelectionToolbarRenderServiceCallback.Stub.asInterface(callback); + } + + protected void transferTouch(@NonNull IBinder source, @NonNull IBinder target) { + final ISelectionToolbarRenderServiceCallback callback = mServiceCallback; + if (callback == null) { + Log.e(TAG, "transferTouch(): no server callback"); + return; + } + try { + callback.transferTouch(source, target); + } catch (RemoteException e) { + // no-op + } + } /** * Called when showing the selection toolbar. */ - public abstract void onShow(ShowInfo showInfo, RemoteCallbackWrapper callbackWrapper); + public abstract void onShow(int callingUid, ShowInfo showInfo, + RemoteCallbackWrapper callbackWrapper); /** * Called when hiding the selection toolbar. @@ -115,13 +161,22 @@ public abstract class SelectionToolbarRenderService extends Service { public abstract void onDismiss(long widgetToken); /** - * Add avadoc. + * Called when showing the selection toolbar for a specific timeout. This avoids the client + * forgot to call dismiss to clean the state. + */ + public void onToolbarShowTimeout(int callingUid) { + // no-op + } + + /** + * Callback to notify the client toolbar events. */ public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback { private final ISelectionToolbarCallback mRemoteCallback; RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) { + // TODO(b/215497659): handle if the binder dies. mRemoteCallback = remoteCallback; } @@ -130,25 +185,16 @@ public abstract class SelectionToolbarRenderService extends Service { try { mRemoteCallback.onShown(widgetInfo); } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + // no-op } } @Override - public void onHidden(long widgetToken) { + public void onToolbarShowTimeout() { try { - mRemoteCallback.onHidden(widgetToken); + mRemoteCallback.onToolbarShowTimeout(); } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } - } - - @Override - public void onDismissed(long widgetToken) { - try { - mRemoteCallback.onDismissed(widgetToken); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + // no-op } } @@ -157,7 +203,7 @@ public abstract class SelectionToolbarRenderService extends Service { try { mRemoteCallback.onWidgetUpdated(widgetInfo); } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + // no-op } } @@ -166,7 +212,7 @@ public abstract class SelectionToolbarRenderService extends Service { try { mRemoteCallback.onMenuItemClicked(item); } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + // no-op } } @@ -175,8 +221,37 @@ public abstract class SelectionToolbarRenderService extends Service { try { mRemoteCallback.onError(errorCode); } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + // no-op } } } + + private class CleanCacheRunnable implements Runnable { + + int mCleanUid; + + CleanCacheRunnable(int cleanUid) { + mCleanUid = cleanUid; + } + + @Override + public void run() { + Pair toolbarPair = mCache.get(mCleanUid); + if (toolbarPair != null) { + Log.w(TAG, "CleanCacheRunnable: remove " + mCleanUid + " from cache."); + mCache.remove(mCleanUid); + onToolbarShowTimeout(mCleanUid); + } + } + } + + /** + * A listener to notify the service to the transfer touch focus. + */ + public interface TransferTouchListener { + /** + * Notify the service to transfer the touch focus. + */ + void onTransferTouch(IBinder source, IBinder target); + } } diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl index 48af7b96b1955..aaeb12012f68d 100644 --- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl +++ b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl @@ -25,9 +25,8 @@ import android.view.selectiontoolbar.WidgetInfo; */ oneway interface ISelectionToolbarCallback { void onShown(in WidgetInfo info); - void onHidden(long widgetToken); - void onDismissed(long widgetToken); void onWidgetUpdated(in WidgetInfo info); + void onToolbarShowTimeout(); void onMenuItemClicked(in ToolbarMenuItem item); void onError(int errorCode); } diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.java b/core/java/android/view/selectiontoolbar/SelectionContext.java deleted file mode 100644 index 21b8d8f11d258..0000000000000 --- a/core/java/android/view/selectiontoolbar/SelectionContext.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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 android.view.selectiontoolbar; - -import android.annotation.NonNull; -import android.os.Parcelable; - -import com.android.internal.util.DataClass; - -/** - * The class holds information for a selection. - * - * @hide - */ -@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true) -public final class SelectionContext implements Parcelable { - - /** - * The start index of a selection. - */ - private final int mStartIndex; - - /** - * The end index of a selection. - */ - private final int mEndIndex; - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/SelectionContext.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - /* package-private */ SelectionContext( - int startIndex, - int endIndex) { - this.mStartIndex = startIndex; - this.mEndIndex = endIndex; - - // onConstructed(); // You can define this method to get a callback - } - - /** - * The start index of a selection. - */ - @DataClass.Generated.Member - public int getStartIndex() { - return mStartIndex; - } - - /** - * The end index of a selection. - */ - @DataClass.Generated.Member - public int getEndIndex() { - return mEndIndex; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "SelectionContext { " + - "startIndex = " + mStartIndex + ", " + - "endIndex = " + mEndIndex + - " }"; - } - - @Override - @DataClass.Generated.Member - public boolean equals(@android.annotation.Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(SelectionContext other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } - - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - SelectionContext that = (SelectionContext) o; - //noinspection PointlessBooleanExpression - return true - && mStartIndex == that.mStartIndex - && mEndIndex == that.mEndIndex; - } - - @Override - @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - - int _hash = 1; - _hash = 31 * _hash + mStartIndex; - _hash = 31 * _hash + mEndIndex; - return _hash; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - dest.writeInt(mStartIndex); - dest.writeInt(mEndIndex); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ SelectionContext(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - int startIndex = in.readInt(); - int endIndex = in.readInt(); - - this.mStartIndex = startIndex; - this.mEndIndex = endIndex; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public SelectionContext[] newArray(int size) { - return new SelectionContext[size]; - } - - @Override - public SelectionContext createFromParcel(@NonNull android.os.Parcel in) { - return new SelectionContext(in); - } - }; - - /** - * A builder for {@link SelectionContext} - */ - @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member - public static final class Builder { - - private int mStartIndex; - private int mEndIndex; - - private long mBuilderFieldsSet = 0L; - - /** - * Creates a new Builder. - * - * @param startIndex - * The start index of a selection. - * @param endIndex - * The end index of a selection. - */ - public Builder( - int startIndex, - int endIndex) { - mStartIndex = startIndex; - mEndIndex = endIndex; - } - - /** - * The start index of a selection. - */ - @DataClass.Generated.Member - public @NonNull Builder setStartIndex(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mStartIndex = value; - return this; - } - - /** - * The end index of a selection. - */ - @DataClass.Generated.Member - public @NonNull Builder setEndIndex(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x2; - mEndIndex = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull SelectionContext build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used - - SelectionContext o = new SelectionContext( - mStartIndex, - mEndIndex); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } - - @DataClass.Generated( - time = 1639488292248L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/SelectionContext.java", - inputSignatures = "private final int mStartIndex\nprivate final int mEndIndex\nclass SelectionContext extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java index ba45b85e08a3f..6de031628768e 100644 --- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java +++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java @@ -47,6 +47,16 @@ public final class SelectionToolbarManager { private static final String REMOTE_SELECTION_TOOLBAR_ENABLED = "remote_selection_toolbar_enabled"; + /** + * Used to mark a toolbar that has no toolbar token id. + */ + public static final long NO_TOOLBAR_ID = 0; + + /** + * The error code that do not allow to create multiple toolbar. + */ + public static final int ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR = 1; + @NonNull private final Context mContext; private final ISelectionToolbarManager mService; diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java index bbbd5c0df0b40..594b6bc7400a0 100644 --- a/core/java/android/view/selectiontoolbar/ShowInfo.java +++ b/core/java/android/view/selectiontoolbar/ShowInfo.java @@ -17,10 +17,14 @@ package android.view.selectiontoolbar; import android.annotation.NonNull; +import android.graphics.Rect; +import android.os.IBinder; import android.os.Parcelable; import com.android.internal.util.DataClass; +import java.util.List; + /** * The class holds menu information for render service to render the selection toolbar. @@ -29,14 +33,47 @@ import com.android.internal.util.DataClass; */ @DataClass(genToString = true, genEqualsHashCode = true) public final class ShowInfo implements Parcelable { + /** * The token that is used to identify the selection toolbar. This is initially set to 0 * until a selection toolbar has been created for the showToolbar request. */ private final long mWidgetToken; - // TODO: add members when the code really uses it + /** + * If the toolbar menu items need to be re-layout. + */ + private final boolean mLayoutRequired; + /** + * The menu items to be rendered in the selection toolbar. + */ + @NonNull + private final List mMenuItems; + + /** + * A rect specifying where the selection toolbar on the screen. + */ + @NonNull + private final Rect mContentRect; + + /** + * A recommended maximum suggested width of the selection toolbar. + */ + private final int mSuggestedWidth; + + /** + * The portion of the screen that is available to the selection toolbar. + */ + @NonNull + private final Rect mViewPortOnScreen; + + /** + * The host application's input token, this allows the remote render service to transfer + * the touch focus to the host application. + */ + @NonNull + private final IBinder mHostInputToken; @@ -57,24 +94,108 @@ public final class ShowInfo implements Parcelable { * Creates a new ShowInfo. * * @param widgetToken - * The token that is used to identify the selection toolbar. + * The token that is used to identify the selection toolbar. This is initially set to 0 + * until a selection toolbar has been created for the showToolbar request. + * @param layoutRequired + * If the toolbar menu items need to be re-layout. + * @param menuItems + * The menu items to be rendered in the selection toolbar. + * @param contentRect + * A rect specifying where the selection toolbar on the screen. + * @param suggestedWidth + * A recommended maximum suggested width of the selection toolbar. + * @param viewPortOnScreen + * The portion of the screen that is available to the selection toolbar. + * @param hostInputToken + * The host application's input token, this allows the remote render service to transfer + * the touch focus to the host application. */ @DataClass.Generated.Member public ShowInfo( - long widgetToken) { + long widgetToken, + boolean layoutRequired, + @NonNull List menuItems, + @NonNull Rect contentRect, + int suggestedWidth, + @NonNull Rect viewPortOnScreen, + @NonNull IBinder hostInputToken) { this.mWidgetToken = widgetToken; + this.mLayoutRequired = layoutRequired; + this.mMenuItems = menuItems; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMenuItems); + this.mContentRect = contentRect; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mContentRect); + this.mSuggestedWidth = suggestedWidth; + this.mViewPortOnScreen = viewPortOnScreen; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mViewPortOnScreen); + this.mHostInputToken = hostInputToken; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostInputToken); // onConstructed(); // You can define this method to get a callback } /** - * The token that is used to identify the selection toolbar. + * The token that is used to identify the selection toolbar. This is initially set to 0 + * until a selection toolbar has been created for the showToolbar request. */ @DataClass.Generated.Member public long getWidgetToken() { return mWidgetToken; } + /** + * If the toolbar menu items need to be re-layout. + */ + @DataClass.Generated.Member + public boolean isLayoutRequired() { + return mLayoutRequired; + } + + /** + * The menu items to be rendered in the selection toolbar. + */ + @DataClass.Generated.Member + public @NonNull List getMenuItems() { + return mMenuItems; + } + + /** + * A rect specifying where the selection toolbar on the screen. + */ + @DataClass.Generated.Member + public @NonNull Rect getContentRect() { + return mContentRect; + } + + /** + * A recommended maximum suggested width of the selection toolbar. + */ + @DataClass.Generated.Member + public int getSuggestedWidth() { + return mSuggestedWidth; + } + + /** + * The portion of the screen that is available to the selection toolbar. + */ + @DataClass.Generated.Member + public @NonNull Rect getViewPortOnScreen() { + return mViewPortOnScreen; + } + + /** + * The host application's input token, this allows the remote render service to transfer + * the touch focus to the host application. + */ + @DataClass.Generated.Member + public @NonNull IBinder getHostInputToken() { + return mHostInputToken; + } + @Override @DataClass.Generated.Member public String toString() { @@ -82,7 +203,13 @@ public final class ShowInfo implements Parcelable { // String fieldNameToString() { ... } return "ShowInfo { " + - "widgetToken = " + mWidgetToken + + "widgetToken = " + mWidgetToken + ", " + + "layoutRequired = " + mLayoutRequired + ", " + + "menuItems = " + mMenuItems + ", " + + "contentRect = " + mContentRect + ", " + + "suggestedWidth = " + mSuggestedWidth + ", " + + "viewPortOnScreen = " + mViewPortOnScreen + ", " + + "hostInputToken = " + mHostInputToken + " }"; } @@ -99,7 +226,13 @@ public final class ShowInfo implements Parcelable { ShowInfo that = (ShowInfo) o; //noinspection PointlessBooleanExpression return true - && mWidgetToken == that.mWidgetToken; + && mWidgetToken == that.mWidgetToken + && mLayoutRequired == that.mLayoutRequired + && java.util.Objects.equals(mMenuItems, that.mMenuItems) + && java.util.Objects.equals(mContentRect, that.mContentRect) + && mSuggestedWidth == that.mSuggestedWidth + && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen) + && java.util.Objects.equals(mHostInputToken, that.mHostInputToken); } @Override @@ -110,6 +243,12 @@ public final class ShowInfo implements Parcelable { int _hash = 1; _hash = 31 * _hash + Long.hashCode(mWidgetToken); + _hash = 31 * _hash + Boolean.hashCode(mLayoutRequired); + _hash = 31 * _hash + java.util.Objects.hashCode(mMenuItems); + _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect); + _hash = 31 * _hash + mSuggestedWidth; + _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen); + _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken); return _hash; } @@ -119,7 +258,15 @@ public final class ShowInfo implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } + byte flg = 0; + if (mLayoutRequired) flg |= 0x2; + dest.writeByte(flg); dest.writeLong(mWidgetToken); + dest.writeParcelableList(mMenuItems, flags); + dest.writeTypedObject(mContentRect, flags); + dest.writeInt(mSuggestedWidth); + dest.writeTypedObject(mViewPortOnScreen, flags); + dest.writeStrongBinder(mHostInputToken); } @Override @@ -133,9 +280,31 @@ public final class ShowInfo implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } + byte flg = in.readByte(); + boolean layoutRequired = (flg & 0x2) != 0; long widgetToken = in.readLong(); + List menuItems = new java.util.ArrayList<>(); + in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader()); + Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR); + int suggestedWidth = in.readInt(); + Rect viewPortOnScreen = (Rect) in.readTypedObject(Rect.CREATOR); + IBinder hostInputToken = (IBinder) in.readStrongBinder(); this.mWidgetToken = widgetToken; + this.mLayoutRequired = layoutRequired; + this.mMenuItems = menuItems; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMenuItems); + this.mContentRect = contentRect; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mContentRect); + this.mSuggestedWidth = suggestedWidth; + this.mViewPortOnScreen = viewPortOnScreen; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mViewPortOnScreen); + this.mHostInputToken = hostInputToken; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHostInputToken); // onConstructed(); // You can define this method to get a callback } @@ -155,10 +324,10 @@ public final class ShowInfo implements Parcelable { }; @DataClass.Generated( - time = 1639488262761L, + time = 1643186262604L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java", - inputSignatures = "private final long mWidgetToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") + inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java index 5af232c7a34d4..89347c6133101 100644 --- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java +++ b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java @@ -18,7 +18,9 @@ package android.view.selectiontoolbar; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.drawable.Icon; import android.os.Parcelable; +import android.view.MenuItem; import com.android.internal.util.DataClass; @@ -30,11 +32,85 @@ import com.android.internal.util.DataClass; @DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true) public final class ToolbarMenuItem implements Parcelable { + /** + * The priority of menu item is unknown. + */ + public static final int PRIORITY_UNKNOWN = 0; + + /** + * The priority of menu item is shown in primary selection toolbar. + */ + public static final int PRIORITY_PRIMARY = 1; + + /** + * The priority of menu item is shown in overflow selection toolbar. + */ + public static final int PRIORITY_OVERFLOW = 2; + /** * The id of the menu item. + * + * @see MenuItem#getItemId() */ private final int mItemId; + /** + * The title of the menu item. + * + * @see MenuItem#getTitle() + */ + @NonNull + private final CharSequence mTitle; + + /** + * The content description of the menu item. + * + * @see MenuItem#getContentDescription() + */ + @Nullable + private final CharSequence mContentDescription; + + /** + * The group id of the menu item. + * + * @see MenuItem#getGroupId() + */ + private final int mGroupId; + + /** + * The icon id of the menu item. + * + * @see MenuItem#getIcon() + */ + @Nullable + private final Icon mIcon; + + /** + * The tooltip text of the menu item. + * + * @see MenuItem#getTooltipText() + */ + @Nullable + private final CharSequence mTooltipText; + + /** + * The priority of the menu item used to display the order of the menu item. + */ + private final int mPriority; + + /** + * Returns the priority from a given {@link MenuItem}. + */ + public static int getPriorityFromMenuItem(MenuItem menuItem) { + if (menuItem.requiresActionButton()) { + return PRIORITY_PRIMARY; + } else if (menuItem.requiresOverflow()) { + return PRIORITY_OVERFLOW; + } + return PRIORITY_UNKNOWN; + } + + // Code below generated by codegen v1.0.23. @@ -50,22 +126,118 @@ public final class ToolbarMenuItem implements Parcelable { //@formatter:off + @android.annotation.IntDef(prefix = "PRIORITY_", value = { + PRIORITY_UNKNOWN, + PRIORITY_PRIMARY, + PRIORITY_OVERFLOW + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Priority {} + + @DataClass.Generated.Member + public static String priorityToString(@Priority int value) { + switch (value) { + case PRIORITY_UNKNOWN: + return "PRIORITY_UNKNOWN"; + case PRIORITY_PRIMARY: + return "PRIORITY_PRIMARY"; + case PRIORITY_OVERFLOW: + return "PRIORITY_OVERFLOW"; + default: return Integer.toHexString(value); + } + } + @DataClass.Generated.Member /* package-private */ ToolbarMenuItem( - int itemId) { + int itemId, + @NonNull CharSequence title, + @Nullable CharSequence contentDescription, + int groupId, + @Nullable Icon icon, + @Nullable CharSequence tooltipText, + int priority) { this.mItemId = itemId; + this.mTitle = title; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mTitle); + this.mContentDescription = contentDescription; + this.mGroupId = groupId; + this.mIcon = icon; + this.mTooltipText = tooltipText; + this.mPriority = priority; // onConstructed(); // You can define this method to get a callback } /** * The id of the menu item. + * + * @see MenuItem#getItemId() */ @DataClass.Generated.Member public int getItemId() { return mItemId; } + /** + * The title of the menu item. + * + * @see MenuItem#getTitle() + */ + @DataClass.Generated.Member + public @NonNull CharSequence getTitle() { + return mTitle; + } + + /** + * The content description of the menu item. + * + * @see MenuItem#getContentDescription() + */ + @DataClass.Generated.Member + public @Nullable CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * The group id of the menu item. + * + * @see MenuItem#getGroupId() + */ + @DataClass.Generated.Member + public int getGroupId() { + return mGroupId; + } + + /** + * The icon id of the menu item. + * + * @see MenuItem#getIcon() + */ + @DataClass.Generated.Member + public @Nullable Icon getIcon() { + return mIcon; + } + + /** + * The tooltip text of the menu item. + * + * @see MenuItem#getTooltipText() + */ + @DataClass.Generated.Member + public @Nullable CharSequence getTooltipText() { + return mTooltipText; + } + + /** + * The priority of the menu item used to display the order of the menu item. + */ + @DataClass.Generated.Member + public int getPriority() { + return mPriority; + } + @Override @DataClass.Generated.Member public String toString() { @@ -73,7 +245,13 @@ public final class ToolbarMenuItem implements Parcelable { // String fieldNameToString() { ... } return "ToolbarMenuItem { " + - "itemId = " + mItemId + + "itemId = " + mItemId + ", " + + "title = " + mTitle + ", " + + "contentDescription = " + mContentDescription + ", " + + "groupId = " + mGroupId + ", " + + "icon = " + mIcon + ", " + + "tooltipText = " + mTooltipText + ", " + + "priority = " + mPriority + " }"; } @@ -90,7 +268,13 @@ public final class ToolbarMenuItem implements Parcelable { ToolbarMenuItem that = (ToolbarMenuItem) o; //noinspection PointlessBooleanExpression return true - && mItemId == that.mItemId; + && mItemId == that.mItemId + && java.util.Objects.equals(mTitle, that.mTitle) + && java.util.Objects.equals(mContentDescription, that.mContentDescription) + && mGroupId == that.mGroupId + && java.util.Objects.equals(mIcon, that.mIcon) + && java.util.Objects.equals(mTooltipText, that.mTooltipText) + && mPriority == that.mPriority; } @Override @@ -101,6 +285,12 @@ public final class ToolbarMenuItem implements Parcelable { int _hash = 1; _hash = 31 * _hash + mItemId; + _hash = 31 * _hash + java.util.Objects.hashCode(mTitle); + _hash = 31 * _hash + java.util.Objects.hashCode(mContentDescription); + _hash = 31 * _hash + mGroupId; + _hash = 31 * _hash + java.util.Objects.hashCode(mIcon); + _hash = 31 * _hash + java.util.Objects.hashCode(mTooltipText); + _hash = 31 * _hash + mPriority; return _hash; } @@ -110,7 +300,18 @@ public final class ToolbarMenuItem implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } + byte flg = 0; + if (mContentDescription != null) flg |= 0x4; + if (mIcon != null) flg |= 0x10; + if (mTooltipText != null) flg |= 0x20; + dest.writeByte(flg); dest.writeInt(mItemId); + dest.writeCharSequence(mTitle); + if (mContentDescription != null) dest.writeCharSequence(mContentDescription); + dest.writeInt(mGroupId); + if (mIcon != null) dest.writeTypedObject(mIcon, flags); + if (mTooltipText != null) dest.writeCharSequence(mTooltipText); + dest.writeInt(mPriority); } @Override @@ -124,9 +325,24 @@ public final class ToolbarMenuItem implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } + byte flg = in.readByte(); int itemId = in.readInt(); + CharSequence title = (CharSequence) in.readCharSequence(); + CharSequence contentDescription = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence(); + int groupId = in.readInt(); + Icon icon = (flg & 0x10) == 0 ? null : (Icon) in.readTypedObject(Icon.CREATOR); + CharSequence tooltipText = (flg & 0x20) == 0 ? null : (CharSequence) in.readCharSequence(); + int priority = in.readInt(); this.mItemId = itemId; + this.mTitle = title; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mTitle); + this.mContentDescription = contentDescription; + this.mGroupId = groupId; + this.mIcon = icon; + this.mTooltipText = tooltipText; + this.mPriority = priority; // onConstructed(); // You can define this method to get a callback } @@ -153,6 +369,12 @@ public final class ToolbarMenuItem implements Parcelable { public static final class Builder { private int mItemId; + private @NonNull CharSequence mTitle; + private @Nullable CharSequence mContentDescription; + private int mGroupId; + private @Nullable Icon mIcon; + private @Nullable CharSequence mTooltipText; + private int mPriority; private long mBuilderFieldsSet = 0L; @@ -161,14 +383,42 @@ public final class ToolbarMenuItem implements Parcelable { * * @param itemId * The id of the menu item. + * @param title + * The title of the menu item. + * @param contentDescription + * The content description of the menu item. + * @param groupId + * The group id of the menu item. + * @param icon + * The icon id of the menu item. + * @param tooltipText + * The tooltip text of the menu item. + * @param priority + * The priority of the menu item used to display the order of the menu item. */ public Builder( - int itemId) { + int itemId, + @NonNull CharSequence title, + @Nullable CharSequence contentDescription, + int groupId, + @Nullable Icon icon, + @Nullable CharSequence tooltipText, + int priority) { mItemId = itemId; + mTitle = title; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mTitle); + mContentDescription = contentDescription; + mGroupId = groupId; + mIcon = icon; + mTooltipText = tooltipText; + mPriority = priority; } /** * The id of the menu item. + * + * @see MenuItem#getItemId() */ @DataClass.Generated.Member public @NonNull Builder setItemId(int value) { @@ -178,18 +428,100 @@ public final class ToolbarMenuItem implements Parcelable { return this; } + /** + * The title of the menu item. + * + * @see MenuItem#getTitle() + */ + @DataClass.Generated.Member + public @NonNull Builder setTitle(@NonNull CharSequence value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mTitle = value; + return this; + } + + /** + * The content description of the menu item. + * + * @see MenuItem#getContentDescription() + */ + @DataClass.Generated.Member + public @NonNull Builder setContentDescription(@NonNull CharSequence value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mContentDescription = value; + return this; + } + + /** + * The group id of the menu item. + * + * @see MenuItem#getGroupId() + */ + @DataClass.Generated.Member + public @NonNull Builder setGroupId(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mGroupId = value; + return this; + } + + /** + * The icon id of the menu item. + * + * @see MenuItem#getIcon() + */ + @DataClass.Generated.Member + public @NonNull Builder setIcon(@NonNull Icon value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mIcon = value; + return this; + } + + /** + * The tooltip text of the menu item. + * + * @see MenuItem#getTooltipText() + */ + @DataClass.Generated.Member + public @NonNull Builder setTooltipText(@NonNull CharSequence value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mTooltipText = value; + return this; + } + + /** + * The priority of the menu item used to display the order of the menu item. + */ + @DataClass.Generated.Member + public @NonNull Builder setPriority(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; + mPriority = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull ToolbarMenuItem build() { checkNotUsed(); - mBuilderFieldsSet |= 0x2; // Mark builder used + mBuilderFieldsSet |= 0x80; // Mark builder used ToolbarMenuItem o = new ToolbarMenuItem( - mItemId); + mItemId, + mTitle, + mContentDescription, + mGroupId, + mIcon, + mTooltipText, + mPriority); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x2) != 0) { + if ((mBuilderFieldsSet & 0x80) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -197,10 +529,10 @@ public final class ToolbarMenuItem implements Parcelable { } @DataClass.Generated( - time = 1639488328542L, + time = 1643200806234L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java", - inputSignatures = "private final int mItemId\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)") + inputSignatures = "public static final int PRIORITY_UNKNOWN\npublic static final int PRIORITY_PRIMARY\npublic static final int PRIORITY_OVERFLOW\nprivate final int mItemId\nprivate final @android.annotation.NonNull java.lang.CharSequence mTitle\nprivate final @android.annotation.Nullable java.lang.CharSequence mContentDescription\nprivate final int mGroupId\nprivate final @android.annotation.Nullable android.graphics.drawable.Icon mIcon\nprivate final @android.annotation.Nullable java.lang.CharSequence mTooltipText\nprivate final int mPriority\npublic static int getPriorityFromMenuItem(android.view.MenuItem)\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java index 961d8ac8e5e04..5d0fd473c9144 100644 --- a/core/java/android/view/selectiontoolbar/WidgetInfo.java +++ b/core/java/android/view/selectiontoolbar/WidgetInfo.java @@ -17,7 +17,10 @@ package android.view.selectiontoolbar; import android.annotation.NonNull; +import android.graphics.Rect; +import android.os.Parcel; import android.os.Parcelable; +import android.view.SurfaceControlViewHost; import com.android.internal.util.DataClass; @@ -35,7 +38,18 @@ public final class WidgetInfo implements Parcelable { */ private final long mWidgetToken; - // TODO: add members when the code really uses it + /** + * A Rect that defines the size and positioning of the remote view with respect to + * its host window. + */ + @NonNull + private final Rect mContentRect; + + /** + * The SurfacePackage pointing to the remote view. + */ + @NonNull + private final SurfaceControlViewHost.SurfacePackage mSurfacePackage; @@ -57,11 +71,24 @@ public final class WidgetInfo implements Parcelable { * * @param widgetToken * The token that is used to identify the selection toolbar. + * @param contentRect + * A Rect that defines the size and positioning of the remote view with respect to + * its host window. + * @param surfacePackage + * The SurfacePackage pointing to the remote view. */ @DataClass.Generated.Member public WidgetInfo( - long widgetToken) { + long widgetToken, + @NonNull Rect contentRect, + @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { this.mWidgetToken = widgetToken; + this.mContentRect = contentRect; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mContentRect); + this.mSurfacePackage = surfacePackage; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSurfacePackage); // onConstructed(); // You can define this method to get a callback } @@ -74,6 +101,23 @@ public final class WidgetInfo implements Parcelable { return mWidgetToken; } + /** + * A Rect that defines the size and positioning of the remote view with respect to + * its host window. + */ + @DataClass.Generated.Member + public @NonNull Rect getContentRect() { + return mContentRect; + } + + /** + * The SurfacePackage pointing to the remote view. + */ + @DataClass.Generated.Member + public @NonNull SurfaceControlViewHost.SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + @Override @DataClass.Generated.Member public String toString() { @@ -81,7 +125,9 @@ public final class WidgetInfo implements Parcelable { // String fieldNameToString() { ... } return "WidgetInfo { " + - "widgetToken = " + mWidgetToken + + "widgetToken = " + mWidgetToken + ", " + + "contentRect = " + mContentRect + ", " + + "surfacePackage = " + mSurfacePackage + " }"; } @@ -98,7 +144,9 @@ public final class WidgetInfo implements Parcelable { WidgetInfo that = (WidgetInfo) o; //noinspection PointlessBooleanExpression return true - && mWidgetToken == that.mWidgetToken; + && mWidgetToken == that.mWidgetToken + && java.util.Objects.equals(mContentRect, that.mContentRect) + && java.util.Objects.equals(mSurfacePackage, that.mSurfacePackage); } @Override @@ -109,16 +157,20 @@ public final class WidgetInfo implements Parcelable { int _hash = 1; _hash = 31 * _hash + Long.hashCode(mWidgetToken); + _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect); + _hash = 31 * _hash + java.util.Objects.hashCode(mSurfacePackage); return _hash; } @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } dest.writeLong(mWidgetToken); + dest.writeTypedObject(mContentRect, flags); + dest.writeTypedObject(mSurfacePackage, flags); } @Override @@ -128,13 +180,21 @@ public final class WidgetInfo implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ WidgetInfo(@NonNull android.os.Parcel in) { + /* package-private */ WidgetInfo(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } long widgetToken = in.readLong(); + Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR); + SurfaceControlViewHost.SurfacePackage surfacePackage = (SurfaceControlViewHost.SurfacePackage) in.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR); this.mWidgetToken = widgetToken; + this.mContentRect = contentRect; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mContentRect); + this.mSurfacePackage = surfacePackage; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSurfacePackage); // onConstructed(); // You can define this method to get a callback } @@ -148,16 +208,16 @@ public final class WidgetInfo implements Parcelable { } @Override - public WidgetInfo createFromParcel(@NonNull android.os.Parcel in) { + public WidgetInfo createFromParcel(@NonNull Parcel in) { return new WidgetInfo(in); } }; @DataClass.Generated( - time = 1639488254020L, + time = 1643281495056L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java", - inputSignatures = "private final long mWidgetToken\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") + inputSignatures = "private final long mWidgetToken\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final @android.annotation.NonNull android.view.SurfaceControlViewHost.SurfacePackage mSurfacePackage\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java index ced24e00da63d..ae4227bd4f235 100644 --- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java +++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java @@ -19,8 +19,10 @@ package com.android.server.selectiontoolbar; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.IBinder; import android.service.selectiontoolbar.ISelectionToolbarRenderService; import android.service.selectiontoolbar.SelectionToolbarRenderService; +import android.util.Slog; import android.view.selectiontoolbar.ISelectionToolbarCallback; import android.view.selectiontoolbar.ShowInfo; @@ -35,12 +37,14 @@ final class RemoteSelectionToolbarRenderService extends AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; private final ComponentName mComponentName; + private final IBinder mRemoteCallback; - - RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId) { + RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId, + IBinder callback) { super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent( serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface); mComponentName = serviceName; + mRemoteCallback = callback; // Bind right away. connect(); } @@ -50,19 +54,31 @@ final class RemoteSelectionToolbarRenderService extends return TIMEOUT_IDLE_UNBIND_MS; } + @Override // from ServiceConnector.Impl + protected void onServiceConnectionStatusChanged(ISelectionToolbarRenderService service, + boolean connected) { + try { + if (connected) { + service.onConnected(mRemoteCallback); + } + } catch (Exception e) { + Slog.w(TAG, "Exception calling onConnected().", e); + } + } + public ComponentName getComponentName() { return mComponentName; } - public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) { - run((s) -> s.onShow(showInfo, callback)); + public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) { + run((s) -> s.onShow(callingUid, showInfo, callback)); } public void onHide(long widgetToken) { run((s) -> s.onHide(widgetToken)); } - public void onDismiss(long widgetToken) { - run((s) -> s.onDismiss(widgetToken)); + public void onDismiss(int callingUid, long widgetToken) { + run((s) -> s.onDismiss(callingUid, widgetToken)); } } diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java index 235f547feed0c..525a9311a7232 100644 --- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java +++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java @@ -23,12 +23,17 @@ import android.app.AppGlobals; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.hardware.input.InputManagerInternal; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.service.selectiontoolbar.ISelectionToolbarRenderServiceCallback; import android.util.Slog; import android.view.selectiontoolbar.ISelectionToolbarCallback; import android.view.selectiontoolbar.ShowInfo; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; import com.android.server.infra.AbstractPerUserSystemService; final class SelectionToolbarManagerServiceImpl extends @@ -41,9 +46,14 @@ final class SelectionToolbarManagerServiceImpl extends @Nullable private RemoteSelectionToolbarRenderService mRemoteService; + InputManagerInternal mInputManagerInternal; + private final SelectionToolbarRenderServiceRemoteCallback mRemoteServiceCallback = + new SelectionToolbarRenderServiceRemoteCallback(); + protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master, @NonNull Object lock, int userId) { super(master, lock, userId); + mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); updateRemoteServiceLocked(); } @@ -78,7 +88,7 @@ final class SelectionToolbarManagerServiceImpl extends void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) { final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked(); if (remoteService != null) { - remoteService.onShow(showInfo, callback); + remoteService.onShow(Binder.getCallingUid(), showInfo, callback); } } @@ -94,7 +104,7 @@ final class SelectionToolbarManagerServiceImpl extends void dismissToolbar(long widgetToken) { final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked(); if (remoteService != null) { - remoteService.onDismiss(widgetToken); + remoteService.onDismiss(Binder.getCallingUid(), widgetToken); } } @@ -105,7 +115,7 @@ final class SelectionToolbarManagerServiceImpl extends final String serviceName = getComponentNameLocked(); final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent, - mUserId); + mUserId, mRemoteServiceCallback); } return mRemoteService; } @@ -125,4 +135,17 @@ final class SelectionToolbarManagerServiceImpl extends } return si; } + + private void transferTouchFocus(IBinder source, IBinder target) { + mInputManagerInternal.transferTouchFocus(source, target); + } + + private final class SelectionToolbarRenderServiceRemoteCallback extends + ISelectionToolbarRenderServiceCallback.Stub { + + @Override + public void transferTouch(IBinder source, IBinder target) { + transferTouchFocus(source, target); + } + } } From 6e56f6bac9d9cf8f825fc3e804ccee5f666d0987 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Fri, 24 Dec 2021 20:34:49 +0800 Subject: [PATCH 2/3] The client implementation for remote selection bar The DefaultSelectionToolbarRenderService renders the menu items and callbacks to the application with the SurfacePackage and the relative postition, the application will set the SurfacePackage to the SurfaceView and put the PopupWindow on the correct position. The MenuItemRepr is a copy from LocalFloatingToolbarPopup, no logic change in this class. The above changes don't contain the solution for transfering the touch focus on the non-toolbar part to the application process. The part will be implemented on the follow up change. Bug: 190030331 Bug: 205823020 Test: manual. Enable the flag, try to selectin the text then the toolbar can show successfully. Can open opverflow menu, scroll the view, the selection toolbar also changes the position. Merged-In: I3915e8165b4d371700cb5fb9e7eb23d46f39bd5c Change-Id: I1f2add3e93cee9d7af20cba9ccd4e0b393afffcb --- .../floatingtoolbar/FloatingToolbarPopup.java | 4 +- .../RemoteFloatingToolbarPopup.java | 514 +++++++++++++++++- 2 files changed, 497 insertions(+), 21 deletions(-) diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java index f47700c6e7391..f7af67b3b2a89 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java @@ -16,6 +16,7 @@ package com.android.internal.widget.floatingtoolbar; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.view.MenuItem; @@ -84,7 +85,8 @@ public interface FloatingToolbarPopup { * @see PopupWindow#setFocusable(boolean) * @see PopupWindow.OnDismissListener */ - boolean setOutsideTouchable(boolean outsideTouchable, PopupWindow.OnDismissListener onDismiss); + boolean setOutsideTouchable(boolean outsideTouchable, + @Nullable PopupWindow.OnDismissListener onDismiss); /** * Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java index b3a8128012fc7..8c2eb1044e012 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java @@ -16,13 +16,47 @@ package com.android.internal.widget.floatingtoolbar; +import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UiThread; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; import android.view.MenuItem; +import android.view.SurfaceView; import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.selectiontoolbar.ISelectionToolbarCallback; import android.view.selectiontoolbar.SelectionToolbarManager; +import android.view.selectiontoolbar.ShowInfo; +import android.view.selectiontoolbar.ToolbarMenuItem; +import android.view.selectiontoolbar.WidgetInfo; +import android.widget.LinearLayout; import android.widget.PopupWindow; +import com.android.internal.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -34,55 +68,495 @@ import java.util.Objects; */ public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup { - private final SelectionToolbarManager mSelectionToolbarManager; - // Parent for the popup window. - private final View mParent; + private static final boolean DEBUG = + Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.VERBOSE); - public RemoteFloatingToolbarPopup(Context context, View parent) { - // TODO: implement it - mParent = Objects.requireNonNull(parent); - mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class); + private static final int TOOLBAR_STATE_SHOWN = 1; + private static final int TOOLBAR_STATE_HIDDEN = 2; + private static final int TOOLBAR_STATE_DISMISSED = 3; + + @IntDef(prefix = {"TOOLBAR_STATE_"}, value = { + TOOLBAR_STATE_SHOWN, + TOOLBAR_STATE_HIDDEN, + TOOLBAR_STATE_DISMISSED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ToolbarState { } + @NonNull + private final SelectionToolbarManager mSelectionToolbarManager; + // Parent for the popup window. + @NonNull + private final View mParent; + // A popup window used for showing menu items rendered by the remote system process + @NonNull + private final PopupWindow mPopupWindow; + // The callback to handle remote rendered selection toolbar. + @NonNull + private final SelectionToolbarCallbackImpl mSelectionToolbarCallback; + + // tracks this popup state. + private @ToolbarState int mState; + + // The token of the current showing floating toolbar. + private long mFloatingToolbarToken; + private final Rect mPreviousContentRect = new Rect(); + private List mMenuItems; + private MenuItem.OnMenuItemClickListener mMenuItemClickListener; + private int mSuggestedWidth; + private final Rect mScreenViewPort = new Rect(); + private boolean mWidthChanged = true; + + private final int[] mCoordsOnScreen = new int[2]; + private final int[] mCoordsOnWindow = new int[2]; + + public RemoteFloatingToolbarPopup(Context context, View parent) { + mParent = Objects.requireNonNull(parent); + mPopupWindow = createPopupWindow(context); + mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class); + mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this); + mFloatingToolbarToken = NO_TOOLBAR_ID; + } + + @UiThread @Override public void show(List menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) { - // TODO: implement it + Objects.requireNonNull(menuItems); + Objects.requireNonNull(menuItemClickListener); + if (isShowing() && Objects.equals(menuItems, mMenuItems) + && Objects.equals(contentRect, mPreviousContentRect)) { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "Ignore duplicate show() for the same content."); + } + return; + } + + boolean isLayoutRequired = mMenuItems == null + || !MenuItemRepr.reprEquals(menuItems, mMenuItems) + || mWidthChanged; + if (isLayoutRequired) { + mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken); + doDismissPopupWindow(); + } + mMenuItemClickListener = menuItemClickListener; + mMenuItems = menuItems; + + mParent.getWindowVisibleDisplayFrame(mScreenViewPort); + final int suggestWidth = mSuggestedWidth > 0 + ? mSuggestedWidth + : mParent.getResources().getDimensionPixelSize( + R.dimen.floating_toolbar_preferred_width); + final ShowInfo showInfo = new ShowInfo( + mFloatingToolbarToken, isLayoutRequired, + getToolbarMenuItems(mMenuItems), + contentRect, + suggestWidth, + mScreenViewPort, + mParent.getViewRootImpl().getInputToken()); + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "RemoteFloatingToolbarPopup.show() for " + showInfo); + } + mSelectionToolbarManager.showToolbar(showInfo, mSelectionToolbarCallback); + mPreviousContentRect.set(contentRect); } + @UiThread + @Override + public void dismiss() { + if (mState == TOOLBAR_STATE_DISMISSED) { + Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "The floating toolbar already dismissed."); + return; + } + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "RemoteFloatingToolbarPopup.dismiss()."); + } + mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken); + doDismissPopupWindow(); + } + + @UiThread @Override public void hide() { - // TODO: implement it + if (mState == TOOLBAR_STATE_DISMISSED || mState == TOOLBAR_STATE_HIDDEN) { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "The floating toolbar already dismissed or hidden."); + } + return; + } + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "RemoteFloatingToolbarPopup.hide()."); + } + mSelectionToolbarManager.hideToolbar(mFloatingToolbarToken); + mState = TOOLBAR_STATE_HIDDEN; + mPopupWindow.dismiss(); } + @UiThread @Override public void setSuggestedWidth(int suggestedWidth) { - // TODO: implement it + int difference = Math.abs(suggestedWidth - mSuggestedWidth); + mWidthChanged = difference > (mSuggestedWidth * 0.2); + mSuggestedWidth = suggestedWidth; } @Override public void setWidthChanged(boolean widthChanged) { - // no-op - } - - @Override - public void dismiss() { - // TODO: implement it + mWidthChanged = widthChanged; } + @UiThread @Override public boolean isHidden() { - return false; + return mState == TOOLBAR_STATE_HIDDEN; } + @UiThread @Override public boolean isShowing() { - return false; + return mState == TOOLBAR_STATE_SHOWN; } + @UiThread @Override public boolean setOutsideTouchable(boolean outsideTouchable, - PopupWindow.OnDismissListener onDismiss) { - return false; + @Nullable PopupWindow.OnDismissListener onDismiss) { + if (mState == TOOLBAR_STATE_DISMISSED) { + return false; + } + boolean ret = false; + if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) { + mPopupWindow.setOutsideTouchable(outsideTouchable); + mPopupWindow.setFocusable(!outsideTouchable); + mPopupWindow.update(); + ret = true; + } + mPopupWindow.setOnDismissListener(onDismiss); + return ret; + } + + private void updatePopupWindowContent(WidgetInfo widgetInfo) { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "updatePopupWindowContent."); + } + ViewGroup contentContainer = (ViewGroup) mPopupWindow.getContentView(); + contentContainer.removeAllViews(); + SurfaceView surfaceView = new SurfaceView(mParent.getContext()); + surfaceView.setZOrderOnTop(true); + surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); + surfaceView.setChildSurfacePackage(widgetInfo.getSurfacePackage()); + contentContainer.addView(surfaceView); + } + + private MenuItem getMenuItemByToolbarMenuItem(ToolbarMenuItem toolbarMenuItem) { + for (MenuItem item : mMenuItems) { + if (toolbarMenuItem.getItemId() == item.getItemId()) { + return item; + } + } + return null; + } + + private Point getCoordinatesInWindow(int x, int y) { + // We later specify the location of PopupWindow relative to the attached window. + // The idea here is that 1) we can get the location of a View in both window coordinates + // and screen coordinates, where the offset between them should be equal to the window + // origin, and 2) we can use an arbitrary for this calculation while calculating the + // location of the rootview is supposed to be least expensive. + // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid + // the following calculation. + mParent.getRootView().getLocationOnScreen(mCoordsOnScreen); + mParent.getRootView().getLocationInWindow(mCoordsOnWindow); + int windowLeftOnScreen = mCoordsOnScreen[0] - mCoordsOnWindow[0]; + int windowTopOnScreen = mCoordsOnScreen[1] - mCoordsOnWindow[1]; + return new Point(Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); + } + + private static List getToolbarMenuItems(List menuItems) { + final List list = new ArrayList<>(menuItems.size()); + for (MenuItem menuItem : menuItems) { + // TODO: use ToolbarMenuItem.Builder(MenuItem) instead + ToolbarMenuItem toolbarMenuItem = new ToolbarMenuItem.Builder(menuItem.getItemId(), + menuItem.getTitle(), menuItem.getContentDescription(), menuItem.getGroupId(), + convertDrawableToIcon(menuItem.getIcon()), + menuItem.getTooltipText(), + ToolbarMenuItem.getPriorityFromMenuItem(menuItem)).build(); + list.add(toolbarMenuItem); + } + return list; + } + + private static Icon convertDrawableToIcon(Drawable drawable) { + if (drawable == null) { + return null; + } + if (drawable instanceof BitmapDrawable) { + final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (bitmapDrawable.getBitmap() != null) { + return Icon.createWithBitmap(bitmapDrawable.getBitmap()); + } + } + final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return Icon.createWithBitmap(bitmap); + } + + private static PopupWindow createPopupWindow(Context content) { + ViewGroup popupContentHolder = new LinearLayout(content); + PopupWindow popupWindow = new PopupWindow(popupContentHolder); + popupWindow.setClippingEnabled(false); + popupWindow.setWindowLayoutType( + WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); + popupWindow.setAnimationStyle(0); + popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + return popupWindow; + } + + private void doDismissPopupWindow() { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "RemoteFloatingToolbarPopup.doDismiss()."); + } + mState = TOOLBAR_STATE_DISMISSED; + mMenuItems = null; + mMenuItemClickListener = null; + mFloatingToolbarToken = 0; + mSuggestedWidth = 0; + mWidthChanged = true; + resetCoords(); + mPreviousContentRect.setEmpty(); + mScreenViewPort.setEmpty(); + mPopupWindow.dismiss(); + } + + private void resetCoords() { + mCoordsOnScreen[0] = 0; + mCoordsOnScreen[1] = 0; + mCoordsOnWindow[0] = 0; + mCoordsOnWindow[1] = 0; + } + + private void runOnUiThread(Runnable runnable) { + mParent.post(runnable); + } + + private void onShow(WidgetInfo info) { + runOnUiThread(() -> { + mFloatingToolbarToken = info.getWidgetToken(); + mState = TOOLBAR_STATE_SHOWN; + updatePopupWindowContent(info); + Rect contentRect = info.getContentRect(); + mPopupWindow.setWidth(contentRect.width()); + mPopupWindow.setHeight(contentRect.height()); + final Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top); + mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, coords.x, coords.y); + }); + } + + private void onWidgetUpdated(WidgetInfo info) { + runOnUiThread(() -> { + if (!isShowing()) { + Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "onWidgetUpdated(): The widget isn't showing."); + return; + } + updatePopupWindowContent(info); + Rect contentRect = info.getContentRect(); + Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top); + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "PopupWindow x= " + coords.x + " y= " + coords.y + " w=" + + contentRect.width() + " h=" + contentRect.height()); + } + mPopupWindow.update(coords.x, coords.y, contentRect.width(), contentRect.height()); + }); + } + + private void onToolbarShowTimeout() { + runOnUiThread(() -> { + if (mState == TOOLBAR_STATE_DISMISSED) { + return; + } + doDismissPopupWindow(); + }); + } + + private void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) { + runOnUiThread(() -> { + if (mMenuItems == null || mMenuItemClickListener == null) { + return; + } + MenuItem item = getMenuItemByToolbarMenuItem(toolbarMenuItem); + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "SelectionToolbarCallbackImpl onMenuItemClicked. toolbarMenuItem=" + + toolbarMenuItem + " item=" + item); + } + // TODO: handle the menu item like clipboard + if (item != null) { + mMenuItemClickListener.onMenuItemClick(item); + } else { + Log.e(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "onMenuItemClicked: cannot find menu item."); + } + }); + } + + private static class SelectionToolbarCallbackImpl extends ISelectionToolbarCallback.Stub { + + private final WeakReference mRemotePopup; + + SelectionToolbarCallbackImpl(RemoteFloatingToolbarPopup popup) { + mRemotePopup = new WeakReference<>(popup); + } + + @Override + public void onShown(WidgetInfo info) { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "SelectionToolbarCallbackImpl onShown: " + info); + } + final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); + if (remoteFloatingToolbarPopup != null) { + remoteFloatingToolbarPopup.onShow(info); + } else { + Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "Lost remoteFloatingToolbarPopup reference for onShown."); + } + } + + @Override + public void onWidgetUpdated(WidgetInfo info) { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "SelectionToolbarCallbackImpl onWidgetUpdated: info = " + info); + } + final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); + if (remoteFloatingToolbarPopup != null) { + remoteFloatingToolbarPopup.onWidgetUpdated(info); + } else { + Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "Lost remoteFloatingToolbarPopup reference for onWidgetUpdated."); + } + } + + @Override + public void onToolbarShowTimeout() { + final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); + if (remoteFloatingToolbarPopup != null) { + remoteFloatingToolbarPopup.onToolbarShowTimeout(); + } else { + Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "Lost remoteFloatingToolbarPopup reference for onToolbarShowTimeout."); + } + } + + @Override + public void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) { + final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); + if (remoteFloatingToolbarPopup != null) { + remoteFloatingToolbarPopup.onMenuItemClicked(toolbarMenuItem); + } else { + Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "Lost remoteFloatingToolbarPopup reference for onMenuItemClicked."); + } + } + + @Override + public void onError(int errorCode) { + if (DEBUG) { + Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, + "SelectionToolbarCallbackImpl onError: " + errorCode); + } + } + } + + /** + * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup. + */ + static final class MenuItemRepr { + + public final int mItemId; + public final int mGroupId; + @Nullable + public final String mTitle; + @Nullable private final Drawable mIcon; + + private MenuItemRepr( + int itemId, int groupId, @Nullable CharSequence title, + @Nullable Drawable icon) { + mItemId = itemId; + mGroupId = groupId; + mTitle = (title == null) ? null : title.toString(); + mIcon = icon; + } + + /** + * Creates an instance of MenuItemRepr for the specified menu item. + */ + public static MenuItemRepr of(MenuItem menuItem) { + return new MenuItemRepr( + menuItem.getItemId(), + menuItem.getGroupId(), + menuItem.getTitle(), + menuItem.getIcon()); + } + + /** + * Returns this object's hashcode. + */ + @Override + public int hashCode() { + return Objects.hash(mItemId, mGroupId, mTitle, mIcon); + } + + /** + * Returns true if this object is the same as the specified object. + */ + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof LocalFloatingToolbarPopup.MenuItemRepr)) { + return false; + } + final MenuItemRepr other = (MenuItemRepr) o; + return mItemId == other.mItemId + && mGroupId == other.mGroupId + && TextUtils.equals(mTitle, other.mTitle) + // Many Drawables (icons) do not implement equals(). Using equals() here instead + // of reference comparisons in case a Drawable subclass implements equals(). + && Objects.equals(mIcon, other.mIcon); + } + + /** + * Returns true if the two menu item collections are the same based on MenuItemRepr. + */ + public static boolean reprEquals( + Collection menuItems1, Collection menuItems2) { + if (menuItems1.size() != menuItems2.size()) { + return false; + } + + final Iterator menuItems2Iter = menuItems2.iterator(); + for (MenuItem menuItem1 : menuItems1) { + final MenuItem menuItem2 = menuItems2Iter.next(); + if (!MenuItemRepr.of(menuItem1).equals( + MenuItemRepr.of(menuItem2))) { + return false; + } + } + return true; + } } } From b0040b03c22c2daa0c3a8cc10aac8a2fa9a73c24 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Mon, 17 Jan 2022 22:41:31 +0800 Subject: [PATCH 3/3] Handle touch focus when clicking toolbar window transparent area We use a fix size window to hold the toolbar no matter if we show it horizontally or vertically. We should transder the touch focus to the application behid the toolbar when clicking toolbar window transparent area. Bug: 190030331 Bug: 214158202 Test: manual. The toolbar can dismiss when clicking the transparent area in this fix size window. Merged-In: I3915e8165b4d371700cb5fb9e7eb23d46f39bd5c Change-Id: I73bdec96303edab77acc55af3d7871d504614be7 --- .../selectiontoolbar/FloatingToolbarRoot.java | 35 ++++++++++++++----- .../RemoteSelectionToolbar.java | 29 +++++++++++++-- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java index 5e4262b970ebf..04491f0cc85df 100644 --- a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java +++ b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java @@ -18,7 +18,9 @@ package android.service.selectiontoolbar; import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; +import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; @@ -31,10 +33,12 @@ import android.widget.LinearLayout; @SuppressLint("ViewConstructor") public class FloatingToolbarRoot extends LinearLayout { + private static final boolean DEBUG = false; + private static final String TAG = "FloatingToolbarRoot"; + private final IBinder mTargetInputToken; private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener; - private float mDownX; - private float mDownY; + private Rect mContentRect; public FloatingToolbarRoot(Context context, IBinder targetInputToken, SelectionToolbarRenderService.TransferTouchListener transferTouchListener) { @@ -44,16 +48,29 @@ public class FloatingToolbarRoot extends LinearLayout { setFocusable(false); } + /** + * Sets the Rect that shows the selection toolbar content. + */ + public void setContentRect(Rect contentRect) { + mContentRect = contentRect; + } + @Override @SuppressLint("ClickableViewAccessibility") public boolean dispatchTouchEvent(MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - mDownX = event.getX(); - mDownY = event.getY(); - // TODO: Check x, y if we need to transfer touch focus to application - //mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(), - // mTargetInputToken); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + int downX = (int) event.getX(); + int downY = (int) event.getY(); + if (DEBUG) { + Log.d(TAG, "downX=" + downX + " downY=" + downY); + } + // TODO(b/215497659): Check FLAG_WINDOW_IS_PARTIALLY_OBSCURED + if (!mContentRect.contains(downX, downY)) { + if (DEBUG) { + Log.d(TAG, "Transfer touch focus to application."); + } + mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(), + mTargetInputToken); } } return super.dispatchTouchEvent(event); diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java index cbe232b7b39aa..179d39de2cfb6 100644 --- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java +++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java @@ -158,6 +158,8 @@ final class RemoteSelectionToolbar { private final Rect mPreviousContentRect = new Rect(); private final Rect mTempContentRect = new Rect(); + private final Rect mTempContentRectForRoot = new Rect(); + private final int[] mTempCoords = new int[2]; RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken, SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper, @@ -214,7 +216,13 @@ final class RemoteSelectionToolbar { mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener); mCloseOverflowAnimation = new AnimationSet(true); mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener); - mShowAnimation = createEnterAnimation(mContentContainer); + mShowAnimation = createEnterAnimation(mContentContainer, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + updateFloatingToolbarRootContentRect(); + } + }); mDismissAnimation = createExitAnimation( mContentContainer, 150, // startDelay @@ -241,6 +249,20 @@ final class RemoteSelectionToolbar { }; } + private void updateFloatingToolbarRootContentRect() { + if (mSurfaceControlViewHost == null) { + return; + } + final FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView(); + mContentContainer.getLocationOnScreen(mTempCoords); + int contentLeft = mTempCoords[0]; + int contentTop = mTempCoords[1]; + mTempContentRectForRoot.set(contentLeft, contentTop, + contentLeft + mContentContainer.getWidth(), + contentTop + mContentContainer.getHeight()); + root.setContentRect(mTempContentRectForRoot); + } + private WidgetInfo createWidgetInfo() { mTempContentRect.set(mRelativeCoordsForToolbar.x, mRelativeCoordsForToolbar.y, mRelativeCoordsForToolbar.x + mPopupWidth, @@ -514,6 +536,7 @@ final class RemoteSelectionToolbar { isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth; float actualOverflowButtonX = overflowButtonX + deltaContainerWidth; mOverflowButton.setX(actualOverflowButtonX); + updateFloatingToolbarRootContentRect(); } }; widthAnimation.setInterpolator(mLogAccelerateInterpolator); @@ -589,6 +612,7 @@ final class RemoteSelectionToolbar { isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth; float actualOverflowButtonX = overflowButtonX + deltaContainerWidth; mOverflowButton.setX(actualOverflowButtonX); + updateFloatingToolbarRootContentRect(); } }; widthAnimation.setInterpolator(mFastOutSlowInInterpolator); @@ -1303,10 +1327,11 @@ final class RemoteSelectionToolbar { * * @param view The view to animate */ - private static AnimatorSet createEnterAnimation(View view) { + private static AnimatorSet createEnterAnimation(View view, Animator.AnimatorListener listener) { AnimatorSet animation = new AnimatorSet(); animation.playTogether( ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150)); + animation.addListener(listener); return animation; }