/* * Copyright (C) 2011 Google Inc. * Licensed to The Android Open Source Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mailcommon; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.provider.ContactsContract; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.webkit.WebView; import android.widget.TextView; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.Charset; /** * <p>Handles display and behavior of the context menu for known actionable content in WebViews. * Requires an Activity to bind to for Context resolution and to start other activites.</p> * <br> * <p>Activity/Fragment clients must forward the 'onContextItemSelected' method.</p> * <br> * Dependencies: * <ul> * <li>res/menu/webview_context_menu.xml</li> * <li>res/values/webview_context_menu_strings.xml</li> * </ul> */ public abstract class WebViewContextMenu implements OnCreateContextMenuListener, MenuItem.OnMenuItemClickListener { private Activity mActivity; protected static enum MenuType { OPEN_MENU, COPY_LINK_MENU, SHARE_LINK_MENU, DIAL_MENU, SMS_MENU, ADD_CONTACT_MENU, COPY_PHONE_MENU, EMAIL_CONTACT_MENU, COPY_MAIL_MENU, MAP_MENU, COPY_GEO_MENU, } protected static enum MenuGroupType { PHONE_GROUP, EMAIL_GROUP, GEO_GROUP, ANCHOR_GROUP, } public WebViewContextMenu(Activity host) { this.mActivity = host; } // For our copy menu items. private class Copy implements MenuItem.OnMenuItemClickListener { private final CharSequence mText; public Copy(CharSequence text) { mText = text; } @Override public boolean onMenuItemClick(MenuItem item) { copy(mText); return true; } } // For our share menu items. private class Share implements MenuItem.OnMenuItemClickListener { private final String mUri; public Share(String text) { mUri = text; } @Override public boolean onMenuItemClick(MenuItem item) { shareLink(mUri); return true; } } private boolean showShareLinkMenuItem() { PackageManager pm = mActivity.getPackageManager(); Intent send = new Intent(Intent.ACTION_SEND); send.setType("text/plain"); ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY); return ri != null; } private void shareLink(String url) { Intent send = new Intent(Intent.ACTION_SEND); send.setType("text/plain"); send.putExtra(Intent.EXTRA_TEXT, url); try { mActivity.startActivity(Intent.createChooser(send, mActivity.getText( getChooserTitleStringResIdForMenuType(MenuType.SHARE_LINK_MENU)))); } catch(android.content.ActivityNotFoundException ex) { // if no app handles it, do nothing } } private void copy(CharSequence text) { ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE); clipboard.setPrimaryClip(ClipData.newPlainText(null, text)); } public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) { // FIXME: This is copied over almost directly from BrowserActivity. // Would like to find a way to combine the two (Bug 1251210). WebView webview = (WebView) v; WebView.HitTestResult result = webview.getHitTestResult(); if (result == null) { return; } int type = result.getType(); switch (type) { case WebView.HitTestResult.UNKNOWN_TYPE: case WebView.HitTestResult.EDIT_TEXT_TYPE: return; default: break; } // Note, http://b/issue?id=1106666 is requesting that // an inflated menu can be used again. This is not available // yet, so inflate each time (yuk!) MenuInflater inflater = mActivity.getMenuInflater(); // Also, we are copying the menu file from browser until // 1251210 is fixed. inflater.inflate(getMenuResourceId(), menu); // Initially make set the menu item handler this WebViewContextMenu, which will default to // calling the non-abstract subclass's implementation. for (int i = 0; i < menu.size(); i++) { final MenuItem menuItem = menu.getItem(i); menuItem.setOnMenuItemClickListener(this); } // Show the correct menu group String extra = result.getExtra(); menu.setGroupVisible(getMenuGroupResId(MenuGroupType.PHONE_GROUP), type == WebView.HitTestResult.PHONE_TYPE); menu.setGroupVisible(getMenuGroupResId(MenuGroupType.EMAIL_GROUP), type == WebView.HitTestResult.EMAIL_TYPE); menu.setGroupVisible(getMenuGroupResId(MenuGroupType.GEO_GROUP), type == WebView.HitTestResult.GEO_TYPE); menu.setGroupVisible(getMenuGroupResId(MenuGroupType.ANCHOR_GROUP), type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE); // Setup custom handling depending on the type switch (type) { case WebView.HitTestResult.PHONE_TYPE: String decodedPhoneExtra; try { decodedPhoneExtra = URLDecoder.decode(extra, Charset.defaultCharset().name()); } catch (UnsupportedEncodingException ignore) { // Should never happen; default charset is UTF-8 decodedPhoneExtra = extra; } menu.setHeaderTitle(decodedPhoneExtra); // Dial final MenuItem dialMenuItem = menu.findItem(getMenuResIdForMenuType(MenuType.DIAL_MENU)); // remove the on click listener dialMenuItem.setOnMenuItemClickListener(null); dialMenuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(WebView.SCHEME_TEL + extra))); // Send SMS final MenuItem sendSmsMenuItem = menu.findItem(getMenuResIdForMenuType(MenuType.SMS_MENU)); // remove the on click listener sendSmsMenuItem.setOnMenuItemClickListener(null); sendSmsMenuItem.setIntent(new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + extra))); // Add to contacts final Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT); addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); addIntent.putExtra(ContactsContract.Intents.Insert.PHONE, decodedPhoneExtra); final MenuItem addToContactsMenuItem = menu.findItem(getMenuResIdForMenuType(MenuType.ADD_CONTACT_MENU)); // remove the on click listener addToContactsMenuItem.setOnMenuItemClickListener(null); addToContactsMenuItem.setIntent(addIntent); // Copy menu.findItem(getMenuResIdForMenuType(MenuType.COPY_PHONE_MENU)). setOnMenuItemClickListener(new Copy(extra)); break; case WebView.HitTestResult.EMAIL_TYPE: menu.setHeaderTitle(extra); menu.findItem(getMenuResIdForMenuType(MenuType.EMAIL_CONTACT_MENU)).setIntent( new Intent(Intent.ACTION_VIEW, Uri .parse(WebView.SCHEME_MAILTO + extra))); menu.findItem(getMenuResIdForMenuType(MenuType.COPY_MAIL_MENU)). setOnMenuItemClickListener(new Copy(extra)); break; case WebView.HitTestResult.GEO_TYPE: menu.setHeaderTitle(extra); String geoExtra = ""; try { geoExtra = URLEncoder.encode(extra, Charset.defaultCharset().name()); } catch (UnsupportedEncodingException ignore) { // Should never happen; default charset is UTF-8 } final MenuItem viewMapMenuItem = menu.findItem(getMenuResIdForMenuType(MenuType.MAP_MENU)); // remove the on click listener viewMapMenuItem.setOnMenuItemClickListener(null); viewMapMenuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(WebView.SCHEME_GEO + geoExtra))); menu.findItem(getMenuResIdForMenuType(MenuType.COPY_GEO_MENU)). setOnMenuItemClickListener(new Copy(extra)); break; case WebView.HitTestResult.SRC_ANCHOR_TYPE: case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: // FIXME: Make this look like the normal menu header // We cannot use the normal menu header because we need to // edit the ContextMenu after it has been created. final TextView titleView = (TextView) LayoutInflater.from(mActivity) .inflate(getTitleViewLayoutResId(MenuType.SHARE_LINK_MENU), null); menu.setHeaderView(titleView); menu.findItem(getMenuResIdForMenuType(MenuType.SHARE_LINK_MENU)).setVisible( showShareLinkMenuItem()); // The documentation for WebView indicates that if the HitTestResult is // SRC_ANCHOR_TYPE or the url would be specified in the extra. We don't need to // call requestFocusNodeHref(). If we wanted to handle UNKNOWN HitTestResults, we // would. With this knowledge, we can just set the title titleView.setText(extra); menu.findItem(getMenuResIdForMenuType(MenuType.COPY_LINK_MENU)). setOnMenuItemClickListener(new Copy(extra)); final MenuItem openLinkMenuItem = menu.findItem(getMenuResIdForMenuType(MenuType.OPEN_MENU)); // remove the on click listener openLinkMenuItem.setOnMenuItemClickListener(null); openLinkMenuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(extra))); menu.findItem(getMenuResIdForMenuType(MenuType.SHARE_LINK_MENU)). setOnMenuItemClickListener(new Share(extra)); break; default: break; } } @Override public boolean onMenuItemClick(MenuItem item) { return onMenuItemSelected(item); } /** * Returns the menu type from the given resource id * @param menuResId resource id of the menu * @return MenuType for the specified menu resource id */ abstract protected MenuType getMenuTypeFromResId(int menuResId); /** * Returns the menu resource id for the specified menu type * @param menuType type of the specified menu * @return menu resource id */ abstract protected int getMenuResIdForMenuType(MenuType menuType); /** * Returns the resource id of the string to be used when showing a chooser for a menu * @param menuType type of the specified menu * @return string resource id */ abstract protected int getChooserTitleStringResIdForMenuType(MenuType menuType); /** * Returns the resource id of the layout to be used for the title of the specified menu * @param menuType type of the specified menu * @return layout resource id */ abstract protected int getTitleViewLayoutResId(MenuType menuType); /** * Returns the menu group resource id for the specified menu group type. * @param menuGroupType menu group type * @return menu group resource id */ abstract protected int getMenuGroupResId(MenuGroupType menuGroupType); /** * Returns the resource id for the web view context menu */ abstract protected int getMenuResourceId(); /** * Called when a menu item is not handled by the context menu. */ abstract protected boolean onMenuItemSelected(MenuItem menuItem); }