Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.content.browser; | 5 package org.chromium.content.browser; |
|
boliu
2016/11/01 00:37:36
remove all imports of this class outside of conten
Jinsuk Kim
2016/11/01 04:43:56
Done.
| |
| 6 | 6 |
| 7 import android.annotation.TargetApi; | 7 import android.annotation.TargetApi; |
| 8 import android.app.Activity; | |
| 9 import android.app.SearchManager; | |
| 10 import android.content.ClipboardManager; | |
| 11 import android.content.ContentResolver; | |
| 12 import android.content.Context; | |
| 13 import android.content.Intent; | |
| 14 import android.content.pm.PackageManager; | |
| 15 import android.content.pm.ResolveInfo; | |
| 16 import android.content.res.Resources; | |
| 17 import android.graphics.Rect; | |
| 8 import android.os.Build; | 18 import android.os.Build; |
| 19 import android.provider.Browser; | |
| 20 import android.text.TextUtils; | |
| 21 import android.util.SparseBooleanArray; | |
| 9 import android.view.ActionMode; | 22 import android.view.ActionMode; |
| 23 import android.view.Menu; | |
| 24 import android.view.MenuInflater; | |
| 25 import android.view.MenuItem; | |
| 10 import android.view.View; | 26 import android.view.View; |
| 11 import android.view.ViewConfiguration; | 27 import android.view.ViewConfiguration; |
| 28 import android.view.WindowManager; | |
| 12 | 29 |
| 13 import org.chromium.base.Log; | 30 import org.chromium.base.Log; |
| 31 import org.chromium.base.VisibleForTesting; | |
| 32 import org.chromium.base.metrics.RecordUserAction; | |
| 33 import org.chromium.content.R; | |
| 34 import org.chromium.content.browser.input.FloatingPastePopupMenu; | |
| 35 import org.chromium.content.browser.input.ImeAdapter; | |
| 36 import org.chromium.content.browser.input.LGEmailActionModeWorkaround; | |
| 37 import org.chromium.content.browser.input.LegacyPastePopupMenu; | |
| 38 import org.chromium.content.browser.input.PastePopupMenu; | |
| 39 import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate; | |
| 40 import org.chromium.content_public.browser.WebContents; | |
| 41 import org.chromium.ui.base.DeviceFormFactor; | |
| 42 import org.chromium.ui.base.WindowAndroid; | |
| 43 import org.chromium.ui.touch_selection.SelectionEventType; | |
| 44 | |
| 45 import java.util.List; | |
| 14 | 46 |
| 15 /** | 47 /** |
| 16 * An ActionMode for in-page web content selection. This class wraps an ActionMo de created | 48 * An ActionMode for in-page web content selection. This class wraps an ActionMo de created |
| 17 * by the associated View, providing modified interaction with that ActionMode. | 49 * by the associated View, providing modified interaction with that ActionMode. |
| 50 * | |
| 51 * Embedders use {@link WebActionModeDelegate}, a delegate of this class, to bui ld | |
| 52 * {@link ActionMode.Callback} instance to configure the selection action mode t asks to | |
| 53 * their requirements. | |
| 18 */ | 54 */ |
| 19 @TargetApi(Build.VERSION_CODES.M) | 55 @TargetApi(Build.VERSION_CODES.M) |
| 20 public class WebActionMode { | 56 public class WebActionMode implements ActionModeCallbackHelper { |
| 21 private static final String TAG = "cr.WebActionMode"; | 57 private static final String TAG = "cr.WebActionMode"; |
| 22 | 58 |
| 59 /** Google search doesn't support requests slightly larger than this. */ | |
| 60 public static final int MAX_SEARCH_QUERY_LENGTH = 1000; | |
|
boliu
2016/11/01 00:37:36
this should be in the interface (I think it may ne
Jinsuk Kim
2016/11/01 04:43:56
Done.
| |
| 61 | |
| 62 /** | |
| 63 * Android Intent size limitations prevent sending over a megabyte of data. Limit | |
| 64 * query lengths to 100kB because other things may be added to the Intent. | |
| 65 */ | |
| 66 public static final int MAX_SHARE_QUERY_LENGTH = 100000; | |
|
boliu
2016/11/01 00:37:36
private
Jinsuk Kim
2016/11/01 04:43:56
Done.
| |
| 67 | |
| 68 // TODO(hush): Use these constants from android.webkit.WebSettings, when the y are made | |
| 69 // available. crbug.com/546762. | |
| 70 public static final int MENU_ITEM_SHARE = 1 << 0; | |
|
boliu
2016/11/01 00:37:36
all private here
Jinsuk Kim
2016/11/01 04:43:56
Moved to the abstract class ActionModeCallbackHelp
| |
| 71 public static final int MENU_ITEM_WEB_SEARCH = 1 << 1; | |
| 72 public static final int MENU_ITEM_PROCESS_TEXT = 1 << 2; | |
| 73 | |
| 74 public static final EmptyActionCallback EMPTY_CALLBACK = new EmptyActionCall back(); | |
| 75 | |
| 23 // Default delay for reshowing the {@link ActionMode} after it has been | 76 // Default delay for reshowing the {@link ActionMode} after it has been |
| 24 // hidden. This avoids flickering issues if there are trailing rect | 77 // hidden. This avoids flickering issues if there are trailing rect |
| 25 // invalidations after the ActionMode is shown. For example, after the user | 78 // invalidations after the ActionMode is shown. For example, after the user |
| 26 // stops dragging a selection handle, in turn showing the ActionMode, the | 79 // stops dragging a selection handle, in turn showing the ActionMode, the |
| 27 // selection change response will be asynchronous. 300ms should accomodate | 80 // selection change response will be asynchronous. 300ms should accomodate |
| 28 // most such trailing, async delays. | 81 // most such trailing, async delays. |
| 29 private static final int SHOW_DELAY_MS = 300; | 82 private static final int SHOW_DELAY_MS = 300; |
| 30 | 83 |
| 31 protected final ActionMode mActionMode; | 84 // Creation failure event can be shared among WebActionMode instances as it depends on |
| 32 private final View mView; | 85 // underlying Android platform version. |
| 33 private boolean mHidden; | 86 private static boolean sFloatingActionModeCreationFailed; |
|
boliu
2016/11/01 00:37:36
this was not static before, any reason why was mad
Jinsuk Kim
2016/11/01 04:43:56
Previously this flag was being set for every CVC.
boliu
2016/11/01 22:24:32
Did you test that? What if failure is due to some
Jinsuk Kim
2016/11/02 03:48:43
Done. Please note that the flag is already shared
| |
| 34 private boolean mPendingInvalidateContentRect; | 87 |
| 88 private final Context mContext; | |
| 89 private final WindowAndroid mWindowAndroid; | |
| 90 private final WebContents mWebContents; | |
| 91 private final ActionMode.Callback mCallback; | |
| 92 private final RenderCoordinates mRenderCoordinates; | |
| 93 | |
| 94 // Selection rectangle in DIP. | |
| 95 private final Rect mSelectionRect = new Rect(); | |
| 35 | 96 |
| 36 // Self-repeating task that repeatedly hides the ActionMode. This is | 97 // Self-repeating task that repeatedly hides the ActionMode. This is |
| 37 // required because ActionMode only exposes a temporary hide routine. | 98 // required because ActionMode only exposes a temporary hide routine. |
| 38 private final Runnable mRepeatingHideRunnable; | 99 private final Runnable mRepeatingHideRunnable; |
| 39 | 100 |
| 101 private View mView; | |
| 102 private ActionMode mActionMode; | |
| 103 private boolean mDraggingSelection; | |
| 104 | |
| 105 // Boolean array with mappings from menu item to a flag indicating it is all owed. | |
| 106 // The menu items are allowed by default if they not contained in the array. | |
| 107 private SparseBooleanArray mAllowedMenuItems; | |
| 108 private boolean mMenuDefaultAllowed; | |
| 109 | |
| 110 private boolean mHidden; | |
| 111 private boolean mPendingInvalidateContentRect; | |
| 112 | |
| 113 private boolean mEditable; | |
| 114 private boolean mIsPasswordType; | |
| 115 private boolean mIsInsertion; | |
| 116 | |
| 117 // Indicates whether the action mode needs to be redrawn since last invalida tion. | |
| 118 private boolean mNeedsPrepare; | |
| 119 | |
| 120 private boolean mUnselectAllOnDismiss; | |
| 121 private String mLastSelectedText; | |
| 122 | |
| 123 // Tracks whether a selection is currently active. When applied to selected text, indicates | |
| 124 // whether the last selected text is still highlighted. | |
| 125 private boolean mHasSelection; | |
| 126 | |
| 127 // Lazily created paste popup menu, triggered either via long press in an | |
| 128 // editable region or from tapping the insertion handle. | |
| 129 private PastePopupMenu mPastePopupMenu; | |
| 130 private boolean mWasPastePopupShowingOnInsertionDragStart; | |
| 131 | |
| 132 // The client that implements Contextual Search functionality, or null if no ne exists. | |
| 133 private ContextualSearchClient mContextualSearchClient; | |
| 134 | |
| 40 /** | 135 /** |
| 41 * Constructs a SelectActionMode instance wrapping a concrete ActionMode. | 136 * Create {@link WebActionMode} instance. |
| 42 * @param actionMode the wrapped ActionMode. | 137 * @param context Context for action mode. |
| 43 * @param view the associated View. | 138 * @param window WindowAndroid instance. |
| 139 * @param webContents WebContents instance. | |
| 140 * @param view Container view. | |
| 141 * @param renderCoordinates Coordinates info used to position elements. | |
| 142 * @param callback ActionMode.Callback handling the callbacks from action mo de. | |
| 44 */ | 143 */ |
| 45 public WebActionMode(ActionMode actionMode, View view) { | 144 public WebActionMode(Context context, WindowAndroid window, WebContents webC ontents, |
| 46 assert actionMode != null; | 145 View view, RenderCoordinates renderCoordinates, ActionMode.Callback callback) { |
| 47 assert view != null; | 146 mContext = context; |
| 48 mActionMode = actionMode; | 147 mWindowAndroid = window; |
| 148 mWebContents = webContents; | |
| 49 mView = view; | 149 mView = view; |
| 150 mRenderCoordinates = renderCoordinates; | |
| 151 mCallback = callback; | |
| 152 mMenuDefaultAllowed = true; | |
| 50 mRepeatingHideRunnable = new Runnable() { | 153 mRepeatingHideRunnable = new Runnable() { |
| 51 @Override | 154 @Override |
| 52 public void run() { | 155 public void run() { |
| 53 assert mHidden; | 156 assert mHidden; |
| 54 final long hideDuration = getDefaultHideDuration(); | 157 final long hideDuration = getDefaultHideDuration(); |
| 55 // Ensure the next hide call occurs before the ActionMode reappe ars. | 158 // Ensure the next hide call occurs before the ActionMode reappe ars. |
| 56 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); | 159 mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); |
| 57 hideTemporarily(hideDuration); | 160 hideTemporarily(hideDuration); |
| 58 } | 161 } |
| 59 }; | 162 }; |
| 60 } | 163 } |
| 61 | 164 |
| 62 /** | 165 /** |
| 166 * Update the container view. | |
| 167 */ | |
| 168 void setContainerView(View view) { | |
| 169 assert view != null; | |
| 170 | |
| 171 // Cleans up action mode before switching to a new container view. | |
| 172 if (isActionModeValid()) finishActionMode(); | |
| 173 mUnselectAllOnDismiss = true; | |
| 174 destroyPastePopup(); | |
| 175 | |
| 176 mView = view; | |
| 177 } | |
| 178 | |
| 179 @Override | |
| 180 public boolean isActionModeValid() { | |
| 181 return mActionMode != null; | |
| 182 } | |
| 183 | |
| 184 // True if action mode is not yet initialized or set to no-op mode. | |
| 185 private boolean isEmpty() { | |
| 186 return mCallback == EMPTY_CALLBACK; | |
| 187 } | |
| 188 | |
| 189 @Override | |
| 190 public void setAllowedMenuItems(SparseBooleanArray allowedMenuItems, boolean defaultValue) { | |
| 191 mAllowedMenuItems = allowedMenuItems; | |
| 192 mMenuDefaultAllowed = defaultValue; | |
| 193 } | |
| 194 | |
| 195 /** | |
| 196 * Show (activate) android action mode by starting it. | |
| 197 * | |
| 198 * <p>Action mode in floating mode is tried first, and then falls back to | |
| 199 * a normal one if allowed. | |
| 200 * @param allowFallback A flag indicating if we allow for falling back to | |
| 201 * normal action mode in case floating action mode creation fails. | |
| 202 * @return {@code true} if the action mode was started; {@code false} otherw ise due to | |
| 203 * the condition not being met. | |
| 204 */ | |
| 205 public boolean showActionMode(boolean allowFallback) { | |
| 206 if (isEmpty()) return false; | |
| 207 | |
| 208 // Just refreshes the view if it is already showing. | |
| 209 if (isActionModeValid()) { | |
| 210 invalidateActionMode(); | |
| 211 return false; | |
|
boliu
2016/11/01 00:37:36
this should return true?
Jinsuk Kim
2016/11/01 04:43:56
CVC.showSelectActionMode() uses this value to dete
boliu
2016/11/01 22:24:32
You sure? This is meant to return whether showActi
Jinsuk Kim
2016/11/02 03:48:43
This is what was being done before change:
| |
| 212 } | |
| 213 | |
| 214 // On ICS, startActionMode throws an NPE when getParent() is null. | |
| 215 ActionMode actionMode = null; | |
| 216 if (mView.getParent() != null) { | |
| 217 assert mWebContents != null; | |
| 218 if (supportsFloatingActionMode()) actionMode = startFloatingActionMo de(); | |
| 219 if (actionMode == null && allowFallback) actionMode = mView.startAct ionMode(mCallback); | |
| 220 } | |
| 221 if (actionMode != null) { | |
| 222 // This is to work around an LGE email issue. See crbug.com/651706 f or more details. | |
| 223 LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode); | |
| 224 } | |
| 225 mActionMode = actionMode; | |
| 226 return true; | |
| 227 } | |
| 228 | |
| 229 /** | |
| 230 * Tell if the platform supports floating type action mode. Used not to repe atedly | |
| 231 * attempt the creation if the request fails once at the beginning. Also che ck | |
| 232 * platform version since the floating type is supported only on M or later version | |
| 233 * of Android platform. | |
| 234 */ | |
| 235 public static boolean supportsFloatingActionMode() { | |
| 236 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false; | |
| 237 return !sFloatingActionModeCreationFailed; | |
| 238 } | |
| 239 | |
| 240 private static void setFloatingActionModeCreationFailed() { | |
| 241 sFloatingActionModeCreationFailed = true; | |
| 242 } | |
| 243 | |
| 244 @TargetApi(Build.VERSION_CODES.M) | |
| 245 private ActionMode startFloatingActionMode() { | |
| 246 ActionMode actionMode = mView.startActionMode( | |
| 247 new FloatingWebActionModeCallback(this, mCallback), ActionMode.T YPE_FLOATING); | |
| 248 if (actionMode == null) setFloatingActionModeCreationFailed(); | |
| 249 return actionMode; | |
| 250 } | |
| 251 | |
| 252 void showPastePopup(int x, int y) { | |
| 253 if (mView.getParent() == null || mView.getVisibility() != View.VISIBLE) { | |
| 254 return; | |
| 255 } | |
| 256 | |
| 257 if (!isInsertion() || (!supportsFloatingActionMode() && !canPaste())) re turn; | |
| 258 | |
| 259 PastePopupMenu pastePopupMenu = getPastePopup(); | |
| 260 if (pastePopupMenu == null) return; | |
| 261 | |
| 262 // Coordinates are in DIP. | |
| 263 final float deviceScale = mRenderCoordinates.getDeviceScaleFactor(); | |
| 264 final int xPix = (int) (x * deviceScale); | |
| 265 final int yPix = (int) (y * deviceScale); | |
| 266 final float topControlsShownPix = mRenderCoordinates.getContentOffsetYPi x(); | |
|
boliu
2016/11/01 00:37:36
this variable this got renamed to browserControlsS
Jinsuk Kim
2016/11/01 04:43:56
Thanks. rebased.
| |
| 267 try { | |
| 268 pastePopupMenu.show(xPix, (int) (yPix + topControlsShownPix)); | |
| 269 } catch (WindowManager.BadTokenException e) { | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 void hidePastePopup() { | |
| 274 if (mPastePopupMenu != null) mPastePopupMenu.hide(); | |
| 275 } | |
| 276 | |
| 277 private PastePopupMenu getPastePopup() { | |
| 278 if (mPastePopupMenu == null) { | |
| 279 PastePopupMenuDelegate delegate = new PastePopupMenuDelegate() { | |
| 280 @Override | |
| 281 public void paste() { | |
| 282 mWebContents.paste(); | |
| 283 mWebContents.dismissTextHandles(); | |
| 284 } | |
| 285 }; | |
| 286 Context windowContext = mWindowAndroid.getContext().get(); | |
| 287 if (windowContext == null) return null; | |
| 288 if (supportsFloatingActionMode()) { | |
| 289 mPastePopupMenu = new FloatingPastePopupMenu(windowContext, mVie w, delegate); | |
| 290 } else { | |
| 291 mPastePopupMenu = new LegacyPastePopupMenu(windowContext, mView, delegate); | |
| 292 } | |
| 293 } | |
| 294 return mPastePopupMenu; | |
| 295 } | |
| 296 | |
| 297 void destroyPastePopup() { | |
| 298 hidePastePopup(); | |
| 299 mPastePopupMenu = null; | |
| 300 } | |
| 301 | |
| 302 @VisibleForTesting | |
| 303 public boolean isPastePopupShowing() { | |
| 304 return mPastePopupMenu != null && mPastePopupMenu.isShowing(); | |
| 305 } | |
| 306 | |
| 307 private Context getContext() { | |
| 308 return mContext; | |
| 309 } | |
| 310 | |
| 311 // Composition methods for android.view.ActionMode | |
| 312 | |
| 313 /** | |
| 314 * @see ActionMode#getType()} | |
| 315 */ | |
| 316 public int getType() { | |
|
boliu
2016/11/01 00:37:36
package visible
Jinsuk Kim
2016/11/01 04:43:56
In fact it's not used any more. Removed.
| |
| 317 return isActionModeValid() ? mActionMode.getType() : ActionMode.TYPE_PRI MARY; | |
| 318 } | |
| 319 | |
| 320 /** | |
| 63 * @see ActionMode#finish() | 321 * @see ActionMode#finish() |
| 64 */ | 322 */ |
| 65 public void finish() { | 323 @Override |
| 66 mActionMode.finish(); | 324 public void finishActionMode() { |
| 325 if (isActionModeValid()) mActionMode.finish(); | |
| 67 } | 326 } |
| 68 | 327 |
| 69 /** | 328 /** |
| 70 * @see ActionMode#invalidate() | 329 * @see ActionMode#invalidate() |
| 71 * Note that invalidation will also reset visibility state. The caller | 330 * Note that invalidation will also reset visibility state. The caller |
| 72 * should account for this when making subsequent visibility updates. | 331 * should account for this when making subsequent visibility updates. |
| 73 */ | 332 */ |
| 74 public void invalidate() { | 333 public void invalidateActionMode() { |
|
boliu
2016/11/01 00:37:36
private
Jinsuk Kim
2016/11/01 04:43:56
Done.
| |
| 334 if (!isActionModeValid()) return; | |
| 75 if (mHidden) { | 335 if (mHidden) { |
| 76 assert canHide(); | 336 assert canHide(); |
| 77 mHidden = false; | 337 mHidden = false; |
| 78 mView.removeCallbacks(mRepeatingHideRunnable); | 338 mView.removeCallbacks(mRepeatingHideRunnable); |
| 79 mPendingInvalidateContentRect = false; | 339 mPendingInvalidateContentRect = false; |
| 80 } | 340 } |
| 81 | 341 |
| 82 // Try/catch necessary for framework bug, crbug.com/446717. | 342 // Try/catch necessary for framework bug, crbug.com/446717. |
| 83 try { | 343 try { |
| 84 mActionMode.invalidate(); | 344 mActionMode.invalidate(); |
| 85 } catch (NullPointerException e) { | 345 } catch (NullPointerException e) { |
| 86 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); | 346 Log.w(TAG, "Ignoring NPE from ActionMode.invalidate() as workaround for L", e); |
| 87 } | 347 } |
| 88 } | 348 } |
| 89 | 349 |
|
boliu
2016/11/01 00:37:36
I read up to this far..
Jinsuk Kim
2016/11/01 04:43:56
Acknowledged.
| |
| 90 /** | 350 /** |
| 91 * @see ActionMode#invalidateContentRect() | 351 * @see ActionMode#invalidateContentRect() |
| 92 */ | 352 */ |
| 93 public void invalidateContentRect() { | 353 public void invalidateContentRect() { |
| 94 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 354 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| 95 if (mHidden) { | 355 if (mHidden) { |
| 96 mPendingInvalidateContentRect = true; | 356 mPendingInvalidateContentRect = true; |
| 97 } else { | 357 } else { |
| 98 mPendingInvalidateContentRect = false; | 358 mPendingInvalidateContentRect = false; |
| 99 mActionMode.invalidateContentRect(); | 359 if (isActionModeValid()) mActionMode.invalidateContentRect(); |
| 100 } | 360 } |
| 101 } | 361 } |
| 102 } | 362 } |
| 103 | 363 |
| 104 /** | 364 /** |
| 105 * @see ActionMode#onWindowFocusChanged() | 365 * @see ActionMode#onWindowFocusChanged() |
| 106 */ | 366 */ |
| 107 public void onWindowFocusChanged(boolean hasWindowFocus) { | 367 public void onWindowFocusChanged(boolean hasWindowFocus) { |
| 108 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 368 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| 109 mActionMode.onWindowFocusChanged(hasWindowFocus); | 369 if (isActionModeValid()) mActionMode.onWindowFocusChanged(hasWindowF ocus); |
| 110 } | 370 } |
| 111 } | 371 } |
| 112 | 372 |
| 113 /** | 373 /** |
| 114 * Hide or reveal the ActionMode. Note that this only has visible | 374 * Hide or reveal the ActionMode. Note that this only has visible |
| 115 * side-effects if the underlying ActionMode supports hiding. | 375 * side-effects if the underlying ActionMode supports hiding. |
| 116 * @param hide whether to hide or show the ActionMode. | 376 * @param hide whether to hide or show the ActionMode. |
| 117 */ | 377 */ |
| 118 public void hide(boolean hide) { | 378 void hideActionMode(boolean hide) { |
| 119 if (!canHide()) return; | 379 if (!canHide()) return; |
| 120 if (mHidden == hide) return; | 380 if (mHidden == hide) return; |
| 121 mHidden = hide; | 381 mHidden = hide; |
| 122 if (mHidden) { | 382 if (mHidden) { |
| 123 mRepeatingHideRunnable.run(); | 383 mRepeatingHideRunnable.run(); |
| 124 } else { | 384 } else { |
| 125 mHidden = false; | 385 mHidden = false; |
| 126 mView.removeCallbacks(mRepeatingHideRunnable); | 386 mView.removeCallbacks(mRepeatingHideRunnable); |
| 127 hideTemporarily(SHOW_DELAY_MS); | 387 hideTemporarily(SHOW_DELAY_MS); |
| 128 if (mPendingInvalidateContentRect) { | 388 if (mPendingInvalidateContentRect) { |
| 129 mPendingInvalidateContentRect = false; | 389 mPendingInvalidateContentRect = false; |
| 130 invalidateContentRect(); | 390 invalidateContentRect(); |
| 131 } | 391 } |
| 132 } | 392 } |
| 133 } | 393 } |
| 134 | 394 |
| 135 /** | 395 /** |
| 136 * @see ActionMode#hide(long) | 396 * @see ActionMode#hide(long) |
| 137 */ | 397 */ |
| 138 private void hideTemporarily(long duration) { | 398 private void hideTemporarily(long duration) { |
| 139 assert canHide(); | 399 assert canHide(); |
| 140 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 400 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| 141 mActionMode.hide(duration); | 401 if (isActionModeValid()) mActionMode.hide(duration); |
| 142 } | 402 } |
| 143 } | 403 } |
| 144 | 404 |
| 145 private boolean canHide() { | 405 private boolean canHide() { |
| 146 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 406 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M |
| 147 return mActionMode.getType() == ActionMode.TYPE_FLOATING; | 407 && isActionModeValid() |
| 148 } | 408 && mActionMode.getType() == ActionMode.TYPE_FLOATING; |
| 149 return false; | |
| 150 } | 409 } |
| 151 | 410 |
| 152 private long getDefaultHideDuration() { | 411 private long getDefaultHideDuration() { |
| 153 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | 412 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| 154 return ViewConfiguration.getDefaultActionModeHideDuration(); | 413 return ViewConfiguration.getDefaultActionModeHideDuration(); |
| 155 } | 414 } |
| 156 return 2000; | 415 return 2000; |
| 157 } | 416 } |
| 417 | |
| 418 // Default handlers for action mode callbacks. | |
| 419 | |
| 420 @Override | |
| 421 public boolean onCreateActionMode(ActionMode mode, Menu menu) { | |
| 422 mode.setTitle(DeviceFormFactor.isTablet(getContext()) | |
| 423 ? getContext().getString(R.string.actionbar_textselectio n_title) | |
| 424 : null); | |
| 425 mode.setSubtitle(null); | |
| 426 createActionMenu(mode, menu); | |
| 427 mNeedsPrepare = false; | |
| 428 return true; | |
| 429 } | |
| 430 | |
| 431 @Override | |
| 432 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { | |
| 433 if (mNeedsPrepare) { | |
| 434 menu.clear(); | |
| 435 createActionMenu(mode, menu); | |
| 436 mNeedsPrepare = false; | |
| 437 return true; | |
| 438 } | |
| 439 return false; | |
| 440 } | |
| 441 | |
| 442 /** | |
| 443 * Initialize the menu by populating all the available items. Embedders shou ld remove | |
| 444 * the items that are not relevant to the input text being edited. | |
| 445 */ | |
| 446 public static void initializeMenu(Context context, ActionMode mode, Menu men u) { | |
| 447 try { | |
| 448 mode.getMenuInflater().inflate(R.menu.select_action_menu, menu); | |
| 449 } catch (Resources.NotFoundException e) { | |
| 450 // TODO(tobiasjs) by the time we get here we have already | |
| 451 // caused a resource loading failure to be logged. WebView | |
| 452 // resource access needs to be improved so that this | |
| 453 // logspam can be avoided. | |
| 454 new MenuInflater(context).inflate(R.menu.select_action_menu, menu); | |
| 455 } | |
| 456 } | |
| 457 | |
| 458 private void createActionMenu(ActionMode mode, Menu menu) { | |
| 459 initializeMenu(mContext, mode, menu); | |
| 460 | |
| 461 if (!isSelectionEditable() || !canPaste()) { | |
| 462 menu.removeItem(R.id.select_action_menu_paste); | |
| 463 } | |
| 464 | |
| 465 if (isInsertion()) { | |
| 466 menu.removeItem(R.id.select_action_menu_select_all); | |
| 467 menu.removeItem(R.id.select_action_menu_cut); | |
| 468 menu.removeItem(R.id.select_action_menu_copy); | |
| 469 menu.removeItem(R.id.select_action_menu_share); | |
| 470 menu.removeItem(R.id.select_action_menu_web_search); | |
| 471 return; | |
| 472 } | |
| 473 | |
| 474 if (!isSelectionEditable()) { | |
| 475 menu.removeItem(R.id.select_action_menu_cut); | |
| 476 } | |
| 477 | |
| 478 if (isSelectionEditable() || !isSelectActionModeAllowed(MENU_ITEM_SHARE) ) { | |
| 479 menu.removeItem(R.id.select_action_menu_share); | |
| 480 } | |
| 481 | |
| 482 if (isSelectionEditable() || isIncognito() | |
| 483 || !isSelectActionModeAllowed(MENU_ITEM_WEB_SEARCH)) { | |
| 484 menu.removeItem(R.id.select_action_menu_web_search); | |
| 485 } | |
| 486 | |
| 487 if (isSelectionPassword()) { | |
| 488 menu.removeItem(R.id.select_action_menu_copy); | |
| 489 menu.removeItem(R.id.select_action_menu_cut); | |
| 490 return; | |
| 491 } | |
| 492 | |
| 493 initializeTextProcessingMenu(menu); | |
| 494 } | |
| 495 | |
| 496 private boolean canPaste() { | |
| 497 ClipboardManager clipMgr = (ClipboardManager) | |
| 498 getContext().getSystemService(Context.CLIPBOARD_SERVICE); | |
| 499 return clipMgr.hasPrimaryClip(); | |
| 500 } | |
| 501 | |
| 502 /** | |
| 503 * Intialize the menu items for processing text, if there is any. | |
| 504 */ | |
| 505 private void initializeTextProcessingMenu(Menu menu) { | |
| 506 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M | |
| 507 || !isSelectActionModeAllowed(MENU_ITEM_PROCESS_TEXT)) { | |
| 508 return; | |
| 509 } | |
| 510 | |
| 511 PackageManager packageManager = getContext().getPackageManager(); | |
| 512 List<ResolveInfo> supportedActivities = | |
| 513 packageManager.queryIntentActivities(createProcessTextIntent(), 0); | |
| 514 for (int i = 0; i < supportedActivities.size(); i++) { | |
| 515 ResolveInfo resolveInfo = supportedActivities.get(i); | |
| 516 CharSequence label = resolveInfo.loadLabel(getContext().getPackageMa nager()); | |
| 517 menu.add(R.id.select_action_menu_text_processing_menus, Menu.NONE, i , label) | |
| 518 .setIntent(createProcessTextIntentForResolveInfo(resolveInfo )) | |
| 519 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | |
| 520 } | |
| 521 } | |
| 522 | |
| 523 @TargetApi(Build.VERSION_CODES.M) | |
| 524 private Intent createProcessTextIntent() { | |
| 525 return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/ plain"); | |
| 526 } | |
| 527 | |
| 528 @TargetApi(Build.VERSION_CODES.M) | |
| 529 private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { | |
| 530 boolean isReadOnly = !isSelectionEditable(); | |
| 531 return createProcessTextIntent() | |
| 532 .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, isReadOnly) | |
| 533 .setClassName(info.activityInfo.packageName, info.activityInfo.n ame); | |
| 534 } | |
| 535 | |
| 536 @Override | |
| 537 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | |
| 538 if (!isActionModeValid()) return true; | |
| 539 | |
| 540 int id = item.getItemId(); | |
| 541 int groupId = item.getGroupId(); | |
| 542 | |
| 543 if (id == R.id.select_action_menu_select_all) { | |
| 544 selectAll(); | |
| 545 } else if (id == R.id.select_action_menu_cut) { | |
| 546 cut(); | |
| 547 mode.finish(); | |
| 548 } else if (id == R.id.select_action_menu_copy) { | |
| 549 copy(); | |
| 550 mode.finish(); | |
| 551 } else if (id == R.id.select_action_menu_paste) { | |
| 552 paste(); | |
| 553 mode.finish(); | |
| 554 } else if (id == R.id.select_action_menu_share) { | |
| 555 share(); | |
| 556 mode.finish(); | |
| 557 } else if (id == R.id.select_action_menu_web_search) { | |
| 558 search(); | |
| 559 mode.finish(); | |
| 560 } else if (groupId == R.id.select_action_menu_text_processing_menus) { | |
| 561 processText(item.getIntent()); | |
| 562 // The ActionMode is not dismissed to match the behavior with | |
| 563 // TextView in Android M. | |
| 564 } else { | |
| 565 return false; | |
| 566 } | |
| 567 return true; | |
| 568 } | |
| 569 | |
| 570 @Override | |
| 571 public void onDestroyActionMode() { | |
| 572 mActionMode = null; | |
| 573 if (mUnselectAllOnDismiss) { | |
| 574 mWebContents.dismissTextHandles(); | |
| 575 mWebContents.unselect(); | |
| 576 } | |
| 577 } | |
| 578 | |
| 579 /** | |
| 580 * Called when an ActionMode needs to be positioned on screen, potentially o ccluding view | |
| 581 * content. Note this may be called on a per-frame basis. | |
| 582 * | |
| 583 * @param mode The ActionMode that requires positioning. | |
| 584 * @param view The View that originated the ActionMode, in whose coordinates the Rect should | |
| 585 * be provided. | |
| 586 * @param outRect The Rect to be populated with the content position. | |
| 587 */ | |
| 588 @Override | |
| 589 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { | |
| 590 float deviceScale = mRenderCoordinates.getDeviceScaleFactor(); | |
| 591 outRect.set((int) (mSelectionRect.left * deviceScale), | |
| 592 (int) (mSelectionRect.top * deviceScale), | |
| 593 (int) (mSelectionRect.right * deviceScale), | |
| 594 (int) (mSelectionRect.bottom * deviceScale)); | |
| 595 | |
| 596 // The selection coordinates are relative to the content viewport, but w e need | |
| 597 // coordinates relative to the containing View. | |
| 598 outRect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); | |
| 599 } | |
| 600 | |
| 601 /** | |
| 602 * Perform a select all action. | |
| 603 */ | |
| 604 public void selectAll() { | |
| 605 mWebContents.selectAll(); | |
| 606 // Even though the above statement logged a SelectAll user action, we wa nt to | |
| 607 // track whether the focus was in an editable field, so log that too. | |
| 608 if (isSelectionEditable()) { | |
| 609 RecordUserAction.record("MobileActionMode.SelectAllWasEditable"); | |
| 610 } else { | |
| 611 RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable"); | |
| 612 } | |
| 613 } | |
| 614 | |
| 615 /** | |
| 616 * Perform a cut (to clipboard) action. | |
| 617 */ | |
| 618 public void cut() { | |
| 619 mWebContents.cut(); | |
| 620 } | |
| 621 | |
| 622 /** | |
| 623 * Perform a copy (to clipboard) action. | |
| 624 */ | |
| 625 public void copy() { | |
| 626 mWebContents.copy(); | |
| 627 } | |
| 628 | |
| 629 /** | |
| 630 * Perform a paste action. | |
| 631 */ | |
| 632 public void paste() { | |
| 633 mWebContents.paste(); | |
| 634 } | |
| 635 | |
| 636 /** | |
| 637 * Perform a share action. | |
| 638 */ | |
| 639 public void share() { | |
| 640 RecordUserAction.record("MobileActionMode.Share"); | |
| 641 String query = sanitizeQuery(getSelectedText(), MAX_SHARE_QUERY_LENGTH); | |
| 642 if (TextUtils.isEmpty(query)) return; | |
| 643 | |
| 644 Intent send = new Intent(Intent.ACTION_SEND); | |
| 645 send.setType("text/plain"); | |
| 646 send.putExtra(Intent.EXTRA_TEXT, query); | |
| 647 try { | |
| 648 Intent i = Intent.createChooser(send, mContext.getString(R.string.ac tionbar_share)); | |
| 649 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
| 650 mContext.startActivity(i); | |
| 651 } catch (android.content.ActivityNotFoundException ex) { | |
| 652 // If no app handles it, do nothing. | |
| 653 } | |
| 654 } | |
| 655 | |
| 656 /** | |
| 657 * Perform a processText action (translating the text, for example). | |
| 658 */ | |
| 659 public void processText(Intent intent) { | |
| 660 RecordUserAction.record("MobileActionMode.ProcessTextIntent"); | |
| 661 assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; | |
| 662 | |
| 663 String query = sanitizeQuery(getSelectedText(), MAX_SEARCH_QUERY_LENGTH) ; | |
| 664 if (TextUtils.isEmpty(query)) return; | |
| 665 | |
| 666 intent.putExtra(Intent.EXTRA_PROCESS_TEXT, query); | |
| 667 | |
| 668 // Intent is sent by WindowAndroid by default. | |
| 669 try { | |
| 670 mWindowAndroid.showIntent(intent, new WindowAndroid.IntentCallback() { | |
| 671 @Override | |
| 672 public void onIntentCompleted(WindowAndroid window, | |
| 673 int resultCode, ContentResolver contentResolver, Int ent data) { | |
| 674 onReceivedProcessTextResult(resultCode, data); | |
| 675 } | |
| 676 }, null); | |
| 677 } catch (android.content.ActivityNotFoundException ex) { | |
| 678 // If no app handles it, do nothing. | |
| 679 } | |
| 680 } | |
| 681 | |
| 682 /** | |
| 683 * Perform a search action. | |
| 684 */ | |
| 685 public void search() { | |
| 686 RecordUserAction.record("MobileActionMode.WebSearch"); | |
| 687 String query = sanitizeQuery(getSelectedText(), MAX_SEARCH_QUERY_LENGTH) ; | |
| 688 if (TextUtils.isEmpty(query)) return; | |
| 689 | |
| 690 Intent i = new Intent(Intent.ACTION_WEB_SEARCH); | |
| 691 i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); | |
| 692 i.putExtra(SearchManager.QUERY, query); | |
| 693 i.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); | |
| 694 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
| 695 try { | |
| 696 mContext.startActivity(i); | |
| 697 } catch (android.content.ActivityNotFoundException ex) { | |
| 698 // If no app handles it, do nothing. | |
| 699 } | |
| 700 } | |
| 701 | |
| 702 /** | |
| 703 * @return true if the current selection is of password type. | |
| 704 */ | |
| 705 public boolean isSelectionPassword() { | |
| 706 return mIsPasswordType; | |
| 707 } | |
| 708 | |
| 709 /** | |
| 710 * @return true iff the current selection is editable (e.g. text within an i nput field). | |
| 711 */ | |
| 712 public boolean isSelectionEditable() { | |
| 713 return mEditable; | |
| 714 } | |
| 715 | |
| 716 /** | |
| 717 * @return true if the current selection is an insertion point. | |
| 718 */ | |
| 719 @VisibleForTesting | |
| 720 public boolean isInsertion() { | |
| 721 return mIsInsertion; | |
| 722 } | |
| 723 | |
| 724 @Override | |
| 725 public boolean isIncognito() { | |
| 726 return mWebContents.isIncognito(); | |
| 727 } | |
| 728 | |
| 729 /** | |
| 730 * @param actionModeItem the flag for the action mode item in question. The valid flags are | |
| 731 * {@link #MENU_ITEM_SHARE}, {@link #MENU_ITEM_WEB_SEARCH}, and | |
| 732 * {@link #MENU_ITEM_PROCESS_TEXT}. | |
| 733 * @return true if the menu item action is allowed. Otherwise, the menu item | |
| 734 * should be removed from the menu. | |
| 735 */ | |
| 736 private boolean isSelectActionModeAllowed(int actionModeItem) { | |
| 737 boolean isAllowedByClient = mAllowedMenuItems != null | |
| 738 ? mAllowedMenuItems.get(actionModeItem, mMenuDefaultAllowed) | |
| 739 : mMenuDefaultAllowed; | |
| 740 if (actionModeItem == MENU_ITEM_SHARE) { | |
| 741 return isAllowedByClient && isShareAvailable(); | |
| 742 } | |
| 743 if (actionModeItem == MENU_ITEM_WEB_SEARCH) { | |
| 744 return isAllowedByClient && isWebSearchAvailable(); | |
| 745 } | |
| 746 return isAllowedByClient; | |
| 747 } | |
| 748 | |
| 749 @Override | |
| 750 public void onReceivedProcessTextResult(int resultCode, Intent data) { | |
| 751 if (mWebContents == null || resultCode != Activity.RESULT_OK || data == null) return; | |
| 752 | |
| 753 // Do not handle the result if no text is selected or current selection is not editable. | |
| 754 if (!mHasSelection || !isSelectionEditable()) return; | |
| 755 | |
| 756 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEX T); | |
| 757 if (result != null) { | |
| 758 // TODO(hush): Use a variant of replace that re-selects the replaced text. | |
| 759 // crbug.com/546710 | |
| 760 mWebContents.replace(result.toString()); | |
| 761 } | |
| 762 } | |
| 763 | |
| 764 void restoreSelectionPopupsIfNecessary() { | |
| 765 if (mHasSelection && isActionModeValid()) showActionMode(true); | |
| 766 } | |
| 767 | |
| 768 // All coordinates are in DIP. | |
| 769 void onSelectionEvent(int eventType, int xAnchor, int yAnchor, | |
| 770 int left, int top, int right, int bottom, boolean isScrollInProgress , | |
| 771 boolean touchScrollInProgress, ImeAdapter imeAdapter) { | |
| 772 // Ensure the provided selection coordinates form a non-empty rect, as r equired by | |
| 773 // the selection action mode. | |
| 774 if (left == right) ++right; | |
| 775 if (top == bottom) ++bottom; | |
| 776 switch (eventType) { | |
| 777 case SelectionEventType.SELECTION_HANDLES_SHOWN: | |
| 778 mSelectionRect.set(left, top, right, bottom); | |
| 779 mHasSelection = true; | |
| 780 mUnselectAllOnDismiss = true; | |
| 781 if (showActionMode(true) && !isActionModeValid()) { | |
| 782 if (isSelectionEditable()) { | |
| 783 imeAdapter.moveCursorToSelectionEnd(); | |
| 784 } else { | |
| 785 if (mWebContents != null) mWebContents.unselect(); | |
| 786 } | |
| 787 } | |
| 788 break; | |
| 789 | |
| 790 case SelectionEventType.SELECTION_HANDLES_MOVED: | |
| 791 mSelectionRect.set(left, top, right, bottom); | |
| 792 invalidateContentRect(); | |
| 793 break; | |
| 794 | |
| 795 case SelectionEventType.SELECTION_HANDLES_CLEARED: | |
| 796 mHasSelection = false; | |
| 797 mUnselectAllOnDismiss = false; | |
| 798 mSelectionRect.setEmpty(); | |
| 799 finishActionMode(); | |
| 800 mDraggingSelection = false; | |
| 801 break; | |
| 802 | |
| 803 case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED: | |
| 804 mDraggingSelection = true; | |
| 805 updateActionModeVisibility(touchScrollInProgress); | |
| 806 break; | |
| 807 | |
| 808 case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED: | |
| 809 mDraggingSelection = false; | |
| 810 updateActionModeVisibility(touchScrollInProgress); | |
| 811 break; | |
| 812 | |
| 813 case SelectionEventType.INSERTION_HANDLE_SHOWN: | |
| 814 mSelectionRect.set(left, top, right, bottom); | |
| 815 setIsInsertion(true); | |
| 816 break; | |
| 817 | |
| 818 case SelectionEventType.INSERTION_HANDLE_MOVED: | |
| 819 mSelectionRect.set(left, top, right, bottom); | |
| 820 if (!isScrollInProgress && isPastePopupShowing()) { | |
| 821 showPastePopup(xAnchor, yAnchor); | |
| 822 } else { | |
| 823 hidePastePopup(); | |
| 824 } | |
| 825 break; | |
| 826 | |
| 827 case SelectionEventType.INSERTION_HANDLE_TAPPED: | |
| 828 if (mWasPastePopupShowingOnInsertionDragStart) { | |
| 829 hidePastePopup(); | |
| 830 } else { | |
| 831 showPastePopup(xAnchor, yAnchor); | |
| 832 } | |
| 833 mWasPastePopupShowingOnInsertionDragStart = false; | |
| 834 break; | |
| 835 | |
| 836 case SelectionEventType.INSERTION_HANDLE_CLEARED: | |
| 837 hidePastePopup(); | |
| 838 setIsInsertion(false); | |
| 839 mSelectionRect.setEmpty(); | |
| 840 break; | |
| 841 | |
| 842 case SelectionEventType.INSERTION_HANDLE_DRAG_STARTED: | |
| 843 mWasPastePopupShowingOnInsertionDragStart = isPastePopupShowing( ); | |
| 844 hidePastePopup(); | |
| 845 break; | |
| 846 | |
| 847 case SelectionEventType.INSERTION_HANDLE_DRAG_STOPPED: | |
| 848 if (mWasPastePopupShowingOnInsertionDragStart) { | |
| 849 showPastePopup(xAnchor, yAnchor); | |
| 850 } | |
| 851 mWasPastePopupShowingOnInsertionDragStart = false; | |
| 852 break; | |
| 853 | |
| 854 case SelectionEventType.SELECTION_ESTABLISHED: | |
| 855 case SelectionEventType.SELECTION_DISSOLVED: | |
| 856 break; | |
| 857 | |
| 858 default: | |
| 859 assert false : "Invalid selection event type."; | |
| 860 } | |
| 861 | |
| 862 if (mContextualSearchClient != null) { | |
| 863 final float deviceScale = mRenderCoordinates.getDeviceScaleFactor(); | |
| 864 int xAnchorPix = (int) (xAnchor * deviceScale); | |
| 865 int yAnchorPix = (int) (yAnchor * deviceScale); | |
| 866 mContextualSearchClient.onSelectionEvent(eventType, xAnchorPix, yAnc horPix); | |
| 867 } | |
| 868 } | |
| 869 | |
| 870 void onSelectionChanged(String text) { | |
| 871 mLastSelectedText = text; | |
| 872 if (mContextualSearchClient != null) { | |
| 873 mContextualSearchClient.onSelectionChanged(text); | |
| 874 } | |
| 875 } | |
| 876 | |
| 877 // The client that implements Contextual Search functionality, or null if no ne exists. | |
| 878 void setContextualSearchClient(ContextualSearchClient contextualSearchClient ) { | |
| 879 mContextualSearchClient = contextualSearchClient; | |
| 880 } | |
| 881 | |
| 882 void onShowUnhandledTapUIIfNeeded(int x, int y) { | |
| 883 if (mContextualSearchClient != null) { | |
| 884 mContextualSearchClient.showUnhandledTapUIIfNeeded(x, y); | |
| 885 } | |
| 886 } | |
| 887 | |
| 888 void destroyActionModeAndUnselect() { | |
| 889 mUnselectAllOnDismiss = true; | |
| 890 } | |
| 891 | |
| 892 void destroyActionModeAndKeepSelection() { | |
| 893 mUnselectAllOnDismiss = false; | |
| 894 } | |
| 895 | |
| 896 void updateSelectionState(boolean editable, boolean isPassword) { | |
| 897 if (!editable) hidePastePopup(); | |
| 898 if (isActionModeValid() | |
| 899 && (editable != isSelectionEditable() || isPassword != isSelecti onPassword())) { | |
| 900 mActionMode.invalidate(); | |
| 901 mNeedsPrepare = true; | |
| 902 } | |
| 903 mEditable = editable; | |
| 904 mIsPasswordType = isPassword; | |
| 905 } | |
| 906 | |
| 907 /** | |
| 908 * @return Whether the page has an active, touch-controlled selection region . | |
| 909 */ | |
| 910 @VisibleForTesting | |
| 911 public boolean hasSelection() { | |
| 912 return mHasSelection; | |
| 913 } | |
| 914 | |
| 915 void updateActionModeVisibility(boolean touchScrollInProgress) { | |
| 916 // The active fling count isn't reliable with WebView, so only use the | |
| 917 // active touch scroll signal for hiding. The fling animation movement | |
| 918 // will naturally hide the ActionMode by invalidating its content rect. | |
| 919 hideActionMode(mDraggingSelection || touchScrollInProgress); | |
| 920 } | |
| 921 | |
| 922 @Override | |
| 923 public String getSelectedText() { | |
| 924 return mHasSelection ? mLastSelectedText : ""; | |
| 925 } | |
| 926 | |
| 927 private void setIsInsertion(boolean insertion) { | |
| 928 if (isActionModeValid() && mIsInsertion != insertion) mNeedsPrepare = tr ue; | |
| 929 mIsInsertion = insertion; | |
| 930 } | |
| 931 | |
| 932 /** | |
| 933 * Trim a given string query to be processed safely. | |
| 934 * | |
| 935 * @param query a raw query to sanitize. | |
| 936 * @param maxLength maximum length to which the query will be truncated. | |
| 937 */ | |
| 938 public static String sanitizeQuery(String query, int maxLength) { | |
| 939 if (TextUtils.isEmpty(query) || query.length() < maxLength) return query ; | |
| 940 Log.w(TAG, "Truncating oversized query (" + query.length() + ")."); | |
| 941 return query.substring(0, maxLength) + "…"; | |
| 942 } | |
| 943 | |
| 944 private boolean isShareAvailable() { | |
| 945 Intent intent = new Intent(Intent.ACTION_SEND); | |
| 946 intent.setType("text/plain"); | |
| 947 return mContext.getPackageManager().queryIntentActivities(intent, | |
| 948 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; | |
| 949 } | |
| 950 | |
| 951 private boolean isWebSearchAvailable() { | |
| 952 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); | |
| 953 intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); | |
| 954 return mContext.getPackageManager().queryIntentActivities(intent, | |
| 955 PackageManager.MATCH_DEFAULT_ONLY).size() > 0; | |
| 956 } | |
| 957 | |
| 958 /** | |
| 959 * Empty {@link ActionMode.Callback} that does nothing. Used for {@link #emp ty()}. | |
| 960 */ | |
| 961 private static class EmptyActionCallback implements ActionMode.Callback { | |
| 962 @Override | |
| 963 public boolean onCreateActionMode(ActionMode mode, Menu menu) { | |
| 964 return false; | |
| 965 } | |
| 966 | |
| 967 @Override | |
| 968 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { | |
| 969 return false; | |
| 970 } | |
| 971 | |
| 972 @Override | |
| 973 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { | |
| 974 return false; | |
| 975 } | |
| 976 | |
| 977 @Override | |
| 978 public void onDestroyActionMode(ActionMode mode) {} | |
| 979 }; | |
| 158 } | 980 } |
| OLD | NEW |