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

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

Issue 2740103006: Implement SmartText selection. (Closed)
Patch Set: Made ~ContextSelectionClient() public and inlined SelectionPopupControler.unhideActionMode() Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.content.browser; 5 package org.chromium.content.browser;
6 6
7 import android.annotation.TargetApi; 7 import android.annotation.TargetApi;
8 import android.app.Activity; 8 import android.app.Activity;
9 import android.app.SearchManager; 9 import android.app.SearchManager;
10 import android.content.ClipboardManager; 10 import android.content.ClipboardManager;
11 import android.content.Context; 11 import android.content.Context;
12 import android.content.Intent; 12 import android.content.Intent;
13 import android.content.pm.PackageManager; 13 import android.content.pm.PackageManager;
14 import android.content.pm.ResolveInfo; 14 import android.content.pm.ResolveInfo;
15 import android.content.res.Resources; 15 import android.content.res.Resources;
16 import android.graphics.Rect; 16 import android.graphics.Rect;
17 import android.os.Build; 17 import android.os.Build;
18 import android.provider.Browser; 18 import android.provider.Browser;
19 import android.text.TextUtils; 19 import android.text.TextUtils;
20 import android.view.ActionMode; 20 import android.view.ActionMode;
21 import android.view.Menu; 21 import android.view.Menu;
22 import android.view.MenuInflater; 22 import android.view.MenuInflater;
23 import android.view.MenuItem; 23 import android.view.MenuItem;
24 import android.view.View; 24 import android.view.View;
25 import android.view.ViewConfiguration; 25 import android.view.ViewConfiguration;
26 import android.view.WindowManager; 26 import android.view.WindowManager;
27 27
28 import org.chromium.base.BuildInfo;
28 import org.chromium.base.Log; 29 import org.chromium.base.Log;
29 import org.chromium.base.VisibleForTesting; 30 import org.chromium.base.VisibleForTesting;
30 import org.chromium.base.metrics.RecordUserAction; 31 import org.chromium.base.metrics.RecordUserAction;
31 import org.chromium.content.R; 32 import org.chromium.content.R;
32 import org.chromium.content.browser.input.FloatingPastePopupMenu; 33 import org.chromium.content.browser.input.FloatingPastePopupMenu;
33 import org.chromium.content.browser.input.ImeAdapter; 34 import org.chromium.content.browser.input.ImeAdapter;
34 import org.chromium.content.browser.input.LGEmailActionModeWorkaround; 35 import org.chromium.content.browser.input.LGEmailActionModeWorkaround;
35 import org.chromium.content.browser.input.LegacyPastePopupMenu; 36 import org.chromium.content.browser.input.LegacyPastePopupMenu;
36 import org.chromium.content.browser.input.PastePopupMenu; 37 import org.chromium.content.browser.input.PastePopupMenu;
37 import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate; 38 import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
38 import org.chromium.content_public.browser.ActionModeCallbackHelper; 39 import org.chromium.content_public.browser.ActionModeCallbackHelper;
39 import org.chromium.content_public.browser.WebContents; 40 import org.chromium.content_public.browser.WebContents;
40 import org.chromium.ui.base.DeviceFormFactor; 41 import org.chromium.ui.base.DeviceFormFactor;
41 import org.chromium.ui.base.WindowAndroid; 42 import org.chromium.ui.base.WindowAndroid;
42 import org.chromium.ui.touch_selection.SelectionEventType; 43 import org.chromium.ui.touch_selection.SelectionEventType;
43 44
44 import java.util.List; 45 import java.util.List;
45 46
46 /** 47 /**
47 * A class that handles input-related web content selection UI like action mode 48 * 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, 49 * and paste popup view. It wraps an {@link ActionMode} created by the associate d view,
49 * providing modified interaction with it. 50 * providing modified interaction with it.
50 * 51 *
51 * Embedders can use {@link ActionModeCallbackHelper} implemented by this class 52 * Embedders can use {@link ActionModeCallbackHelper} implemented by this class
52 * to create {@link ActionMode.Callback} instance and configure the selection ac tion 53 * to create {@link ActionMode.Callback} instance and configure the selection ac tion
53 * mode tasks to their requirements. 54 * mode tasks to their requirements.
54 */ 55 */
55 @TargetApi(Build.VERSION_CODES.M) 56 @TargetApi(Build.VERSION_CODES.M)
56 public class SelectionPopupController extends ActionModeCallbackHelper { 57 public class SelectionPopupController extends ActionModeCallbackHelper {
57 private static final String TAG = "cr.SelectionPopCtlr"; // 20 char limit 58 private static final String TAG = "SelectionPopupCtlr"; // 20 char limit
58 59
59 /** 60 /**
60 * Android Intent size limitations prevent sending over a megabyte of data. Limit 61 * 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. 62 * query lengths to 100kB because other things may be added to the Intent.
62 */ 63 */
63 private static final int MAX_SHARE_QUERY_LENGTH = 100000; 64 private static final int MAX_SHARE_QUERY_LENGTH = 100000;
64 65
65 // Default delay for reshowing the {@link ActionMode} after it has been 66 // Default delay for reshowing the {@link ActionMode} after it has been
66 // hidden. This avoids flickering issues if there are trailing rect 67 // hidden. This avoids flickering issues if there are trailing rect
67 // invalidations after the ActionMode is shown. For example, after the user 68 // invalidations after the ActionMode is shown. For example, after the user
68 // stops dragging a selection handle, in turn showing the ActionMode, the 69 // stops dragging a selection handle, in turn showing the ActionMode, the
69 // selection change response will be asynchronous. 300ms should accomodate 70 // selection change response will be asynchronous. 300ms should accomodate
70 // most such trailing, async delays. 71 // most such trailing, async delays.
71 private static final int SHOW_DELAY_MS = 300; 72 private static final int SHOW_DELAY_MS = 300;
72 73
74 // A large value to force text processing menu items to be at the end of the
75 // context menu. Chosen to be bigger than the order of possible items in the
76 // XML template.
77 // TODO(timav): remove this constant and use show/hide for Assist item inste ad
78 // of adding and removing it once we switch to Android O SDK. The show/hide method
79 // does not require ordering information.
80 private static final int MENU_ITEM_ORDER_TEXT_PROCESS_START = 100;
81
73 private final Context mContext; 82 private final Context mContext;
74 private final WindowAndroid mWindowAndroid; 83 private final WindowAndroid mWindowAndroid;
75 private final WebContents mWebContents; 84 private final WebContents mWebContents;
76 private final RenderCoordinates mRenderCoordinates; 85 private final RenderCoordinates mRenderCoordinates;
77 private final ImeAdapter mImeAdapter; 86 private final ImeAdapter mImeAdapter;
78 private ActionMode.Callback mCallback; 87 private ActionMode.Callback mCallback;
79 88
80 // Selection rectangle in DIP. 89 // Selection rectangle in DIP.
81 private final Rect mSelectionRect = new Rect(); 90 private final Rect mSelectionRect = new Rect();
82 91
(...skipping 21 matching lines...) Expand all
104 private boolean mHasSelection; 113 private boolean mHasSelection;
105 114
106 // Lazily created paste popup menu, triggered either via long press in an 115 // Lazily created paste popup menu, triggered either via long press in an
107 // editable region or from tapping the insertion handle. 116 // editable region or from tapping the insertion handle.
108 private PastePopupMenu mPastePopupMenu; 117 private PastePopupMenu mPastePopupMenu;
109 private boolean mWasPastePopupShowingOnInsertionDragStart; 118 private boolean mWasPastePopupShowingOnInsertionDragStart;
110 119
111 // The client that processes textual selection, or null if none exists. 120 // The client that processes textual selection, or null if none exists.
112 private SelectionClient mSelectionClient; 121 private SelectionClient mSelectionClient;
113 122
123 // The classificaton result of the selected text if the selection exists and
124 // ContextSelectionProvider was able to classify it, otherwise null.
125 private ContextSelectionProvider.Result mClassificationResult;
126
127 // The resource ID for Assist menu item.
128 private int mAssistMenuItemId;
129
130 // This variable is set to true when the classification request is in progre ss.
131 private boolean mPendingClassificationRequest;
132
114 /** 133 /**
115 * Create {@link SelectionPopupController} instance. 134 * Create {@link SelectionPopupController} instance.
116 * @param context Context for action mode. 135 * @param context Context for action mode.
117 * @param window WindowAndroid instance. 136 * @param window WindowAndroid instance.
118 * @param webContents WebContents instance. 137 * @param webContents WebContents instance.
119 * @param view Container view. 138 * @param view Container view.
120 * @param renderCoordinates Coordinates info used to position elements. 139 * @param renderCoordinates Coordinates info used to position elements.
121 * @param imeAdapter ImeAdapter instance to handle cursor position. 140 * @param imeAdapter ImeAdapter instance to handle cursor position.
122 */ 141 */
123 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents, 142 public SelectionPopupController(Context context, WindowAndroid window, WebCo ntents webContents,
(...skipping 10 matching lines...) Expand all
134 mRepeatingHideRunnable = new Runnable() { 153 mRepeatingHideRunnable = new Runnable() {
135 @Override 154 @Override
136 public void run() { 155 public void run() {
137 assert mHidden; 156 assert mHidden;
138 final long hideDuration = getDefaultHideDuration(); 157 final long hideDuration = getDefaultHideDuration();
139 // Ensure the next hide call occurs before the ActionMode reappe ars. 158 // Ensure the next hide call occurs before the ActionMode reappe ars.
140 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); 159 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1);
141 hideActionModeTemporarily(hideDuration); 160 hideActionModeTemporarily(hideDuration);
142 } 161 }
143 }; 162 };
163
164 mSelectionClient =
165 ContextSelectionClient.create(new ContextSelectionCallback(), wi ndow, webContents);
166
167 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to
168 // Android O SDK and remove |mAssistMenuItemId|.
169 if (BuildInfo.isAtLeastO()) {
170 mAssistMenuItemId =
171 mContext.getResources().getIdentifier("textAssist", "id", "a ndroid");
172 }
144 } 173 }
145 174
146 /** 175 /**
147 * Update the container view. 176 * Update the container view.
148 */ 177 */
149 void setContainerView(View view) { 178 void setContainerView(View view) {
150 assert view != null; 179 assert view != null;
151 180
152 // Cleans up action mode before switching to a new container view. 181 // Cleans up action mode before switching to a new container view.
153 if (isActionModeValid()) finishActionMode(); 182 if (isActionModeValid()) finishActionMode();
154 mUnselectAllOnDismiss = true; 183 mUnselectAllOnDismiss = true;
155 destroyPastePopup(); 184 destroyPastePopup();
156 185
157 mView = view; 186 mView = view;
158 } 187 }
159 188
160 /** 189 /**
161 * Set the action mode callback. 190 * Set the action mode callback.
162 * @param callback ActionMode.Callback handling the callbacks from action mo de. 191 * @param callback ActionMode.Callback handling the callbacks from action mo de.
163 */ 192 */
164 void setCallback(ActionMode.Callback callback) { 193 void setCallback(ActionMode.Callback callback) {
165 mCallback = callback; 194 mCallback = callback;
166 } 195 }
167 196
168 @Override 197 @Override
169 public boolean isActionModeValid() { 198 public boolean isActionModeValid() {
170 return mActionMode != null; 199 return mActionMode != null;
171 } 200 }
172 201
173 // True if action mode is not yet initialized or set to no-op mode. 202 // True if action mode is initialized to a working (not a no-op) mode.
174 private boolean isEmpty() { 203 private boolean isActionModeSupported() {
175 return mCallback == EMPTY_CALLBACK; 204 return mCallback != EMPTY_CALLBACK;
176 } 205 }
177 206
178 @Override 207 @Override
179 public void setAllowedMenuItems(int allowedMenuItems) { 208 public void setAllowedMenuItems(int allowedMenuItems) {
180 mAllowedMenuItems = allowedMenuItems; 209 mAllowedMenuItems = allowedMenuItems;
181 } 210 }
182 211
183 /** 212 /**
184 * Show (activate) android action mode by starting it. 213 * Show (activate) android action mode by starting it.
185 * 214 *
186 * <p>Action mode in floating mode is tried first, and then falls back to 215 * <p>Action mode in floating mode is tried first, and then falls back to
187 * a normal one. 216 * a normal one.
188 * @return {@code true} if the action mode started successfully or is alread y on. 217 * <p> If the action mode cannot be created the selection is cleared.
189 */ 218 */
190 public boolean showActionMode() { 219 public void showActionModeOrClearOnFailure() {
191 if (isEmpty()) return false; 220 if (!isActionModeSupported()) return;
192 221
193 // Just refreshes the view if it is already showing. 222 // Just refresh the view if action mode already exists.
194 if (isActionModeValid()) { 223 if (isActionModeValid()) {
195 // Try/catch necessary for framework bug, crbug.com/446717. 224 // Try/catch necessary for framework bug, crbug.com/446717.
196 try { 225 try {
197 mActionMode.invalidate(); 226 mActionMode.invalidate();
198 } catch (NullPointerException e) { 227 } catch (NullPointerException e) {
199 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaro und for L", e); 228 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaro und for L", e);
200 } 229 }
201 hideActionMode(false); 230 hideActionMode(false);
202 return true; 231 return;
203 } 232 }
233
204 assert mWebContents != null; 234 assert mWebContents != null;
205 ActionMode actionMode = supportsFloatingActionMode() 235 ActionMode actionMode = supportsFloatingActionMode()
206 ? startFloatingActionMode() 236 ? startFloatingActionMode()
207 : mView.startActionMode(mCallback); 237 : mView.startActionMode(mCallback);
208 if (actionMode != null) { 238 if (actionMode != null) {
209 // This is to work around an LGE email issue. See crbug.com/651706 f or more details. 239 // This is to work around an LGE email issue. See crbug.com/651706 f or more details.
210 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode); 240 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode);
211 } 241 }
212 mActionMode = actionMode; 242 mActionMode = actionMode;
213 mUnselectAllOnDismiss = true; 243 mUnselectAllOnDismiss = true;
214 return isActionModeValid(); 244
245 if (!isActionModeValid()) clearSelection();
215 } 246 }
216 247
217 @TargetApi(Build.VERSION_CODES.M) 248 @TargetApi(Build.VERSION_CODES.M)
218 private ActionMode startFloatingActionMode() { 249 private ActionMode startFloatingActionMode() {
219 ActionMode actionMode = mView.startActionMode( 250 ActionMode actionMode = mView.startActionMode(
220 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING); 251 new FloatingActionModeCallback(this, mCallback), ActionMode.TYPE _FLOATING);
221 return actionMode; 252 return actionMode;
222 } 253 }
223 254
224 void createAndShowPastePopup(int x, int y) { 255 void createAndShowPastePopup(int x, int y) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
279 return mPastePopupMenu != null; 310 return mPastePopupMenu != null;
280 } 311 }
281 312
282 // Composition methods for android.view.ActionMode 313 // Composition methods for android.view.ActionMode
283 314
284 /** 315 /**
285 * @see ActionMode#finish() 316 * @see ActionMode#finish()
286 */ 317 */
287 @Override 318 @Override
288 public void finishActionMode() { 319 public void finishActionMode() {
320 mPendingClassificationRequest = false;
321 mHidden = false;
322 if (mView != null) mView.removeCallbacks(mRepeatingHideRunnable);
323
289 if (isActionModeValid()) { 324 if (isActionModeValid()) {
290 mActionMode.finish(); 325 mActionMode.finish();
291 326
292 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response. 327 // Should be nulled out in case #onDestroyActionMode() is not invoke d in response.
293 mActionMode = null; 328 mActionMode = null;
294 } 329 }
295 } 330 }
296 331
297 /** 332 /**
298 * @see ActionMode#invalidateContentRect() 333 * @see ActionMode#invalidateContentRect()
(...skipping 18 matching lines...) Expand all
317 * side-effects if the underlying ActionMode supports hiding. 352 * side-effects if the underlying ActionMode supports hiding.
318 * @param hide whether to hide or show the ActionMode. 353 * @param hide whether to hide or show the ActionMode.
319 */ 354 */
320 void hideActionMode(boolean hide) { 355 void hideActionMode(boolean hide) {
321 if (!canHideActionMode()) return; 356 if (!canHideActionMode()) return;
322 if (mHidden == hide) return; 357 if (mHidden == hide) return;
323 mHidden = hide; 358 mHidden = hide;
324 if (mHidden) { 359 if (mHidden) {
325 mRepeatingHideRunnable.run(); 360 mRepeatingHideRunnable.run();
326 } else { 361 } else {
327 mHidden = false;
328 mView.removeCallbacks(mRepeatingHideRunnable); 362 mView.removeCallbacks(mRepeatingHideRunnable);
363 // To show the action mode that is being hidden call hide() again wi th a short delay.
329 hideActionModeTemporarily(SHOW_DELAY_MS); 364 hideActionModeTemporarily(SHOW_DELAY_MS);
330 } 365 }
331 } 366 }
332 367
333 /** 368 /**
334 * @see ActionMode#hide(long) 369 * @see ActionMode#hide(long)
335 */ 370 */
336 private void hideActionModeTemporarily(long duration) { 371 private void hideActionModeTemporarily(long duration) {
337 assert canHideActionMode(); 372 assert canHideActionMode();
338 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 373 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
382 // TODO(tobiasjs) by the time we get here we have already 417 // TODO(tobiasjs) by the time we get here we have already
383 // caused a resource loading failure to be logged. WebView 418 // caused a resource loading failure to be logged. WebView
384 // resource access needs to be improved so that this 419 // resource access needs to be improved so that this
385 // logspam can be avoided. 420 // logspam can be avoided.
386 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); 421 new MenuInflater(context).inflate(R.menu.select_action_menu, menu);
387 } 422 }
388 } 423 }
389 424
390 private void createActionMenu(ActionMode mode, Menu menu) { 425 private void createActionMenu(ActionMode mode, Menu menu) {
391 initializeMenu(mContext, mode, menu); 426 initializeMenu(mContext, mode, menu);
427 updateAssistMenuItem(menu);
392 428
393 if (!isSelectionEditable() || !canPaste()) { 429 if (!isSelectionEditable() || !canPaste()) {
394 menu.removeItem(R.id.select_action_menu_paste); 430 menu.removeItem(R.id.select_action_menu_paste);
395 } 431 }
396 432
397 if (isInsertion()) { 433 if (isInsertion()) {
398 menu.removeItem(R.id.select_action_menu_select_all); 434 menu.removeItem(R.id.select_action_menu_select_all);
399 menu.removeItem(R.id.select_action_menu_cut); 435 menu.removeItem(R.id.select_action_menu_cut);
400 menu.removeItem(R.id.select_action_menu_copy); 436 menu.removeItem(R.id.select_action_menu_copy);
401 menu.removeItem(R.id.select_action_menu_share); 437 menu.removeItem(R.id.select_action_menu_share);
(...skipping 22 matching lines...) Expand all
424 460
425 initializeTextProcessingMenu(menu); 461 initializeTextProcessingMenu(menu);
426 } 462 }
427 463
428 private boolean canPaste() { 464 private boolean canPaste() {
429 ClipboardManager clipMgr = (ClipboardManager) 465 ClipboardManager clipMgr = (ClipboardManager)
430 mContext.getSystemService(Context.CLIPBOARD_SERVICE); 466 mContext.getSystemService(Context.CLIPBOARD_SERVICE);
431 return clipMgr.hasPrimaryClip(); 467 return clipMgr.hasPrimaryClip();
432 } 468 }
433 469
470 private void updateAssistMenuItem(Menu menu) {
471 // The assist menu item ID has to be equal to android.R.id.textAssist. U ntil we compile
472 // with Android O SDK where this ID is defined we replace the correspond ing inflated
473 // item with an item with the proper ID.
474 // TODO(timav): Use android.R.id.textAssist for the Assist item id once we switch to
475 // Android O SDK and remove |mAssistMenuItemId|.
476 menu.removeItem(R.id.select_action_menu_assist);
477
478 // There is no Assist functionality before Android O.
479 if (!BuildInfo.isAtLeastO() || mAssistMenuItemId == 0) return;
480
481 if (mClassificationResult != null && mClassificationResult.hasNamedActio n()) {
482 menu.add(mAssistMenuItemId, mAssistMenuItemId, 1, mClassificationRes ult.label)
483 .setIcon(mClassificationResult.icon);
484 }
485 }
486
434 /** 487 /**
435 * Intialize the menu items for processing text, if there is any. 488 * Intialize the menu items for processing text, if there is any.
436 */ 489 */
437 private void initializeTextProcessingMenu(Menu menu) { 490 private void initializeTextProcessingMenu(Menu menu) {
438 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M 491 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
439 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { 492 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) {
440 return; 493 return;
441 } 494 }
442 495
443 PackageManager packageManager = mContext.getPackageManager(); 496 PackageManager packageManager = mContext.getPackageManager();
444 List<ResolveInfo> supportedActivities = 497 List<ResolveInfo> supportedActivities =
445 packageManager.queryIntentActivities(createProcessTextIntent(), 0); 498 packageManager.queryIntentActivities(createProcessTextIntent(), 0);
446 for (int i = 0; i < supportedActivities.size(); i++) { 499 for (int i = 0; i < supportedActivities.size(); i++) {
447 ResolveInfo resolveInfo = supportedActivities.get(i); 500 ResolveInfo resolveInfo = supportedActivities.get(i);
448 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r()); 501 CharSequence label = resolveInfo.loadLabel(mContext.getPackageManage r());
449 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i , label) 502 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE,
503 MENU_ITEM_ORDER_TEXT_PROCESS_START + i, label)
450 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) 504 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo ))
451 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 505 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
452 } 506 }
453 } 507 }
454 508
455 @TargetApi(Build.VERSION_CODES.M) 509 @TargetApi(Build.VERSION_CODES.M)
456 private static Intent createProcessTextIntent() { 510 private static Intent createProcessTextIntent() {
457 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); 511 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain");
458 } 512 }
459 513
460 @TargetApi(Build.VERSION_CODES.M) 514 @TargetApi(Build.VERSION_CODES.M)
461 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { 515 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) {
462 boolean isReadOnly = !isSelectionEditable(); 516 boolean isReadOnly = !isSelectionEditable();
463 return createProcessTextIntent() 517 return createProcessTextIntent()
464 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) 518 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly)
465 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); 519 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame);
466 } 520 }
467 521
468 @Override 522 @Override
469 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 523 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
470 if (!isActionModeValid()) return true; 524 if (!isActionModeValid()) return true;
471 525
472 int id = item.getItemId(); 526 int id = item.getItemId();
473 int groupId = item.getGroupId(); 527 int groupId = item.getGroupId();
474 528
475 if (id == R.id.select_action_menu_select_all) { 529 if (id == mAssistMenuItemId) {
530 doAssistAction();
531 mode.finish();
532 } else if (id == R.id.select_action_menu_select_all) {
476 selectAll(); 533 selectAll();
477 } else if (id == R.id.select_action_menu_cut) { 534 } else if (id == R.id.select_action_menu_cut) {
478 cut(); 535 cut();
479 mode.finish(); 536 mode.finish();
480 } else if (id == R.id.select_action_menu_copy) { 537 } else if (id == R.id.select_action_menu_copy) {
481 copy(); 538 copy();
482 mode.finish(); 539 mode.finish();
483 } else if (id == R.id.select_action_menu_paste) { 540 } else if (id == R.id.select_action_menu_paste) {
484 paste(); 541 paste();
485 mode.finish(); 542 mode.finish();
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
524 (int) (mSelectionRect.top * deviceScale), 581 (int) (mSelectionRect.top * deviceScale),
525 (int) (mSelectionRect.right * deviceScale), 582 (int) (mSelectionRect.right * deviceScale),
526 (int) (mSelectionRect.bottom * deviceScale)); 583 (int) (mSelectionRect.bottom * deviceScale));
527 584
528 // The selection coordinates are relative to the content viewport, but w e need 585 // The selection coordinates are relative to the content viewport, but w e need
529 // coordinates relative to the containing View. 586 // coordinates relative to the containing View.
530 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); 587 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix());
531 } 588 }
532 589
533 /** 590 /**
591 * Perform an action that depends on the semantics of the selected text.
592 */
593 @VisibleForTesting
594 void doAssistAction() {
595 if (mClassificationResult == null || !mClassificationResult.hasNamedActi on()) return;
596
597 assert mClassificationResult.onClickListener != null
598 || mClassificationResult.intent != null;
599
600 if (mClassificationResult.onClickListener != null) {
601 mClassificationResult.onClickListener.onClick(mView);
602 return;
603 }
604
605 if (mClassificationResult.intent != null) {
606 Context context = mWindowAndroid.getContext().get();
607 if (context == null) return;
608
609 context.startActivity(mClassificationResult.intent);
610 return;
611 }
612 }
613
614 /**
534 * Perform a select all action. 615 * Perform a select all action.
535 */ 616 */
536 @VisibleForTesting 617 @VisibleForTesting
537 void selectAll() { 618 void selectAll() {
538 mWebContents.selectAll(); 619 mWebContents.selectAll();
620 mClassificationResult = null;
621 showActionModeOrClearOnFailure();
539 // Even though the above statement logged a SelectAll user action, we wa nt to 622 // Even though the above statement logged a SelectAll user action, we wa nt to
540 // track whether the focus was in an editable field, so log that too. 623 // track whether the focus was in an editable field, so log that too.
541 if (isSelectionEditable()) { 624 if (isSelectionEditable()) {
542 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); 625 RecordUserAction.record("MobileActionMode.SelectAllWasEditable");
543 } else { 626 } else {
544 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); 627 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable");
545 } 628 }
546 } 629 }
547 630
548 /** 631 /**
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after
701 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); 784 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T);
702 if (result != null) { 785 if (result != null) {
703 // TODO(hush): Use a variant of replace that re-selects the replaced text. 786 // TODO(hush): Use a variant of replace that re-selects the replaced text.
704 // crbug.com/546710 787 // crbug.com/546710
705 mWebContents.replace(result.toString()); 788 mWebContents.replace(result.toString());
706 } 789 }
707 } 790 }
708 791
709 void restoreSelectionPopupsIfNecessary() { 792 void restoreSelectionPopupsIfNecessary() {
710 if (mHasSelection && !isActionModeValid()) { 793 if (mHasSelection && !isActionModeValid()) {
711 if (!showActionMode()) clearSelection(); 794 showActionModeOrClearOnFailure();
712 } 795 }
713 } 796 }
714 797
715 // All coordinates are in DIP. 798 // All coordinates are in DIP.
716 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, 799 void onSelectionEvent(int eventType, int xAnchor, int yAnchor,
717 int left, int top, int right, int bottom, boolean isScrollInProgress , 800 int left, int top, int right, int bottom, boolean isScrollInProgress ,
718 boolean touchScrollInProgress) { 801 boolean touchScrollInProgress) {
719 // Ensure the provided selection coordinates form a non-empty rect, as r equired by 802 // Ensure the provided selection coordinates form a non-empty rect, as r equired by
720 // the selection action mode. 803 // the selection action mode.
721 if (left == right) ++right; 804 if (left == right) ++right;
722 if (top == bottom) ++bottom; 805 if (top == bottom) ++bottom;
723 switch (eventType) { 806 switch (eventType) {
724 case SelectionEventType.SELECTION_HANDLES_SHOWN: 807 case SelectionEventType.SELECTION_HANDLES_SHOWN:
725 mSelectionRect.set(left, top, right, bottom); 808 mSelectionRect.set(left, top, right, bottom);
726 mHasSelection = true; 809 mHasSelection = true;
727 mUnselectAllOnDismiss = true; 810 mUnselectAllOnDismiss = true;
728 if (!showActionMode()) clearSelection(); 811 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) {
812 // Rely on |mSelectionClient| sending a classification reque st and the request
813 // always calling onClassified() callback.
814 mPendingClassificationRequest = true;
815 } else {
816 showActionModeOrClearOnFailure();
817 }
729 break; 818 break;
730 819
731 case SelectionEventType.SELECTION_HANDLES_MOVED: 820 case SelectionEventType.SELECTION_HANDLES_MOVED:
732 mSelectionRect.set(left, top, right, bottom); 821 mSelectionRect.set(left, top, right, bottom);
733 invalidateContentRect(); 822 invalidateContentRect();
734 break; 823 break;
735 824
736 case SelectionEventType.SELECTION_HANDLES_CLEARED: 825 case SelectionEventType.SELECTION_HANDLES_CLEARED:
737 mHasSelection = false; 826 mHasSelection = false;
738 mUnselectAllOnDismiss = false; 827 mUnselectAllOnDismiss = false;
739 mSelectionRect.setEmpty(); 828 mSelectionRect.setEmpty();
740 finishActionMode(); 829 finishActionMode();
741 break; 830 break;
742 831
743 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: 832 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED:
744 hideActionMode(true); 833 hideActionMode(true);
745 break; 834 break;
746 835
747 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: 836 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED:
748 hideActionMode(false); 837 if (mSelectionClient != null && mSelectionClient.sendsSelectionP opupUpdates()) {
838 // Rely on |mSelectionClient| sending a classification reque st and the request
839 // always calling onClassified() callback.
840 mPendingClassificationRequest = true;
841 } else {
842 hideActionMode(false);
843 }
749 break; 844 break;
750 845
751 case SelectionEventType.INSERTION_HANDLE_SHOWN: 846 case SelectionEventType.INSERTION_HANDLE_SHOWN:
752 mSelectionRect.set(left, top, right, bottom); 847 mSelectionRect.set(left, top, right, bottom);
753 mIsInsertion = true; 848 mIsInsertion = true;
754 break; 849 break;
755 850
756 case SelectionEventType.INSERTION_HANDLE_MOVED: 851 case SelectionEventType.INSERTION_HANDLE_MOVED:
757 mSelectionRect.set(left, top, right, bottom); 852 mSelectionRect.set(left, top, right, bottom);
758 if (!isScrollInProgress && isPastePopupShowing()) { 853 if (!isScrollInProgress && isPastePopupShowing()) {
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
799 int yAnchorPix = (int) (yAnchor * deviceScale); 894 int yAnchorPix = (int) (yAnchor * deviceScale);
800 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ; 895 mSelectionClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix) ;
801 } 896 }
802 } 897 }
803 898
804 /** 899 /**
805 * Clears the current text selection. Note that we will try to move cursor t o selection 900 * Clears the current text selection. Note that we will try to move cursor t o selection
806 * end if applicable. 901 * end if applicable.
807 */ 902 */
808 void clearSelection() { 903 void clearSelection() {
809 if (mWebContents == null || isEmpty()) return; 904 if (mWebContents == null || !isActionModeSupported()) return;
810 mWebContents.collapseSelection(); 905 mWebContents.collapseSelection();
906 mClassificationResult = null;
811 } 907 }
812 908
813 void onSelectionChanged(String text) { 909 void onSelectionChanged(String text) {
814 mLastSelectedText = text; 910 mLastSelectedText = text;
815 if (mSelectionClient != null) { 911 if (mSelectionClient != null) {
816 mSelectionClient.onSelectionChanged(text); 912 mSelectionClient.onSelectionChanged(text);
817 } 913 }
818 } 914 }
819 915
820 // The client that implements selection augmenting functionality, or null if none exists. 916 // The client that implements selection augmenting functionality, or null if none exists.
821 void setSelectionClient(SelectionClient selectionClient) { 917 void setSelectionClient(SelectionClient selectionClient) {
822 mSelectionClient = selectionClient; 918 mSelectionClient = selectionClient;
919
920 mClassificationResult = null;
921
922 assert !mPendingClassificationRequest;
923 assert !mHidden;
823 } 924 }
824 925
825 void onShowUnhandledTapUIIfNeeded(int x, int y) { 926 void onShowUnhandledTapUIIfNeeded(int x, int y) {
826 if (mSelectionClient != null) { 927 if (mSelectionClient != null) {
827 mSelectionClient.showUnhandledTapUIIfNeeded(x, y); 928 mSelectionClient.showUnhandledTapUIIfNeeded(x, y);
828 } 929 }
829 } 930 }
830 931
831 void destroyActionModeAndUnselect() { 932 void destroyActionModeAndUnselect() {
832 mUnselectAllOnDismiss = true; 933 mUnselectAllOnDismiss = true;
(...skipping 26 matching lines...) Expand all
859 public String getSelectedText() { 960 public String getSelectedText() {
860 return mHasSelection ? mLastSelectedText : ""; 961 return mHasSelection ? mLastSelectedText : "";
861 } 962 }
862 963
863 private boolean isShareAvailable() { 964 private boolean isShareAvailable() {
864 Intent intent = new Intent(Intent.ACTION_SEND); 965 Intent intent = new Intent(Intent.ACTION_SEND);
865 intent.setType("text/plain"); 966 intent.setType("text/plain");
866 return mContext.getPackageManager().queryIntentActivities(intent, 967 return mContext.getPackageManager().queryIntentActivities(intent,
867 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; 968 PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
868 } 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 // The classificationresult is a property of the selection. Keep it even the action
986 // mode has been dismissed.
987 mClassificationResult = result;
988
989 // Do not recreate the action mode if it has been cancelled (by Acti onMode.finish())
990 // and not recreated after that.
991 if (!pendingClassificationRequest && !isActionModeValid()) {
992 assert !mHidden;
993 return;
994 }
995
996 // Update the selection range if needed.
997 if (!(result.startAdjust == 0 && result.endAdjust == 0)) {
998 // This call causes SELECTION_HANDLES_MOVED event
999 mWebContents.adjustSelectionByCharacterOffset(result.startAdjust , result.endAdjust);
1000 }
1001
1002 // Rely on this method to clear |mHidden| and unhide the action mode .
1003 showActionModeOrClearOnFailure();
1004 }
1005 };
869 } 1006 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698