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

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

Issue 2740103006: Implement SmartText selection. (Closed)
Patch Set: Went back to the large constant for the text processing order Created 3 years, 8 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.ClipboardManager; 10 import android.content.ClipboardManager;
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
47 * A class that handles input-related web content selection UI like action mode 47 * A class that handles input-related web content selection UI like action mode
48 * and paste popup view. It wraps an {@link ActionMode} created by the associate d view, 48 * and paste popup view. It wraps an {@link ActionMode} created by the associate d view,
49 * providing modified interaction with it. 49 * providing modified interaction with it.
50 * 50 *
51 * Embedders can use {@link ActionModeCallbackHelper} implemented by this class 51 * Embedders can use {@link ActionModeCallbackHelper} implemented by this class
52 * to create {@link ActionMode.Callback} instance and configure the selection ac tion 52 * to create {@link ActionMode.Callback} instance and configure the selection ac tion
53 * mode tasks to their requirements. 53 * mode tasks to their requirements.
54 */ 54 */
55 @TargetApi(Build.VERSION_CODES.M) 55 @TargetApi(Build.VERSION_CODES.M)
56 public class SelectionPopupController extends ActionModeCallbackHelper { 56 public class SelectionPopupController extends ActionModeCallbackHelper {
57 private static final String TAG = "cr.SelectionPopCtlr"; // 20 char limit 57 private static final String TAG = "SelectionPopupCtlr"; // 20 char limit
58 58
59 /** 59 /**
60 * Android Intent size limitations prevent sending over a megabyte of data. Limit 60 * Android Intent size limitations prevent sending over a megabyte of data. Limit
61 * query lengths to 100kB because other things may be added to the Intent. 61 * query lengths to 100kB because other things may be added to the Intent.
62 */ 62 */
63 private static final int MAX_SHARE_QUERY_LENGTH = 100000; 63 private static final int MAX_SHARE_QUERY_LENGTH = 100000;
64 64
65 // Default delay for reshowing the {@link ActionMode} after it has been 65 // Default delay for reshowing the {@link ActionMode} after it has been
66 // hidden. This avoids flickering issues if there are trailing rect 66 // hidden. This avoids flickering issues if there are trailing rect
67 // invalidations after the ActionMode is shown. For example, after the user 67 // invalidations after the ActionMode is shown. For example, after the user
68 // stops dragging a selection handle, in turn showing the ActionMode, the 68 // stops dragging a selection handle, in turn showing the ActionMode, the
69 // selection change response will be asynchronous. 300ms should accomodate 69 // selection change response will be asynchronous. 300ms should accomodate
70 // most such trailing, async delays. 70 // most such trailing, async delays.
71 private static final int SHOW_DELAY_MS = 300; 71 private static final int SHOW_DELAY_MS = 300;
72 72
73 // A large value to force text processing menu items to be at the end of the
74 // context menu. Chosen to be bigger than the order of possible items in the
75 // XML template.
76 // TODO(timav): remove this constant and use show/hide for Assist item inste ad
77 // of adding and removing it once we switch to Android O SDK. The show/hide method
Ted C 2017/03/29 17:43:51 ok, I think this is the relevant comment for why w
Tima Vaisburd 2017/03/29 22:56:37 Yes.
78 // does not require ordering information.
79 private static final int MENU_ITEM_ORDER_TEXT_PROCESS_START = 100;
80
73 private final Context mContext; 81 private final Context mContext;
74 private final WindowAndroid mWindowAndroid; 82 private final WindowAndroid mWindowAndroid;
75 private final WebContents mWebContents; 83 private final WebContents mWebContents;
76 private final RenderCoordinates mRenderCoordinates; 84 private final RenderCoordinates mRenderCoordinates;
77 private final ImeAdapter mImeAdapter; 85 private final ImeAdapter mImeAdapter;
78 private ActionMode.Callback mCallback; 86 private ActionMode.Callback mCallback;
79 87
80 // Selection rectangle in DIP. 88 // Selection rectangle in DIP.
81 private final Rect mSelectionRect = new Rect(); 89 private final Rect mSelectionRect = new Rect();
82 90
(...skipping 24 matching lines...) Expand all
107 private boolean mHasSelection; 115 private boolean mHasSelection;
108 116
109 // Lazily created paste popup menu, triggered either via long press in an 117 // Lazily created paste popup menu, triggered either via long press in an
110 // editable region or from tapping the insertion handle. 118 // editable region or from tapping the insertion handle.
111 private PastePopupMenu mPastePopupMenu; 119 private PastePopupMenu mPastePopupMenu;
112 private boolean mWasPastePopupShowingOnInsertionDragStart; 120 private boolean mWasPastePopupShowingOnInsertionDragStart;
113 121
114 // The client that processes textual selection, or null if none exists. 122 // The client that processes textual selection, or null if none exists.
115 private SelectionClient mSelectionClient; 123 private SelectionClient mSelectionClient;
116 124
125 // The classificaton result of the selected text if the selection exists and
126 // ContextSelectionProvider was able to classify it, otherwise null.
127 private ContextSelectionProvider.Result mClassificationResult;
128
129 // The resource ID for Assist menu item.
130 private int mAssistMenuItemId;
131
132 // This variable is set to true when the classification request is in progre ss.
133 private boolean mPendingClassificationRequest;
134
117 /** 135 /**
118 * Create {@link SelectionPopupController} instance. 136 * Create {@link SelectionPopupController} instance.
119 * @param context Context for action mode. 137 * @param context Context for action mode.
120 * @param window WindowAndroid instance. 138 * @param window WindowAndroid instance.
121 * @param webContents WebContents instance. 139 * @param webContents WebContents instance.
122 * @param view Container view. 140 * @param view Container view.
123 * @param renderCoordinates Coordinates info used to position elements. 141 * @param renderCoordinates Coordinates info used to position elements.
124 * @param imeAdapter ImeAdapter instance to handle cursor position. 142 * @param imeAdapter ImeAdapter instance to handle cursor position.
125 */ 143 */
126 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, 144 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents,
(...skipping 10 matching lines...) Expand all
137 mRepeatingHideRunnable = new Runnable() { 155 mRepeatingHideRunnable = new Runnable() {
138 @Override 156 @Override
139 public void run() { 157 public void run() {
140 assert mHidden; 158 assert mHidden;
141 final long hideDuration = getDefaultHideDuration(); 159 final long hideDuration = getDefaultHideDuration();
142 // Ensure the next hide call occurs before the ActionMode reappe ars. 160 // Ensure the next hide call occurs before the ActionMode reappe ars.
143 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); 161 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1);
144 hideActionModeTemporarily(hideDuration); 162 hideActionModeTemporarily(hideDuration);
145 } 163 }
146 }; 164 };
165
166 mSelectionClient =
167 ContextSelectionClient.create(new ContextSelectionCallback(), wi ndow, webContents);
168
169 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to
170 // Android O SDK and remove |mAssistMenuItemId|.
171 mAssistMenuItemId = mContext.getResources().getIdentifier("textAssist", "id", "android");
Ted C 2017/03/29 17:43:51 are some of these things only applicable in O? Sh
Tima Vaisburd 2017/03/29 22:56:37 Done.
147 } 172 }
148 173
149 /** 174 /**
150 * Update the container view. 175 * Update the container view.
151 */ 176 */
152 void setContainerView(View view) { 177 void setContainerView(View view) {
153 assert view != null; 178 assert view != null;
154 179
155 // Cleans up action mode before switching to a new container view. 180 // Cleans up action mode before switching to a new container view.
156 if (isActionModeValid()) finishActionMode(); 181 if (isActionModeValid()) finishActionMode();
157 mUnselectAllOnDismiss = true; 182 mUnselectAllOnDismiss = true;
158 destroyPastePopup(); 183 destroyPastePopup();
159 184
160 mView = view; 185 mView = view;
161 } 186 }
162 187
163 /** 188 /**
164 * Set the action mode callback. 189 * Set the action mode callback.
165 * @param callback ActionMode.Callback handling the callbacks from action mo de. 190 * @param callback ActionMode.Callback handling the callbacks from action mo de.
166 */ 191 */
167 void setCallback(ActionMode.Callback callback) { 192 void setCallback(ActionMode.Callback callback) {
168 mCallback = callback; 193 mCallback = callback;
169 } 194 }
170 195
171 @Override 196 @Override
172 public boolean isActionModeValid() { 197 public boolean isActionModeValid() {
173 return mActionMode != null; 198 return mActionMode != null;
174 } 199 }
175 200
176 // True if action mode is not yet initialized or set to no-op mode. 201 // True if action mode is initialized to a working (not a no-op) mode.
177 private boolean isEmpty() { 202 private boolean isActionModeSupported() {
178 return mCallback == EMPTY_CALLBACK; 203 return mCallback != EMPTY_CALLBACK;
179 } 204 }
180 205
181 @Override 206 @Override
182 public void setAllowedMenuItems(int allowedMenuItems) { 207 public void setAllowedMenuItems(int allowedMenuItems) {
183 mAllowedMenuItems = allowedMenuItems; 208 mAllowedMenuItems = allowedMenuItems;
184 } 209 }
185 210
186 /** 211 /**
187 * Show (activate) android action mode by starting it. 212 * Show (activate) android action mode by starting it.
188 * 213 *
189 * <p>Action mode in floating mode is tried first, and then falls back to 214 * <p>Action mode in floating mode is tried first, and then falls back to
190 * a normal one. 215 * a normal one.
191 * @return {@code true} if the action mode started successfully or is alread y on. 216 * <p> If the action mode cannot be created the selection is cleared.
192 */ 217 */
193 public boolean showActionMode() { 218 public void showActionModeOrClearOnFailure() {
194 if (isEmpty()) return false; 219 if (!isActionModeSupported()) return;
195 220
196 // Just refreshes the view if it is already showing. 221 // Just refresh the view if action mode already exists.
197 if (isActionModeValid()) { 222 if (isActionModeValid()) {
198 invalidateActionMode(); 223 invalidateActionMode();
199 return true; 224 return;
200 } 225 }
201 226
202 if (mView.getParent() != null) { 227 if (mView.getParent() != null) {
203 // On ICS, startActionMode throws an NPE when getParent() is null. 228 // On ICS, startActionMode throws an NPE when getParent() is null.
204 assert mWebContents != null; 229 assert mWebContents != null;
205 ActionMode actionMode = supportsFloatingActionMode() 230 ActionMode actionMode = supportsFloatingActionMode()
206 ? startFloatingActionMode() 231 ? startFloatingActionMode()
207 : mView.startActionMode(mCallback); 232 : mView.startActionMode(mCallback);
208 if (actionMode != null) { 233 if (actionMode != null) {
209 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. 234 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details.
210 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; 235 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ;
211 } 236 }
212 mActionMode = actionMode; 237 mActionMode = actionMode;
213 } 238 }
214 mUnselectAllOnDismiss = true; 239 mUnselectAllOnDismiss = true;
215 return isActionModeValid(); 240 if (!isActionModeValid()) clearSelection();
216 } 241 }
217 242
218 @TargetApi(Build.VERSION_CODES.M) 243 @TargetApi(Build.VERSION_CODES.M)
219 private ActionMode startFloatingActionMode() { 244 private ActionMode startFloatingActionMode() {
220 ActionMode actionMode = mView.startActionMode( 245 ActionMode actionMode = mView.startActionMode(
221 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); 246 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING);
222 return actionMode; 247 return actionMode;
223 } 248 }
224 249
225 void createAndShowPastePopup(int x, int y) { 250 void createAndShowPastePopup(int x, int y) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
280 return mPastePopupMenu != null; 305 return mPastePopupMenu != null;
281 } 306 }
282 307
283 // Composition methods for android.view.ActionMode 308 // Composition methods for android.view.ActionMode
284 309
285 /** 310 /**
286 * @see ActionMode#finish() 311 * @see ActionMode#finish()
287 */ 312 */
288 @Override 313 @Override
289 public void finishActionMode() { 314 public void finishActionMode() {
315 mPendingClassificationRequest = false;
316 mHidden = false;
317 if (mView != null) mView.removeCallbacks(mRepeatingHideRunnable);
318
290 if (isActionModeValid()) { 319 if (isActionModeValid()) {
291 mActionMode.finish(); 320 mActionMode.finish();
292 321
293 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. 322 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response.
294 mActionMode = null; 323 mActionMode = null;
295 } 324 }
296 } 325 }
297 326
298 /** 327 /**
299 * @see ActionMode#invalidate() 328 * @see ActionMode#invalidate()
300 * Note that invalidation will also reset visibility state. The caller 329 * Note that invalidation will also reset visibility state. The caller
301 * should account for this when making subsequent visibility updates. 330 * should account for this when making subsequent visibility updates.
302 */ 331 */
303 private void invalidateActionMode() { 332 private void invalidateActionMode() {
304 if (!isActionModeValid()) return; 333 if (!isActionModeValid()) return;
305 if (mHidden) { 334 if (mHidden) {
306 assert canHideActionMode(); 335 assert canHideActionMode();
307 mHidden = false; 336 mHidden = false;
308 mView.removeCallbacks(mRepeatingHideRunnable); 337 unhideActionMode();
309 } 338 }
310 339
311 // Try/catch necessary for framework bug, crbug.com/446717. 340 // Try/catch necessary for framework bug, crbug.com/446717.
312 try { 341 try {
313 mActionMode.invalidate(); 342 mActionMode.invalidate();
314 } catch (NullPointerException e) { 343 } catch (NullPointerException e) {
315 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); 344 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e);
316 } 345 }
317 } 346 }
318 347
(...skipping 21 matching lines...) Expand all
340 * @param hide whether to hide or show the ActionMode. 369 * @param hide whether to hide or show the ActionMode.
341 */ 370 */
342 void hideActionMode(boolean hide) { 371 void hideActionMode(boolean hide) {
343 if (!canHideActionMode()) return; 372 if (!canHideActionMode()) return;
344 if (mHidden == hide) return; 373 if (mHidden == hide) return;
345 mHidden = hide; 374 mHidden = hide;
346 if (mHidden) { 375 if (mHidden) {
347 mRepeatingHideRunnable.run(); 376 mRepeatingHideRunnable.run();
348 } else { 377 } else {
349 mHidden = false; 378 mHidden = false;
350 mView.removeCallbacks(mRepeatingHideRunnable); 379 unhideActionMode();
351 hideActionModeTemporarily(SHOW_DELAY_MS);
352 } 380 }
353 } 381 }
354 382
383 private void unhideActionMode() {
384 mView.removeCallbacks(mRepeatingHideRunnable);
385 // To show the action mode that is being hidden call hide() again with a short delay.
386 hideActionModeTemporarily(SHOW_DELAY_MS);
387 }
388
355 /** 389 /**
356 * @see ActionMode#hide(long) 390 * @see ActionMode#hide(long)
357 */ 391 */
358 private void hideActionModeTemporarily(long duration) { 392 private void hideActionModeTemporarily(long duration) {
359 assert canHideActionMode(); 393 assert canHideActionMode();
360 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 394 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
361 if (isActionModeValid()) mActionMode.hide(duration); 395 if (isActionModeValid()) mActionMode.hide(duration);
362 } 396 }
363 } 397 }
364 398
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
406 // caused a resource loading failure to be logged. WebView 440 // caused a resource loading failure to be logged. WebView
407 // resource access needs to be improved so that this 441 // resource access needs to be improved so that this
408 // logspam can be avoided. 442 // logspam can be avoided.
409 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); 443 new MenuInflater(context).inflate(R.menu.select_action_menu, menu);
410 } 444 }
411 } 445 }
412 446
413 private void createActionMenu(ActionMode mode, Menu menu) { 447 private void createActionMenu(ActionMode mode, Menu menu) {
414 mNeedsPrepare = false; 448 mNeedsPrepare = false;
415 initializeMenu(mContext, mode, menu); 449 initializeMenu(mContext, mode, menu);
450 updateAssistMenuItem(menu);
416 451
417 if (!isSelectionEditable() || !canPaste()) { 452 if (!isSelectionEditable() || !canPaste()) {
418 menu.removeItem(R.id.select_action_menu_paste); 453 menu.removeItem(R.id.select_action_menu_paste);
419 } 454 }
420 455
421 if (isInsertion()) { 456 if (isInsertion()) {
422 menu.removeItem(R.id.select_action_menu_select_all); 457 menu.removeItem(R.id.select_action_menu_select_all);
423 menu.removeItem(R.id.select_action_menu_cut); 458 menu.removeItem(R.id.select_action_menu_cut);
424 menu.removeItem(R.id.select_action_menu_copy); 459 menu.removeItem(R.id.select_action_menu_copy);
425 menu.removeItem(R.id.select_action_menu_share); 460 menu.removeItem(R.id.select_action_menu_share);
(...skipping 22 matching lines...) Expand all
448 483
449 initializeTextProcessingMenu(menu); 484 initializeTextProcessingMenu(menu);
450 } 485 }
451 486
452 private boolean canPaste() { 487 private boolean canPaste() {
453 ClipboardManager clipMgr = (ClipboardManager) 488 ClipboardManager clipMgr = (ClipboardManager)
454 mContext.getSystemService(Context.CLIPBOARD_SERVICE); 489 mContext.getSystemService(Context.CLIPBOARD_SERVICE);
455 return clipMgr.hasPrimaryClip(); 490 return clipMgr.hasPrimaryClip();
456 } 491 }
457 492
493 private void updateAssistMenuItem(Menu menu) {
494 // The assist menu item ID has to be equal to android.R.id.textAssist. U ntil we compile
Ted C 2017/03/29 17:43:51 I think here we should again early out if not O to
Tima Vaisburd 2017/03/29 22:56:37 Done.
495 // with Android O SDK where this ID is defined we replace the correspond ing inflated
496 // item with an item with the proper ID.
497 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to
498 // Android O SDK and remove |mAssistMenuItemId|.
499 menu.removeItem(R.id.select_action_menu_assist);
500 if (mAssistMenuItemId == 0) return;
501
502 if (mClassificationResult != null && mClassificationResult.hasNamedActio n()) {
503 menu.add(mAssistMenuItemId, mAssistMenuItemId, 1, mClassificationRes ult.label)
504 .setIcon(mClassificationResult.icon);
505 }
506 }
507
458 /** 508 /**
459 * Intialize the menu items for processing text, if there is any. 509 * Intialize the menu items for processing text, if there is any.
460 */ 510 */
461 private void initializeTextProcessingMenu(Menu menu) { 511 private void initializeTextProcessingMenu(Menu menu) {
462 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M 512 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
463 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { 513 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) {
464 return; 514 return;
465 } 515 }
466 516
467 PackageManager packageManager = mContext.getPackageManager(); 517 PackageManager packageManager = mContext.getPackageManager();
468 List<ResolveInfo> supportedActivities = 518 List<ResolveInfo> supportedActivities =
469 packageManager.queryIntentActivities(createProcessTextIntent(), 0); 519 packageManager.queryIntentActivities(createProcessTextIntent(), 0);
470 for (int i = 0; i < supportedActivities.size(); i++) { 520 for (int i = 0; i < supportedActivities.size(); i++) {
471 ResolveInfo resolveInfo = supportedActivities.get(i); 521 ResolveInfo resolveInfo = supportedActivities.get(i);
472 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); 522 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r());
473 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i , label) 523 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE,
524 MENU_ITEM_ORDER_TEXT_PROCESS_START + i, label)
Ted C 2017/03/29 17:43:51 is this the indenting that clang format wants to d
Tima Vaisburd 2017/03/29 22:56:37 Yes. I fixed it back.
474 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) 525 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo ))
475 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 526 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
476 } 527 }
477 } 528 }
478 529
479 @TargetApi(Build.VERSION_CODES.M) 530 @TargetApi(Build.VERSION_CODES.M)
480 private static Intent createProcessTextIntent() { 531 private static Intent createProcessTextIntent() {
481 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); 532 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain");
482 } 533 }
483 534
484 @TargetApi(Build.VERSION_CODES.M) 535 @TargetApi(Build.VERSION_CODES.M)
485 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { 536 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) {
486 boolean isReadOnly = !isSelectionEditable(); 537 boolean isReadOnly = !isSelectionEditable();
487 return createProcessTextIntent() 538 return createProcessTextIntent()
488 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) 539 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly)
489 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); 540 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame);
490 } 541 }
491 542
492 @Override 543 @Override
493 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 544 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
494 if (!isActionModeValid()) return true; 545 if (!isActionModeValid()) return true;
495 546
496 int id = item.getItemId(); 547 int id = item.getItemId();
497 int groupId = item.getGroupId(); 548 int groupId = item.getGroupId();
498 549
499 if (id == R.id.select_action_menu_select_all) { 550 if (id == mAssistMenuItemId) {
551 doAssistAction();
552 mode.finish();
553 } else if (id == R.id.select_action_menu_select_all) {
500 selectAll(); 554 selectAll();
501 } else if (id == R.id.select_action_menu_cut) { 555 } else if (id == R.id.select_action_menu_cut) {
502 cut(); 556 cut();
503 mode.finish(); 557 mode.finish();
504 } else if (id == R.id.select_action_menu_copy) { 558 } else if (id == R.id.select_action_menu_copy) {
505 copy(); 559 copy();
506 mode.finish(); 560 mode.finish();
507 } else if (id == R.id.select_action_menu_paste) { 561 } else if (id == R.id.select_action_menu_paste) {
508 paste(); 562 paste();
509 mode.finish(); 563 mode.finish();
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
548 (int) (mSelectionRect.top * deviceScale), 602 (int) (mSelectionRect.top * deviceScale),
549 (int) (mSelectionRect.right * deviceScale), 603 (int) (mSelectionRect.right * deviceScale),
550 (int) (mSelectionRect.bottom * deviceScale)); 604 (int) (mSelectionRect.bottom * deviceScale));
551 605
552 // The selection coordinates are relative to the content viewport, but w e need 606 // The selection coordinates are relative to the content viewport, but w e need
553 // coordinates relative to the containing View. 607 // coordinates relative to the containing View.
554 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); 608 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix());
555 } 609 }
556 610
557 /** 611 /**
612 * Perform an action that depends on the semantics of the selected text.
613 */
614 @VisibleForTesting
615 void doAssistAction() {
616 if (mClassificationResult == null || !mClassificationResult.hasNamedActi on()) return;
617
618 assert mClassificationResult.onClickListener != null
619 || mClassificationResult.intent != null;
620
621 if (mClassificationResult.onClickListener != null) {
622 mClassificationResult.onClickListener.onClick(mView);
623 return;
624 }
625
626 if (mClassificationResult.intent != null) {
627 Context context = mWindowAndroid.getContext().get();
628 if (context == null) return;
629
630 context.startActivity(mClassificationResult.intent);
631 return;
632 }
633 }
634
635 /**
558 * Perform a select all action. 636 * Perform a select all action.
559 */ 637 */
560 @VisibleForTesting 638 @VisibleForTesting
561 void selectAll() { 639 void selectAll() {
562 mWebContents.selectAll(); 640 mWebContents.selectAll();
641 mClassificationResult = null;
amaralp 2017/03/29 01:18:26 I think it makes more sense to have the reset in s
Tima Vaisburd 2017/03/29 01:52:29 This would break the rotation, I think? When rotat
amaralp 2017/03/29 03:20:58 Oh right I forgot about rotation. Wouldn't you als
Tima Vaisburd 2017/03/29 06:09:26 Yes, this is great catch, but I still need to figu
642 mNeedsPrepare = true;
643 invalidateActionMode();
amaralp 2017/03/29 01:18:26 Once you rebase this should be showActionMode()
Tima Vaisburd 2017/03/29 01:52:29 Yes. In the current CL it is showActionModeOrClear
563 // Even though the above statement logged a SelectAll user action, we wa nt to 644 // Even though the above statement logged a SelectAll user action, we wa nt to
564 // track whether the focus was in an editable field, so log that too. 645 // track whether the focus was in an editable field, so log that too.
565 if (isSelectionEditable()) { 646 if (isSelectionEditable()) {
566 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); 647 RecordUserAction.record("MobileActionMode.SelectAllWasEditable");
567 } else { 648 } else {
568 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); 649 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable");
569 } 650 }
570 } 651 }
571 652
572 /** 653 /**
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after
725 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); 806 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T);
726 if (result != null) { 807 if (result != null) {
727 // TODO(hush): Use a variant of replace that re-selects the replaced text. 808 // TODO(hush): Use a variant of replace that re-selects the replaced text.
728 // crbug.com/546710 809 // crbug.com/546710
729 mWebContents.replace(result.toString()); 810 mWebContents.replace(result.toString());
730 } 811 }
731 } 812 }
732 813
733 void restoreSelectionPopupsIfNecessary() { 814 void restoreSelectionPopupsIfNecessary() {
734 if (mHasSelection && !isActionModeValid()) { 815 if (mHasSelection && !isActionModeValid()) {
735 if (!showActionMode()) clearSelection(); 816 showActionModeOrClearOnFailure();
736 } 817 }
737 } 818 }
738 819
739 // All coordinates are in DIP. 820 // All coordinates are in DIP.
740 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, 821 void onSelectionEvent(int eventType, int xAnchor, int yAnchor,
741 int left, int top, int right, int bottom, boolean isScrollInProgress , 822 int left, int top, int right, int bottom, boolean isScrollInProgress ,
742 boolean touchScrollInProgress) { 823 boolean touchScrollInProgress) {
743 // Ensure the provided selection coordinates form a non-empty rect, as r equired by 824 // Ensure the provided selection coordinates form a non-empty rect, as r equired by
744 // the selection action mode. 825 // the selection action mode.
745 if (left == right) ++right; 826 if (left == right) ++right;
746 if (top == bottom) ++bottom; 827 if (top == bottom) ++bottom;
747 switch (eventType) { 828 switch (eventType) {
748 case SelectionEventType.SELECTION_HANDLES_SHOWN: 829 case SelectionEventType.SELECTION_HANDLES_SHOWN:
749 mSelectionRect.set(left, top, right, bottom); 830 mSelectionRect.set(left, top, right, bottom);
750 mHasSelection = true; 831 mHasSelection = true;
751 mUnselectAllOnDismiss = true; 832 mUnselectAllOnDismiss = true;
752 if (!showActionMode()) clearSelection(); 833 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) {
834 // Rely on |mSelectionClient| sending a classification reque st and the request
835 // always calling onClassified() callback.
836 mPendingClassificationRequest = true;
837 } else {
838 showActionModeOrClearOnFailure();
839 }
753 break; 840 break;
754 841
755 case SelectionEventType.SELECTION_HANDLES_MOVED: 842 case SelectionEventType.SELECTION_HANDLES_MOVED:
756 mSelectionRect.set(left, top, right, bottom); 843 mSelectionRect.set(left, top, right, bottom);
757 invalidateContentRect(); 844 invalidateContentRect();
758 break; 845 break;
759 846
760 case SelectionEventType.SELECTION_HANDLES_CLEARED: 847 case SelectionEventType.SELECTION_HANDLES_CLEARED:
761 mHasSelection = false; 848 mHasSelection = false;
762 mUnselectAllOnDismiss = false; 849 mUnselectAllOnDismiss = false;
763 mSelectionRect.setEmpty(); 850 mSelectionRect.setEmpty();
764 finishActionMode(); 851 finishActionMode();
765 break; 852 break;
766 853
767 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: 854 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED:
768 hideActionMode(true); 855 hideActionMode(true);
769 break; 856 break;
770 857
771 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: 858 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED:
772 hideActionMode(false); 859 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) {
860 // Rely on |mSelectionClient| sending a classification reque st and the request
861 // always calling onClassified() callback.
862 mPendingClassificationRequest = true;
863 } else {
864 hideActionMode(false);
865 }
773 break; 866 break;
774 867
775 case SelectionEventType.INSERTION_HANDLE_SHOWN: 868 case SelectionEventType.INSERTION_HANDLE_SHOWN:
776 mSelectionRect.set(left, top, right, bottom); 869 mSelectionRect.set(left, top, right, bottom);
777 setIsInsertion(true); 870 setIsInsertion(true);
778 break; 871 break;
779 872
780 case SelectionEventType.INSERTION_HANDLE_MOVED: 873 case SelectionEventType.INSERTION_HANDLE_MOVED:
781 mSelectionRect.set(left, top, right, bottom); 874 mSelectionRect.set(left, top, right, bottom);
782 if (!isScrollInProgress && isPastePopupShowing()) { 875 if (!isScrollInProgress && isPastePopupShowing()) {
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
823 int yAnchorPix = (int) (yAnchor * deviceScale); 916 int yAnchorPix = (int) (yAnchor * deviceScale);
824 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ; 917 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ;
825 } 918 }
826 } 919 }
827 920
828 /** 921 /**
829 * Clears the current text selection. Note that we will try to move cursor t o selection 922 * Clears the current text selection. Note that we will try to move cursor t o selection
830 * end if applicable. 923 * end if applicable.
831 */ 924 */
832 void clearSelection() { 925 void clearSelection() {
833 if (mWebContents == null || isEmpty()) return; 926 if (mWebContents == null || !isActionModeSupported()) return;
834 mWebContents.collapseSelection(); 927 mWebContents.collapseSelection();
928 mClassificationResult = null;
835 } 929 }
836 930
837 void onSelectionChanged(String text) { 931 void onSelectionChanged(String text) {
838 mLastSelectedText = text; 932 mLastSelectedText = text;
839 if (mSelectionClient != null) { 933 if (mSelectionClient != null) {
840 mSelectionClient.onSelectionChanged(text); 934 mSelectionClient.onSelectionChanged(text);
841 } 935 }
842 } 936 }
843 937
844 // The client that implements selection augmenting functionality, or null if none exists. 938 // The client that implements selection augmenting functionality, or null if none exists.
845 void setSelectionClient(SelectionClient selectionClient) { 939 void setSelectionClient(SelectionClient selectionClient) {
846 mSelectionClient = selectionClient; 940 mSelectionClient = selectionClient;
941
942 mClassificationResult = null;
943
944 assert !mPendingClassificationRequest;
945 assert !mHidden;
847 } 946 }
848 947
849 void onShowUnhandledTapUIIfNeeded(int x, int y) { 948 void onShowUnhandledTapUIIfNeeded(int x, int y) {
850 if (mSelectionClient != null) { 949 if (mSelectionClient != null) {
851 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); 950 mSelectionClient.showUnhandledTapUIIfNeeded(x, y);
852 } 951 }
853 } 952 }
854 953
855 void destroyActionModeAndUnselect() { 954 void destroyActionModeAndUnselect() {
856 mUnselectAllOnDismiss = true; 955 mUnselectAllOnDismiss = true;
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
891 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; 990 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue;
892 mIsInsertion = insertion; 991 mIsInsertion = insertion;
893 } 992 }
894 993
895 private boolean isShareAvailable() { 994 private boolean isShareAvailable() {
896 Intent intent = new Intent(Intent.ACTION_SEND); 995 Intent intent = new Intent(Intent.ACTION_SEND);
897 intent.setType("text/plain"); 996 intent.setType("text/plain");
898 return mContext.getPackageManager().queryIntentActivities(intent, 997 return mContext.getPackageManager().queryIntentActivities(intent,
899 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; 998 PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
900 } 999 }
1000
1001 // The callback class that delivers result from a ContextSelectionClient.
1002 private class ContextSelectionCallback implements ContextSelectionProvider.R esultCallback {
1003 @Override
1004 public void onClassified(ContextSelectionProvider.Result result) {
1005 boolean pendingClassificationRequest = mPendingClassificationRequest ;
1006 mPendingClassificationRequest = false;
1007
1008 // If the selection does not exist any more, discard |result|.
1009 if (!mHasSelection) {
1010 assert !mHidden;
1011 assert mClassificationResult == null;
1012 return;
1013 }
1014
1015 // Determine whether we need to recreate the menu in case we are doi ng invalidate().
1016 final boolean hadOldResult =
1017 mClassificationResult != null && mClassificationResult.hasNa medAction();
1018 final boolean hasNewResult = result != null && result.hasNamedAction ();
1019
1020 mClassificationResult = result;
amaralp 2017/03/29 01:18:25 I still don't understand why you'd want to keep th
Tima Vaisburd 2017/03/29 01:52:29 I'm not sure I understood your question. I save it
Tima Vaisburd 2017/03/29 06:09:26 I think I got your question: there is a case where
1021
1022 // Do not recreate the action mode if it has been cancelled (by Acti onMode.finish())
1023 // and not recreated after that.
1024 if (!pendingClassificationRequest && !isActionModeValid()) {
amaralp 2017/03/29 01:18:26 Why would you want to showActionMode if pendingCla
Tima Vaisburd 2017/03/29 01:52:29 I think the following scenario is possible: 1. Re
amaralp 2017/03/29 03:20:58 That makes sense. Thanks for the clarification!
1025 assert !mHidden;
1026 return;
1027 }
1028
1029 // Update the selection range if needed.
1030 if (!(result.startAdjust == 0 && result.endAdjust == 0)) {
1031 // This call causes SELECTION_HANDLES_MOVED event
1032 mWebContents.adjustSelectionByCharacterOffset(result.startAdjust , result.endAdjust);
1033 }
1034
1035 // For simplicity always recreate the menu if the new result exists.
1036 mNeedsPrepare = hasNewResult || hadOldResult;
1037
1038 // Rely on this method to clear mHidden and unhide the action mode.
1039 showActionModeOrClearOnFailure();
1040 }
1041 };
901 } 1042 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698