WebViewContextMenu.java revision e307785b44deec1eb2aad5ecf83ea4b581779baa
1/* 2 * Copyright (C) 2011 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mail.browse; 19 20import android.app.Activity; 21import android.content.ClipData; 22import android.content.ClipboardManager; 23import android.content.Context; 24import android.content.Intent; 25import android.content.pm.PackageManager; 26import android.content.pm.ResolveInfo; 27import android.net.Uri; 28import android.provider.ContactsContract; 29import android.view.ContextMenu; 30import android.view.ContextMenu.ContextMenuInfo; 31import android.view.MenuInflater; 32import android.view.MenuItem; 33import android.view.View; 34import android.view.View.OnCreateContextMenuListener; 35import android.webkit.WebView; 36 37import com.android.mail.R; 38 39import java.io.UnsupportedEncodingException; 40import java.net.URLDecoder; 41import java.net.URLEncoder; 42import java.nio.charset.Charset; 43 44/** 45 * <p>Handles display and behavior of the context menu for known actionable content in WebViews. 46 * Requires an Activity to bind to for Context resolution and to start other activites.</p> 47 * <br> 48 * Dependencies: 49 * <ul> 50 * <li>res/menu/webview_context_menu.xml</li> 51 * </ul> 52 */ 53public class WebViewContextMenu implements OnCreateContextMenuListener, 54 MenuItem.OnMenuItemClickListener { 55 56 private final boolean mSupportsDial; 57 private final boolean mSupportsSms; 58 59 private Activity mActivity; 60 61 protected static enum MenuType { 62 OPEN_MENU, 63 COPY_LINK_MENU, 64 SHARE_LINK_MENU, 65 DIAL_MENU, 66 SMS_MENU, 67 ADD_CONTACT_MENU, 68 COPY_PHONE_MENU, 69 EMAIL_CONTACT_MENU, 70 COPY_MAIL_MENU, 71 MAP_MENU, 72 COPY_GEO_MENU, 73 } 74 75 public WebViewContextMenu(Activity host) { 76 mActivity = host; 77 78 // Query the package manager to see if the device 79 // has an app that supports ACTION_DIAL or ACTION_SENDTO 80 // with the appropriate uri schemes. 81 final PackageManager pm = mActivity.getPackageManager(); 82 mSupportsDial = !pm.queryIntentActivities( 83 new Intent(Intent.ACTION_DIAL, Uri.parse(WebView.SCHEME_TEL)), 84 PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); 85 mSupportsSms = !pm.queryIntentActivities( 86 new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:")), 87 PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); 88 } 89 90 // For our copy menu items. 91 private class Copy implements MenuItem.OnMenuItemClickListener { 92 private final CharSequence mText; 93 94 public Copy(CharSequence text) { 95 mText = text; 96 } 97 98 @Override 99 public boolean onMenuItemClick(MenuItem item) { 100 copy(mText); 101 return true; 102 } 103 } 104 105 // For our share menu items. 106 private class Share implements MenuItem.OnMenuItemClickListener { 107 private final String mUri; 108 109 public Share(String text) { 110 mUri = text; 111 } 112 113 @Override 114 public boolean onMenuItemClick(MenuItem item) { 115 shareLink(mUri); 116 return true; 117 } 118 } 119 120 private boolean showShareLinkMenuItem() { 121 PackageManager pm = mActivity.getPackageManager(); 122 Intent send = new Intent(Intent.ACTION_SEND); 123 send.setType("text/plain"); 124 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); 125 return ri != null; 126 } 127 128 private void shareLink(String url) { 129 Intent send = new Intent(Intent.ACTION_SEND); 130 send.setType("text/plain"); 131 send.putExtra(Intent.EXTRA_TEXT, url); 132 133 try { 134 mActivity.startActivity(Intent.createChooser(send, mActivity.getText( 135 getChooserTitleStringResIdForMenuType(MenuType.SHARE_LINK_MENU)))); 136 } catch(android.content.ActivityNotFoundException ex) { 137 // if no app handles it, do nothing 138 } 139 } 140 141 private void copy(CharSequence text) { 142 ClipboardManager clipboard = 143 (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE); 144 clipboard.setPrimaryClip(ClipData.newPlainText(null, text)); 145 } 146 147 @Override 148 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) { 149 // FIXME: This is copied over almost directly from BrowserActivity. 150 // Would like to find a way to combine the two (Bug 1251210). 151 152 WebView webview = (WebView) v; 153 WebView.HitTestResult result = webview.getHitTestResult(); 154 if (result == null) { 155 return; 156 } 157 158 int type = result.getType(); 159 switch (type) { 160 case WebView.HitTestResult.UNKNOWN_TYPE: 161 case WebView.HitTestResult.EDIT_TEXT_TYPE: 162 return; 163 default: 164 break; 165 } 166 167 // Note, http://b/issue?id=1106666 is requesting that 168 // an inflated menu can be used again. This is not available 169 // yet, so inflate each time (yuk!) 170 MenuInflater inflater = mActivity.getMenuInflater(); 171 // Also, we are copying the menu file from browser until 172 // 1251210 is fixed. 173 inflater.inflate(getMenuResourceId(), menu); 174 175 // Initially make set the menu item handler this WebViewContextMenu, which will default to 176 // calling the non-abstract subclass's implementation. 177 for (int i = 0; i < menu.size(); i++) { 178 final MenuItem menuItem = menu.getItem(i); 179 menuItem.setOnMenuItemClickListener(this); 180 } 181 182 183 // Show the correct menu group 184 String extra = result.getExtra(); 185 menu.setGroupVisible(R.id.PHONE_MENU, type == WebView.HitTestResult.PHONE_TYPE); 186 menu.setGroupVisible(R.id.EMAIL_MENU, type == WebView.HitTestResult.EMAIL_TYPE); 187 menu.setGroupVisible(R.id.GEO_MENU, type == WebView.HitTestResult.GEO_TYPE); 188 menu.setGroupVisible(R.id.ANCHOR_MENU, type == WebView.HitTestResult.SRC_ANCHOR_TYPE 189 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 190 menu.setGroupVisible(R.id.IMAGE_MENU, type == WebView.HitTestResult.IMAGE_TYPE 191 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 192 193 // Setup custom handling depending on the type 194 switch (type) { 195 case WebView.HitTestResult.PHONE_TYPE: 196 String decodedPhoneExtra; 197 try { 198 decodedPhoneExtra = URLDecoder.decode(extra, Charset.defaultCharset().name()); 199 } 200 catch (UnsupportedEncodingException ignore) { 201 // Should never happen; default charset is UTF-8 202 decodedPhoneExtra = extra; 203 } 204 205 menu.setHeaderTitle(decodedPhoneExtra); 206 // Dial 207 final MenuItem dialMenuItem = 208 menu.findItem(getMenuResIdForMenuType(MenuType.DIAL_MENU)); 209 210 if (mSupportsDial) { 211 // remove the on click listener 212 dialMenuItem.setOnMenuItemClickListener(null); 213 dialMenuItem.setIntent(new Intent(Intent.ACTION_DIAL, 214 Uri.parse(WebView.SCHEME_TEL + extra))); 215 } else { 216 dialMenuItem.setVisible(false); 217 } 218 219 // Send SMS 220 final MenuItem sendSmsMenuItem = 221 menu.findItem(getMenuResIdForMenuType(MenuType.SMS_MENU)); 222 if (mSupportsSms) { 223 // remove the on click listener 224 sendSmsMenuItem.setOnMenuItemClickListener(null); 225 sendSmsMenuItem.setIntent(new Intent(Intent.ACTION_SENDTO, 226 Uri.parse("smsto:" + extra))); 227 } else { 228 sendSmsMenuItem.setVisible(false); 229 } 230 231 // Add to contacts 232 final Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 233 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); 234 235 addIntent.putExtra(ContactsContract.Intents.Insert.PHONE, decodedPhoneExtra); 236 final MenuItem addToContactsMenuItem = 237 menu.findItem(getMenuResIdForMenuType(MenuType.ADD_CONTACT_MENU)); 238 // remove the on click listener 239 addToContactsMenuItem.setOnMenuItemClickListener(null); 240 addToContactsMenuItem.setIntent(addIntent); 241 242 // Copy 243 menu.findItem(getMenuResIdForMenuType(MenuType.COPY_PHONE_MENU)). 244 setOnMenuItemClickListener(new Copy(extra)); 245 break; 246 247 case WebView.HitTestResult.EMAIL_TYPE: 248 menu.setHeaderTitle(extra); 249 menu.findItem(getMenuResIdForMenuType(MenuType.EMAIL_CONTACT_MENU)).setIntent( 250 new Intent(Intent.ACTION_VIEW, Uri 251 .parse(WebView.SCHEME_MAILTO + extra))); 252 menu.findItem(getMenuResIdForMenuType(MenuType.COPY_MAIL_MENU)). 253 setOnMenuItemClickListener(new Copy(extra)); 254 break; 255 256 case WebView.HitTestResult.GEO_TYPE: 257 menu.setHeaderTitle(extra); 258 String geoExtra = ""; 259 try { 260 geoExtra = URLEncoder.encode(extra, Charset.defaultCharset().name()); 261 } 262 catch (UnsupportedEncodingException ignore) { 263 // Should never happen; default charset is UTF-8 264 } 265 final MenuItem viewMapMenuItem = 266 menu.findItem(getMenuResIdForMenuType(MenuType.MAP_MENU)); 267 // remove the on click listener 268 viewMapMenuItem.setOnMenuItemClickListener(null); 269 viewMapMenuItem.setIntent(new Intent(Intent.ACTION_VIEW, 270 Uri.parse(WebView.SCHEME_GEO + geoExtra))); 271 menu.findItem(getMenuResIdForMenuType(MenuType.COPY_GEO_MENU)). 272 setOnMenuItemClickListener(new Copy(extra)); 273 break; 274 275 case WebView.HitTestResult.SRC_ANCHOR_TYPE: 276 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: 277 menu.findItem(getMenuResIdForMenuType(MenuType.SHARE_LINK_MENU)).setVisible( 278 showShareLinkMenuItem()); 279 280 // The documentation for WebView indicates that if the HitTestResult is 281 // SRC_ANCHOR_TYPE or the url would be specified in the extra. We don't need to 282 // call requestFocusNodeHref(). If we wanted to handle UNKNOWN HitTestResults, we 283 // would. With this knowledge, we can just set the title 284 menu.setHeaderTitle(extra); 285 286 menu.findItem(getMenuResIdForMenuType(MenuType.COPY_LINK_MENU)). 287 setOnMenuItemClickListener(new Copy(extra)); 288 289 final MenuItem openLinkMenuItem = 290 menu.findItem(getMenuResIdForMenuType(MenuType.OPEN_MENU)); 291 // remove the on click listener 292 openLinkMenuItem.setOnMenuItemClickListener(null); 293 openLinkMenuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(extra))); 294 295 menu.findItem(getMenuResIdForMenuType(MenuType.SHARE_LINK_MENU)). 296 setOnMenuItemClickListener(new Share(extra)); 297 break; 298 case WebView.HitTestResult.IMAGE_TYPE: 299 default: 300 break; 301 } 302 } 303 304 @Override 305 public boolean onMenuItemClick(MenuItem item) { 306 return onMenuItemSelected(item); 307 } 308 309 /** 310 * Returns the menu resource id for the specified menu type 311 * @param menuType type of the specified menu 312 * @return menu resource id 313 */ 314 protected int getMenuResIdForMenuType(MenuType menuType) { 315 switch(menuType) { 316 case OPEN_MENU: 317 return R.id.open_context_menu_id; 318 case COPY_LINK_MENU: 319 return R.id.copy_link_context_menu_id; 320 case SHARE_LINK_MENU: 321 return R.id.share_link_context_menu_id; 322 case DIAL_MENU: 323 return R.id.dial_context_menu_id; 324 case SMS_MENU: 325 return R.id.sms_context_menu_id; 326 case ADD_CONTACT_MENU: 327 return R.id.add_contact_context_menu_id; 328 case COPY_PHONE_MENU: 329 return R.id.copy_phone_context_menu_id; 330 case EMAIL_CONTACT_MENU: 331 return R.id.email_context_menu_id; 332 case COPY_MAIL_MENU: 333 return R.id.copy_mail_context_menu_id; 334 case MAP_MENU: 335 return R.id.map_context_menu_id; 336 case COPY_GEO_MENU: 337 return R.id.copy_geo_context_menu_id; 338 default: 339 throw new IllegalStateException("Unexpected MenuType"); 340 } 341 } 342 343 /** 344 * Returns the resource id of the string to be used when showing a chooser for a menu 345 * @param menuType type of the specified menu 346 * @return string resource id 347 */ 348 protected int getChooserTitleStringResIdForMenuType(MenuType menuType) { 349 switch(menuType) { 350 case SHARE_LINK_MENU: 351 return R.string.choosertitle_sharevia; 352 default: 353 throw new IllegalStateException("Unexpected MenuType"); 354 } 355 } 356 357 /** 358 * Returns the resource id for the web view context menu 359 */ 360 protected int getMenuResourceId() { 361 return R.menu.webview_context_menu; 362 } 363 364 365 /** 366 * Called when a menu item is not handled by the context menu. 367 */ 368 protected boolean onMenuItemSelected(MenuItem menuItem) { 369 return mActivity.onOptionsItemSelected(menuItem); 370 } 371} 372