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.ClipboardManager; | 10 import android.content.ClipboardManager; |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
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 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
107 private boolean mHasSelection; | 107 private boolean mHasSelection; |
108 | 108 |
109 // Lazily created paste popup menu, triggered either via long press in an | 109 // Lazily created paste popup menu, triggered either via long press in an |
110 // editable region or from tapping the insertion handle. | 110 // editable region or from tapping the insertion handle. |
111 private PastePopupMenu mPastePopupMenu; | 111 private PastePopupMenu mPastePopupMenu; |
112 private boolean mWasPastePopupShowingOnInsertionDragStart; | 112 private boolean mWasPastePopupShowingOnInsertionDragStart; |
113 | 113 |
114 // The client that processes textual selection, or null if none exists. | 114 // The client that processes textual selection, or null if none exists. |
115 private SelectionClient mSelectionClient; | 115 private SelectionClient mSelectionClient; |
116 | 116 |
117 // The classificaton result of the selected text if the selection exists and | |
118 // ContextSelectionProvider was able to classify it, otherwise null. | |
119 private ContextSelectionProvider.Result mClassificationResult; | |
120 | |
121 // The resource ID for Assist menu item. | |
122 private int mAssistMenuItemId; | |
123 | |
124 // This variable is set to true when the classification request is in progre ss. | |
125 private boolean mPendingClassificationRequest; | |
126 | |
117 /** | 127 /** |
118 * Create {@link SelectionPopupController} instance. | 128 * Create {@link SelectionPopupController} instance. |
119 * @param context Context for action mode. | 129 * @param context Context for action mode. |
120 * @param window WindowAndroid instance. | 130 * @param window WindowAndroid instance. |
121 * @param webContents WebContents instance. | 131 * @param webContents WebContents instance. |
122 * @param view Container view. | 132 * @param view Container view. |
123 * @param renderCoordinates Coordinates info used to position elements. | 133 * @param renderCoordinates Coordinates info used to position elements. |
124 * @param imeAdapter ImeAdapter instance to handle cursor position. | 134 * @param imeAdapter ImeAdapter instance to handle cursor position. |
125 */ | 135 */ |
126 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, | 136 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, |
(...skipping 10 matching lines...) Expand all Loading... | |
137 mRepeatingHideRunnable = new Runnable() { | 147 mRepeatingHideRunnable = new Runnable() { |
138 @Override | 148 @Override |
139 public void run() { | 149 public void run() { |
140 assert mHidden; | 150 assert mHidden; |
141 final long hideDuration = getDefaultHideDuration(); | 151 final long hideDuration = getDefaultHideDuration(); |
142 // Ensure the next hide call occurs before the ActionMode reappe ars. | 152 // Ensure the next hide call occurs before the ActionMode reappe ars. |
143 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); | 153 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); |
144 hideActionModeTemporarily(hideDuration); | 154 hideActionModeTemporarily(hideDuration); |
145 } | 155 } |
146 }; | 156 }; |
157 | |
158 mSelectionClient = | |
159 ContextSelectionClient.create(new ContextSelectionCallback(), wi ndow, webContents); | |
160 | |
161 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to | |
162 // Android O SDK and remove |mAssistMenuItemId|. | |
163 mAssistMenuItemId = mContext.getResources().getIdentifier("textAssist", "id", "android"); | |
147 } | 164 } |
148 | 165 |
149 /** | 166 /** |
150 * Update the container view. | 167 * Update the container view. |
151 */ | 168 */ |
152 void setContainerView(View view) { | 169 void setContainerView(View view) { |
153 assert view != null; | 170 assert view != null; |
154 | 171 |
155 // Cleans up action mode before switching to a new container view. | 172 // Cleans up action mode before switching to a new container view. |
156 if (isActionModeValid()) finishActionMode(); | 173 if (isActionModeValid()) finishActionMode(); |
157 mUnselectAllOnDismiss = true; | 174 mUnselectAllOnDismiss = true; |
158 destroyPastePopup(); | 175 destroyPastePopup(); |
159 | 176 |
160 mView = view; | 177 mView = view; |
161 } | 178 } |
162 | 179 |
163 /** | 180 /** |
164 * Set the action mode callback. | 181 * Set the action mode callback. |
165 * @param callback ActionMode.Callback handling the callbacks from action mo de. | 182 * @param callback ActionMode.Callback handling the callbacks from action mo de. |
166 */ | 183 */ |
167 void setCallback(ActionMode.Callback callback) { | 184 void setCallback(ActionMode.Callback callback) { |
168 mCallback = callback; | 185 mCallback = callback; |
169 } | 186 } |
170 | 187 |
171 @Override | 188 @Override |
172 public boolean isActionModeValid() { | 189 public boolean isActionModeValid() { |
173 return mActionMode != null; | 190 return mActionMode != null; |
174 } | 191 } |
175 | 192 |
176 // True if action mode is not yet initialized or set to no-op mode. | 193 // True if action mode is initialized to a working (not a no-op) mode. |
177 private boolean isEmpty() { | 194 private boolean isActionModeSupported() { |
178 return mCallback == EMPTY_CALLBACK; | 195 return mCallback != EMPTY_CALLBACK; |
179 } | 196 } |
180 | 197 |
181 @Override | 198 @Override |
182 public void setAllowedMenuItems(int allowedMenuItems) { | 199 public void setAllowedMenuItems(int allowedMenuItems) { |
183 mAllowedMenuItems = allowedMenuItems; | 200 mAllowedMenuItems = allowedMenuItems; |
184 } | 201 } |
185 | 202 |
186 /** | 203 /** |
187 * Show (activate) android action mode by starting it. | 204 * Show (activate) android action mode by starting it. |
188 * | 205 * |
189 * <p>Action mode in floating mode is tried first, and then falls back to | 206 * <p>Action mode in floating mode is tried first, and then falls back to |
190 * a normal one. | 207 * a normal one. |
191 * @return {@code true} if the action mode started successfully or is alread y on. | 208 * <p> If the action mode cannot be created the selection is cleared. |
192 */ | 209 */ |
193 public boolean showActionMode() { | 210 public void showActionModeOrClearOnFailure() { |
194 if (isEmpty()) return false; | 211 if (!isActionModeSupported()) return; |
195 | 212 |
196 // Just refreshes the view if it is already showing. | 213 // Just refresh the view if action mode already exists. |
197 if (isActionModeValid()) { | 214 if (isActionModeValid()) { |
198 invalidateActionMode(); | 215 invalidateActionMode(); |
199 return true; | 216 return; |
200 } | 217 } |
201 | 218 |
202 if (mView.getParent() != null) { | 219 if (mView.getParent() != null) { |
203 // On ICS, startActionMode throws an NPE when getParent() is null. | 220 // On ICS, startActionMode throws an NPE when getParent() is null. |
204 assert mWebContents != null; | 221 assert mWebContents != null; |
205 ActionMode actionMode = supportsFloatingActionMode() | 222 ActionMode actionMode = supportsFloatingActionMode() |
206 ? startFloatingActionMode() | 223 ? startFloatingActionMode() |
207 : mView.startActionMode(mCallback); | 224 : mView.startActionMode(mCallback); |
208 if (actionMode != null) { | 225 if (actionMode != null) { |
209 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. | 226 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. |
210 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; | 227 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; |
211 } | 228 } |
212 mActionMode = actionMode; | 229 mActionMode = actionMode; |
213 } | 230 } |
214 mUnselectAllOnDismiss = true; | 231 mUnselectAllOnDismiss = true; |
215 return isActionModeValid(); | 232 if (!isActionModeValid()) clearSelection(); |
216 } | 233 } |
217 | 234 |
218 @TargetApi(Build.VERSION_CODES.M) | 235 @TargetApi(Build.VERSION_CODES.M) |
219 private ActionMode startFloatingActionMode() { | 236 private ActionMode startFloatingActionMode() { |
220 ActionMode actionMode = mView.startActionMode( | 237 ActionMode actionMode = mView.startActionMode( |
221 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); | 238 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); |
222 return actionMode; | 239 return actionMode; |
223 } | 240 } |
224 | 241 |
225 void createAndShowPastePopup(int x, int y) { | 242 void createAndShowPastePopup(int x, int y) { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
280 return mPastePopupMenu != null; | 297 return mPastePopupMenu != null; |
281 } | 298 } |
282 | 299 |
283 // Composition methods for android.view.ActionMode | 300 // Composition methods for android.view.ActionMode |
284 | 301 |
285 /** | 302 /** |
286 * @see ActionMode#finish() | 303 * @see ActionMode#finish() |
287 */ | 304 */ |
288 @Override | 305 @Override |
289 public void finishActionMode() { | 306 public void finishActionMode() { |
307 mPendingClassificationRequest = false; | |
308 mHidden = false; | |
309 if (mView != null) mView.removeCallbacks(mRepeatingHideRunnable); | |
310 | |
290 if (isActionModeValid()) { | 311 if (isActionModeValid()) { |
291 mActionMode.finish(); | 312 mActionMode.finish(); |
292 | 313 |
293 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. | 314 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. |
294 mActionMode = null; | 315 mActionMode = null; |
295 } | 316 } |
296 } | 317 } |
297 | 318 |
298 /** | 319 /** |
299 * @see ActionMode#invalidate() | 320 * @see ActionMode#invalidate() |
300 * Note that invalidation will also reset visibility state. The caller | 321 * Note that invalidation will also reset visibility state. The caller |
301 * should account for this when making subsequent visibility updates. | 322 * should account for this when making subsequent visibility updates. |
302 */ | 323 */ |
303 private void invalidateActionMode() { | 324 private void invalidateActionMode() { |
304 if (!isActionModeValid()) return; | 325 if (!isActionModeValid()) return; |
305 if (mHidden) { | 326 if (mHidden) { |
306 assert canHideActionMode(); | 327 assert canHideActionMode(); |
307 mHidden = false; | 328 mHidden = false; |
308 mView.removeCallbacks(mRepeatingHideRunnable); | 329 unhideActionMode(); |
309 } | 330 } |
310 | 331 |
311 // Try/catch necessary for framework bug, crbug.com/446717. | 332 // Try/catch necessary for framework bug, crbug.com/446717. |
312 try { | 333 try { |
313 mActionMode.invalidate(); | 334 mActionMode.invalidate(); |
314 } catch (NullPointerException e) { | 335 } catch (NullPointerException e) { |
315 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); | 336 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); |
316 } | 337 } |
317 } | 338 } |
318 | 339 |
(...skipping 21 matching lines...) Expand all Loading... | |
340 * @param hide whether to hide or show the ActionMode. | 361 * @param hide whether to hide or show the ActionMode. |
341 */ | 362 */ |
342 void hideActionMode(boolean hide) { | 363 void hideActionMode(boolean hide) { |
343 if (!canHideActionMode()) return; | 364 if (!canHideActionMode()) return; |
344 if (mHidden == hide) return; | 365 if (mHidden == hide) return; |
345 mHidden = hide; | 366 mHidden = hide; |
346 if (mHidden) { | 367 if (mHidden) { |
347 mRepeatingHideRunnable.run(); | 368 mRepeatingHideRunnable.run(); |
348 } else { | 369 } else { |
349 mHidden = false; | 370 mHidden = false; |
350 mView.removeCallbacks(mRepeatingHideRunnable); | 371 unhideActionMode(); |
351 hideActionModeTemporarily(SHOW_DELAY_MS); | |
352 } | 372 } |
353 } | 373 } |
354 | 374 |
375 private void unhideActionMode() { | |
376 mView.removeCallbacks(mRepeatingHideRunnable); | |
377 // To show the action mode that is being hidden call hide() again with a short delay. | |
378 hideActionModeTemporarily(SHOW_DELAY_MS); | |
379 } | |
380 | |
355 /** | 381 /** |
356 * @see ActionMode#hide(long) | 382 * @see ActionMode#hide(long) |
357 */ | 383 */ |
358 private void hideActionModeTemporarily(long duration) { | 384 private void hideActionModeTemporarily(long duration) { |
359 assert canHideActionMode(); | 385 assert canHideActionMode(); |
360 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 386 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
361 if (isActionModeValid()) mActionMode.hide(duration); | 387 if (isActionModeValid()) mActionMode.hide(duration); |
362 } | 388 } |
363 } | 389 } |
364 | 390 |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
406 // caused a resource loading failure to be logged. WebView | 432 // caused a resource loading failure to be logged. WebView |
407 // resource access needs to be improved so that this | 433 // resource access needs to be improved so that this |
408 // logspam can be avoided. | 434 // logspam can be avoided. |
409 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); | 435 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); |
410 } | 436 } |
411 } | 437 } |
412 | 438 |
413 private void createActionMenu(ActionMode mode, Menu menu) { | 439 private void createActionMenu(ActionMode mode, Menu menu) { |
414 mNeedsPrepare = false; | 440 mNeedsPrepare = false; |
415 initializeMenu(mContext, mode, menu); | 441 initializeMenu(mContext, mode, menu); |
442 updateAssistMenuItem(menu); | |
416 | 443 |
417 if (!isSelectionEditable() || !canPaste()) { | 444 if (!isSelectionEditable() || !canPaste()) { |
418 menu.removeItem(R.id.select_action_menu_paste); | 445 menu.removeItem(R.id.select_action_menu_paste); |
419 } | 446 } |
420 | 447 |
421 if (isInsertion()) { | 448 if (isInsertion()) { |
422 menu.removeItem(R.id.select_action_menu_select_all); | 449 menu.removeItem(R.id.select_action_menu_select_all); |
423 menu.removeItem(R.id.select_action_menu_cut); | 450 menu.removeItem(R.id.select_action_menu_cut); |
424 menu.removeItem(R.id.select_action_menu_copy); | 451 menu.removeItem(R.id.select_action_menu_copy); |
425 menu.removeItem(R.id.select_action_menu_share); | 452 menu.removeItem(R.id.select_action_menu_share); |
(...skipping 22 matching lines...) Expand all Loading... | |
448 | 475 |
449 initializeTextProcessingMenu(menu); | 476 initializeTextProcessingMenu(menu); |
450 } | 477 } |
451 | 478 |
452 private boolean canPaste() { | 479 private boolean canPaste() { |
453 ClipboardManager clipMgr = (ClipboardManager) | 480 ClipboardManager clipMgr = (ClipboardManager) |
454 mContext.getSystemService(Context.CLIPBOARD_SERVICE); | 481 mContext.getSystemService(Context.CLIPBOARD_SERVICE); |
455 return clipMgr.hasPrimaryClip(); | 482 return clipMgr.hasPrimaryClip(); |
456 } | 483 } |
457 | 484 |
485 private void updateAssistMenuItem(Menu menu) { | |
486 // The assist menu item ID has to be equal to android.R.id.textAssist. U ntil we compile | |
487 // with Android O SDK where this ID is defined we replace the correspond ing inflated | |
488 // item with an item with the proper ID. | |
489 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to | |
490 // Android O SDK and remove |mAssistMenuItemId|. | |
491 menu.removeItem(R.id.select_action_menu_assist); | |
492 if (mAssistMenuItemId == 0) return; | |
493 | |
494 if (mClassificationResult != null && mClassificationResult.hasNamedActio n()) { | |
495 menu.add(mAssistMenuItemId, mAssistMenuItemId, 1, mClassificationRes ult.label) | |
496 .setIcon(mClassificationResult.icon); | |
497 } | |
498 } | |
499 | |
458 /** | 500 /** |
459 * Intialize the menu items for processing text, if there is any. | 501 * Intialize the menu items for processing text, if there is any. |
460 */ | 502 */ |
461 private void initializeTextProcessingMenu(Menu menu) { | 503 private void initializeTextProcessingMenu(Menu menu) { |
462 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M | 504 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M |
463 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { | 505 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { |
464 return; | 506 return; |
465 } | 507 } |
466 | 508 |
467 PackageManager packageManager = mContext.getPackageManager(); | 509 PackageManager packageManager = mContext.getPackageManager(); |
468 List<ResolveInfo> supportedActivities = | 510 List<ResolveInfo> supportedActivities = |
469 packageManager.queryIntentActivities(createProcessTextIntent(), 0); | 511 packageManager.queryIntentActivities(createProcessTextIntent(), 0); |
512 if (supportedActivities.size() == 0) return; | |
513 | |
514 // Force text processing menu at the end. | |
515 final int order = getMaximumItemOrder(menu) + 1; | |
516 | |
470 for (int i = 0; i < supportedActivities.size(); i++) { | 517 for (int i = 0; i < supportedActivities.size(); i++) { |
471 ResolveInfo resolveInfo = supportedActivities.get(i); | 518 ResolveInfo resolveInfo = supportedActivities.get(i); |
472 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); | 519 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); |
473 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i , label) | 520 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, o rder + i, label) |
474 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) | 521 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) |
475 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | 522 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); |
476 } | 523 } |
477 } | 524 } |
478 | 525 |
526 /** | |
527 * Returns largest order among the menu items or 0 if no item contains the o rdering | |
528 * information (android:orderInCategory). The categories are not considered, the | |
529 * return value would only make sense if all items belong to the same catego ry. | |
530 */ | |
531 private static int getMaximumItemOrder(Menu menu) { | |
Tima Vaisburd
2017/03/28 23:16:01
Determine the maximum value of the order. We canno
boliu
2017/03/28 23:24:15
Err, this is worse than the the constant, because
Tima Vaisburd
2017/03/29 00:08:15
I like the constant better, too.
| |
532 // The mask to remove all documented categories. | |
533 int mask = ~(Menu.CATEGORY_ALTERNATIVE | Menu.CATEGORY_CONTAINER | Menu. CATEGORY_SECONDARY | |
534 | Menu.CATEGORY_SYSTEM); | |
535 | |
536 int result = 0; | |
537 for (int i = 0; i < menu.size(); ++i) { | |
538 result = Math.max(result, menu.getItem(i).getOrder() & mask); | |
539 } | |
540 return result; | |
541 } | |
542 | |
479 @TargetApi(Build.VERSION_CODES.M) | 543 @TargetApi(Build.VERSION_CODES.M) |
480 private static Intent createProcessTextIntent() { | 544 private static Intent createProcessTextIntent() { |
481 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); | 545 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); |
482 } | 546 } |
483 | 547 |
484 @TargetApi(Build.VERSION_CODES.M) | 548 @TargetApi(Build.VERSION_CODES.M) |
485 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { | 549 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { |
486 boolean isReadOnly = !isSelectionEditable(); | 550 boolean isReadOnly = !isSelectionEditable(); |
487 return createProcessTextIntent() | 551 return createProcessTextIntent() |
488 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) | 552 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) |
489 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); | 553 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); |
490 } | 554 } |
491 | 555 |
492 @Override | 556 @Override |
493 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | 557 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
494 if (!isActionModeValid()) return true; | 558 if (!isActionModeValid()) return true; |
495 | 559 |
496 int id = item.getItemId(); | 560 int id = item.getItemId(); |
497 int groupId = item.getGroupId(); | 561 int groupId = item.getGroupId(); |
498 | 562 |
499 if (id == R.id.select_action_menu_select_all) { | 563 if (id == mAssistMenuItemId) { |
564 doAssistAction(); | |
565 mode.finish(); | |
566 } else if (id == R.id.select_action_menu_select_all) { | |
500 selectAll(); | 567 selectAll(); |
501 } else if (id == R.id.select_action_menu_cut) { | 568 } else if (id == R.id.select_action_menu_cut) { |
502 cut(); | 569 cut(); |
503 mode.finish(); | 570 mode.finish(); |
504 } else if (id == R.id.select_action_menu_copy) { | 571 } else if (id == R.id.select_action_menu_copy) { |
505 copy(); | 572 copy(); |
506 mode.finish(); | 573 mode.finish(); |
507 } else if (id == R.id.select_action_menu_paste) { | 574 } else if (id == R.id.select_action_menu_paste) { |
508 paste(); | 575 paste(); |
509 mode.finish(); | 576 mode.finish(); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
548 (int) (mSelectionRect.top * deviceScale), | 615 (int) (mSelectionRect.top * deviceScale), |
549 (int) (mSelectionRect.right * deviceScale), | 616 (int) (mSelectionRect.right * deviceScale), |
550 (int) (mSelectionRect.bottom * deviceScale)); | 617 (int) (mSelectionRect.bottom * deviceScale)); |
551 | 618 |
552 // The selection coordinates are relative to the content viewport, but w e need | 619 // The selection coordinates are relative to the content viewport, but w e need |
553 // coordinates relative to the containing View. | 620 // coordinates relative to the containing View. |
554 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); | 621 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); |
555 } | 622 } |
556 | 623 |
557 /** | 624 /** |
625 * Perform an action that depends on the semantics of the selected text. | |
626 */ | |
627 @VisibleForTesting | |
628 void doAssistAction() { | |
629 if (mClassificationResult == null || !mClassificationResult.hasNamedActi on()) return; | |
630 | |
631 assert mClassificationResult.onClickListener != null | |
632 || mClassificationResult.intent != null; | |
633 | |
634 if (mClassificationResult.onClickListener != null) { | |
635 mClassificationResult.onClickListener.onClick(mView); | |
636 return; | |
637 } | |
638 | |
639 if (mClassificationResult.intent != null) { | |
640 Context context = mWindowAndroid.getContext().get(); | |
641 if (context == null) return; | |
642 | |
643 context.startActivity(mClassificationResult.intent); | |
644 return; | |
645 } | |
646 } | |
647 | |
648 /** | |
558 * Perform a select all action. | 649 * Perform a select all action. |
559 */ | 650 */ |
560 @VisibleForTesting | 651 @VisibleForTesting |
561 void selectAll() { | 652 void selectAll() { |
562 mWebContents.selectAll(); | 653 mWebContents.selectAll(); |
654 mClassificationResult = null; | |
655 mNeedsPrepare = true; | |
656 invalidateActionMode(); | |
563 // Even though the above statement logged a SelectAll user action, we wa nt to | 657 // 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. | 658 // track whether the focus was in an editable field, so log that too. |
565 if (isSelectionEditable()) { | 659 if (isSelectionEditable()) { |
566 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); | 660 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); |
567 } else { | 661 } else { |
568 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); | 662 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); |
569 } | 663 } |
570 } | 664 } |
571 | 665 |
572 /** | 666 /** |
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
725 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); | 819 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); |
726 if (result != null) { | 820 if (result != null) { |
727 // TODO(hush): Use a variant of replace that re-selects the replaced text. | 821 // TODO(hush): Use a variant of replace that re-selects the replaced text. |
728 // crbug.com/546710 | 822 // crbug.com/546710 |
729 mWebContents.replace(result.toString()); | 823 mWebContents.replace(result.toString()); |
730 } | 824 } |
731 } | 825 } |
732 | 826 |
733 void restoreSelectionPopupsIfNecessary() { | 827 void restoreSelectionPopupsIfNecessary() { |
734 if (mHasSelection && !isActionModeValid()) { | 828 if (mHasSelection && !isActionModeValid()) { |
735 if (!showActionMode()) clearSelection(); | 829 showActionModeOrClearOnFailure(); |
736 } | 830 } |
737 } | 831 } |
738 | 832 |
739 // All coordinates are in DIP. | 833 // All coordinates are in DIP. |
740 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, | 834 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, |
741 int left, int top, int right, int bottom, boolean isScrollInProgress , | 835 int left, int top, int right, int bottom, boolean isScrollInProgress , |
742 boolean touchScrollInProgress) { | 836 boolean touchScrollInProgress) { |
743 // Ensure the provided selection coordinates form a non-empty rect, as r equired by | 837 // Ensure the provided selection coordinates form a non-empty rect, as r equired by |
744 // the selection action mode. | 838 // the selection action mode. |
745 if (left == right) ++right; | 839 if (left == right) ++right; |
746 if (top == bottom) ++bottom; | 840 if (top == bottom) ++bottom; |
747 switch (eventType) { | 841 switch (eventType) { |
748 case SelectionEventType.SELECTION_HANDLES_SHOWN: | 842 case SelectionEventType.SELECTION_HANDLES_SHOWN: |
749 mSelectionRect.set(left, top, right, bottom); | 843 mSelectionRect.set(left, top, right, bottom); |
750 mHasSelection = true; | 844 mHasSelection = true; |
751 mUnselectAllOnDismiss = true; | 845 mUnselectAllOnDismiss = true; |
752 if (!showActionMode()) clearSelection(); | 846 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) { |
847 // Rely on |mSelectionClient| sending a classification reque st and the request | |
848 // always calling onClassified() callback. | |
849 mPendingClassificationRequest = true; | |
850 } else { | |
851 showActionModeOrClearOnFailure(); | |
852 } | |
753 break; | 853 break; |
754 | 854 |
755 case SelectionEventType.SELECTION_HANDLES_MOVED: | 855 case SelectionEventType.SELECTION_HANDLES_MOVED: |
756 mSelectionRect.set(left, top, right, bottom); | 856 mSelectionRect.set(left, top, right, bottom); |
757 invalidateContentRect(); | 857 invalidateContentRect(); |
758 break; | 858 break; |
759 | 859 |
760 case SelectionEventType.SELECTION_HANDLES_CLEARED: | 860 case SelectionEventType.SELECTION_HANDLES_CLEARED: |
761 mHasSelection = false; | 861 mHasSelection = false; |
762 mUnselectAllOnDismiss = false; | 862 mUnselectAllOnDismiss = false; |
763 mSelectionRect.setEmpty(); | 863 mSelectionRect.setEmpty(); |
764 finishActionMode(); | 864 finishActionMode(); |
765 break; | 865 break; |
766 | 866 |
767 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: | 867 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: |
768 hideActionMode(true); | 868 hideActionMode(true); |
769 break; | 869 break; |
770 | 870 |
771 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: | 871 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: |
772 hideActionMode(false); | 872 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) { |
873 // Rely on |mSelectionClient| sending a classification reque st and the request | |
874 // always calling onClassified() callback. | |
875 mPendingClassificationRequest = true; | |
876 } else { | |
877 hideActionMode(false); | |
878 } | |
773 break; | 879 break; |
774 | 880 |
775 case SelectionEventType.INSERTION_HANDLE_SHOWN: | 881 case SelectionEventType.INSERTION_HANDLE_SHOWN: |
776 mSelectionRect.set(left, top, right, bottom); | 882 mSelectionRect.set(left, top, right, bottom); |
777 setIsInsertion(true); | 883 setIsInsertion(true); |
778 break; | 884 break; |
779 | 885 |
780 case SelectionEventType.INSERTION_HANDLE_MOVED: | 886 case SelectionEventType.INSERTION_HANDLE_MOVED: |
781 mSelectionRect.set(left, top, right, bottom); | 887 mSelectionRect.set(left, top, right, bottom); |
782 if (!isScrollInProgress && isPastePopupShowing()) { | 888 if (!isScrollInProgress && isPastePopupShowing()) { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
823 int yAnchorPix = (int) (yAnchor * deviceScale); | 929 int yAnchorPix = (int) (yAnchor * deviceScale); |
824 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ; | 930 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ; |
825 } | 931 } |
826 } | 932 } |
827 | 933 |
828 /** | 934 /** |
829 * Clears the current text selection. Note that we will try to move cursor t o selection | 935 * Clears the current text selection. Note that we will try to move cursor t o selection |
830 * end if applicable. | 936 * end if applicable. |
831 */ | 937 */ |
832 void clearSelection() { | 938 void clearSelection() { |
833 if (mWebContents == null || isEmpty()) return; | 939 if (mWebContents == null || !isActionModeSupported()) return; |
834 mWebContents.collapseSelection(); | 940 mWebContents.collapseSelection(); |
941 mClassificationResult = null; | |
835 } | 942 } |
836 | 943 |
837 void onSelectionChanged(String text) { | 944 void onSelectionChanged(String text) { |
838 mLastSelectedText = text; | 945 mLastSelectedText = text; |
839 if (mSelectionClient != null) { | 946 if (mSelectionClient != null) { |
840 mSelectionClient.onSelectionChanged(text); | 947 mSelectionClient.onSelectionChanged(text); |
841 } | 948 } |
842 } | 949 } |
843 | 950 |
844 // The client that implements selection augmenting functionality, or null if none exists. | 951 // The client that implements selection augmenting functionality, or null if none exists. |
845 void setSelectionClient(SelectionClient selectionClient) { | 952 void setSelectionClient(SelectionClient selectionClient) { |
846 mSelectionClient = selectionClient; | 953 mSelectionClient = selectionClient; |
954 | |
955 mClassificationResult = null; | |
956 | |
957 assert !mPendingClassificationRequest; | |
958 assert !mHidden; | |
847 } | 959 } |
848 | 960 |
849 void onShowUnhandledTapUIIfNeeded(int x, int y) { | 961 void onShowUnhandledTapUIIfNeeded(int x, int y) { |
850 if (mSelectionClient != null) { | 962 if (mSelectionClient != null) { |
851 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); | 963 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); |
852 } | 964 } |
853 } | 965 } |
854 | 966 |
855 void destroyActionModeAndUnselect() { | 967 void destroyActionModeAndUnselect() { |
856 mUnselectAllOnDismiss = true; | 968 mUnselectAllOnDismiss = true; |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
891 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; | 1003 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; |
892 mIsInsertion = insertion; | 1004 mIsInsertion = insertion; |
893 } | 1005 } |
894 | 1006 |
895 private boolean isShareAvailable() { | 1007 private boolean isShareAvailable() { |
896 Intent intent = new Intent(Intent.ACTION_SEND); | 1008 Intent intent = new Intent(Intent.ACTION_SEND); |
897 intent.setType("text/plain"); | 1009 intent.setType("text/plain"); |
898 return mContext.getPackageManager().queryIntentActivities(intent, | 1010 return mContext.getPackageManager().queryIntentActivities(intent, |
899 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; | 1011 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; |
900 } | 1012 } |
1013 | |
1014 // The callback class that delivers result from a ContextSelectionClient. | |
1015 private class ContextSelectionCallback implements ContextSelectionProvider.R esultCallback { | |
1016 @Override | |
1017 public void onClassified(ContextSelectionProvider.Result result) { | |
1018 boolean pendingClassificationRequest = mPendingClassificationRequest ; | |
1019 mPendingClassificationRequest = false; | |
1020 | |
1021 // If the selection does not exist any more, discard |result|. | |
1022 if (!mHasSelection) { | |
1023 assert !mHidden; | |
1024 assert mClassificationResult == null; | |
1025 return; | |
1026 } | |
1027 | |
1028 // Determine whether we need to recreate the menu in case we are doi ng invalidate(). | |
1029 final boolean hadOldResult = | |
1030 mClassificationResult != null && mClassificationResult.hasNa medAction(); | |
1031 final boolean hasNewResult = result != null && result.hasNamedAction (); | |
1032 | |
1033 mClassificationResult = result; | |
1034 | |
1035 // Do not recreate the action mode if it has been cancelled (by Acti onMode.finish()) | |
1036 // and not recreated after that. | |
1037 if (!pendingClassificationRequest && !isActionModeValid()) { | |
1038 assert !mHidden; | |
1039 return; | |
1040 } | |
1041 | |
1042 // Update the selection range if needed. | |
1043 if (!(result.startAdjust == 0 && result.endAdjust == 0)) { | |
1044 // This call causes SELECTION_HANDLES_MOVED event | |
1045 mWebContents.adjustSelectionByCharacterOffset(result.startAdjust , result.endAdjust); | |
1046 } | |
1047 | |
1048 // For simplicity always recreate the menu if the new result exists. | |
1049 mNeedsPrepare = hasNewResult || hadOldResult; | |
1050 | |
1051 // Rely on this method to clear mHidden and unhide the action mode. | |
1052 showActionModeOrClearOnFailure(); | |
1053 } | |
1054 }; | |
901 } | 1055 } |
OLD | NEW |