SelectActionModeCallback.java revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.app.AppResource;
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                assert AppResource.STRING_ACTION_BAR_SHARE != 0;
159                assert AppResource.DRAWABLE_ICON_ACTION_BAR_SHARE != 0;
160                menu.add(Menu.NONE, ID_SHARE, Menu.NONE, AppResource.STRING_ACTION_BAR_SHARE).
161                    setIcon(AppResource.DRAWABLE_ICON_ACTION_BAR_SHARE).
162                    setShowAsAction(
163                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
164            }
165
166            if (!mIncognito && isWebSearchAvailable()) {
167                assert AppResource.STRING_ACTION_BAR_WEB_SEARCH != 0;
168                assert AppResource.DRAWABLE_ICON_ACTION_BAR_WEB_SEARCH != 0;
169                menu.add(Menu.NONE, ID_SEARCH, Menu.NONE,
170                        AppResource.STRING_ACTION_BAR_WEB_SEARCH).
171                    setIcon(AppResource.DRAWABLE_ICON_ACTION_BAR_WEB_SEARCH).
172                    setShowAsAction(
173                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
174            }
175        }
176
177        styledAttributes.recycle();
178    }
179
180    @Override
181    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
182        String selection = mActionHandler.getSelectedText();
183        switch(item.getItemId()) {
184            case ID_SELECTALL:
185                mActionHandler.selectAll();
186                break;
187            case ID_CUT:
188                mActionHandler.cut();
189                break;
190            case ID_COPY:
191                mActionHandler.copy();
192                mode.finish();
193                break;
194            case ID_PASTE:
195                mActionHandler.paste();
196                break;
197            case ID_SHARE:
198                if (!TextUtils.isEmpty(selection)) {
199                    Intent send = new Intent(Intent.ACTION_SEND);
200                    send.setType("text/plain");
201                    send.putExtra(Intent.EXTRA_TEXT, selection);
202                    try {
203                        assert AppResource.STRING_ACTION_BAR_SHARE != 0;
204                        Intent i = Intent.createChooser(send, getContext().getString(
205                                AppResource.STRING_ACTION_BAR_SHARE));
206                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
207                        getContext().startActivity(i);
208                    } catch (android.content.ActivityNotFoundException ex) {
209                        // If no app handles it, do nothing.
210                    }
211                }
212                mode.finish();
213                break;
214            case ID_SEARCH:
215                if (!TextUtils.isEmpty(selection)) {
216                    Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
217                    i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
218                    i.putExtra(SearchManager.QUERY, selection);
219                    i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
220                    try {
221                        getContext().startActivity(i);
222                    } catch (android.content.ActivityNotFoundException ex) {
223                        // If no app handles it, do nothing.
224                    }
225                }
226                mode.finish();
227                break;
228            default:
229                return false;
230        }
231        return true;
232    }
233
234    @Override
235    public void onDestroyActionMode(ActionMode mode) {
236        mActionHandler.onDestroyActionMode();
237    }
238
239    private boolean canPaste() {
240        ClipboardManager clipMgr = (ClipboardManager)
241                getContext().getSystemService(Context.CLIPBOARD_SERVICE);
242        return clipMgr.hasPrimaryClip();
243    }
244
245    private boolean isShareHandlerAvailable() {
246        Intent intent = new Intent(Intent.ACTION_SEND);
247        intent.setType("text/plain");
248        return getContext().getPackageManager()
249                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
250    }
251
252    private boolean isWebSearchAvailable() {
253        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
254        intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
255        return getContext().getPackageManager()
256                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
257    }
258}
259