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.Activity;
8import android.app.SearchManager;
9import android.content.ClipboardManager;
10import android.content.Context;
11import android.content.Intent;
12import android.content.pm.PackageManager;
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    /**
27     * An interface to retrieve information about the current selection, and also to perform
28     * actions based on the selection or when the action bar is dismissed.
29     */
30    public interface ActionHandler {
31        /**
32         * Perform a select all action.
33         * @return true iff the action was successful.
34         */
35        boolean selectAll();
36
37        /**
38         * Perform a copy (to clipboard) action.
39         * @return true iff the action was successful.
40         */
41        boolean copy();
42
43        /**
44         * Perform a cut (to clipboard) action.
45         * @return true iff the action was successful.
46         */
47        boolean cut();
48
49        /**
50         * Perform a paste action.
51         * @return true iff the action was successful.
52         */
53        boolean paste();
54
55        /**
56         * @return true iff the current selection is editable (e.g. text within an input field).
57         */
58        boolean isSelectionEditable();
59
60        /**
61         * @return the currently selected text String.
62         */
63        String getSelectedText();
64
65        /**
66         * Called when the onDestroyActionMode of the SelectActionmodeCallback is called.
67         */
68        void onDestroyActionMode();
69    }
70
71    private Context mContext;
72    private ActionHandler mActionHandler;
73    private final boolean mIncognito;
74    private boolean mEditable;
75
76    protected SelectActionModeCallback(
77            Context context, ActionHandler actionHandler, boolean incognito) {
78        mContext = context;
79        mActionHandler = actionHandler;
80        mIncognito = incognito;
81    }
82
83    protected Context getContext() {
84        return mContext;
85    }
86
87    @Override
88    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
89        mode.setTitle(null);
90        mode.setSubtitle(null);
91        mEditable = mActionHandler.isSelectionEditable();
92        createActionMenu(mode, menu);
93        return true;
94    }
95
96    @Override
97    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
98        boolean isEditableNow = mActionHandler.isSelectionEditable();
99        if (mEditable != isEditableNow) {
100            mEditable = isEditableNow;
101            menu.clear();
102            createActionMenu(mode, menu);
103            return true;
104        }
105        return false;
106    }
107
108    private void createActionMenu(ActionMode mode, Menu menu) {
109        mode.getMenuInflater().inflate(R.menu.select_action_menu, menu);
110        if (!mEditable || !canPaste()) {
111            menu.removeItem(R.id.select_action_menu_paste);
112        }
113
114        if (!mEditable) {
115            menu.removeItem(R.id.select_action_menu_cut);
116        }
117
118        if (mEditable || !isShareHandlerAvailable()) {
119            menu.removeItem(R.id.select_action_menu_share);
120        }
121
122        if (mEditable || mIncognito || !isWebSearchAvailable()) {
123            menu.removeItem(R.id.select_action_menu_web_search);
124        }
125    }
126
127    @Override
128    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
129        String selection = mActionHandler.getSelectedText();
130        int id = item.getItemId();
131
132        if (id == R.id.select_action_menu_select_all) {
133            mActionHandler.selectAll();
134        } else if (id == R.id.select_action_menu_cut) {
135            mActionHandler.cut();
136        } else if (id == R.id.select_action_menu_copy) {
137            mActionHandler.copy();
138            mode.finish();
139        } else if (id == R.id.select_action_menu_paste) {
140            mActionHandler.paste();
141        } else if (id == R.id.select_action_menu_share) {
142            if (!TextUtils.isEmpty(selection)) {
143                Intent send = new Intent(Intent.ACTION_SEND);
144                send.setType("text/plain");
145                send.putExtra(Intent.EXTRA_TEXT, selection);
146                try {
147                    Intent i = Intent.createChooser(send, getContext().getString(
148                            R.string.actionbar_share));
149                    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
150                    getContext().startActivity(i);
151                } catch (android.content.ActivityNotFoundException ex) {
152                    // If no app handles it, do nothing.
153                }
154            }
155            mode.finish();
156        } else if (id == R.id.select_action_menu_web_search) {
157            if (!TextUtils.isEmpty(selection)) {
158                Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
159                i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
160                i.putExtra(SearchManager.QUERY, selection);
161                i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
162                if (!(getContext() instanceof Activity)) {
163                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
164                }
165                try {
166                    getContext().startActivity(i);
167                } catch (android.content.ActivityNotFoundException ex) {
168                    // If no app handles it, do nothing.
169                }
170            }
171            mode.finish();
172        } else {
173            return false;
174        }
175        return true;
176    }
177
178    @Override
179    public void onDestroyActionMode(ActionMode mode) {
180        mActionHandler.onDestroyActionMode();
181    }
182
183    private boolean canPaste() {
184        ClipboardManager clipMgr = (ClipboardManager)
185                getContext().getSystemService(Context.CLIPBOARD_SERVICE);
186        return clipMgr.hasPrimaryClip();
187    }
188
189    private boolean isShareHandlerAvailable() {
190        Intent intent = new Intent(Intent.ACTION_SEND);
191        intent.setType("text/plain");
192        return getContext().getPackageManager()
193                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
194    }
195
196    private boolean isWebSearchAvailable() {
197        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
198        intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
199        return getContext().getPackageManager()
200                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
201    }
202}
203