diff options
| author | Tony Mak <tonymak@google.com> | 2020-10-22 20:49:53 +0100 |
|---|---|---|
| committer | Tony Mak <tonymak@google.com> | 2020-11-06 14:36:12 +0000 |
| commit | af501bcaf97e9910fcd9c9698722e2661fc2fc63 (patch) | |
| tree | 34e54a8f3c2212e62514f48fc69317b60829493e /core/java | |
| parent | 6b634145572ba2d041362236a0254e04fa3a2ae9 (diff) | |
Reduce the latency between the selection span is adjusted and the toolbar is shown
Results:
We measure the latency between Editor.startActionModeInternal is called
and FloatingToolbar.show() is called.
When there is only smart action (url)
Before: ~150ms
After ~30ms
When there are 5 smart actions (phone number)
Before: ~400ms
After: ~100ms
Before and after videos:
https://recall.googleplex.com/projects/ea8c4705-96bd-46f0-9f37-786708050727
Fixed a few issues:
1. updateAssistMenuItems() gets the Icons from TextClassification
object, calls loadDrawable on them and creates the MenuItem
objects loadDrawable() is slow, especially if we have a lot of
smart action icons to load. Even worse, we are calling this
function 4 times in a row when selecting something! 1 time from
onCreateActionMode and 3 times from onPrepareActionMode.
The fix here is to avoid reloading the drawable if it is the same
text classification object.
2. From SelectionActionModeHelper, we call startActionModeInternal
before SelectionModifierCursorController.show()
Internally, SelectionModifierCursorController.show()
show the two selection handles by calling startHandle.show() and
endHandle.show(). Apparently, each handle.show() call invalidates the
action model right after it is just created! This explains two of the
unnecessary onPrepareActionModel calls.
The fix is to call SelectionModifierCursorController.show()
before startActionModeInternal() is called.
3. Editor.startActionModeInternal() does not invoke
FloatingToolbar.show() right away.
There are two issues here.
a) Editor.startActionModeInternal() ends up calling
FloatingActionModel.repositionToolbar() which hopefully calls
FloatingToolbar.show(). Sadly, it is not the case
because mViewRectOnScreen is not set at that time. mViewRectOnScreen
is set in next onPreDrawCall() call.
b) When mViewRectOnScreen is finally set and calls repositionToolbar()
again , it still won't call FloatingToolbar.show() immediately
because we think that the toolbar is moving by comparing the previous
content rect and the current content rect. They are different because
the previous one is empty(it wasn't shown before). Becoz we think it is
moving, we schedule the FloatingToolbar.show() call after 50ms.
To fix a, we now update mViewRectOnScreen() right away wihout waiting
for the next onPreDraw call().
To fix b, when the previous content rect is moving, we don't consider
the toolbar as moving anymore.
Bug: 169043706
Test: atest android.widget.TextViewActivityTest
Test: atest
cts/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
Test: atest
cts/tests/tests/widget/src/android/widget/cts/TextViewTest.java
Test: Smart select a phone number and then smart select a link.
Make sure the smart actions menuitems are updated.
Test: Click on a smart linkify link. Then dismiss it by tapping outside.
Change-Id: I634b21ac7ed66a14883dc17e03ef006df5b3f223
Diffstat (limited to 'core/java')
4 files changed, 36 insertions, 23 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index eaa738d59577..f02f2ebe3c1c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4105,6 +4105,8 @@ public class Editor { private final boolean mHasSelection; private final int mHandleHeight; private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>(); + @Nullable + private TextClassification mPrevTextClassification; TextActionModeCallback(@TextActionMode int mode) { mHasSelection = mode == TextActionMode.SELECTION @@ -4255,15 +4257,19 @@ public class Editor { } private void updateAssistMenuItems(Menu menu) { - clearAssistMenuItems(menu); - if (!shouldEnableAssistMenuItems()) { - return; - } final TextClassification textClassification = getSelectionActionModeHelper().getTextClassification(); + if (mPrevTextClassification == textClassification) { + // Already handled. + return; + } + clearAssistMenuItems(menu); if (textClassification == null) { return; } + if (!shouldEnableAssistMenuItems()) { + return; + } if (!textClassification.getActions().isEmpty()) { // Primary assist action (Always shown). final MenuItem item = addAssistMenuItem(menu, @@ -4290,6 +4296,7 @@ public class Editor { MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1, MenuItem.SHOW_AS_ACTION_NEVER); } + mPrevTextClassification = textClassification; } private MenuItem addAssistMenuItem(Menu menu, RemoteAction action, int itemId, int order, diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 32c68bdd0491..e74ce53306c1 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -296,12 +296,12 @@ public final class SelectionActionModeHelper { } else { mTextClassification = null; } + final SelectionModifierCursorController controller = mEditor.getSelectionController(); + if (controller != null + && (mTextView.isTextSelectable() || mTextView.isTextEditable())) { + controller.show(); + } if (mEditor.startActionModeInternal(actionMode)) { - final SelectionModifierCursorController controller = mEditor.getSelectionController(); - if (controller != null - && (mTextView.isTextSelectable() || mTextView.isTextEditable())) { - controller.show(); - } if (result != null) { switch (actionMode) { case Editor.TextActionMode.SELECTION: diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java index 633d6848030e..36913b729f1b 100644 --- a/core/java/com/android/internal/view/FloatingActionMode.java +++ b/core/java/com/android/internal/view/FloatingActionMode.java @@ -29,8 +29,8 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; - import android.widget.PopupWindow; + import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.FloatingToolbar; @@ -148,16 +148,21 @@ public final class FloatingActionMode extends ActionMode { @Override public void invalidateContentRect() { mCallback.onGetContentRect(this, mOriginatingView, mContentRect); - repositionToolbar(); + updateViewLocationInWindow(/* forceRepositionToolbar= */ true); } public void updateViewLocationInWindow() { + updateViewLocationInWindow(/* forceRepositionToolbar= */ false); + } + + private void updateViewLocationInWindow(boolean forceRepositionToolbar) { mOriginatingView.getLocationOnScreen(mViewPositionOnScreen); mOriginatingView.getRootView().getLocationOnScreen(mRootViewPositionOnScreen); mOriginatingView.getGlobalVisibleRect(mViewRectOnScreen); mViewRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]); - if (!Arrays.equals(mViewPositionOnScreen, mPreviousViewPositionOnScreen) + if (forceRepositionToolbar + || !Arrays.equals(mViewPositionOnScreen, mPreviousViewPositionOnScreen) || !mViewRectOnScreen.equals(mPreviousViewRectOnScreen)) { repositionToolbar(); mPreviousViewPositionOnScreen[0] = mViewPositionOnScreen[0]; @@ -192,10 +197,15 @@ public final class FloatingActionMode extends ActionMode { mViewRectOnScreen.bottom + mBottomAllowance)); if (!mContentRectOnScreen.equals(mPreviousContentRectOnScreen)) { - // Content rect is moving. - mOriginatingView.removeCallbacks(mMovingOff); - mFloatingToolbarVisibilityHelper.setMoving(true); - mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY); + // Content rect is moving + if (!mPreviousContentRectOnScreen.isEmpty()) { + mOriginatingView.removeCallbacks(mMovingOff); + mFloatingToolbarVisibilityHelper.setMoving(true); + mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY); + } else { + // mPreviousContentRectOnScreen is empty. That means we are are showing the + // toolbar rather than moving it. And we should show it right away. + } mFloatingToolbar.setContentRect(mContentRectOnScreen); mFloatingToolbar.updateLayout(); diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index d7611dcd0aa0..58817a8a48a9 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -261,18 +261,14 @@ public final class FloatingToolbar { /** * If this is set to true, the action mode view will dismiss itself on touch events outside of - * its window. If the toolbar is already showing, it will be re-shown so that this setting takes - * effect immediately. + * its window. The setting takes effect immediately. * * @param outsideTouchable whether or not this action mode is "outside touchable" * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself */ public void setOutsideTouchable( boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { - if (mPopup.setOutsideTouchable(outsideTouchable, onDismiss) && isShowing()) { - dismiss(); - doShow(); - } + mPopup.setOutsideTouchable(outsideTouchable, onDismiss); } private void doShow() { @@ -530,7 +526,6 @@ public final class FloatingToolbar { /** * Makes this toolbar "outside touchable" and sets the onDismissListener. - * This will take effect the next time the toolbar is re-shown. * * @param outsideTouchable if true, the popup will be made "outside touchable" and * "non focusable". The reverse will happen if false. @@ -548,6 +543,7 @@ public final class FloatingToolbar { if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) { mPopupWindow.setOutsideTouchable(outsideTouchable); mPopupWindow.setFocusable(!outsideTouchable); + mPopupWindow.update(); ret = true; } mPopupWindow.setOnDismissListener(onDismiss); |
