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