| 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 |