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..04491f0cc85df
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
@@ -0,0 +1,78 @@
+/*
+ * 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.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+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 static final boolean DEBUG = false;
+ private static final String TAG = "FloatingToolbarRoot";
+
+ private final IBinder mTargetInputToken;
+ private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
+ private Rect mContentRect;
+
+ public FloatingToolbarRoot(Context context, IBinder targetInputToken,
+ SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
+ super(context);
+ mTargetInputToken = targetInputToken;
+ mTransferTouchListener = transferTouchListener;
+ 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) {
+ 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/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..179d39de2cfb6 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,28 @@ 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();
+ private final Rect mTempContentRectForRoot = new Rect();
+ private final int[] mTempCoords = new int[2];
+
+ 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
@@ -245,53 +216,81 @@ final class RemoteSelectionToolbar implements FloatingToolbarPopup {
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
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 void updateFloatingToolbarRootContentRect() {
+ if (mSurfaceControlViewHost == null) {
+ return;
}
- mPopupWindow.setOnDismissListener(onDismiss);
- return ret;
+ 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,
+ 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);
+ }
+ 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