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; |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |