summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java685
1 files changed, 685 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java
new file mode 100644
index 0000000000..f2e10e50a4
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java
@@ -0,0 +1,685 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: ts=4 sw=4 expandtab:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.geckoview;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.TransactionTooLargeException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import java.util.ArrayList;
+import java.util.List;
+import org.mozilla.gecko.util.ThreadUtils;
+
+/**
+ * Class that implements a basic SelectionActionDelegate. This class is used by GeckoView by default
+ * if the consumer does not explicitly set a SelectionActionDelegate.
+ *
+ * <p>To provide custom actions, extend this class and override the following methods,
+ *
+ * <p>1) Override {@link #getAllActions} to include custom action IDs in the returned array. This
+ * array must include all actions, available or not, and must not change over the class lifetime.
+ *
+ * <p>2) Override {@link #isActionAvailable} to return whether a custom action is currently
+ * available.
+ *
+ * <p>3) Override {@link #prepareAction} to set custom title and/or icon for a custom action.
+ *
+ * <p>4) Override {@link #performAction} to perform a custom action when used.
+ */
+@UiThread
+public class BasicSelectionActionDelegate
+ implements ActionMode.Callback, GeckoSession.SelectionActionDelegate {
+ private static final String LOGTAG = "BasicSelectionAction";
+
+ protected static final String ACTION_PROCESS_TEXT = Intent.ACTION_PROCESS_TEXT;
+
+ private static final String[] FLOATING_TOOLBAR_ACTIONS =
+ new String[] {
+ ACTION_CUT,
+ ACTION_COPY,
+ ACTION_PASTE,
+ ACTION_SELECT_ALL,
+ ACTION_PASTE_AS_PLAIN_TEXT,
+ ACTION_PROCESS_TEXT
+ };
+ private static final String[] FIXED_TOOLBAR_ACTIONS =
+ new String[] {ACTION_SELECT_ALL, ACTION_CUT, ACTION_COPY, ACTION_PASTE};
+
+ // This is limitation of intent text.
+ private static final int MAX_INTENT_TEXT_LENGTH = 100000;
+
+ protected final @NonNull Activity mActivity;
+ protected final boolean mUseFloatingToolbar;
+
+ private boolean mExternalActionsEnabled;
+
+ protected @Nullable ActionMode mActionMode;
+ protected @Nullable GeckoSession mSession;
+ protected @Nullable Selection mSelection;
+ protected boolean mRepopulatedMenu;
+
+ private @Nullable ActionMode mActionModeForClipboardPermission;
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private class Callback2Wrapper extends ActionMode.Callback2 {
+ @Override
+ public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
+ return BasicSelectionActionDelegate.this.onCreateActionMode(actionMode, menu);
+ }
+
+ @Override
+ public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
+ return BasicSelectionActionDelegate.this.onPrepareActionMode(actionMode, menu);
+ }
+
+ @Override
+ public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
+ return BasicSelectionActionDelegate.this.onActionItemClicked(actionMode, menuItem);
+ }
+
+ @Override
+ public void onDestroyActionMode(final ActionMode actionMode) {
+ BasicSelectionActionDelegate.this.onDestroyActionMode(actionMode);
+ }
+
+ @Override
+ public void onGetContentRect(final ActionMode mode, final View view, final Rect outRect) {
+ super.onGetContentRect(mode, view, outRect);
+ BasicSelectionActionDelegate.this.onGetContentRect(mode, view, outRect);
+ }
+ }
+
+ @SuppressWarnings("checkstyle:javadocmethod")
+ public BasicSelectionActionDelegate(final @NonNull Activity activity) {
+ this(activity, Build.VERSION.SDK_INT >= 23);
+ }
+
+ @SuppressWarnings("checkstyle:javadocmethod")
+ public BasicSelectionActionDelegate(
+ final @NonNull Activity activity, final boolean useFloatingToolbar) {
+ mActivity = activity;
+ mUseFloatingToolbar = useFloatingToolbar;
+ mExternalActionsEnabled = true;
+ }
+
+ /**
+ * Set whether to include text actions from other apps in the floating toolbar.
+ *
+ * @param enable True if external actions should be enabled.
+ */
+ public void enableExternalActions(final boolean enable) {
+ ThreadUtils.assertOnUiThread();
+ mExternalActionsEnabled = enable;
+
+ if (mActionMode != null) {
+ mActionMode.invalidate();
+ }
+ }
+
+ /**
+ * Get whether text actions from other apps are enabled.
+ *
+ * @return True if external actions are enabled.
+ */
+ public boolean areExternalActionsEnabled() {
+ return mExternalActionsEnabled;
+ }
+
+ /**
+ * Return list of all actions in proper order, regardless of their availability at present.
+ * Override to add to or remove from the default set.
+ *
+ * @return Array of action IDs in proper order.
+ */
+ protected @NonNull String[] getAllActions() {
+ return mUseFloatingToolbar ? FLOATING_TOOLBAR_ACTIONS : FIXED_TOOLBAR_ACTIONS;
+ }
+
+ /**
+ * Return whether an action is presently available. Override to indicate availability for custom
+ * actions.
+ *
+ * @param id Action ID.
+ * @return True if the action is presently available.
+ */
+ protected boolean isActionAvailable(final @NonNull String id) {
+ if (mSelection == null) {
+ return false;
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && ACTION_PASTE_AS_PLAIN_TEXT.equals(id)) {
+ return false;
+ }
+
+ if (mExternalActionsEnabled && !mSelection.text.isEmpty() && ACTION_PROCESS_TEXT.equals(id)) {
+ return !getProcessTextExportedActivities().isEmpty();
+ }
+
+ return mSelection.isActionAvailable(id);
+ }
+
+ /**
+ * Get exported activities for {@link BasicSelectionActionDelegate#ACTION_PROCESS_TEXT} when text
+ * is selected.
+ *
+ * @return list of exported activities
+ */
+ private @NonNull List<ResolveInfo> getProcessTextExportedActivities() {
+ final PackageManager pm = mActivity.getPackageManager();
+ final List<ResolveInfo> resolvedList =
+ pm.queryIntentActivityOptions(
+ null, null, getProcessTextIntent(null), PackageManager.MATCH_DEFAULT_ONLY);
+ final ArrayList<ResolveInfo> exportedList = new ArrayList<>();
+ for (final ResolveInfo info : resolvedList) {
+ if (info.activityInfo.exported) {
+ exportedList.add(info);
+ }
+ }
+
+ return exportedList;
+ }
+
+ /**
+ * Provides access to whether there are text selection actions available. Override to indicate
+ * availability for custom actions.
+ *
+ * @return True if there are text selection actions available.
+ */
+ public boolean isActionAvailable() {
+ if (mSelection == null) {
+ return false;
+ }
+
+ return isActionAvailable(ACTION_PROCESS_TEXT) || !mSelection.availableActions.isEmpty();
+ }
+
+ /**
+ * Prepare a menu item corresponding to a certain action. Override to prepare menu item for custom
+ * action.
+ *
+ * @param id Action ID.
+ * @param item New menu item to prepare.
+ */
+ protected void prepareAction(final @NonNull String id, final @NonNull MenuItem item) {
+ switch (id) {
+ case ACTION_CUT:
+ item.setTitle(android.R.string.cut);
+ break;
+ case ACTION_COPY:
+ item.setTitle(android.R.string.copy);
+ break;
+ case ACTION_PASTE:
+ item.setTitle(android.R.string.paste);
+ break;
+ case ACTION_PASTE_AS_PLAIN_TEXT:
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ throw new IllegalStateException("Unexpected version for action");
+ }
+ item.setTitle(android.R.string.paste_as_plain_text);
+ break;
+ case ACTION_SELECT_ALL:
+ item.setTitle(android.R.string.selectAll);
+ break;
+ case ACTION_PROCESS_TEXT:
+ throw new IllegalStateException("Unexpected action");
+ }
+ }
+
+ /**
+ * Perform the specified action. Override to perform custom actions.
+ *
+ * @param id Action ID.
+ * @param item Nenu item for the action.
+ * @return True if the action was performed.
+ */
+ protected boolean performAction(final @NonNull String id, final @NonNull MenuItem item) {
+ if (ACTION_PROCESS_TEXT.equals(id)) {
+ try {
+ mActivity.startActivity(item.getIntent());
+ } catch (final ActivityNotFoundException e) {
+ Log.e(LOGTAG, "Cannot perform action", e);
+ return false;
+ }
+ return true;
+ }
+
+ if (mSelection == null) {
+ return false;
+ }
+ mSelection.execute(id);
+
+ // Android behavior is to clear selection on copy.
+ if (ACTION_COPY.equals(id)) {
+ if (mUseFloatingToolbar) {
+ clearSelection();
+ } else {
+ mActionMode.finish();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get the current selection object. This object should not be stored as it does not update when
+ * the selection becomes invalid. Stale actions are ignored.
+ *
+ * @return The {@link GeckoSession.SelectionActionDelegate.Selection} attached to the current
+ * action menu. <code>null</code> if no action menu is active.
+ */
+ public @Nullable Selection getSelection() {
+ return mSelection;
+ }
+
+ /** Clear the current selection, if possible. */
+ public void clearSelection() {
+ if (mSelection == null) {
+ return;
+ }
+
+ if (isActionAvailable(ACTION_COLLAPSE_TO_END)) {
+ mSelection.collapseToEnd();
+ } else if (isActionAvailable(ACTION_UNSELECT)) {
+ mSelection.unselect();
+ } else {
+ mSelection.hide();
+ }
+ }
+
+ private String getSelectedText(final int maxLength) {
+ if (mSelection == null) {
+ return "";
+ }
+
+ if (TextUtils.isEmpty(mSelection.text) || mSelection.text.length() < maxLength) {
+ return mSelection.text;
+ }
+
+ return mSelection.text.substring(0, maxLength);
+ }
+
+ private Intent getProcessTextIntent(@Nullable final ResolveInfo resolveInfo) {
+ final Intent intent = new Intent(Intent.ACTION_PROCESS_TEXT);
+ if (resolveInfo != null) {
+ intent.setComponent(
+ new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
+ }
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType("text/plain");
+ // If using large text, anything intent may throw RemoteException.
+ intent.putExtra(Intent.EXTRA_PROCESS_TEXT, getSelectedText(MAX_INTENT_TEXT_LENGTH));
+ // TODO: implement ability to replace text in Gecko for editable selection (bug 1453137).
+ intent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true);
+ return intent;
+ }
+
+ @Override
+ public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
+ ThreadUtils.assertOnUiThread();
+ final String[] allActions = getAllActions();
+ for (final String actionId : allActions) {
+ if (isActionAvailable(actionId)) {
+ if (!mUseFloatingToolbar && (Build.VERSION.SDK_INT == 22 || Build.VERSION.SDK_INT == 23)) {
+ // Android bug where onPrepareActionMode is not called initially.
+ onPrepareActionMode(actionMode, menu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
+ ThreadUtils.assertOnUiThread();
+ final String[] allActions = getAllActions();
+ boolean changed = false;
+
+ // Whether we are repopulating an existing menu.
+ mRepopulatedMenu = menu.size() != 0;
+
+ // For each action, see if it's available at present, and if necessary,
+ // add to or remove from menu.
+ for (int i = 0; i < allActions.length; i++) {
+ final String actionId = allActions[i];
+ final int menuId = i + Menu.FIRST;
+
+ if (ACTION_PROCESS_TEXT.equals(actionId)) {
+ if (mExternalActionsEnabled && mSelection != null && !mSelection.text.isEmpty()) {
+ final List<ResolveInfo> exportedPackageInfo = getProcessTextExportedActivities();
+ if (!exportedPackageInfo.isEmpty()) {
+ for (final ResolveInfo info : exportedPackageInfo) {
+ final boolean isMenuItemAdded = addProcessTextMenuItem(menu, menuId, info);
+ if (isMenuItemAdded) {
+ changed = true;
+ }
+ }
+ }
+ } else if (menu.findItem(menuId) != null) {
+ menu.removeGroup(menuId);
+ changed = true;
+ }
+ continue;
+ }
+
+ if (isActionAvailable(actionId)) {
+ if (menu.findItem(menuId) == null) {
+ prepareAction(actionId, menu.add(/* group */ Menu.NONE, menuId, menuId, /* title */ ""));
+ changed = true;
+ }
+ } else if (menu.findItem(menuId) != null) {
+ menu.removeItem(menuId);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ private boolean addProcessTextMenuItem(
+ final Menu menu, final int menuId, final ResolveInfo info) {
+ boolean isMenuItemAdded = false;
+ try {
+ menu.addIntentOptions(
+ menuId,
+ menuId,
+ menuId,
+ mActivity.getComponentName(),
+ /* specifiec */ null,
+ getProcessTextIntent(info),
+ /* flags */ Menu.FLAG_APPEND_TO_GROUP, /* items */
+ null);
+ isMenuItemAdded = true;
+ } catch (final RuntimeException e) {
+ if (e.getCause() instanceof TransactionTooLargeException) {
+ // Binder size error. MAX_INTENT_TEXT_LENGTH is still large?
+ Log.e(LOGTAG, "Cannot add intent option", e);
+ } else {
+ throw e;
+ }
+ }
+ return isMenuItemAdded;
+ }
+
+ @Override
+ public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
+ ThreadUtils.assertOnUiThread();
+ MenuItem realMenuItem = null;
+ if (mRepopulatedMenu) {
+ // When we repopulate an existing menu, Android can sometimes give us an old,
+ // deleted MenuItem. Find the current MenuItem that corresponds to the old one.
+ final Menu menu = actionMode.getMenu();
+ final int size = menu.size();
+ for (int i = 0; i < size; i++) {
+ final MenuItem item = menu.getItem(i);
+ if (item == menuItem
+ || (item.getItemId() == menuItem.getItemId()
+ && item.getTitle().equals(menuItem.getTitle()))) {
+ realMenuItem = item;
+ break;
+ }
+ }
+ } else {
+ realMenuItem = menuItem;
+ }
+
+ if (realMenuItem == null) {
+ return false;
+ }
+ final String[] allActions = getAllActions();
+ return performAction(allActions[realMenuItem.getItemId() - Menu.FIRST], realMenuItem);
+ }
+
+ @Override
+ public void onDestroyActionMode(final ActionMode actionMode) {
+ ThreadUtils.assertOnUiThread();
+ if (!mUseFloatingToolbar) {
+ clearSelection();
+ }
+ mSession = null;
+ mSelection = null;
+ mActionMode = null;
+ }
+
+ @SuppressWarnings("checkstyle:javadocmethod")
+ public void onGetContentRect(
+ final @Nullable ActionMode mode, final @Nullable View view, final @NonNull Rect outRect) {
+ ThreadUtils.assertOnUiThread();
+ if (mSelection == null || mSelection.screenRect == null) {
+ return;
+ }
+
+ // outRect has to convert to current window coordinate.
+ final Matrix matrix = new Matrix();
+ mSession.getScreenToWindowManagerOffsetMatrix(matrix);
+ final RectF transformedRect = new RectF();
+ matrix.mapRect(transformedRect, mSelection.screenRect);
+ transformedRect.roundOut(outRect);
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ @Override
+ public void onShowActionRequest(final GeckoSession session, final Selection selection) {
+ ThreadUtils.assertOnUiThread();
+ mSession = session;
+ mSelection = selection;
+
+ if (mActionMode != null) {
+ if (isActionAvailable()) {
+ mActionMode.invalidate();
+ } else {
+ mActionMode.finish();
+ }
+ return;
+ }
+
+ if (mActionModeForClipboardPermission != null) {
+ mActionModeForClipboardPermission.finish();
+ return;
+ }
+
+ if (mUseFloatingToolbar) {
+ mActionMode = mActivity.startActionMode(new Callback2Wrapper(), ActionMode.TYPE_FLOATING);
+ } else {
+ mActionMode = mActivity.startActionMode(this);
+ }
+ }
+
+ @Override
+ public void onHideAction(final GeckoSession session, final int reason) {
+ ThreadUtils.assertOnUiThread();
+ if (mActionMode == null) {
+ return;
+ }
+
+ switch (reason) {
+ case HIDE_REASON_ACTIVE_SCROLL:
+ case HIDE_REASON_ACTIVE_SELECTION:
+ case HIDE_REASON_INVISIBLE_SELECTION:
+ if (mUseFloatingToolbar) {
+ // Hide the floating toolbar when scrolling/selecting.
+ mActionMode.finish();
+ }
+ break;
+
+ case HIDE_REASON_NO_SELECTION:
+ mActionMode.finish();
+ break;
+ }
+ }
+
+ /** Callback class of clipboard permission. This is used on pre-M only */
+ private class ClipboardPermissionCallback implements ActionMode.Callback {
+ private GeckoResult<AllowOrDeny> mResult;
+
+ public ClipboardPermissionCallback(final GeckoResult<AllowOrDeny> result) {
+ mResult = result;
+ }
+
+ @Override
+ public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
+ return BasicSelectionActionDelegate.this.onCreateActionModeForClipboardPermission(
+ actionMode, menu);
+ }
+
+ @Override
+ public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
+ mResult.complete(AllowOrDeny.ALLOW);
+ mResult = null;
+ actionMode.finish();
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(final ActionMode actionMode) {
+ if (mResult != null) {
+ mResult.complete(AllowOrDeny.DENY);
+ }
+ BasicSelectionActionDelegate.this.onDestroyActionModeForClipboardPermission(actionMode);
+ }
+ }
+
+ /** Callback class of clipboard permission for Android M+ */
+ @TargetApi(Build.VERSION_CODES.M)
+ private class ClipboardPermissionCallbackM extends ActionMode.Callback2 {
+ private @Nullable GeckoResult<AllowOrDeny> mResult;
+ private final @NonNull GeckoSession mSession;
+ private final @Nullable Point mPoint;
+
+ public ClipboardPermissionCallbackM(
+ final @NonNull GeckoSession session,
+ final @Nullable Point screenPoint,
+ final @NonNull GeckoResult<AllowOrDeny> result) {
+ mSession = session;
+ mPoint = screenPoint;
+ mResult = result;
+ }
+
+ @Override
+ public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
+ return BasicSelectionActionDelegate.this.onCreateActionModeForClipboardPermission(
+ actionMode, menu);
+ }
+
+ @Override
+ public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
+ mResult.complete(AllowOrDeny.ALLOW);
+ mResult = null;
+ actionMode.finish();
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(final ActionMode actionMode) {
+ if (mResult != null) {
+ mResult.complete(AllowOrDeny.DENY);
+ }
+ BasicSelectionActionDelegate.this.onDestroyActionModeForClipboardPermission(actionMode);
+ }
+
+ @Override
+ public void onGetContentRect(final ActionMode mode, final View view, final Rect outRect) {
+ super.onGetContentRect(mode, view, outRect);
+
+ if (mPoint == null) {
+ return;
+ }
+
+ outRect.set(mPoint.x, mPoint.y, mPoint.x + 1, mPoint.y + 1);
+ }
+ }
+
+ /**
+ * Show action mode bar to request clipboard permission
+ *
+ * @param session The GeckoSession that initiated the callback.
+ * @param permission An {@link ClipboardPermission} describing the permission being requested.
+ * @return A {@link GeckoResult} with {@link AllowOrDeny}, determining the response to the
+ * permission request for this site.
+ */
+ @TargetApi(Build.VERSION_CODES.M)
+ @Override
+ public GeckoResult<AllowOrDeny> onShowClipboardPermissionRequest(
+ final GeckoSession session, final ClipboardPermission permission) {
+ ThreadUtils.assertOnUiThread();
+
+ final GeckoResult<AllowOrDeny> result = new GeckoResult<>();
+
+ if (mActionMode != null) {
+ mActionMode.finish();
+ mActionMode = null;
+ }
+ if (mActionModeForClipboardPermission != null) {
+ mActionModeForClipboardPermission.finish();
+ mActionModeForClipboardPermission = null;
+ }
+
+ if (mUseFloatingToolbar) {
+ mActionModeForClipboardPermission =
+ mActivity.startActionMode(
+ new ClipboardPermissionCallbackM(session, permission.screenPoint, result),
+ ActionMode.TYPE_FLOATING);
+ } else {
+ mActionModeForClipboardPermission =
+ mActivity.startActionMode(new ClipboardPermissionCallback(result));
+ }
+
+ return result;
+ }
+
+ /**
+ * Dismiss action mode for requesting clipboard permission popup or model.
+ *
+ * @param session The GeckoSession that initiated the callback.
+ */
+ @Override
+ public void onDismissClipboardPermissionRequest(final GeckoSession session) {
+ ThreadUtils.assertOnUiThread();
+
+ if (mActionModeForClipboardPermission != null) {
+ mActionModeForClipboardPermission.finish();
+ mActionModeForClipboardPermission = null;
+ }
+ }
+
+ /* package */ boolean onCreateActionModeForClipboardPermission(
+ final ActionMode actionMode, final Menu menu) {
+ final MenuItem item = menu.add(/* group */ Menu.NONE, Menu.FIRST, Menu.FIRST, /* title */ "");
+ item.setTitle(android.R.string.paste);
+ return true;
+ }
+
+ /* package */ void onDestroyActionModeForClipboardPermission(final ActionMode actionMode) {
+ mActionModeForClipboardPermission = null;
+ }
+}