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 |
| 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 // Display operation for ActionMode (show or invalidate) that is delayed bec ause | |
| 74 // the selected text needs to be classified before. See comment to mPostpone dDisplayOp. | |
| 75 private static final int POSTPONED_NONE = 0; | |
| 76 private static final int POSTPONED_SHOW = 1; | |
| 77 private static final int POSTPONED_INVALIDATE = 2; | |
| 78 | |
| 73 private final Context mContext; | 79 private final Context mContext; |
| 74 private final WindowAndroid mWindowAndroid; | 80 private final WindowAndroid mWindowAndroid; |
| 75 private final WebContents mWebContents; | 81 private final WebContents mWebContents; |
| 76 private final RenderCoordinates mRenderCoordinates; | 82 private final RenderCoordinates mRenderCoordinates; |
| 77 private final ImeAdapter mImeAdapter; | 83 private final ImeAdapter mImeAdapter; |
| 78 private ActionMode.Callback mCallback; | 84 private ActionMode.Callback mCallback; |
| 79 | 85 |
| 80 // Selection rectangle in DIP. | 86 // Selection rectangle in DIP. |
| 81 private final Rect mSelectionRect = new Rect(); | 87 private final Rect mSelectionRect = new Rect(); |
| 82 | 88 |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 108 private boolean mHasSelection; | 114 private boolean mHasSelection; |
| 109 | 115 |
| 110 // Lazily created paste popup menu, triggered either via long press in an | 116 // Lazily created paste popup menu, triggered either via long press in an |
| 111 // editable region or from tapping the insertion handle. | 117 // editable region or from tapping the insertion handle. |
| 112 private PastePopupMenu mPastePopupMenu; | 118 private PastePopupMenu mPastePopupMenu; |
| 113 private boolean mWasPastePopupShowingOnInsertionDragStart; | 119 private boolean mWasPastePopupShowingOnInsertionDragStart; |
| 114 | 120 |
| 115 // The client that processes textual selection, or null if none exists. | 121 // The client that processes textual selection, or null if none exists. |
| 116 private SelectionClient mSelectionClient; | 122 private SelectionClient mSelectionClient; |
| 117 | 123 |
| 124 // The classificaton result of the selected text if the selection exists and | |
| 125 // ContextSelectionProvider was able to classify it, otherwise null. | |
| 126 private ContextSelectionProvider.Result mClassificationResult; | |
| 127 | |
| 128 // Postponed display operation for ActionMode. | |
| 129 // | |
| 130 // The |mSelectionClient| might perform a recognition of the selected texts that | |
| 131 // can affect the ActionMode menu. In this case its method sendsPopupMenuUpd ates() | |
| 132 // returns true and the client itself sends the results back asynchronously. | |
| 133 // | |
| 134 // With this kind of SelectionClient we postpone the display of ActionMode m enu | |
| 135 // until the result comes, so we can update the menu before showing it and a void the flicker. | |
| 136 // This variable holds the type of operation we have postponed, i.e. show or invalidate. | |
| 137 private int mPostponedDisplayOp = POSTPONED_NONE; | |
|
boliu
2017/03/22 02:32:12
should mention this is only valid and not NONE if
Tima Vaisburd
2017/03/24 22:13:55
I followed Pedro's and your advises and tried to s
| |
| 138 | |
| 118 /** | 139 /** |
| 119 * Create {@link SelectionPopupController} instance. | 140 * Create {@link SelectionPopupController} instance. |
| 120 * @param context Context for action mode. | 141 * @param context Context for action mode. |
| 121 * @param window WindowAndroid instance. | 142 * @param window WindowAndroid instance. |
| 122 * @param webContents WebContents instance. | 143 * @param webContents WebContents instance. |
| 123 * @param view Container view. | 144 * @param view Container view. |
| 124 * @param renderCoordinates Coordinates info used to position elements. | 145 * @param renderCoordinates Coordinates info used to position elements. |
| 125 * @param imeAdapter ImeAdapter instance to handle cursor position. | 146 * @param imeAdapter ImeAdapter instance to handle cursor position. |
| 126 */ | 147 */ |
| 127 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, | 148 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 138 mRepeatingHideRunnable = new Runnable() { | 159 mRepeatingHideRunnable = new Runnable() { |
| 139 @Override | 160 @Override |
| 140 public void run() { | 161 public void run() { |
| 141 assert mHidden; | 162 assert mHidden; |
| 142 final long hideDuration = getDefaultHideDuration(); | 163 final long hideDuration = getDefaultHideDuration(); |
| 143 // Ensure the next hide call occurs before the ActionMode reappe ars. | 164 // Ensure the next hide call occurs before the ActionMode reappe ars. |
| 144 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); | 165 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); |
| 145 hideActionModeTemporarily(hideDuration); | 166 hideActionModeTemporarily(hideDuration); |
| 146 } | 167 } |
| 147 }; | 168 }; |
| 169 | |
| 170 mSelectionClient = | |
| 171 ContextSelectionClient.create(new ContextSelectionCallback(), wi ndow, webContents); | |
| 148 } | 172 } |
| 149 | 173 |
| 150 /** | 174 /** |
| 151 * Update the container view. | 175 * Update the container view. |
| 152 */ | 176 */ |
| 153 void setContainerView(View view) { | 177 void setContainerView(View view) { |
| 154 assert view != null; | 178 assert view != null; |
| 155 | 179 |
| 156 // Cleans up action mode before switching to a new container view. | 180 // Cleans up action mode before switching to a new container view. |
| 157 if (isActionModeValid()) finishActionMode(); | 181 if (isActionModeValid()) finishActionMode(); |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 182 @Override | 206 @Override |
| 183 public void setAllowedMenuItems(int allowedMenuItems) { | 207 public void setAllowedMenuItems(int allowedMenuItems) { |
| 184 mAllowedMenuItems = allowedMenuItems; | 208 mAllowedMenuItems = allowedMenuItems; |
| 185 } | 209 } |
| 186 | 210 |
| 187 /** | 211 /** |
| 188 * Show (activate) android action mode by starting it. | 212 * Show (activate) android action mode by starting it. |
| 189 * | 213 * |
| 190 * <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 |
| 191 * a normal one. | 215 * a normal one. |
| 192 * @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. |
| 193 */ | 217 */ |
| 194 public boolean showActionMode() { | 218 public void showActionModeOrClearOnFailure() { |
| 195 if (isEmpty()) return false; | 219 if (isEmpty()) { |
| 220 clearSelection(); | |
| 221 return; | |
| 222 } | |
| 196 | 223 |
| 197 // Just refreshes the view if it is already showing. | 224 // Just refreshes the view if it is already showing. |
| 198 if (isActionModeValid()) { | 225 if (isActionModeValid()) { |
| 199 invalidateActionMode(); | 226 invalidateActionMode(); |
| 200 return true; | 227 return; |
| 201 } | 228 } |
| 202 | 229 |
| 203 if (mView.getParent() != null) { | 230 if (mView.getParent() != null) { |
| 204 // On ICS, startActionMode throws an NPE when getParent() is null. | 231 // On ICS, startActionMode throws an NPE when getParent() is null. |
| 205 assert mWebContents != null; | 232 assert mWebContents != null; |
| 206 ActionMode actionMode = supportsFloatingActionMode() | 233 ActionMode actionMode = supportsFloatingActionMode() |
| 207 ? startFloatingActionMode() | 234 ? startFloatingActionMode() |
| 208 : mView.startActionMode(mCallback); | 235 : mView.startActionMode(mCallback); |
| 209 if (actionMode != null) { | 236 if (actionMode != null) { |
| 210 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. | 237 // This is to work around an LGE email issue. See crbug.com/6517 06 for more details. |
| 211 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; | 238 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode) ; |
| 212 } | 239 } |
| 213 mActionMode = actionMode; | 240 mActionMode = actionMode; |
| 214 } | 241 } |
| 215 mUnselectAllOnDismiss = true; | 242 mUnselectAllOnDismiss = true; |
| 216 return isActionModeValid(); | 243 if (!isActionModeValid()) clearSelection(); |
| 217 } | 244 } |
| 218 | 245 |
| 219 @TargetApi(Build.VERSION_CODES.M) | 246 @TargetApi(Build.VERSION_CODES.M) |
| 220 private ActionMode startFloatingActionMode() { | 247 private ActionMode startFloatingActionMode() { |
| 221 ActionMode actionMode = mView.startActionMode( | 248 ActionMode actionMode = mView.startActionMode( |
| 222 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); | 249 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); |
| 223 return actionMode; | 250 return actionMode; |
| 224 } | 251 } |
| 225 | 252 |
| 226 void showPastePopup(int x, int y) { | 253 void showPastePopup(int x, int y) { |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 288 return mPastePopupMenu != null && mPastePopupMenu.isShowing(); | 315 return mPastePopupMenu != null && mPastePopupMenu.isShowing(); |
| 289 } | 316 } |
| 290 | 317 |
| 291 // Composition methods for android.view.ActionMode | 318 // Composition methods for android.view.ActionMode |
| 292 | 319 |
| 293 /** | 320 /** |
| 294 * @see ActionMode#finish() | 321 * @see ActionMode#finish() |
| 295 */ | 322 */ |
| 296 @Override | 323 @Override |
| 297 public void finishActionMode() { | 324 public void finishActionMode() { |
| 325 mPostponedDisplayOp = POSTPONED_NONE; | |
|
boliu
2017/03/22 02:32:12
reset mClassificationResult here too?
Tima Vaisburd
2017/03/24 22:13:55
I wanted to associate mClassificationResult with t
| |
| 298 if (isActionModeValid()) { | 326 if (isActionModeValid()) { |
| 299 mActionMode.finish(); | 327 mActionMode.finish(); |
| 300 | 328 |
| 301 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. | 329 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. |
| 302 mActionMode = null; | 330 mActionMode = null; |
| 303 } | 331 } |
| 304 } | 332 } |
| 305 | 333 |
| 306 /** | 334 /** |
| 307 * @see ActionMode#invalidate() | 335 * @see ActionMode#invalidate() |
| 308 * Note that invalidation will also reset visibility state. The caller | 336 * Note that invalidation will also reset visibility state. The caller |
| 309 * should account for this when making subsequent visibility updates. | 337 * should account for this when making subsequent visibility updates. |
| 310 */ | 338 */ |
| 311 private void invalidateActionMode() { | 339 private void invalidateActionMode() { |
| 312 if (!isActionModeValid()) return; | 340 if (!isActionModeValid()) return; |
| 313 if (mHidden) { | 341 if (mHidden) { |
| 314 assert canHideActionMode(); | 342 assert canHideActionMode(); |
| 315 mHidden = false; | 343 mHidden = false; |
|
boliu
2017/03/22 02:32:12
so... 343-345 is a repeated at 392-394, factor out
Tima Vaisburd
2017/03/24 22:13:55
This code changed. Since I do not try to invalidat
| |
| 316 mView.removeCallbacks(mRepeatingHideRunnable); | 344 mView.removeCallbacks(mRepeatingHideRunnable); |
| 345 hideActionModeTemporarily(SHOW_DELAY_MS); | |
| 317 mPendingInvalidateContentRect = false; | 346 mPendingInvalidateContentRect = false; |
| 318 } | 347 } |
| 319 | 348 |
| 320 // Try/catch necessary for framework bug, crbug.com/446717. | 349 // Try/catch necessary for framework bug, crbug.com/446717. |
| 321 try { | 350 try { |
| 322 mActionMode.invalidate(); | 351 mActionMode.invalidate(); |
| 323 } catch (NullPointerException e) { | 352 } catch (NullPointerException e) { |
| 324 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); | 353 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); |
| 325 } | 354 } |
| 326 } | 355 } |
| 327 | 356 |
| 328 /** | 357 /** |
| 329 * @see ActionMode#invalidateContentRect() | 358 * @see ActionMode#invalidateContentRect() |
| 330 */ | 359 */ |
| 331 public void invalidateContentRect() { | 360 private void invalidateContentRect() { |
| 332 if (supportsFloatingActionMode()) { | 361 if (supportsFloatingActionMode()) { |
| 333 if (mHidden) { | 362 if (mHidden) { |
| 334 mPendingInvalidateContentRect = true; | 363 mPendingInvalidateContentRect = true; |
| 335 } else { | 364 } else { |
| 336 mPendingInvalidateContentRect = false; | 365 mPendingInvalidateContentRect = false; |
| 337 if (isActionModeValid()) mActionMode.invalidateContentRect(); | 366 if (isActionModeValid()) mActionMode.invalidateContentRect(); |
| 338 } | 367 } |
| 339 } | 368 } |
| 340 } | 369 } |
| 341 | 370 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 424 // caused a resource loading failure to be logged. WebView | 453 // caused a resource loading failure to be logged. WebView |
| 425 // resource access needs to be improved so that this | 454 // resource access needs to be improved so that this |
| 426 // logspam can be avoided. | 455 // logspam can be avoided. |
| 427 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); | 456 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); |
| 428 } | 457 } |
| 429 } | 458 } |
| 430 | 459 |
| 431 private void createActionMenu(ActionMode mode, Menu menu) { | 460 private void createActionMenu(ActionMode mode, Menu menu) { |
| 432 mNeedsPrepare = false; | 461 mNeedsPrepare = false; |
| 433 initializeMenu(mContext, mode, menu); | 462 initializeMenu(mContext, mode, menu); |
| 463 updateContextDependentMenuItem(menu); | |
| 434 | 464 |
| 435 if (!isSelectionEditable() || !canPaste()) { | 465 if (!isSelectionEditable() || !canPaste()) { |
| 436 menu.removeItem(R.id.select_action_menu_paste); | 466 menu.removeItem(R.id.select_action_menu_paste); |
| 437 } | 467 } |
| 438 | 468 |
| 439 if (isInsertion()) { | 469 if (isInsertion()) { |
| 440 menu.removeItem(R.id.select_action_menu_select_all); | 470 menu.removeItem(R.id.select_action_menu_select_all); |
| 441 menu.removeItem(R.id.select_action_menu_cut); | 471 menu.removeItem(R.id.select_action_menu_cut); |
| 442 menu.removeItem(R.id.select_action_menu_copy); | 472 menu.removeItem(R.id.select_action_menu_copy); |
| 443 menu.removeItem(R.id.select_action_menu_share); | 473 menu.removeItem(R.id.select_action_menu_share); |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 466 | 496 |
| 467 initializeTextProcessingMenu(menu); | 497 initializeTextProcessingMenu(menu); |
| 468 } | 498 } |
| 469 | 499 |
| 470 private boolean canPaste() { | 500 private boolean canPaste() { |
| 471 ClipboardManager clipMgr = (ClipboardManager) | 501 ClipboardManager clipMgr = (ClipboardManager) |
| 472 mContext.getSystemService(Context.CLIPBOARD_SERVICE); | 502 mContext.getSystemService(Context.CLIPBOARD_SERVICE); |
| 473 return clipMgr.hasPrimaryClip(); | 503 return clipMgr.hasPrimaryClip(); |
| 474 } | 504 } |
| 475 | 505 |
| 506 private void updateContextDependentMenuItem(Menu menu) { | |
| 507 MenuItem assistItem = menu.findItem(R.id.select_action_menu_context_dep) ; | |
| 508 if (assistItem == null) return; | |
| 509 | |
| 510 if (mClassificationResult == null) { | |
| 511 assistItem.setVisible(false).setEnabled(false); | |
| 512 } else { | |
| 513 assistItem.setVisible(true) | |
| 514 .setEnabled(true) | |
| 515 .setTitle(mClassificationResult.label) | |
| 516 .setIcon(mClassificationResult.icon); | |
| 517 } | |
| 518 } | |
| 519 | |
| 476 /** | 520 /** |
| 477 * Intialize the menu items for processing text, if there is any. | 521 * Intialize the menu items for processing text, if there is any. |
| 478 */ | 522 */ |
| 479 private void initializeTextProcessingMenu(Menu menu) { | 523 private void initializeTextProcessingMenu(Menu menu) { |
| 480 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M | 524 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M |
| 481 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { | 525 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { |
| 482 return; | 526 return; |
| 483 } | 527 } |
| 484 | 528 |
| 485 PackageManager packageManager = mContext.getPackageManager(); | 529 PackageManager packageManager = mContext.getPackageManager(); |
| 486 List<ResolveInfo> supportedActivities = | 530 List<ResolveInfo> supportedActivities = |
| 487 packageManager.queryIntentActivities(createProcessTextIntent(), 0); | 531 packageManager.queryIntentActivities(createProcessTextIntent(), 0); |
| 488 for (int i = 0; i < supportedActivities.size(); i++) { | 532 for (int i = 0; i < supportedActivities.size(); i++) { |
| 489 ResolveInfo resolveInfo = supportedActivities.get(i); | 533 ResolveInfo resolveInfo = supportedActivities.get(i); |
| 490 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); | 534 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); |
| 491 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i , label) | 535 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, M enu.NONE, label) |
| 492 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) | 536 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) |
| 493 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | 537 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); |
| 494 } | 538 } |
| 495 } | 539 } |
| 496 | 540 |
| 497 @TargetApi(Build.VERSION_CODES.M) | 541 @TargetApi(Build.VERSION_CODES.M) |
| 498 private static Intent createProcessTextIntent() { | 542 private static Intent createProcessTextIntent() { |
| 499 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); | 543 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); |
| 500 } | 544 } |
| 501 | 545 |
| 502 @TargetApi(Build.VERSION_CODES.M) | 546 @TargetApi(Build.VERSION_CODES.M) |
| 503 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { | 547 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { |
| 504 boolean isReadOnly = !isSelectionEditable(); | 548 boolean isReadOnly = !isSelectionEditable(); |
| 505 return createProcessTextIntent() | 549 return createProcessTextIntent() |
| 506 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) | 550 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) |
| 507 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); | 551 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); |
| 508 } | 552 } |
| 509 | 553 |
| 510 @Override | 554 @Override |
| 511 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | 555 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| 512 if (!isActionModeValid()) return true; | 556 if (!isActionModeValid()) return true; |
| 513 | 557 |
| 514 int id = item.getItemId(); | 558 int id = item.getItemId(); |
| 515 int groupId = item.getGroupId(); | 559 int groupId = item.getGroupId(); |
| 516 | 560 |
| 517 if (id == R.id.select_action_menu_select_all) { | 561 if (id == R.id.select_action_menu_context_dep) { |
| 562 doContextDependentAction(); | |
| 563 mode.finish(); | |
| 564 } else if (id == R.id.select_action_menu_select_all) { | |
| 518 selectAll(); | 565 selectAll(); |
| 519 } else if (id == R.id.select_action_menu_cut) { | 566 } else if (id == R.id.select_action_menu_cut) { |
| 520 cut(); | 567 cut(); |
| 521 mode.finish(); | 568 mode.finish(); |
| 522 } else if (id == R.id.select_action_menu_copy) { | 569 } else if (id == R.id.select_action_menu_copy) { |
| 523 copy(); | 570 copy(); |
| 524 mode.finish(); | 571 mode.finish(); |
| 525 } else if (id == R.id.select_action_menu_paste) { | 572 } else if (id == R.id.select_action_menu_paste) { |
| 526 paste(); | 573 paste(); |
| 527 mode.finish(); | 574 mode.finish(); |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 566 (int) (mSelectionRect.top * deviceScale), | 613 (int) (mSelectionRect.top * deviceScale), |
| 567 (int) (mSelectionRect.right * deviceScale), | 614 (int) (mSelectionRect.right * deviceScale), |
| 568 (int) (mSelectionRect.bottom * deviceScale)); | 615 (int) (mSelectionRect.bottom * deviceScale)); |
| 569 | 616 |
| 570 // The selection coordinates are relative to the content viewport, but w e need | 617 // The selection coordinates are relative to the content viewport, but w e need |
| 571 // coordinates relative to the containing View. | 618 // coordinates relative to the containing View. |
| 572 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); | 619 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); |
| 573 } | 620 } |
| 574 | 621 |
| 575 /** | 622 /** |
| 623 * Perform an action that depends on the semantics of the selected text. | |
| 624 */ | |
| 625 @VisibleForTesting | |
| 626 void doContextDependentAction() { | |
| 627 if (mClassificationResult == null) return; | |
| 628 | |
| 629 assert mClassificationResult.onClickListener != null | |
| 630 || mClassificationResult.intent != null; | |
| 631 | |
| 632 if (mClassificationResult.onClickListener != null) { | |
| 633 mClassificationResult.onClickListener.onClick(mView); | |
| 634 return; | |
| 635 } | |
| 636 | |
| 637 if (mClassificationResult.intent != null) { | |
| 638 Context context = mWindowAndroid.getContext().get(); | |
| 639 if (context == null) return; | |
| 640 | |
| 641 context.startActivity(mClassificationResult.intent); | |
| 642 return; | |
| 643 } | |
| 644 } | |
| 645 | |
| 646 /** | |
| 576 * Perform a select all action. | 647 * Perform a select all action. |
| 577 */ | 648 */ |
| 578 @VisibleForTesting | 649 @VisibleForTesting |
| 579 void selectAll() { | 650 void selectAll() { |
| 580 mWebContents.selectAll(); | 651 mWebContents.selectAll(); |
| 581 // Even though the above statement logged a SelectAll user action, we wa nt to | 652 // Even though the above statement logged a SelectAll user action, we wa nt to |
| 582 // track whether the focus was in an editable field, so log that too. | 653 // track whether the focus was in an editable field, so log that too. |
| 583 if (isSelectionEditable()) { | 654 if (isSelectionEditable()) { |
| 584 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); | 655 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); |
| 585 } else { | 656 } else { |
| (...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 743 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); | 814 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); |
| 744 if (result != null) { | 815 if (result != null) { |
| 745 // TODO(hush): Use a variant of replace that re-selects the replaced text. | 816 // TODO(hush): Use a variant of replace that re-selects the replaced text. |
| 746 // crbug.com/546710 | 817 // crbug.com/546710 |
| 747 mWebContents.replace(result.toString()); | 818 mWebContents.replace(result.toString()); |
| 748 } | 819 } |
| 749 } | 820 } |
| 750 | 821 |
| 751 void restoreSelectionPopupsIfNecessary() { | 822 void restoreSelectionPopupsIfNecessary() { |
| 752 if (mHasSelection && !isActionModeValid()) { | 823 if (mHasSelection && !isActionModeValid()) { |
| 753 if (!showActionMode()) clearSelection(); | 824 showActionModeOrClearOnFailure(); |
| 754 } | 825 } |
| 755 } | 826 } |
| 756 | 827 |
| 757 // All coordinates are in DIP. | 828 // All coordinates are in DIP. |
| 758 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, | 829 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, |
| 759 int left, int top, int right, int bottom, boolean isScrollInProgress , | 830 int left, int top, int right, int bottom, boolean isScrollInProgress , |
| 760 boolean touchScrollInProgress) { | 831 boolean touchScrollInProgress) { |
| 761 // Ensure the provided selection coordinates form a non-empty rect, as r equired by | 832 // Ensure the provided selection coordinates form a non-empty rect, as r equired by |
| 762 // the selection action mode. | 833 // the selection action mode. |
| 763 if (left == right) ++right; | 834 if (left == right) ++right; |
| 764 if (top == bottom) ++bottom; | 835 if (top == bottom) ++bottom; |
| 765 switch (eventType) { | 836 switch (eventType) { |
| 766 case SelectionEventType.SELECTION_HANDLES_SHOWN: | 837 case SelectionEventType.SELECTION_HANDLES_SHOWN: |
| 767 mSelectionRect.set(left, top, right, bottom); | 838 mSelectionRect.set(left, top, right, bottom); |
| 768 mHasSelection = true; | 839 mHasSelection = true; |
| 769 mUnselectAllOnDismiss = true; | 840 mUnselectAllOnDismiss = true; |
| 770 if (!showActionMode()) clearSelection(); | 841 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) { |
| 842 mPostponedDisplayOp = POSTPONED_SHOW; | |
| 843 } else { | |
| 844 showActionModeOrClearOnFailure(); | |
| 845 } | |
| 771 break; | 846 break; |
| 772 | 847 |
| 773 case SelectionEventType.SELECTION_HANDLES_MOVED: | 848 case SelectionEventType.SELECTION_HANDLES_MOVED: |
| 774 mSelectionRect.set(left, top, right, bottom); | 849 mSelectionRect.set(left, top, right, bottom); |
| 775 invalidateContentRect(); | 850 invalidateContentRect(); |
| 776 break; | 851 break; |
| 777 | 852 |
| 778 case SelectionEventType.SELECTION_HANDLES_CLEARED: | 853 case SelectionEventType.SELECTION_HANDLES_CLEARED: |
| 779 mHasSelection = false; | 854 mHasSelection = false; |
| 780 mUnselectAllOnDismiss = false; | 855 mUnselectAllOnDismiss = false; |
| 781 mSelectionRect.setEmpty(); | 856 mSelectionRect.setEmpty(); |
| 782 finishActionMode(); | 857 finishActionMode(); |
| 783 break; | 858 break; |
| 784 | 859 |
| 785 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: | 860 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: |
| 786 hideActionMode(true); | 861 hideActionMode(true); |
| 787 break; | 862 break; |
| 788 | 863 |
| 789 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: | 864 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: |
| 790 hideActionMode(false); | 865 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) { |
| 866 mPostponedDisplayOp = POSTPONED_INVALIDATE; | |
| 867 } else { | |
| 868 hideActionMode(false); | |
| 869 } | |
| 791 break; | 870 break; |
| 792 | 871 |
| 793 case SelectionEventType.INSERTION_HANDLE_SHOWN: | 872 case SelectionEventType.INSERTION_HANDLE_SHOWN: |
| 794 mSelectionRect.set(left, top, right, bottom); | 873 mSelectionRect.set(left, top, right, bottom); |
| 795 setIsInsertion(true); | 874 setIsInsertion(true); |
| 796 break; | 875 break; |
| 797 | 876 |
| 798 case SelectionEventType.INSERTION_HANDLE_MOVED: | 877 case SelectionEventType.INSERTION_HANDLE_MOVED: |
| 799 mSelectionRect.set(left, top, right, bottom); | 878 mSelectionRect.set(left, top, right, bottom); |
| 800 if (!isScrollInProgress && isPastePopupShowing()) { | 879 if (!isScrollInProgress && isPastePopupShowing()) { |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 841 int yAnchorPix = (int) (yAnchor * deviceScale); | 920 int yAnchorPix = (int) (yAnchor * deviceScale); |
| 842 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ; | 921 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ; |
| 843 } | 922 } |
| 844 } | 923 } |
| 845 | 924 |
| 846 /** | 925 /** |
| 847 * Clears the current text selection. Note that we will try to move cursor t o selection | 926 * Clears the current text selection. Note that we will try to move cursor t o selection |
| 848 * end if applicable. | 927 * end if applicable. |
| 849 */ | 928 */ |
| 850 void clearSelection() { | 929 void clearSelection() { |
| 930 mClassificationResult = null; | |
| 851 if (mWebContents == null || isEmpty()) return; | 931 if (mWebContents == null || isEmpty()) return; |
| 852 mWebContents.collapseSelection(); | 932 mWebContents.collapseSelection(); |
| 853 } | 933 } |
| 854 | 934 |
| 855 void onSelectionChanged(String text) { | 935 void onSelectionChanged(String text) { |
| 856 mLastSelectedText = text; | 936 mLastSelectedText = text; |
| 857 if (mSelectionClient != null) { | 937 if (mSelectionClient != null) { |
| 858 mSelectionClient.onSelectionChanged(text); | 938 mSelectionClient.onSelectionChanged(text); |
| 859 } | 939 } |
| 860 } | 940 } |
| 861 | 941 |
| 862 // The client that implements selection augmenting functionality, or null if none exists. | 942 // The client that implements selection augmenting functionality, or null if none exists. |
| 863 void setSelectionClient(SelectionClient selectionClient) { | 943 void setSelectionClient(SelectionClient selectionClient) { |
| 864 mSelectionClient = selectionClient; | 944 mSelectionClient = selectionClient; |
|
boliu
2017/03/22 02:32:12
probably want to reset some state here?
Tima Vaisburd
2017/03/24 22:13:55
Done.
| |
| 865 } | 945 } |
| 866 | 946 |
| 867 void onShowUnhandledTapUIIfNeeded(int x, int y) { | 947 void onShowUnhandledTapUIIfNeeded(int x, int y) { |
| 868 if (mSelectionClient != null) { | 948 if (mSelectionClient != null) { |
| 869 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); | 949 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); |
| 870 } | 950 } |
| 871 } | 951 } |
| 872 | 952 |
| 873 void destroyActionModeAndUnselect() { | 953 void destroyActionModeAndUnselect() { |
| 874 mUnselectAllOnDismiss = true; | 954 mUnselectAllOnDismiss = true; |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 909 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; | 989 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; |
| 910 mIsInsertion = insertion; | 990 mIsInsertion = insertion; |
| 911 } | 991 } |
| 912 | 992 |
| 913 private boolean isShareAvailable() { | 993 private boolean isShareAvailable() { |
| 914 Intent intent = new Intent(Intent.ACTION_SEND); | 994 Intent intent = new Intent(Intent.ACTION_SEND); |
| 915 intent.setType("text/plain"); | 995 intent.setType("text/plain"); |
| 916 return mContext.getPackageManager().queryIntentActivities(intent, | 996 return mContext.getPackageManager().queryIntentActivities(intent, |
| 917 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; | 997 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; |
| 918 } | 998 } |
| 999 | |
| 1000 // The callback class that delivers result from a ContextSelectionClient. | |
| 1001 private class ContextSelectionCallback implements ContextSelectionProvider.R esultCallback { | |
| 1002 @Override | |
| 1003 public void onClassified(ContextSelectionProvider.Result result) { | |
| 1004 // If the selection does not exist any more, discard |result|. | |
| 1005 if (!mHasSelection) { | |
| 1006 mPostponedDisplayOp = POSTPONED_NONE; | |
| 1007 return; | |
| 1008 } | |
| 1009 | |
| 1010 // Update the selection range if needed. | |
| 1011 if (!(result.startAdjust == 0 && result.endAdjust == 0)) { | |
| 1012 // This call causes SELECTION_HANDLES_MOVED event | |
| 1013 mWebContents.adjustSelectionByCharacterOffset(result.startAdjust , result.endAdjust); | |
| 1014 } | |
| 1015 | |
| 1016 final boolean hadPriorResult = mClassificationResult != null; | |
| 1017 mClassificationResult = result.hasNamedAction() ? result : null; | |
| 1018 | |
| 1019 // For simplicity always update the menu if we need to show assist i tem. | |
| 1020 // Also we need to update the menu if the assist item is going to di sappear. | |
| 1021 if (mClassificationResult != null || hadPriorResult) mNeedsPrepare = true; | |
| 1022 | |
| 1023 switch (mPostponedDisplayOp) { | |
| 1024 case POSTPONED_NONE: | |
| 1025 break; | |
| 1026 | |
| 1027 case POSTPONED_SHOW: | |
| 1028 // TODO(timav): if the |result| contained the selection adju stment and | |
| 1029 // we called adjustSelectionByCharacterOffset() above, there is a | |
| 1030 // flicker if SELECTION_HANDLES_MOVED comes later and invali dates the | |
| 1031 // selection rectangle. Consider postponing till SELECTION_H ANDLES_MOVED | |
| 1032 // or introduce delay. | |
| 1033 showActionModeOrClearOnFailure(); | |
| 1034 mPostponedDisplayOp = POSTPONED_NONE; | |
| 1035 break; | |
| 1036 | |
| 1037 case POSTPONED_INVALIDATE: | |
| 1038 invalidateActionMode(); | |
| 1039 mPostponedDisplayOp = POSTPONED_NONE; | |
| 1040 break; | |
| 1041 | |
| 1042 default: | |
| 1043 assert false : "Invalid postponed display operation type."; | |
| 1044 break; | |
| 1045 } | |
| 1046 } | |
| 1047 }; | |
| 919 } | 1048 } |
| OLD | NEW |