Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java |
| index c45ec5aca4667aeb45a3f0e9dd621e122ef2d4ee..4ac3bfc9463464cfbb2fc196a1671c36a0b565c7 100644 |
| --- a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java |
| +++ b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java |
| @@ -54,7 +54,7 @@ import java.util.List; |
| */ |
| @TargetApi(Build.VERSION_CODES.M) |
| public class SelectionPopupController extends ActionModeCallbackHelper { |
| - private static final String TAG = "cr.SelectionPopCtlr"; // 20 char limit |
| + private static final String TAG = "SelectionPopupCtlr"; // 20 char limit |
| /** |
| * Android Intent size limitations prevent sending over a megabyte of data. Limit |
| @@ -70,6 +70,12 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| // most such trailing, async delays. |
| private static final int SHOW_DELAY_MS = 300; |
| + // Display operation for ActionMode (show or invalidate) that is delayed because |
| + // the selected text needs to be classified before. See comment to mPostponedDisplayOp. |
| + private static final int POSTPONED_NONE = 0; |
| + private static final int POSTPONED_SHOW = 1; |
| + private static final int POSTPONED_INVALIDATE = 2; |
| + |
| private final Context mContext; |
| private final WindowAndroid mWindowAndroid; |
| private final WebContents mWebContents; |
| @@ -115,6 +121,72 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| // The client that processes textual selection, or null if none exists. |
| private SelectionClient mSelectionClient; |
| + // The classificaton result of the selected text if the selection exists and |
| + // ContextSelectionProvider was able to classify it, otherwise null. |
| + private ContextSelectionProvider.Result mClassificationResult; |
| + |
| + // Postponed display operation for ActionMode. |
| + // |
| + // The |mSelectionClient| might perform a recognition of the selected texts that |
| + // can affect the ActionMode menu. In this case its method sendsPopupMenuUpdates() |
| + // returns true and the client itself sends the results back asynchronously. |
| + // |
| + // With this kind of SelectionClient we postpone the display of ActionMode menu |
| + // until the result comes, so we can update the menu before showing it and avoid the flicker. |
| + // This variable holds the type of operation we have postponed, i.e. show or invalidate. |
| + private int mPostponedDisplayOp = POSTPONED_NONE; |
| + |
| + // The callback object that delivers result from SelectionClient. |
| + private ContextSelectionProvider.ResultCallback mSelectonCallback = |
| + new ContextSelectionProvider.ResultCallback() { |
| + @Override |
| + public void onClassified(ContextSelectionProvider.Result result) { |
| + // If the selection does not exist any more, discard |result|. |
| + if (!mHasSelection) { |
| + mPostponedDisplayOp = POSTPONED_NONE; |
| + return; |
| + } |
| + |
| + // Update the selection range if needed. |
| + if (!(result.startAdjust == 0 && result.endAdjust == 0)) { |
| + // This call causes SELECTION_HANDLES_MOVED event |
| + mWebContents.adjustSelectionByCharacterOffset( |
| + result.startAdjust, result.endAdjust); |
| + } |
| + |
| + final boolean hadPriorResult = mClassificationResult != null; |
| + mClassificationResult = result.hasNamedAction() ? result : null; |
| + |
| + // For simplicity always update the menu if we need to show assist item. |
| + // Also we need to update the menu if the assist item is going to disappear. |
| + if (mClassificationResult != null || hadPriorResult) mNeedsPrepare = true; |
| + |
| + switch (mPostponedDisplayOp) { |
| + case POSTPONED_NONE: |
| + break; |
| + |
| + case POSTPONED_SHOW: |
| + // TODO(timav): if the |result| contained the selection adjustment and |
| + // we called adjustSelectionByCharacterOffset() above, there is a |
| + // flicker if SELECTION_HANDLES_MOVED comes later and invalidates the |
| + // selection rectangle. Consider postponing till SELECTION_HANDLES_MOVED |
| + // or introduce delay. |
| + showActionModeOrClearOnFailure(); |
| + mPostponedDisplayOp = POSTPONED_NONE; |
| + break; |
| + |
| + case POSTPONED_INVALIDATE: |
| + invalidateActionMode(); |
| + mPostponedDisplayOp = POSTPONED_NONE; |
| + break; |
| + |
| + default: |
| + assert false : "Invalid postponed display operation type."; |
| + break; |
| + } |
| + } |
| + }; |
| + |
| /** |
| * Create {@link SelectionPopupController} instance. |
| * @param context Context for action mode. |
| @@ -145,6 +217,8 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| hideActionModeTemporarily(hideDuration); |
| } |
| }; |
| + |
| + mSelectionClient = ContextSelectionClient.create(mSelectonCallback, window, webContents); |
| } |
| /** |
| @@ -189,15 +263,18 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| * |
| * <p>Action mode in floating mode is tried first, and then falls back to |
| * a normal one. |
| - * @return {@code true} if the action mode started successfully or is already on. |
| + * <p> If the action mode cannot be created the selection is cleared. |
| */ |
| - public boolean showActionMode() { |
| - if (isEmpty()) return false; |
| + public void showActionModeOrClearOnFailure() { |
| + if (isEmpty()) { |
| + clearSelection(); |
| + return; |
| + } |
| // Just refreshes the view if it is already showing. |
| if (isActionModeValid()) { |
| invalidateActionMode(); |
| - return true; |
| + return; |
| } |
| if (mView.getParent() != null) { |
| @@ -213,7 +290,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| mActionMode = actionMode; |
| } |
| mUnselectAllOnDismiss = true; |
| - return isActionModeValid(); |
| + if (!isActionModeValid()) clearSelection(); |
| } |
| @TargetApi(Build.VERSION_CODES.M) |
| @@ -295,6 +372,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| */ |
| @Override |
| public void finishActionMode() { |
| + mPostponedDisplayOp = POSTPONED_NONE; |
| if (isActionModeValid()) { |
| mActionMode.finish(); |
| @@ -314,6 +392,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| assert canHideActionMode(); |
| mHidden = false; |
| mView.removeCallbacks(mRepeatingHideRunnable); |
| + hideActionModeTemporarily(SHOW_DELAY_MS); |
| mPendingInvalidateContentRect = false; |
| } |
| @@ -328,7 +407,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| /** |
| * @see ActionMode#invalidateContentRect() |
| */ |
| - public void invalidateContentRect() { |
| + private void invalidateContentRect() { |
| if (supportsFloatingActionMode()) { |
| if (mHidden) { |
| mPendingInvalidateContentRect = true; |
| @@ -431,6 +510,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| private void createActionMenu(ActionMode mode, Menu menu) { |
| mNeedsPrepare = false; |
| initializeMenu(mContext, mode, menu); |
| + updateContextDependentMenuItem(menu); |
| if (!isSelectionEditable() || !canPaste()) { |
| menu.removeItem(R.id.select_action_menu_paste); |
| @@ -473,6 +553,20 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| return clipMgr.hasPrimaryClip(); |
| } |
| + private void updateContextDependentMenuItem(Menu menu) { |
| + MenuItem assistItem = menu.findItem(R.id.select_action_menu_context_dep); |
| + if (assistItem == null) return; |
| + |
| + if (mClassificationResult == null) { |
| + assistItem.setVisible(false).setEnabled(false); |
| + } else { |
| + assistItem.setVisible(true) |
| + .setEnabled(true) |
| + .setTitle(mClassificationResult.label) |
| + .setIcon(mClassificationResult.icon); |
| + } |
| + } |
| + |
| /** |
| * Intialize the menu items for processing text, if there is any. |
| */ |
| @@ -488,7 +582,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| for (int i = 0; i < supportedActivities.size(); i++) { |
| ResolveInfo resolveInfo = supportedActivities.get(i); |
| CharSequence label = resolveInfo.loadLabel(mContext.getPackageManager()); |
| - menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i, label) |
| + menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, Menu.NONE, label) |
|
Tima Vaisburd
2017/03/21 02:07:02
Here i==0 and i==1 for the order have different se
|
| .setIntent(createProcessTextIntentForResolveInfo(resolveInfo)) |
| .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); |
| } |
| @@ -514,7 +608,10 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| int id = item.getItemId(); |
| int groupId = item.getGroupId(); |
| - if (id == R.id.select_action_menu_select_all) { |
| + if (id == R.id.select_action_menu_context_dep) { |
| + doContextDependentAction(); |
| + mode.finish(); |
| + } else if (id == R.id.select_action_menu_select_all) { |
| selectAll(); |
| } else if (id == R.id.select_action_menu_cut) { |
| cut(); |
| @@ -573,6 +670,30 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| } |
| /** |
| + * Perform an action that depends on the semantics of the selected text. |
| + */ |
| + @VisibleForTesting |
| + void doContextDependentAction() { |
| + if (mClassificationResult == null) return; |
| + |
| + assert mClassificationResult.onClickListener != null |
| + || mClassificationResult.intent != null; |
| + |
| + if (mClassificationResult.onClickListener != null) { |
| + mClassificationResult.onClickListener.onClick(mView); |
| + return; |
| + } |
| + |
| + if (mClassificationResult.intent != null) { |
| + Context context = mWindowAndroid.getContext().get(); |
| + if (context == null) return; |
| + |
| + context.startActivity(mClassificationResult.intent); |
| + return; |
| + } |
| + } |
| + |
| + /** |
| * Perform a select all action. |
| */ |
| @VisibleForTesting |
| @@ -750,7 +871,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| void restoreSelectionPopupsIfNecessary() { |
| if (mHasSelection && !isActionModeValid()) { |
| - if (!showActionMode()) clearSelection(); |
| + showActionModeOrClearOnFailure(); |
| } |
| } |
| @@ -767,7 +888,11 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| mSelectionRect.set(left, top, right, bottom); |
| mHasSelection = true; |
| mUnselectAllOnDismiss = true; |
| - if (!showActionMode()) clearSelection(); |
| + if (mSelectionClient != null && mSelectionClient.sendsSelectionPopupUpdates()) { |
| + mPostponedDisplayOp = POSTPONED_SHOW; |
| + } else { |
| + showActionModeOrClearOnFailure(); |
| + } |
| break; |
| case SelectionEventType.SELECTION_HANDLES_MOVED: |
| @@ -787,7 +912,11 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| break; |
| case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: |
| - hideActionMode(false); |
| + if (mSelectionClient != null && mSelectionClient.sendsSelectionPopupUpdates()) { |
| + mPostponedDisplayOp = POSTPONED_INVALIDATE; |
| + } else { |
| + hideActionMode(false); |
| + } |
| break; |
| case SelectionEventType.INSERTION_HANDLE_SHOWN: |
| @@ -848,6 +977,7 @@ public class SelectionPopupController extends ActionModeCallbackHelper { |
| * end if applicable. |
| */ |
| void clearSelection() { |
| + mClassificationResult = null; |
| if (mWebContents == null || isEmpty()) return; |
| mWebContents.collapseSelection(); |
| } |