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 | |
5 | package org.chromium.content.browser; |
6 | |
7 | import android.app.SearchManager; |
8 | import android.content.ClipboardManager; |
9 | import android.content.Context; |
10 | import android.content.Intent; |
11 | import android.content.pm.PackageManager; |
12 | import android.content.res.TypedArray; |
13 | import android.provider.Browser; |
14 | import android.text.TextUtils; |
15 | import android.view.ActionMode; |
16 | import android.view.Menu; |
17 | import android.view.MenuItem; |
18 | |
19 | import 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 | */ |
25 | public 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.setTitle(null); |
108 | mode.setSubtitle(null); |
109 | mEditable = mActionHandler.isSelectionEditable(); |
110 | createActionMenu(mode, menu); |
111 | return true; |
112 | } |
113 | |
114 | @Override |
115 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
116 | boolean isEditableNow = mActionHandler.isSelectionEditable(); |
117 | if (mEditable != isEditableNow) { |
118 | mEditable = isEditableNow; |
119 | menu.clear(); |
120 | createActionMenu(mode, menu); |
121 | return true; |
122 | } |
123 | return false; |
124 | } |
125 | |
126 | private void createActionMenu(ActionMode mode, Menu menu) { |
127 | TypedArray styledAttributes = getContext().obtainStyledAttributes(ACTION_MODE_ATTRS); |
128 | |
129 | menu.add(Menu.NONE, ID_SELECTALL, Menu.NONE, android.R.string.selectAll). |
130 | setAlphabeticShortcut('a'). |
131 | setIcon(styledAttributes.getResourceId(SELECT_ALL_ATTR_INDEX, 0)). |
132 | setShowAsAction( |
133 | MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
134 | |
135 | if (mEditable) { |
136 | menu.add(Menu.NONE, ID_CUT, Menu.NONE, android.R.string.cut). |
137 | setIcon(styledAttributes.getResourceId(CUT_ATTR_INDEX, 0)). |
138 | setAlphabeticShortcut('x'). |
139 | setShowAsAction( |
140 | MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
141 | } |
142 | |
143 | menu.add(Menu.NONE, ID_COPY, Menu.NONE, android.R.string.copy). |
144 | setIcon(styledAttributes.getResourceId(COPY_ATTR_INDEX, 0)). |
145 | setAlphabeticShortcut('c'). |
146 | setShowAsAction( |
147 | MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
148 | |
149 | if (mEditable && canPaste()) { |
150 | menu.add(Menu.NONE, ID_PASTE, Menu.NONE, android.R.string.paste). |
151 | setIcon(styledAttributes.getResourceId(PASTE_ATTR_INDEX, 0)). |
152 | setAlphabeticShortcut('v'). |
153 | setShowAsAction( |
154 | MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
155 | } |
156 | |
157 | if (!mEditable) { |
158 | if (isShareHandlerAvailable()) { |
159 | menu.add(Menu.NONE, ID_SHARE, Menu.NONE, R.string.actionbar_share). |
160 | setIcon(R.drawable.ic_menu_share_holo_light). |
161 | setShowAsAction( |
162 | MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
163 | } |
164 | |
165 | if (!mIncognito && isWebSearchAvailable()) { |
166 | menu.add(Menu.NONE, ID_SEARCH, Menu.NONE, R.string.actionbar_web_search). |
167 | setIcon(R.drawable.ic_menu_search_holo_light). |
168 | setShowAsAction( |
169 | MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
170 | } |
171 | } |
172 | |
173 | styledAttributes.recycle(); |
174 | } |
175 | |
176 | @Override |
177 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
178 | String selection = mActionHandler.getSelectedText(); |
179 | switch(item.getItemId()) { |
180 | case ID_SELECTALL: |
181 | mActionHandler.selectAll(); |
182 | break; |
183 | case ID_CUT: |
184 | mActionHandler.cut(); |
185 | break; |
186 | case ID_COPY: |
187 | mActionHandler.copy(); |
188 | mode.finish(); |
189 | break; |
190 | case ID_PASTE: |
191 | mActionHandler.paste(); |
192 | break; |
193 | case ID_SHARE: |
194 | if (!TextUtils.isEmpty(selection)) { |
195 | Intent send = new Intent(Intent.ACTION_SEND); |
196 | send.setType("text/plain"); |
197 | send.putExtra(Intent.EXTRA_TEXT, selection); |
198 | try { |
199 | Intent i = Intent.createChooser(send, getContext().getString( |
200 | R.string.actionbar_share)); |
201 | i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
202 | getContext().startActivity(i); |
203 | } catch (android.content.ActivityNotFoundException ex) { |
204 | // If no app handles it, do nothing. |
205 | } |
206 | } |
207 | mode.finish(); |
208 | break; |
209 | case ID_SEARCH: |
210 | if (!TextUtils.isEmpty(selection)) { |
211 | Intent i = new Intent(Intent.ACTION_WEB_SEARCH); |
212 | i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); |
213 | i.putExtra(SearchManager.QUERY, selection); |
214 | i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName()); |
215 | try { |
216 | getContext().startActivity(i); |
217 | } catch (android.content.ActivityNotFoundException ex) { |
218 | // If no app handles it, do nothing. |
219 | } |
220 | } |
221 | mode.finish(); |
222 | break; |
223 | default: |
224 | return false; |
225 | } |
226 | return true; |
227 | } |
228 | |
229 | @Override |
230 | public void onDestroyActionMode(ActionMode mode) { |
231 | mActionHandler.onDestroyActionMode(); |
232 | } |
233 | |
234 | private boolean canPaste() { |
235 | ClipboardManager clipMgr = (ClipboardManager) |
236 | getContext().getSystemService(Context.CLIPBOARD_SERVICE); |
237 | return clipMgr.hasPrimaryClip(); |
238 | } |
239 | |
240 | private boolean isShareHandlerAvailable() { |
241 | Intent intent = new Intent(Intent.ACTION_SEND); |
242 | intent.setType("text/plain"); |
243 | return getContext().getPackageManager() |
244 | .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; |
245 | } |
246 | |
247 | private boolean isWebSearchAvailable() { |
248 | Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); |
249 | intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); |
250 | return getContext().getPackageManager() |
251 | .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; |
252 | } |
253 | } |