Chromium Code Reviews| 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 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 108 private boolean mHasSelection; | 108 private boolean mHasSelection; |
| 109 | 109 |
| 110 // Lazily created paste popup menu, triggered either via long press in an | 110 // Lazily created paste popup menu, triggered either via long press in an |
| 111 // editable region or from tapping the insertion handle. | 111 // editable region or from tapping the insertion handle. |
| 112 private PastePopupMenu mPastePopupMenu; | 112 private PastePopupMenu mPastePopupMenu; |
| 113 private boolean mWasPastePopupShowingOnInsertionDragStart; | 113 private boolean mWasPastePopupShowingOnInsertionDragStart; |
| 114 | 114 |
| 115 // The client that processes textual selection, or null if none exists. | 115 // The client that processes textual selection, or null if none exists. |
| 116 private SelectionClient mSelectionClient; | 116 private SelectionClient mSelectionClient; |
| 117 | 117 |
| 118 // The classificaton result of the selected text if the selection exists and | |
| 119 // ContextSelectionProvider was able to classify it, otherwise null. | |
| 120 private ContextSelectionProvider.Result mClassificationResult; | |
| 121 | |
| 122 // The resource ID for Assist menu item. | |
| 123 private int mAssistMenuItemId; | |
| 124 | |
| 125 // This variable is set to true when the classification request is in progre ss. | |
| 126 private boolean mPendingClassificationRequest; | |
| 127 | |
| 118 /** | 128 /** |
| 119 * Create {@link SelectionPopupController} instance. | 129 * Create {@link SelectionPopupController} instance. |
| 120 * @param context Context for action mode. | 130 * @param context Context for action mode. |
| 121 * @param window WindowAndroid instance. | 131 * @param window WindowAndroid instance. |
| 122 * @param webContents WebContents instance. | 132 * @param webContents WebContents instance. |
| 123 * @param view Container view. | 133 * @param view Container view. |
| 124 * @param renderCoordinates Coordinates info used to position elements. | 134 * @param renderCoordinates Coordinates info used to position elements. |
| 125 * @param imeAdapter ImeAdapter instance to handle cursor position. | 135 * @param imeAdapter ImeAdapter instance to handle cursor position. |
| 126 */ | 136 */ |
| 127 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, | 137 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 138 mRepeatingHideRunnable = new Runnable() { | 148 mRepeatingHideRunnable = new Runnable() { |
| 139 @Override | 149 @Override |
| 140 public void run() { | 150 public void run() { |
| 141 assert mHidden; | 151 assert mHidden; |
| 142 final long hideDuration = getDefaultHideDuration(); | 152 final long hideDuration = getDefaultHideDuration(); |
| 143 // Ensure the next hide call occurs before the ActionMode reappe ars. | 153 // Ensure the next hide call occurs before the ActionMode reappe ars. |
| 144 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); | 154 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); |
| 145 hideActionModeTemporarily(hideDuration); | 155 hideActionModeTemporarily(hideDuration); |
| 146 } | 156 } |
| 147 }; | 157 }; |
| 158 | |
| 159 mSelectionClient = | |
| 160 ContextSelectionClient.create(new ContextSelectionCallback(), wi ndow, webContents); | |
| 161 | |
| 162 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to | |
| 163 // Android O SDK and remove |mAssistMenuItemId|. | |
| 164 mAssistMenuItemId = mContext.getResources().getIdentifier("textAssist", "id", "android"); | |
| 148 } | 165 } |
| 149 | 166 |
| 150 /** | 167 /** |
| 151 * Update the container view. | 168 * Update the container view. |
| 152 */ | 169 */ |
| 153 void setContainerView(View view) { | 170 void setContainerView(View view) { |
| 154 assert view != null; | 171 assert view != null; |
| 155 | 172 |
| 156 // Cleans up action mode before switching to a new container view. | 173 // Cleans up action mode before switching to a new container view. |
| 157 if (isActionModeValid()) finishActionMode(); | 174 if (isActionModeValid()) finishActionMode(); |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 182 @Override | 199 @Override |
| 183 public void setAllowedMenuItems(int allowedMenuItems) { | 200 public void setAllowedMenuItems(int allowedMenuItems) { |
| 184 mAllowedMenuItems = allowedMenuItems; | 201 mAllowedMenuItems = allowedMenuItems; |
| 185 } | 202 } |
| 186 | 203 |
| 187 /** | 204 /** |
| 188 * Show (activate) android action mode by starting it. | 205 * Show (activate) android action mode by starting it. |
| 189 * | 206 * |
| 190 * <p>Action mode in floating mode is tried first, and then falls back to | 207 * <p>Action mode in floating mode is tried first, and then falls back to |
| 191 * a normal one. | 208 * a normal one. |
| 192 * @return {@code true} if the action mode started successfully or is alread y on. | 209 * <p> If the action mode cannot be created the selection is cleared. |
| 193 */ | 210 */ |
| 194 public boolean showActionMode() { | 211 public void showActionModeOrClearOnFailure() { |
| 195 if (isEmpty()) return false; | 212 if (isEmpty()) return; |
|
amaralp
2017/03/25 00:52:27
Wouldn't you also want to clear the selection here
Tima Vaisburd
2017/03/25 02:57:16
I assumed no real action happens before setCallbac
boliu
2017/03/25 21:19:10
It is true. Should rename isEmpty to something lik
Tima Vaisburd
2017/03/27 06:33:04
Does isReady() sound good?
boliu
2017/03/27 17:25:07
Not really. isReady implies it might not be ready.
Tima Vaisburd
2017/03/27 18:19:45
I renamed to isActionModeSupported(), if you like
| |
| 196 | 213 |
| 197 destroyActionModeAndKeepSelection(); | 214 destroyActionModeAndKeepSelection(); |
| 198 | 215 |
| 199 if (mView.getParent() != null) { | 216 if (mView.getParent() != null) { |
| 200 // On ICS, startActionMode throws an NPE when getParent() is null. | 217 // On ICS, startActionMode throws an NPE when getParent() is null. |
| 201 assert mWebContents != null; | 218 assert mWebContents != null; |
| 202 ActionMode actionMode = supportsFloatingActionMode() | 219 ActionMode actionMode = supportsFloatingActionMode() |
| 203 ? startFloatingActionMode() | 220 ? startFloatingActionMode() |
| 204 : mView.startActionMode(mCallback); | 221 : mView.startActionMode(mCallback); |
| 205 if (actionMode != null) { | 222 if (actionMode != null) { |
| 206 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. | 223 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. |
| 207 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; | 224 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; |
| 208 } | 225 } |
| 209 mActionMode = actionMode; | 226 mActionMode = actionMode; |
| 210 } | 227 } |
| 211 mUnselectAllOnDismiss = true; | 228 mUnselectAllOnDismiss = true; |
| 212 return isActionModeValid(); | 229 if (!isActionModeValid()) clearSelection(); |
| 213 } | 230 } |
| 214 | 231 |
| 215 @TargetApi(Build.VERSION_CODES.M) | 232 @TargetApi(Build.VERSION_CODES.M) |
| 216 private ActionMode startFloatingActionMode() { | 233 private ActionMode startFloatingActionMode() { |
| 217 ActionMode actionMode = mView.startActionMode( | 234 ActionMode actionMode = mView.startActionMode( |
| 218 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); | 235 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); |
| 219 return actionMode; | 236 return actionMode; |
| 220 } | 237 } |
| 221 | 238 |
| 222 void createAndShowPastePopup(int x, int y) { | 239 void createAndShowPastePopup(int x, int y) { |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 277 return mPastePopupMenu != null; | 294 return mPastePopupMenu != null; |
| 278 } | 295 } |
| 279 | 296 |
| 280 // Composition methods for android.view.ActionMode | 297 // Composition methods for android.view.ActionMode |
| 281 | 298 |
| 282 /** | 299 /** |
| 283 * @see ActionMode#finish() | 300 * @see ActionMode#finish() |
| 284 */ | 301 */ |
| 285 @Override | 302 @Override |
| 286 public void finishActionMode() { | 303 public void finishActionMode() { |
| 304 mPendingClassificationRequest = false; | |
| 305 mHidden = false; | |
|
amaralp
2017/03/25 00:52:27
My CL crrev.com/2777663002 should make this and th
Tima Vaisburd
2017/03/27 06:33:04
I kept all the checks since I thought invalidate()
| |
| 306 mView.removeCallbacks(mRepeatingHideRunnable); | |
| 307 | |
| 287 if (isActionModeValid()) { | 308 if (isActionModeValid()) { |
| 288 mActionMode.finish(); | 309 mActionMode.finish(); |
| 289 | 310 |
| 290 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. | 311 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. |
| 291 mActionMode = null; | 312 mActionMode = null; |
| 292 } | 313 } |
| 293 } | 314 } |
| 294 | 315 |
| 295 /** | 316 /** |
| 296 * @see ActionMode#invalidateContentRect() | 317 * @see ActionMode#invalidateContentRect() |
| 297 */ | 318 */ |
| 298 public void invalidateContentRect() { | 319 private void invalidateContentRect() { |
| 299 if (supportsFloatingActionMode()) { | 320 if (supportsFloatingActionMode()) { |
| 300 if (mHidden) { | 321 if (mHidden) { |
| 301 mPendingInvalidateContentRect = true; | 322 mPendingInvalidateContentRect = true; |
| 302 } else { | 323 } else { |
| 303 mPendingInvalidateContentRect = false; | 324 mPendingInvalidateContentRect = false; |
| 304 if (isActionModeValid()) mActionMode.invalidateContentRect(); | 325 if (isActionModeValid()) mActionMode.invalidateContentRect(); |
| 305 } | 326 } |
| 306 } | 327 } |
| 307 } | 328 } |
| 308 | 329 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 322 */ | 343 */ |
| 323 void hideActionMode(boolean hide) { | 344 void hideActionMode(boolean hide) { |
| 324 if (!canHideActionMode()) return; | 345 if (!canHideActionMode()) return; |
| 325 if (mHidden == hide) return; | 346 if (mHidden == hide) return; |
| 326 mHidden = hide; | 347 mHidden = hide; |
| 327 if (mHidden) { | 348 if (mHidden) { |
| 328 mRepeatingHideRunnable.run(); | 349 mRepeatingHideRunnable.run(); |
| 329 } else { | 350 } else { |
| 330 mHidden = false; | 351 mHidden = false; |
| 331 mView.removeCallbacks(mRepeatingHideRunnable); | 352 mView.removeCallbacks(mRepeatingHideRunnable); |
| 353 // To show the action mode that is being hidden call hide() again wi th a short delay. | |
| 332 hideActionModeTemporarily(SHOW_DELAY_MS); | 354 hideActionModeTemporarily(SHOW_DELAY_MS); |
| 333 if (mPendingInvalidateContentRect) { | 355 if (mPendingInvalidateContentRect) { |
| 334 mPendingInvalidateContentRect = false; | 356 mPendingInvalidateContentRect = false; |
| 335 invalidateContentRect(); | 357 invalidateContentRect(); |
| 336 } | 358 } |
| 337 } | 359 } |
| 338 } | 360 } |
| 339 | 361 |
| 340 /** | 362 /** |
| 341 * @see ActionMode#hide(long) | 363 * @see ActionMode#hide(long) |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 391 // caused a resource loading failure to be logged. WebView | 413 // caused a resource loading failure to be logged. WebView |
| 392 // resource access needs to be improved so that this | 414 // resource access needs to be improved so that this |
| 393 // logspam can be avoided. | 415 // logspam can be avoided. |
| 394 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); | 416 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); |
| 395 } | 417 } |
| 396 } | 418 } |
| 397 | 419 |
| 398 private void createActionMenu(ActionMode mode, Menu menu) { | 420 private void createActionMenu(ActionMode mode, Menu menu) { |
| 399 mNeedsPrepare = false; | 421 mNeedsPrepare = false; |
| 400 initializeMenu(mContext, mode, menu); | 422 initializeMenu(mContext, mode, menu); |
| 423 updateAssistMenuItem(menu); | |
| 401 | 424 |
| 402 if (!isSelectionEditable() || !canPaste()) { | 425 if (!isSelectionEditable() || !canPaste()) { |
| 403 menu.removeItem(R.id.select_action_menu_paste); | 426 menu.removeItem(R.id.select_action_menu_paste); |
| 404 } | 427 } |
| 405 | 428 |
| 406 if (isInsertion()) { | 429 if (isInsertion()) { |
| 407 menu.removeItem(R.id.select_action_menu_select_all); | 430 menu.removeItem(R.id.select_action_menu_select_all); |
| 408 menu.removeItem(R.id.select_action_menu_cut); | 431 menu.removeItem(R.id.select_action_menu_cut); |
| 409 menu.removeItem(R.id.select_action_menu_copy); | 432 menu.removeItem(R.id.select_action_menu_copy); |
| 410 menu.removeItem(R.id.select_action_menu_share); | 433 menu.removeItem(R.id.select_action_menu_share); |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 433 | 456 |
| 434 initializeTextProcessingMenu(menu); | 457 initializeTextProcessingMenu(menu); |
| 435 } | 458 } |
| 436 | 459 |
| 437 private boolean canPaste() { | 460 private boolean canPaste() { |
| 438 ClipboardManager clipMgr = (ClipboardManager) | 461 ClipboardManager clipMgr = (ClipboardManager) |
| 439 mContext.getSystemService(Context.CLIPBOARD_SERVICE); | 462 mContext.getSystemService(Context.CLIPBOARD_SERVICE); |
| 440 return clipMgr.hasPrimaryClip(); | 463 return clipMgr.hasPrimaryClip(); |
| 441 } | 464 } |
| 442 | 465 |
| 466 private void updateAssistMenuItem(Menu menu) { | |
| 467 // The assist menu item ID has to be equal to android.R.id.textAssist. U ntil we compile | |
| 468 // with Android O SDK where this ID is defined we replace the correspond ing inflated | |
| 469 // item with an item with the proper ID. | |
| 470 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to | |
| 471 // Android O SDK and remove |mAssistMenuItemId|. | |
| 472 menu.removeItem(R.id.select_action_menu_assist); | |
| 473 if (mAssistMenuItemId == 0) return; | |
| 474 | |
| 475 if (mClassificationResult != null && mClassificationResult.hasNamedActio n()) { | |
| 476 menu.add(mAssistMenuItemId, mAssistMenuItemId, 1, mClassificationRes ult.label) | |
| 477 .setIcon(mClassificationResult.icon); | |
| 478 } | |
| 479 } | |
| 480 | |
| 443 /** | 481 /** |
| 444 * Intialize the menu items for processing text, if there is any. | 482 * Intialize the menu items for processing text, if there is any. |
| 445 */ | 483 */ |
| 446 private void initializeTextProcessingMenu(Menu menu) { | 484 private void initializeTextProcessingMenu(Menu menu) { |
| 447 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M | 485 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M |
| 448 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { | 486 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { |
| 449 return; | 487 return; |
| 450 } | 488 } |
| 451 | 489 |
| 452 PackageManager packageManager = mContext.getPackageManager(); | 490 PackageManager packageManager = mContext.getPackageManager(); |
| 453 List<ResolveInfo> supportedActivities = | 491 List<ResolveInfo> supportedActivities = |
| 454 packageManager.queryIntentActivities(createProcessTextIntent(), 0); | 492 packageManager.queryIntentActivities(createProcessTextIntent(), 0); |
| 493 final int order = menu.size(); | |
| 455 for (int i = 0; i < supportedActivities.size(); i++) { | 494 for (int i = 0; i < supportedActivities.size(); i++) { |
| 456 ResolveInfo resolveInfo = supportedActivities.get(i); | 495 ResolveInfo resolveInfo = supportedActivities.get(i); |
| 457 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); | 496 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); |
| 458 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i , label) | 497 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, o rder, label) |
| 459 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) | 498 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) |
| 460 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | 499 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); |
| 461 } | 500 } |
| 462 } | 501 } |
| 463 | 502 |
| 464 @TargetApi(Build.VERSION_CODES.M) | 503 @TargetApi(Build.VERSION_CODES.M) |
| 465 private static Intent createProcessTextIntent() { | 504 private static Intent createProcessTextIntent() { |
| 466 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); | 505 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); |
| 467 } | 506 } |
| 468 | 507 |
| 469 @TargetApi(Build.VERSION_CODES.M) | 508 @TargetApi(Build.VERSION_CODES.M) |
| 470 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { | 509 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { |
| 471 boolean isReadOnly = !isSelectionEditable(); | 510 boolean isReadOnly = !isSelectionEditable(); |
| 472 return createProcessTextIntent() | 511 return createProcessTextIntent() |
| 473 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) | 512 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) |
| 474 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); | 513 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); |
| 475 } | 514 } |
| 476 | 515 |
| 477 @Override | 516 @Override |
| 478 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | 517 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| 479 if (!isActionModeValid()) return true; | 518 if (!isActionModeValid()) return true; |
| 480 | 519 |
| 481 int id = item.getItemId(); | 520 int id = item.getItemId(); |
| 482 int groupId = item.getGroupId(); | 521 int groupId = item.getGroupId(); |
| 483 | 522 |
| 484 if (id == R.id.select_action_menu_select_all) { | 523 if (id == mAssistMenuItemId) { |
| 524 doAssistAction(); | |
| 525 mode.finish(); | |
| 526 } else if (id == R.id.select_action_menu_select_all) { | |
| 485 selectAll(); | 527 selectAll(); |
| 486 } else if (id == R.id.select_action_menu_cut) { | 528 } else if (id == R.id.select_action_menu_cut) { |
| 487 cut(); | 529 cut(); |
| 488 mode.finish(); | 530 mode.finish(); |
| 489 } else if (id == R.id.select_action_menu_copy) { | 531 } else if (id == R.id.select_action_menu_copy) { |
| 490 copy(); | 532 copy(); |
| 491 mode.finish(); | 533 mode.finish(); |
| 492 } else if (id == R.id.select_action_menu_paste) { | 534 } else if (id == R.id.select_action_menu_paste) { |
| 493 paste(); | 535 paste(); |
| 494 mode.finish(); | 536 mode.finish(); |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 533 (int) (mSelectionRect.top * deviceScale), | 575 (int) (mSelectionRect.top * deviceScale), |
| 534 (int) (mSelectionRect.right * deviceScale), | 576 (int) (mSelectionRect.right * deviceScale), |
| 535 (int) (mSelectionRect.bottom * deviceScale)); | 577 (int) (mSelectionRect.bottom * deviceScale)); |
| 536 | 578 |
| 537 // The selection coordinates are relative to the content viewport, but w e need | 579 // The selection coordinates are relative to the content viewport, but w e need |
| 538 // coordinates relative to the containing View. | 580 // coordinates relative to the containing View. |
| 539 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); | 581 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); |
| 540 } | 582 } |
| 541 | 583 |
| 542 /** | 584 /** |
| 585 * Perform an action that depends on the semantics of the selected text. | |
| 586 */ | |
| 587 @VisibleForTesting | |
| 588 void doAssistAction() { | |
| 589 if (mClassificationResult == null || !mClassificationResult.hasNamedActi on()) return; | |
| 590 | |
| 591 assert mClassificationResult.onClickListener != null | |
| 592 || mClassificationResult.intent != null; | |
| 593 | |
| 594 if (mClassificationResult.onClickListener != null) { | |
| 595 mClassificationResult.onClickListener.onClick(mView); | |
| 596 return; | |
| 597 } | |
| 598 | |
| 599 if (mClassificationResult.intent != null) { | |
| 600 Context context = mWindowAndroid.getContext().get(); | |
| 601 if (context == null) return; | |
| 602 | |
| 603 context.startActivity(mClassificationResult.intent); | |
| 604 return; | |
| 605 } | |
| 606 } | |
| 607 | |
| 608 /** | |
| 543 * Perform a select all action. | 609 * Perform a select all action. |
| 544 */ | 610 */ |
| 545 @VisibleForTesting | 611 @VisibleForTesting |
| 546 void selectAll() { | 612 void selectAll() { |
| 547 mWebContents.selectAll(); | 613 mWebContents.selectAll(); |
| 548 // Even though the above statement logged a SelectAll user action, we wa nt to | 614 // Even though the above statement logged a SelectAll user action, we wa nt to |
| 549 // track whether the focus was in an editable field, so log that too. | 615 // track whether the focus was in an editable field, so log that too. |
| 550 if (isSelectionEditable()) { | 616 if (isSelectionEditable()) { |
| 551 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); | 617 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); |
| 552 } else { | 618 } else { |
| (...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 710 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); | 776 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); |
| 711 if (result != null) { | 777 if (result != null) { |
| 712 // TODO(hush): Use a variant of replace that re-selects the replaced text. | 778 // TODO(hush): Use a variant of replace that re-selects the replaced text. |
| 713 // crbug.com/546710 | 779 // crbug.com/546710 |
| 714 mWebContents.replace(result.toString()); | 780 mWebContents.replace(result.toString()); |
| 715 } | 781 } |
| 716 } | 782 } |
| 717 | 783 |
| 718 void restoreSelectionPopupsIfNecessary() { | 784 void restoreSelectionPopupsIfNecessary() { |
| 719 if (mHasSelection && !isActionModeValid()) { | 785 if (mHasSelection && !isActionModeValid()) { |
| 720 if (!showActionMode()) clearSelection(); | 786 showActionModeOrClearOnFailure(); |
| 721 } | 787 } |
| 722 } | 788 } |
| 723 | 789 |
| 724 // All coordinates are in DIP. | 790 // All coordinates are in DIP. |
| 725 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, | 791 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, |
| 726 int left, int top, int right, int bottom, boolean isScrollInProgress , | 792 int left, int top, int right, int bottom, boolean isScrollInProgress , |
| 727 boolean touchScrollInProgress) { | 793 boolean touchScrollInProgress) { |
| 728 // Ensure the provided selection coordinates form a non-empty rect, as r equired by | 794 // Ensure the provided selection coordinates form a non-empty rect, as r equired by |
| 729 // the selection action mode. | 795 // the selection action mode. |
| 730 if (left == right) ++right; | 796 if (left == right) ++right; |
| 731 if (top == bottom) ++bottom; | 797 if (top == bottom) ++bottom; |
| 732 switch (eventType) { | 798 switch (eventType) { |
| 733 case SelectionEventType.SELECTION_HANDLES_SHOWN: | 799 case SelectionEventType.SELECTION_HANDLES_SHOWN: |
| 734 mSelectionRect.set(left, top, right, bottom); | 800 mSelectionRect.set(left, top, right, bottom); |
| 735 mHasSelection = true; | 801 mHasSelection = true; |
| 736 mUnselectAllOnDismiss = true; | 802 mUnselectAllOnDismiss = true; |
| 737 if (!showActionMode()) clearSelection(); | 803 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) { |
| 804 // Rely on |mSelectionClient| sending a classification reque st and the request | |
| 805 // always calling onClassified() callback. | |
| 806 mPendingClassificationRequest = true; | |
| 807 } else { | |
| 808 showActionModeOrClearOnFailure(); | |
| 809 } | |
| 738 break; | 810 break; |
| 739 | 811 |
| 740 case SelectionEventType.SELECTION_HANDLES_MOVED: | 812 case SelectionEventType.SELECTION_HANDLES_MOVED: |
| 741 mSelectionRect.set(left, top, right, bottom); | 813 mSelectionRect.set(left, top, right, bottom); |
| 742 invalidateContentRect(); | 814 invalidateContentRect(); |
| 743 break; | 815 break; |
| 744 | 816 |
| 745 case SelectionEventType.SELECTION_HANDLES_CLEARED: | 817 case SelectionEventType.SELECTION_HANDLES_CLEARED: |
| 746 mHasSelection = false; | 818 mHasSelection = false; |
| 747 mUnselectAllOnDismiss = false; | 819 mUnselectAllOnDismiss = false; |
| 748 mSelectionRect.setEmpty(); | 820 mSelectionRect.setEmpty(); |
| 749 finishActionMode(); | 821 finishActionMode(); |
| 750 break; | 822 break; |
| 751 | 823 |
| 752 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: | 824 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: |
| 753 hideActionMode(true); | 825 hideActionMode(true); |
| 754 break; | 826 break; |
| 755 | 827 |
| 756 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: | 828 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: |
| 757 hideActionMode(false); | 829 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) { |
| 830 // Rely on |mSelectionClient| sending a classification reque st and the request | |
| 831 // always calling onClassified() callback. | |
| 832 mPendingClassificationRequest = true; | |
| 833 } else { | |
| 834 hideActionMode(false); | |
| 835 } | |
| 758 break; | 836 break; |
| 759 | 837 |
| 760 case SelectionEventType.INSERTION_HANDLE_SHOWN: | 838 case SelectionEventType.INSERTION_HANDLE_SHOWN: |
| 761 mSelectionRect.set(left, top, right, bottom); | 839 mSelectionRect.set(left, top, right, bottom); |
| 762 setIsInsertion(true); | 840 setIsInsertion(true); |
| 763 break; | 841 break; |
| 764 | 842 |
| 765 case SelectionEventType.INSERTION_HANDLE_MOVED: | 843 case SelectionEventType.INSERTION_HANDLE_MOVED: |
| 766 mSelectionRect.set(left, top, right, bottom); | 844 mSelectionRect.set(left, top, right, bottom); |
| 767 if (!isScrollInProgress && isPastePopupShowing()) { | 845 if (!isScrollInProgress && isPastePopupShowing()) { |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 810 } | 888 } |
| 811 } | 889 } |
| 812 | 890 |
| 813 /** | 891 /** |
| 814 * Clears the current text selection. Note that we will try to move cursor t o selection | 892 * Clears the current text selection. Note that we will try to move cursor t o selection |
| 815 * end if applicable. | 893 * end if applicable. |
| 816 */ | 894 */ |
| 817 void clearSelection() { | 895 void clearSelection() { |
| 818 if (mWebContents == null || isEmpty()) return; | 896 if (mWebContents == null || isEmpty()) return; |
| 819 mWebContents.collapseSelection(); | 897 mWebContents.collapseSelection(); |
| 898 mClassificationResult = null; | |
| 820 } | 899 } |
| 821 | 900 |
| 822 void onSelectionChanged(String text) { | 901 void onSelectionChanged(String text) { |
| 823 mLastSelectedText = text; | 902 mLastSelectedText = text; |
| 824 if (mSelectionClient != null) { | 903 if (mSelectionClient != null) { |
| 825 mSelectionClient.onSelectionChanged(text); | 904 mSelectionClient.onSelectionChanged(text); |
| 826 } | 905 } |
| 827 } | 906 } |
| 828 | 907 |
| 829 // The client that implements selection augmenting functionality, or null if none exists. | 908 // The client that implements selection augmenting functionality, or null if none exists. |
| 830 void setSelectionClient(SelectionClient selectionClient) { | 909 void setSelectionClient(SelectionClient selectionClient) { |
| 831 mSelectionClient = selectionClient; | 910 mSelectionClient = selectionClient; |
| 911 | |
| 912 mClassificationResult = null; | |
| 913 | |
| 914 assert !mPendingClassificationRequest; | |
| 915 assert !mHidden; | |
| 832 } | 916 } |
| 833 | 917 |
| 834 void onShowUnhandledTapUIIfNeeded(int x, int y) { | 918 void onShowUnhandledTapUIIfNeeded(int x, int y) { |
| 835 if (mSelectionClient != null) { | 919 if (mSelectionClient != null) { |
| 836 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); | 920 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); |
| 837 } | 921 } |
| 838 } | 922 } |
| 839 | 923 |
| 840 void destroyActionModeAndUnselect() { | 924 void destroyActionModeAndUnselect() { |
| 841 mUnselectAllOnDismiss = true; | 925 mUnselectAllOnDismiss = true; |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 876 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; | 960 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; |
| 877 mIsInsertion = insertion; | 961 mIsInsertion = insertion; |
| 878 } | 962 } |
| 879 | 963 |
| 880 private boolean isShareAvailable() { | 964 private boolean isShareAvailable() { |
| 881 Intent intent = new Intent(Intent.ACTION_SEND); | 965 Intent intent = new Intent(Intent.ACTION_SEND); |
| 882 intent.setType("text/plain"); | 966 intent.setType("text/plain"); |
| 883 return mContext.getPackageManager().queryIntentActivities(intent, | 967 return mContext.getPackageManager().queryIntentActivities(intent, |
| 884 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; | 968 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; |
| 885 } | 969 } |
| 970 | |
| 971 // The callback class that delivers result from a ContextSelectionClient. | |
| 972 private class ContextSelectionCallback implements ContextSelectionProvider.R esultCallback { | |
| 973 @Override | |
| 974 public void onClassified(ContextSelectionProvider.Result result) { | |
| 975 boolean pendingClassificationRequest = mPendingClassificationRequest ; | |
| 976 mPendingClassificationRequest = false; | |
| 977 | |
| 978 // If the selection does not exist any more, discard |result|. | |
| 979 if (!mHasSelection) { | |
| 980 assert !mHidden; | |
| 981 assert mClassificationResult == null; | |
| 982 return; | |
| 983 } | |
| 984 | |
| 985 mClassificationResult = result; | |
| 986 | |
| 987 // The request might be cancelled by finishActionMode(). Keep the re sult and quit. | |
|
amaralp
2017/03/25 00:52:27
Why do you want to keep the result if the request
Tima Vaisburd
2017/03/25 02:57:16
This onClassified() may come after finish and recr
Tima Vaisburd
2017/03/27 06:33:04
Updated.
| |
| 988 if (!pendingClassificationRequest) { | |
| 989 assert !mHidden; | |
| 990 return; | |
| 991 } | |
| 992 | |
| 993 // Update the selection range if needed. | |
| 994 if (!(result.startAdjust == 0 && result.endAdjust == 0)) { | |
| 995 // This call causes SELECTION_HANDLES_MOVED event | |
| 996 mWebContents.adjustSelectionByCharacterOffset(result.startAdjust , result.endAdjust); | |
| 997 } | |
| 998 | |
| 999 // Rely on this method to clear mHidden and unhide the action mode. | |
| 1000 showActionModeOrClearOnFailure(); | |
| 1001 } | |
| 1002 }; | |
| 886 } | 1003 } |
| OLD | NEW |