Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(371)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java

Issue 2785853002: Selection Action mode triggered like a context menu (Closed)
Patch Set: fixing tests Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.content.browser; 5 package org.chromium.content.browser;
6 6
7 import android.annotation.TargetApi; 7 import android.annotation.TargetApi;
8 import android.app.Activity; 8 import android.app.Activity;
9 import android.app.SearchManager; 9 import android.app.SearchManager;
10 import android.content.ClipData; 10 import android.content.ClipData;
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
111 111
112 private boolean mEditable; 112 private boolean mEditable;
113 private boolean mIsPasswordType; 113 private boolean mIsPasswordType;
114 private boolean mIsInsertion; 114 private boolean mIsInsertion;
115 private boolean mCanSelectAllForPastePopup; 115 private boolean mCanSelectAllForPastePopup;
116 private boolean mCanEditRichlyForPastePopup; 116 private boolean mCanEditRichlyForPastePopup;
117 117
118 private boolean mUnselectAllOnDismiss; 118 private boolean mUnselectAllOnDismiss;
119 private String mLastSelectedText; 119 private String mLastSelectedText;
120 120
121 // Tracks whether a selection is currently active. When applied to selected text, indicates
122 // whether the last selected text is still highlighted.
123 private boolean mHasSelection;
124
125 // Lazily created paste popup menu, triggered either via long press in an 121 // Lazily created paste popup menu, triggered either via long press in an
126 // editable region or from tapping the insertion handle. 122 // editable region or from tapping the insertion handle.
127 private PastePopupMenu mPastePopupMenu; 123 private PastePopupMenu mPastePopupMenu;
128 private boolean mWasPastePopupShowingOnInsertionDragStart; 124 private boolean mWasPastePopupShowingOnInsertionDragStart;
129 125
130 // The client that processes textual selection, or null if none exists. 126 // The client that processes textual selection, or null if none exists.
131 private SelectionClient mSelectionClient; 127 private SelectionClient mSelectionClient;
132 128
133 // The classificaton result of the selected text if the selection exists and 129 // The classificaton result of the selected text if the selection exists and
134 // SmartSelectionProvider was able to classify it, otherwise null. 130 // SmartSelectionProvider was able to classify it, otherwise null.
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
169 final long hideDuration = getDefaultHideDuration(); 165 final long hideDuration = getDefaultHideDuration();
170 // Ensure the next hide call occurs before the ActionMode reappe ars. 166 // Ensure the next hide call occurs before the ActionMode reappe ars.
171 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); 167 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1);
172 hideActionModeTemporarily(hideDuration); 168 hideActionModeTemporarily(hideDuration);
173 } 169 }
174 }; 170 };
175 171
176 mSelectionClient = 172 mSelectionClient =
177 SmartSelectionClient.create(new SmartSelectionCallback(), window , webContents); 173 SmartSelectionClient.create(new SmartSelectionCallback(), window , webContents);
178 174
175 mLastSelectedText = "";
179 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to 176 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to
180 // Android O SDK and remove |mAssistMenuItemId|. 177 // Android O SDK and remove |mAssistMenuItemId|.
181 if (BuildInfo.isAtLeastO()) { 178 if (BuildInfo.isAtLeastO()) {
182 mAssistMenuItemId = 179 mAssistMenuItemId =
183 mContext.getResources().getIdentifier("textAssist", "id", "a ndroid"); 180 mContext.getResources().getIdentifier("textAssist", "id", "a ndroid");
184 } 181 }
185 182
186 nativeInit(webContents); 183 nativeInit(webContents);
187 } 184 }
188 185
(...skipping 27 matching lines...) Expand all
216 // True if action mode is initialized to a working (not a no-op) mode. 213 // True if action mode is initialized to a working (not a no-op) mode.
217 private boolean isActionModeSupported() { 214 private boolean isActionModeSupported() {
218 return mCallback != EMPTY_CALLBACK; 215 return mCallback != EMPTY_CALLBACK;
219 } 216 }
220 217
221 @Override 218 @Override
222 public void setAllowedMenuItems(int allowedMenuItems) { 219 public void setAllowedMenuItems(int allowedMenuItems) {
223 mAllowedMenuItems = allowedMenuItems; 220 mAllowedMenuItems = allowedMenuItems;
224 } 221 }
225 222
223 public void showSelectionMenu(int left, int top, int right, int bottom, bool ean isEditable,
224 boolean isPasswordType, String selectionText, boolean canSelectAll,
225 boolean canRichlyEdit, boolean shouldSuggest) {
226 mSelectionRect.set(left, top, right, bottom);
227 mEditable = isEditable;
228 mLastSelectedText = selectionText;
229 mIsPasswordType = isPasswordType;
230 mCanSelectAllForPastePopup = canSelectAll;
231 mCanEditRichlyForPastePopup = canRichlyEdit;
232 mUnselectAllOnDismiss = true;
233 if (hasSelection()) {
234 if (mSelectionClient != null
235 && mSelectionClient.requestSelectionPopupUpdates(shouldSugge st)) {
236 // Rely on |mSelectionClient| sending a classification request a nd the request
237 // always calling onClassified() callback.
238 mPendingShowActionMode = true;
239 } else {
240 showActionModeOrClearOnFailure();
241 }
242
243 } else {
244 createAndShowPastePopup();
245 }
246 }
247
226 /** 248 /**
227 * Show (activate) android action mode by starting it. 249 * Show (activate) android action mode by starting it.
228 * 250 *
229 * <p>Action mode in floating mode is tried first, and then falls back to 251 * <p>Action mode in floating mode is tried first, and then falls back to
230 * a normal one. 252 * a normal one.
231 * <p> If the action mode cannot be created the selection is cleared. 253 * <p> If the action mode cannot be created the selection is cleared.
232 */ 254 */
233 public void showActionModeOrClearOnFailure() { 255 public void showActionModeOrClearOnFailure() {
234 mPendingShowActionMode = false; 256 mPendingShowActionMode = false;
235 257
236 if (!isActionModeSupported() || !mHasSelection) return; 258 if (!isActionModeSupported() || !hasSelection()) return;
237 259
238 // Just refresh the view if action mode already exists. 260 // Just refresh the view if action mode already exists.
239 if (isActionModeValid()) { 261 if (isActionModeValid()) {
240 // Try/catch necessary for framework bug, crbug.com/446717. 262 // Try/catch necessary for framework bug, crbug.com/446717.
241 try { 263 try {
242 mActionMode.invalidate(); 264 mActionMode.invalidate();
243 } catch (NullPointerException e) { 265 } catch (NullPointerException e) {
244 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaro und for L", e); 266 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaro und for L", e);
245 } 267 }
246 hideActionMode(false); 268 hideActionMode(false);
(...skipping 14 matching lines...) Expand all
261 if (!isActionModeValid()) clearSelection(); 283 if (!isActionModeValid()) clearSelection();
262 } 284 }
263 285
264 @TargetApi(Build.VERSION_CODES.M) 286 @TargetApi(Build.VERSION_CODES.M)
265 private ActionMode startFloatingActionMode() { 287 private ActionMode startFloatingActionMode() {
266 ActionMode actionMode = mView.startActionMode( 288 ActionMode actionMode = mView.startActionMode(
267 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); 289 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING);
268 return actionMode; 290 return actionMode;
269 } 291 }
270 292
271 void createAndShowPastePopup( 293 private void createAndShowPastePopup() {
272 int left, int top, int right, int bottom, boolean canSelectAll, bool ean canEditRichly) {
273 if (mView.getParent() == null || mView.getVisibility() != View.VISIBLE) { 294 if (mView.getParent() == null || mView.getVisibility() != View.VISIBLE) {
274 return; 295 return;
275 } 296 }
276 297
277 if (!supportsFloatingActionMode() && !canPaste()) return; 298 if (!supportsFloatingActionMode() && !canPaste()) return;
278 destroyPastePopup(); 299 destroyPastePopup();
279 mSelectionRect.set(left, top, right, bottom);
280 mCanSelectAllForPastePopup = canSelectAll;
281 mCanEditRichlyForPastePopup = canEditRichly;
282 PastePopupMenuDelegate delegate = new PastePopupMenuDelegate() { 300 PastePopupMenuDelegate delegate = new PastePopupMenuDelegate() {
283 @Override 301 @Override
284 public void paste() { 302 public void paste() {
285 mWebContents.paste(); 303 mWebContents.paste();
286 mWebContents.dismissTextHandles(); 304 mWebContents.dismissTextHandles();
287 } 305 }
288 306
289 @Override 307 @Override
290 public void pasteAsPlainText() { 308 public void pasteAsPlainText() {
291 mWebContents.pasteAsPlainText(); 309 mWebContents.pasteAsPlainText();
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
486 updateAssistMenuItem(descriptor); 504 updateAssistMenuItem(descriptor);
487 505
488 // TODO(ctzsm): Remove "paste as plain text" for now, need to add it bac k when 506 // TODO(ctzsm): Remove "paste as plain text" for now, need to add it bac k when
489 // crrev.com/2785853002 landed. 507 // crrev.com/2785853002 landed.
490 descriptor.removeItem(R.id.select_action_menu_paste_as_plain_text); 508 descriptor.removeItem(R.id.select_action_menu_paste_as_plain_text);
491 509
492 if (!isSelectionEditable() || !canPaste()) { 510 if (!isSelectionEditable() || !canPaste()) {
493 descriptor.removeItem(R.id.select_action_menu_paste); 511 descriptor.removeItem(R.id.select_action_menu_paste);
494 } 512 }
495 513
496 if (isInsertion()) { 514 if (!hasSelection()) {
497 descriptor.removeItem(R.id.select_action_menu_select_all); 515 descriptor.removeItem(R.id.select_action_menu_select_all);
498 descriptor.removeItem(R.id.select_action_menu_cut); 516 descriptor.removeItem(R.id.select_action_menu_cut);
499 descriptor.removeItem(R.id.select_action_menu_copy); 517 descriptor.removeItem(R.id.select_action_menu_copy);
500 descriptor.removeItem(R.id.select_action_menu_share); 518 descriptor.removeItem(R.id.select_action_menu_share);
501 descriptor.removeItem(R.id.select_action_menu_web_search); 519 descriptor.removeItem(R.id.select_action_menu_web_search);
502 return descriptor; 520 return descriptor;
503 } 521 }
504 522
505 if (!isSelectionEditable()) { 523 if (!isSelectionEditable()) {
506 descriptor.removeItem(R.id.select_action_menu_cut); 524 descriptor.removeItem(R.id.select_action_menu_cut);
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
723 } 741 }
724 } 742 }
725 743
726 /** 744 /**
727 * Perform a select all action. 745 * Perform a select all action.
728 */ 746 */
729 @VisibleForTesting 747 @VisibleForTesting
730 void selectAll() { 748 void selectAll() {
731 mWebContents.selectAll(); 749 mWebContents.selectAll();
732 mClassificationResult = null; 750 mClassificationResult = null;
733 if (needsActionMenuUpdate()) showActionModeOrClearOnFailure();
734
735 // Even though the above statement logged a SelectAll user action, we wa nt to 751 // Even though the above statement logged a SelectAll user action, we wa nt to
736 // track whether the focus was in an editable field, so log that too. 752 // track whether the focus was in an editable field, so log that too.
737 if (isSelectionEditable()) { 753 if (isSelectionEditable()) {
738 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); 754 RecordUserAction.record("MobileActionMode.SelectAllWasEditable");
739 } else { 755 } else {
740 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); 756 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable");
741 } 757 }
742 } 758 }
743 759
744 /** 760 /**
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after
901 return isAllowedByClient && isShareAvailable(); 917 return isAllowedByClient && isShareAvailable();
902 } 918 }
903 return isAllowedByClient; 919 return isAllowedByClient;
904 } 920 }
905 921
906 @Override 922 @Override
907 public void onReceivedProcessTextResult(int resultCode, Intent data) { 923 public void onReceivedProcessTextResult(int resultCode, Intent data) {
908 if (mWebContents == null || resultCode != Activity.RESULT_OK || data == null) return; 924 if (mWebContents == null || resultCode != Activity.RESULT_OK || data == null) return;
909 925
910 // Do not handle the result if no text is selected or current selection is not editable. 926 // Do not handle the result if no text is selected or current selection is not editable.
911 if (!mHasSelection || !isSelectionEditable()) return; 927 if (!hasSelection() || !isSelectionEditable()) return;
yosin_UTC9 2017/05/25 04:47:20 It is better to have another patch to replace |mHa
amaralp1 2017/05/25 07:11:56 Done. https://codereview.chromium.org/2904033002
912 928
913 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); 929 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T);
914 if (result != null) { 930 if (result != null) {
915 // TODO(hush): Use a variant of replace that re-selects the replaced text. 931 // TODO(hush): Use a variant of replace that re-selects the replaced text.
916 // crbug.com/546710 932 // crbug.com/546710
917 mWebContents.replace(result.toString()); 933 mWebContents.replace(result.toString());
918 } 934 }
919 } 935 }
920 936
921 void restoreSelectionPopupsIfNecessary() { 937 void restoreSelectionPopupsIfNecessary() {
922 if (mHasSelection && !isActionModeValid()) { 938 if (hasSelection() && !isActionModeValid()) {
923 showActionModeOrClearOnFailure(); 939 showActionModeOrClearOnFailure();
924 } 940 }
925 } 941 }
926 942
927 // All coordinates are in DIP. 943 // All coordinates are in DIP.
928 @CalledByNative 944 @CalledByNative
929 private void onSelectionEvent(int eventType, int left, int top, int right, i nt bottom) { 945 private void onSelectionEvent(int eventType, int left, int top, int right, i nt bottom) {
930 // Ensure the provided selection coordinates form a non-empty rect, as r equired by 946 // Ensure the provided selection coordinates form a non-empty rect, as r equired by
931 // the selection action mode. 947 // the selection action mode.
932 if (left == right) ++right; 948 if (left == right) ++right;
933 if (top == bottom) ++bottom; 949 if (top == bottom) ++bottom;
934 switch (eventType) { 950 switch (eventType) {
935 case SelectionEventType.SELECTION_HANDLES_SHOWN: 951 case SelectionEventType.SELECTION_HANDLES_SHOWN:
936 mSelectionRect.set(left, top, right, bottom);
937 mHasSelection = true;
938 mUnselectAllOnDismiss = true;
939 if (mSelectionClient != null
940 && mSelectionClient.requestSelectionPopupUpdates(true /* suggest */)) {
941 // Rely on |mSelectionClient| sending a classification reque st and the request
942 // always calling onClassified() callback.
943 mPendingShowActionMode = true;
944 } else {
945 showActionModeOrClearOnFailure();
946 }
947 break; 952 break;
948 953
949 case SelectionEventType.SELECTION_HANDLES_MOVED: 954 case SelectionEventType.SELECTION_HANDLES_MOVED:
950 mSelectionRect.set(left, top, right, bottom); 955 mSelectionRect.set(left, top, right, bottom);
951 if (mPendingShowActionMode) { 956 if (mPendingShowActionMode) {
952 showActionModeOrClearOnFailure(); 957 showActionModeOrClearOnFailure();
953 } else { 958 } else {
954 invalidateContentRect(); 959 invalidateContentRect();
955 } 960 }
956 break; 961 break;
957 962
958 case SelectionEventType.SELECTION_HANDLES_CLEARED: 963 case SelectionEventType.SELECTION_HANDLES_CLEARED:
959 mHasSelection = false; 964 mLastSelectedText = "";
960 mUnselectAllOnDismiss = false; 965 mUnselectAllOnDismiss = false;
961 mSelectionRect.setEmpty(); 966 mSelectionRect.setEmpty();
962 if (mSelectionClient != null) mSelectionClient.cancelAllRequests (); 967 if (mSelectionClient != null) mSelectionClient.cancelAllRequests ();
963 finishActionMode(); 968 finishActionMode();
964 break; 969 break;
965 970
966 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: 971 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED:
967 hideActionMode(true); 972 hideActionMode(true);
968 break; 973 break;
969 974
970 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: 975 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED:
971 if (mSelectionClient != null 976 mWebContents.showContextMenuAtTouchHandle(left, bottom);
972 && mSelectionClient.requestSelectionPopupUpdates(false / * suggest */)) {
973 // Rely on |mSelectionClient| sending a classification reque st and the request
974 // always calling onClassified() callback.
975 } else {
976 hideActionMode(false);
977 }
978 break; 977 break;
979 978
980 case SelectionEventType.INSERTION_HANDLE_SHOWN: 979 case SelectionEventType.INSERTION_HANDLE_SHOWN:
981 mSelectionRect.set(left, top, right, bottom); 980 mSelectionRect.set(left, top, right, bottom);
982 mIsInsertion = true; 981 mIsInsertion = true;
983 break; 982 break;
984 983
985 case SelectionEventType.INSERTION_HANDLE_MOVED: 984 case SelectionEventType.INSERTION_HANDLE_MOVED:
986 mSelectionRect.set(left, top, right, bottom); 985 mSelectionRect.set(left, top, right, bottom);
987 if (!mScrollInProgress && isPastePopupShowing()) { 986 if (!mScrollInProgress && isPastePopupShowing()) {
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
1089 mIsPasswordType = isPassword; 1088 mIsPasswordType = isPassword;
1090 if (isActionModeValid()) mActionMode.invalidate(); 1089 if (isActionModeValid()) mActionMode.invalidate();
1091 } 1090 }
1092 } 1091 }
1093 1092
1094 /** 1093 /**
1095 * @return Whether the page has an active, touch-controlled selection region . 1094 * @return Whether the page has an active, touch-controlled selection region .
1096 */ 1095 */
1097 @VisibleForTesting 1096 @VisibleForTesting
1098 public boolean hasSelection() { 1097 public boolean hasSelection() {
1099 return mHasSelection; 1098 return mLastSelectedText.length() != 0;
1100 } 1099 }
1101 1100
1102 @Override 1101 @Override
1103 public String getSelectedText() { 1102 public String getSelectedText() {
1104 return mHasSelection ? mLastSelectedText : ""; 1103 return mLastSelectedText;
1105 } 1104 }
1106 1105
1107 private boolean isShareAvailable() { 1106 private boolean isShareAvailable() {
1108 Intent intent = new Intent(Intent.ACTION_SEND); 1107 Intent intent = new Intent(Intent.ACTION_SEND);
1109 intent.setType("text/plain"); 1108 intent.setType("text/plain");
1110 return mContext.getPackageManager().queryIntentActivities(intent, 1109 return mContext.getPackageManager().queryIntentActivities(intent,
1111 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; 1110 PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
1112 } 1111 }
1113 1112
1114 // TODO(timav): Use |TextClassifier| instead of |Object| after we switch to Android SDK 26. 1113 // TODO(timav): Use |TextClassifier| instead of |Object| after we switch to Android SDK 26.
(...skipping 20 matching lines...) Expand all
1135 */ 1134 */
1136 public Object getCustomTextClassifier() { 1135 public Object getCustomTextClassifier() {
1137 return mSelectionClient == null ? null : mSelectionClient.getCustomTextC lassifier(); 1136 return mSelectionClient == null ? null : mSelectionClient.getCustomTextC lassifier();
1138 } 1137 }
1139 1138
1140 // The callback class that delivers result from a SmartSelectionClient. 1139 // The callback class that delivers result from a SmartSelectionClient.
1141 private class SmartSelectionCallback implements SmartSelectionProvider.Resul tCallback { 1140 private class SmartSelectionCallback implements SmartSelectionProvider.Resul tCallback {
1142 @Override 1141 @Override
1143 public void onClassified(SmartSelectionProvider.Result result) { 1142 public void onClassified(SmartSelectionProvider.Result result) {
1144 // If the selection does not exist any more, discard |result|. 1143 // If the selection does not exist any more, discard |result|.
1145 if (!mHasSelection) { 1144 if (!hasSelection()) {
1146 assert !mHidden; 1145 assert !mHidden;
1147 assert mClassificationResult == null; 1146 assert mClassificationResult == null;
1148 mPendingShowActionMode = false; 1147 mPendingShowActionMode = false;
1149 return; 1148 return;
1150 } 1149 }
1151 1150
1152 // Do not allow classifier to shorten the selection. If the suggeste d selection is 1151 // Do not allow classifier to shorten the selection. If the suggeste d selection is
1153 // smaller than the original we throw away classification result and show the menu. 1152 // smaller than the original we throw away classification result and show the menu.
1154 // TODO(amaralp): This was added to fix the SelectAll problem in 1153 // TODO(amaralp): This was added to fix the SelectAll problem in
1155 // http://crbug.com/714106. Once we know the cause of the original s election we can 1154 // http://crbug.com/714106. Once we know the cause of the original s election we can
(...skipping 25 matching lines...) Expand all
1181 if (mPendingShowActionMode) return; 1180 if (mPendingShowActionMode) return;
1182 } 1181 }
1183 1182
1184 // Rely on this method to clear |mHidden| and unhide the action mode . 1183 // Rely on this method to clear |mHidden| and unhide the action mode .
1185 showActionModeOrClearOnFailure(); 1184 showActionModeOrClearOnFailure();
1186 } 1185 }
1187 }; 1186 };
1188 1187
1189 private native void nativeInit(WebContents webContents); 1188 private native void nativeInit(WebContents webContents);
1190 } 1189 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698