summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorTony Mak <tonymak@google.com>2020-10-22 20:49:53 +0100
committerTony Mak <tonymak@google.com>2020-11-06 14:36:12 +0000
commitaf501bcaf97e9910fcd9c9698722e2661fc2fc63 (patch)
tree34e54a8f3c2212e62514f48fc69317b60829493e /core/java
parent6b634145572ba2d041362236a0254e04fa3a2ae9 (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')
-rw-r--r--core/java/android/widget/Editor.java15
-rw-r--r--core/java/android/widget/SelectionActionModeHelper.java10
-rw-r--r--core/java/com/android/internal/view/FloatingActionMode.java24
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java10
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);