SelectActionModeCallback.java revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser;
6
7import android.app.SearchManager;
8import android.content.ClipboardManager;
9import android.content.Context;
10import android.content.Intent;
11import android.content.pm.PackageManager;
12import android.content.res.TypedArray;
13import android.provider.Browser;
14import android.text.TextUtils;
15import android.view.ActionMode;
16import android.view.Menu;
17import android.view.MenuItem;
18
19import org.chromium.content.R;
20
21/**
22 * An ActionMode.Callback for in-page selection. This class handles both the editable and
23 * non-editable cases.
24 */
25public class SelectActionModeCallback implements ActionMode.Callback {
26    private static final int SELECT_ALL_ATTR_INDEX = 0;
27    private static final int CUT_ATTR_INDEX = 1;
28    private static final int COPY_ATTR_INDEX = 2;
29    private static final int PASTE_ATTR_INDEX = 3;
30    private static final int[] ACTION_MODE_ATTRS = {
31        android.R.attr.actionModeSelectAllDrawable,
32        android.R.attr.actionModeCutDrawable,
33        android.R.attr.actionModeCopyDrawable,
34        android.R.attr.actionModePasteDrawable,
35    };
36
37    private static final int ID_SELECTALL = 0;
38    private static final int ID_COPY = 1;
39    private static final int ID_SHARE = 2;
40    private static final int ID_SEARCH = 3;
41    private static final int ID_CUT = 4;
42    private static final int ID_PASTE = 5;
43
44    /**
45     * An interface to retrieve information about the current selection, and also to perform
46     * actions based on the selection or when the action bar is dismissed.
47     */
48    public interface ActionHandler {
49        /**
50         * Perform a select all action.
51         * @return true iff the action was successful.
52         */
53        boolean selectAll();
54
55        /**
56         * Perform a copy (to clipboard) action.
57         * @return true iff the action was successful.
58         */
59        boolean copy();
60
61        /**
62         * Perform a cut (to clipboard) action.
63         * @return true iff the action was successful.
64         */
65        boolean cut();
66
67        /**
68         * Perform a paste action.
69         * @return true iff the action was successful.
70         */
71        boolean paste();
72
73        /**
74         * @return true iff the current selection is editable (e.g. text within an input field).
75         */
76        boolean isSelectionEditable();
77
78        /**
79         * @return the currently selected text String.
80         */
81        String getSelectedText();
82
83        /**
84         * Called when the onDestroyActionMode of the SelectActionmodeCallback is called.
85         */
86        void onDestroyActionMode();
87    }
88
89    private Context mContext;
90    private ActionHandler mActionHandler;
91    private final boolean mIncognito;
92    private boolean mEditable;
93
94    protected SelectActionModeCallback(
95            Context context, ActionHandler actionHandler, boolean incognito) {
96        mContext = context;
97        mActionHandler = actionHandler;
98        mIncognito = incognito;
99    }
100
101    protected Context getContext() {
102        return mContext;
103    }
104
105    @Override
106    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
107        mode.setSubtitle(null);
108        mEditable = mActionHandler.isSelectionEditable();
109        createActionMenu(mode, menu);
110        return true;
111    }
112
113    @Override
114    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
115        boolean isEditableNow = mActionHandler.isSelectionEditable();
116        if (mEditable != isEditableNow) {
117            mEditable = isEditableNow;
118            menu.clear();
119            createActionMenu(mode, menu);
120            return true;
121        }
122        return false;
123    }
124
125    private void createActionMenu(ActionMode mode, Menu menu) {
126        TypedArray styledAttributes = getContext().obtainStyledAttributes(ACTION_MODE_ATTRS);
127
128        menu.add(Menu.NONE, ID_SELECTALL, Menu.NONE, android.R.string.selectAll).
129            setAlphabeticShortcut('a').
130            setIcon(styledAttributes.getResourceId(SELECT_ALL_ATTR_INDEX, 0)).
131            setShowAsAction(
132                    MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
133
134        if (mEditable) {
135            menu.add(Menu.NONE, ID_CUT, Menu.NONE, android.R.string.cut).
136            setIcon(styledAttributes.getResourceId(CUT_ATTR_INDEX, 0)).
137            setAlphabeticShortcut('x').
138            setShowAsAction(
139                    MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
140        }
141
142        menu.add(Menu.NONE, ID_COPY, Menu.NONE, android.R.string.copy).
143            setIcon(styledAttributes.getResourceId(COPY_ATTR_INDEX, 0)).
144            setAlphabeticShortcut('c').
145            setShowAsAction(
146                    MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
147
148        if (mEditable && canPaste()) {
149            menu.add(Menu.NONE, ID_PASTE, Menu.NONE, android.R.string.paste).
150                setIcon(styledAttributes.getResourceId(PASTE_ATTR_INDEX, 0)).
151                setAlphabeticShortcut('v').
152                setShowAsAction(
153                        MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
154        }
155
156        if (!mEditable) {
157            if (isShareHandlerAvailable()) {
158                menu.add(Menu.NONE, ID_SHARE, Menu.NONE, R.string.actionbar_share).
159                    setIcon(R.drawable.ic_menu_share_holo_light).
160                    setShowAsAction(
161                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
162            }
163
164            if (!mIncognito && isWebSearchAvailable()) {
165                menu.add(Menu.NONE, ID_SEARCH, Menu.NONE, R.string.actionbar_web_search).
166                    setIcon(R.drawable.ic_menu_search_holo_light).
167                    setShowAsAction(
168                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
169            }
170        }
171
172        styledAttributes.recycle();
173    }
174
175    @Override
176    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
177        String selection = mActionHandler.getSelectedText();
178        switch(item.getItemId()) {
179            case ID_SELECTALL:
180                mActionHandler.selectAll();
181                break;
182            case ID_CUT:
183                mActionHandler.cut();
184                break;
185            case ID_COPY:
186                mActionHandler.copy();
187                mode.finish();
188                break;
189            case ID_PASTE:
190                mActionHandler.paste();
191                break;
192            case ID_SHARE:
193                if (!TextUtils.isEmpty(selection)) {
194                    Intent send = new Intent(Intent.ACTION_SEND);
195                    send.setType("text/plain");
196                    send.putExtra(Intent.EXTRA_TEXT, selection);
197                    try {
198                        Intent i = Intent.createChooser(send, getContext().getString(
199                                R.string.actionbar_share));
200                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
201                        getContext().startActivity(i);
202                    } catch (android.content.ActivityNotFoundException ex) {
203                        // If no app handles it, do nothing.
204                    }
205                }
206                mode.finish();
207                break;
208            case ID_SEARCH:
209                if (!TextUtils.isEmpty(selection)) {
210                    Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
211                    i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
212                    i.putExtra(SearchManager.QUERY, selection);
213                    i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
214                    try {
215                        getContext().startActivity(i);
216                    } catch (android.content.ActivityNotFoundException ex) {
217                        // If no app handles it, do nothing.
218                    }
219                }
220                mode.finish();
221                break;
222            default:
223                return false;
224        }
225        return true;
226    }
227
228    @Override
229    public void onDestroyActionMode(ActionMode mode) {
230        mActionHandler.onDestroyActionMode();
231    }
232
233    private boolean canPaste() {
234        ClipboardManager clipMgr = (ClipboardManager)
235                getContext().getSystemService(Context.CLIPBOARD_SERVICE);
236        return clipMgr.hasPrimaryClip();
237    }
238
239    private boolean isShareHandlerAvailable() {
240        Intent intent = new Intent(Intent.ACTION_SEND);
241        intent.setType("text/plain");
242        return getContext().getPackageManager()
243                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
244    }
245
246    private boolean isWebSearchAvailable() {
247        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
248        intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
249        return getContext().getPackageManager()
250                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
251    }
252}
253