OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 |
(...skipping 25 matching lines...) Expand all Loading... |
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 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
487 updateAssistMenuItem(descriptor); | 505 updateAssistMenuItem(descriptor); |
488 | 506 |
489 // TODO(ctzsm): Remove "paste as plain text" for now, need to add it bac
k when | 507 // TODO(ctzsm): Remove "paste as plain text" for now, need to add it bac
k when |
490 // crrev.com/2785853002 landed. | 508 // crrev.com/2785853002 landed. |
491 descriptor.removeItem(R.id.select_action_menu_paste_as_plain_text); | 509 descriptor.removeItem(R.id.select_action_menu_paste_as_plain_text); |
492 | 510 |
493 if (!isSelectionEditable() || !canPaste()) { | 511 if (!isSelectionEditable() || !canPaste()) { |
494 descriptor.removeItem(R.id.select_action_menu_paste); | 512 descriptor.removeItem(R.id.select_action_menu_paste); |
495 } | 513 } |
496 | 514 |
497 if (isInsertion()) { | 515 if (!hasSelection()) { |
498 descriptor.removeItem(R.id.select_action_menu_select_all); | 516 descriptor.removeItem(R.id.select_action_menu_select_all); |
499 descriptor.removeItem(R.id.select_action_menu_cut); | 517 descriptor.removeItem(R.id.select_action_menu_cut); |
500 descriptor.removeItem(R.id.select_action_menu_copy); | 518 descriptor.removeItem(R.id.select_action_menu_copy); |
501 descriptor.removeItem(R.id.select_action_menu_share); | 519 descriptor.removeItem(R.id.select_action_menu_share); |
502 descriptor.removeItem(R.id.select_action_menu_web_search); | 520 descriptor.removeItem(R.id.select_action_menu_web_search); |
503 return descriptor; | 521 return descriptor; |
504 } | 522 } |
505 | 523 |
506 if (!isSelectionEditable()) { | 524 if (!isSelectionEditable()) { |
507 descriptor.removeItem(R.id.select_action_menu_cut); | 525 descriptor.removeItem(R.id.select_action_menu_cut); |
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
724 } | 742 } |
725 } | 743 } |
726 | 744 |
727 /** | 745 /** |
728 * Perform a select all action. | 746 * Perform a select all action. |
729 */ | 747 */ |
730 @VisibleForTesting | 748 @VisibleForTesting |
731 void selectAll() { | 749 void selectAll() { |
732 mWebContents.selectAll(); | 750 mWebContents.selectAll(); |
733 mClassificationResult = null; | 751 mClassificationResult = null; |
734 if (needsActionMenuUpdate()) showActionModeOrClearOnFailure(); | |
735 | |
736 // Even though the above statement logged a SelectAll user action, we wa
nt to | 752 // Even though the above statement logged a SelectAll user action, we wa
nt to |
737 // track whether the focus was in an editable field, so log that too. | 753 // track whether the focus was in an editable field, so log that too. |
738 if (isSelectionEditable()) { | 754 if (isSelectionEditable()) { |
739 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); | 755 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); |
740 } else { | 756 } else { |
741 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); | 757 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); |
742 } | 758 } |
743 } | 759 } |
744 | 760 |
745 /** | 761 /** |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
927 | 943 |
928 // All coordinates are in DIP. | 944 // All coordinates are in DIP. |
929 @CalledByNative | 945 @CalledByNative |
930 private void onSelectionEvent(int eventType, int left, int top, int right, i
nt bottom) { | 946 private void onSelectionEvent(int eventType, int left, int top, int right, i
nt bottom) { |
931 // Ensure the provided selection coordinates form a non-empty rect, as r
equired by | 947 // Ensure the provided selection coordinates form a non-empty rect, as r
equired by |
932 // the selection action mode. | 948 // the selection action mode. |
933 if (left == right) ++right; | 949 if (left == right) ++right; |
934 if (top == bottom) ++bottom; | 950 if (top == bottom) ++bottom; |
935 switch (eventType) { | 951 switch (eventType) { |
936 case SelectionEventType.SELECTION_HANDLES_SHOWN: | 952 case SelectionEventType.SELECTION_HANDLES_SHOWN: |
937 mSelectionRect.set(left, top, right, bottom); | |
938 mHasSelection = true; | |
939 mUnselectAllOnDismiss = true; | |
940 if (mSelectionClient != null | |
941 && mSelectionClient.requestSelectionPopupUpdates(true /*
suggest */)) { | |
942 // Rely on |mSelectionClient| sending a classification reque
st and the request | |
943 // always calling onClassified() callback. | |
944 mPendingShowActionMode = true; | |
945 } else { | |
946 showActionModeOrClearOnFailure(); | |
947 } | |
948 break; | 953 break; |
949 | 954 |
950 case SelectionEventType.SELECTION_HANDLES_MOVED: | 955 case SelectionEventType.SELECTION_HANDLES_MOVED: |
951 mSelectionRect.set(left, top, right, bottom); | 956 mSelectionRect.set(left, top, right, bottom); |
952 if (mPendingShowActionMode) { | 957 if (mPendingShowActionMode) { |
953 showActionModeOrClearOnFailure(); | 958 showActionModeOrClearOnFailure(); |
954 } else { | 959 } else { |
955 invalidateContentRect(); | 960 invalidateContentRect(); |
956 } | 961 } |
957 break; | 962 break; |
958 | 963 |
959 case SelectionEventType.SELECTION_HANDLES_CLEARED: | 964 case SelectionEventType.SELECTION_HANDLES_CLEARED: |
960 mHasSelection = false; | 965 mLastSelectedText = ""; |
961 mUnselectAllOnDismiss = false; | 966 mUnselectAllOnDismiss = false; |
962 mSelectionRect.setEmpty(); | 967 mSelectionRect.setEmpty(); |
963 if (mSelectionClient != null) mSelectionClient.cancelAllRequests
(); | 968 if (mSelectionClient != null) mSelectionClient.cancelAllRequests
(); |
964 finishActionMode(); | 969 finishActionMode(); |
965 break; | 970 break; |
966 | 971 |
967 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: | 972 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: |
968 hideActionMode(true); | 973 hideActionMode(true); |
969 break; | 974 break; |
970 | 975 |
971 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: | 976 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: |
972 if (mSelectionClient != null | 977 mWebContents.showContextMenuAtTouchHandle(left, bottom); |
973 && mSelectionClient.requestSelectionPopupUpdates(false /
* suggest */)) { | |
974 // Rely on |mSelectionClient| sending a classification reque
st and the request | |
975 // always calling onClassified() callback. | |
976 } else { | |
977 hideActionMode(false); | |
978 } | |
979 break; | 978 break; |
980 | 979 |
981 case SelectionEventType.INSERTION_HANDLE_SHOWN: | 980 case SelectionEventType.INSERTION_HANDLE_SHOWN: |
982 mSelectionRect.set(left, top, right, bottom); | 981 mSelectionRect.set(left, top, right, bottom); |
983 mIsInsertion = true; | 982 mIsInsertion = true; |
984 break; | 983 break; |
985 | 984 |
986 case SelectionEventType.INSERTION_HANDLE_MOVED: | 985 case SelectionEventType.INSERTION_HANDLE_MOVED: |
987 mSelectionRect.set(left, top, right, bottom); | 986 mSelectionRect.set(left, top, right, bottom); |
988 if (!mScrollInProgress && isPastePopupShowing()) { | 987 if (!mScrollInProgress && isPastePopupShowing()) { |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1090 mIsPasswordType = isPassword; | 1089 mIsPasswordType = isPassword; |
1091 if (isActionModeValid()) mActionMode.invalidate(); | 1090 if (isActionModeValid()) mActionMode.invalidate(); |
1092 } | 1091 } |
1093 } | 1092 } |
1094 | 1093 |
1095 /** | 1094 /** |
1096 * @return Whether the page has an active, touch-controlled selection region
. | 1095 * @return Whether the page has an active, touch-controlled selection region
. |
1097 */ | 1096 */ |
1098 @VisibleForTesting | 1097 @VisibleForTesting |
1099 public boolean hasSelection() { | 1098 public boolean hasSelection() { |
1100 return mHasSelection; | 1099 return mLastSelectedText.length() != 0; |
1101 } | 1100 } |
1102 | 1101 |
1103 @Override | 1102 @Override |
1104 public String getSelectedText() { | 1103 public String getSelectedText() { |
1105 return hasSelection() ? mLastSelectedText : ""; | 1104 return mLastSelectedText; |
1106 } | 1105 } |
1107 | 1106 |
1108 private boolean isShareAvailable() { | 1107 private boolean isShareAvailable() { |
1109 Intent intent = new Intent(Intent.ACTION_SEND); | 1108 Intent intent = new Intent(Intent.ACTION_SEND); |
1110 intent.setType("text/plain"); | 1109 intent.setType("text/plain"); |
1111 return mContext.getPackageManager().queryIntentActivities(intent, | 1110 return mContext.getPackageManager().queryIntentActivities(intent, |
1112 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; | 1111 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; |
1113 } | 1112 } |
1114 | 1113 |
1115 // TODO(timav): Use |TextClassifier| instead of |Object| after we switch to
Android SDK 26. | 1114 // TODO(timav): Use |TextClassifier| instead of |Object| after we switch to
Android SDK 26. |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1182 if (mPendingShowActionMode) return; | 1181 if (mPendingShowActionMode) return; |
1183 } | 1182 } |
1184 | 1183 |
1185 // Rely on this method to clear |mHidden| and unhide the action mode
. | 1184 // Rely on this method to clear |mHidden| and unhide the action mode
. |
1186 showActionModeOrClearOnFailure(); | 1185 showActionModeOrClearOnFailure(); |
1187 } | 1186 } |
1188 }; | 1187 }; |
1189 | 1188 |
1190 private native void nativeInit(WebContents webContents); | 1189 private native void nativeInit(WebContents webContents); |
1191 } | 1190 } |
OLD | NEW |