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