diff --git a/res/values/strings.xml b/res/values/strings.xml
index c1cfb2b26..0851d3823 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1141,4 +1141,29 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
the import operation. Please install a file manager application from Android Market
Open Market
Close
+
+ Open for viewing
+ Share link
+ Copy link to clipboard
+ Link
+
+ Image
+ View image
+ Save image
+ Download image
+ Copy image URL to clipboard
+ Image URL
+
+ Call number
+ Save to Contacts
+ Copy phone number to clipboard
+ Phone number
+
+ Send mail
+ Save to Contacts
+ Copy email address to clipboard
+ Email address
+
+ Saved image as \"%s\"
+ Saving the image failed.
diff --git a/src/com/fsck/k9/helper/Contacts.java b/src/com/fsck/k9/helper/Contacts.java
index 7be312c22..c7beb05fd 100644
--- a/src/com/fsck/k9/helper/Contacts.java
+++ b/src/com/fsck/k9/helper/Contacts.java
@@ -104,6 +104,14 @@ public abstract class Contacts {
*/
public abstract void createContact(Address email);
+ /**
+ * Start the activity to add a phone number to an existing contact or add a new one.
+ *
+ * @param phoneNumber
+ * The phone number to add to a contact, or to use when creating a new contact.
+ */
+ public abstract void addPhoneContact(String phoneNumber);
+
/**
* Check whether the provided email address belongs to one of the contacts.
*
diff --git a/src/com/fsck/k9/helper/ContactsSdk5.java b/src/com/fsck/k9/helper/ContactsSdk5.java
index 83b21b567..74c19193a 100644
--- a/src/com/fsck/k9/helper/ContactsSdk5.java
+++ b/src/com/fsck/k9/helper/ContactsSdk5.java
@@ -9,6 +9,8 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.Intents.Insert;
+
import com.fsck.k9.mail.Address;
import com.fsck.k9.K9;
@@ -85,6 +87,15 @@ public class ContactsSdk5 extends com.fsck.k9.helper.Contacts {
mContext.startActivity(contactIntent);
}
+ @Override
+ public void addPhoneContact(final String phoneNumber) {
+ Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
+ addIntent.putExtra(Insert.PHONE, Uri.decode(phoneNumber));
+ addIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(addIntent);
+ }
+
@Override
public boolean isInContacts(final String emailAddress) {
boolean result = false;
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index a19796d8c..3dc1c92a1 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -7,16 +7,26 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
-import android.view.View.OnClickListener;
+import android.view.View.*;
+import android.webkit.WebView;
+import android.webkit.WebView.HitTestResult;
import android.widget.Button;
import android.widget.LinearLayout;
+import android.widget.Toast;
+
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
@@ -25,16 +35,51 @@ import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.crypto.PgpData;
+import com.fsck.k9.helper.ClipboardManager;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
+import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
+import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
import java.util.List;
public class SingleMessageView extends LinearLayout implements OnClickListener,
- MessageHeader.OnLayoutChangedListener {
+ MessageHeader.OnLayoutChangedListener, OnCreateContextMenuListener {
+ private static final int MENU_ITEM_LINK_VIEW = Menu.FIRST;
+ private static final int MENU_ITEM_LINK_SHARE = Menu.FIRST + 1;
+ private static final int MENU_ITEM_LINK_COPY = Menu.FIRST + 2;
+
+ private static final int MENU_ITEM_IMAGE_VIEW = Menu.FIRST;
+ private static final int MENU_ITEM_IMAGE_SAVE = Menu.FIRST + 1;
+ private static final int MENU_ITEM_IMAGE_COPY = Menu.FIRST + 2;
+
+ private static final int MENU_ITEM_PHONE_CALL = Menu.FIRST;
+ private static final int MENU_ITEM_PHONE_SAVE = Menu.FIRST + 1;
+ private static final int MENU_ITEM_PHONE_COPY = Menu.FIRST + 2;
+
+ private static final int MENU_ITEM_EMAIL_SEND = Menu.FIRST;
+ private static final int MENU_ITEM_EMAIL_SAVE = Menu.FIRST + 1;
+ private static final int MENU_ITEM_EMAIL_COPY = Menu.FIRST + 2;
+
+ private static final String[] ATTACHMENT_PROJECTION = new String[] {
+ AttachmentProviderColumns._ID,
+ AttachmentProviderColumns.DISPLAY_NAME
+ };
+ private static final int DISPLAY_NAME_INDEX = 1;
+
+
private boolean mScreenReaderEnabled;
private MessageCryptoView mCryptoView;
private MessageWebView mMessageContentView;
@@ -57,11 +102,15 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
private View mAttachmentsContainer;
private LinearLayout mInsideAttachmentsContainer;
private SavedState mSavedState;
+ private ClipboardManager mClipboardManager;
+
public void initialize(Activity activity) {
mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
mMessageContentView.configure();
+ activity.registerForContextMenu(mMessageContentView);
+ mMessageContentView.setOnCreateContextMenuListener(this);
mHeaderPlaceHolder = (LinearLayout) findViewById(R.id.message_view_header_container);
@@ -114,6 +163,210 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
mShowMessageAction.setOnClickListener(this);
mShowAttachmentsAction.setOnClickListener(this);
mShowPicturesAction.setOnClickListener(this);
+
+ mClipboardManager = ClipboardManager.getInstance(activity);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu);
+
+ WebView webview = (WebView) v;
+ WebView.HitTestResult result = webview.getHitTestResult();
+ int type = result.getType();
+ Context context = getContext();
+
+ switch (type) {
+ case HitTestResult.SRC_ANCHOR_TYPE: {
+ final String url = result.getExtra();
+ OnMenuItemClickListener listener = new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ITEM_LINK_VIEW: {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ getContext().startActivity(intent);
+ break;
+ }
+ case MENU_ITEM_LINK_SHARE: {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TEXT, url);
+ getContext().startActivity(intent);
+ break;
+ }
+ case MENU_ITEM_LINK_COPY: {
+ String label = getContext().getString(
+ R.string.webview_contextmenu_link_clipboard_label);
+ mClipboardManager.setText(label, url);
+ break;
+ }
+ }
+ return true;
+ }
+ };
+
+ menu.setHeaderTitle(url);
+
+ menu.add(Menu.NONE, MENU_ITEM_LINK_VIEW, 0,
+ context.getString(R.string.webview_contextmenu_link_view_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_LINK_SHARE, 1,
+ context.getString(R.string.webview_contextmenu_link_share_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_LINK_COPY, 2,
+ context.getString(R.string.webview_contextmenu_link_copy_action))
+ .setOnMenuItemClickListener(listener);
+
+ break;
+ }
+ case HitTestResult.IMAGE_TYPE:
+ case HitTestResult.SRC_IMAGE_ANCHOR_TYPE: {
+ final String url = result.getExtra();
+ final boolean externalImage = url.startsWith("http");
+ OnMenuItemClickListener listener = new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ITEM_IMAGE_VIEW: {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ if (!externalImage) {
+ // Grant read permission if this points to our
+ // AttachmentProvider
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ getContext().startActivity(intent);
+ break;
+ }
+ case MENU_ITEM_IMAGE_SAVE: {
+ new DownloadImageTask().execute(url);
+ break;
+ }
+ case MENU_ITEM_IMAGE_COPY: {
+ String label = getContext().getString(
+ R.string.webview_contextmenu_image_clipboard_label);
+ mClipboardManager.setText(label, url);
+ break;
+ }
+ }
+ return true;
+ }
+ };
+
+ menu.setHeaderTitle((externalImage) ?
+ url : context.getString(R.string.webview_contextmenu_image_title));
+
+ menu.add(Menu.NONE, MENU_ITEM_IMAGE_VIEW, 0,
+ context.getString(R.string.webview_contextmenu_image_view_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_IMAGE_SAVE, 1,
+ (externalImage) ?
+ context.getString(R.string.webview_contextmenu_image_download_action) :
+ context.getString(R.string.webview_contextmenu_image_save_action))
+ .setOnMenuItemClickListener(listener);
+
+ if (externalImage) {
+ menu.add(Menu.NONE, MENU_ITEM_IMAGE_COPY, 2,
+ context.getString(R.string.webview_contextmenu_image_copy_action))
+ .setOnMenuItemClickListener(listener);
+ }
+
+ break;
+ }
+ case HitTestResult.PHONE_TYPE: {
+ final String phoneNumber = result.getExtra();
+ OnMenuItemClickListener listener = new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ITEM_PHONE_CALL: {
+ Uri uri = Uri.parse(WebView.SCHEME_TEL + phoneNumber);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ getContext().startActivity(intent);
+ break;
+ }
+ case MENU_ITEM_PHONE_SAVE: {
+ Contacts contacts = Contacts.getInstance(getContext());
+ contacts.addPhoneContact(phoneNumber);
+ break;
+ }
+ case MENU_ITEM_PHONE_COPY: {
+ String label = getContext().getString(
+ R.string.webview_contextmenu_phone_clipboard_label);
+ mClipboardManager.setText(label, phoneNumber);
+ break;
+ }
+ }
+
+ return true;
+ }
+ };
+
+ menu.setHeaderTitle(phoneNumber);
+
+ menu.add(Menu.NONE, MENU_ITEM_PHONE_CALL, 0,
+ context.getString(R.string.webview_contextmenu_phone_call_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_PHONE_SAVE, 1,
+ context.getString(R.string.webview_contextmenu_phone_save_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_PHONE_COPY, 2,
+ context.getString(R.string.webview_contextmenu_phone_copy_action))
+ .setOnMenuItemClickListener(listener);
+
+ break;
+ }
+ case WebView.HitTestResult.EMAIL_TYPE: {
+ final String email = result.getExtra();
+ OnMenuItemClickListener listener = new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ITEM_EMAIL_SEND: {
+ Uri uri = Uri.parse(WebView.SCHEME_MAILTO + email);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ getContext().startActivity(intent);
+ break;
+ }
+ case MENU_ITEM_EMAIL_SAVE: {
+ Contacts contacts = Contacts.getInstance(getContext());
+ contacts.createContact(new Address(email));
+ break;
+ }
+ case MENU_ITEM_EMAIL_COPY: {
+ String label = getContext().getString(
+ R.string.webview_contextmenu_email_clipboard_label);
+ mClipboardManager.setText(label, email);
+ break;
+ }
+ }
+
+ return true;
+ }
+ };
+
+ menu.setHeaderTitle(email);
+
+ menu.add(Menu.NONE, MENU_ITEM_EMAIL_SEND, 0,
+ context.getString(R.string.webview_contextmenu_email_send_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_EMAIL_SAVE, 1,
+ context.getString(R.string.webview_contextmenu_email_save_action))
+ .setOnMenuItemClickListener(listener);
+
+ menu.add(Menu.NONE, MENU_ITEM_EMAIL_COPY, 2,
+ context.getString(R.string.webview_contextmenu_email_copy_action))
+ .setOnMenuItemClickListener(listener);
+
+ break;
+ }
+ }
}
@Override
@@ -555,4 +808,117 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
out.writeInt((this.showPictures) ? 1 : 0);
}
}
+
+ class DownloadImageTask extends AsyncTask {
+ @Override
+ protected String doInBackground(String... params) {
+ String urlString = params[0];
+ try {
+ boolean externalImage = urlString.startsWith("http");
+
+ String filename = null;
+ String mimeType = null;
+ InputStream in = null;
+
+ try {
+ if (externalImage) {
+ URL url = new URL(urlString);
+ URLConnection conn = url.openConnection();
+ in = conn.getInputStream();
+
+ String path = url.getPath();
+
+ // Try to get the filename from the URL
+ int start = path.lastIndexOf("/");
+ if (start != -1 && start + 1 < path.length()) {
+ filename = URLDecoder.decode(path.substring(start + 1), "UTF-8");
+ } else {
+ // Use a dummy filename if necessary
+ filename = "saved_image";
+ }
+
+ // Get the MIME type if we couldn't find a file extension
+ if (filename.indexOf('.') == -1) {
+ mimeType = conn.getContentType();
+ }
+ } else {
+ ContentResolver contentResolver = getContext().getContentResolver();
+ Uri uri = Uri.parse(urlString);
+
+ // Get the filename from AttachmentProvider
+ Cursor cursor = contentResolver.query(uri, ATTACHMENT_PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ filename = cursor.getString(DISPLAY_NAME_INDEX);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ // Use a dummy filename if necessary
+ if (filename == null) {
+ filename = "saved_image";
+ }
+
+ // Get the MIME type if we couldn't find a file extension
+ if (filename.indexOf('.') == -1) {
+ mimeType = contentResolver.getType(uri);
+ }
+
+ in = contentResolver.openInputStream(uri);
+ }
+
+ // Do we still need an extension?
+ if (filename.indexOf('.') == -1) {
+ // Use JPEG as fallback
+ String extension = "jpeg";
+ if (mimeType != null) {
+ // Try to find an extension for the given MIME type
+ String ext = MimeUtility.getExtensionByMimeType(mimeType);
+ if (ext != null) {
+ extension = ext;
+ }
+ }
+ filename += "." + extension;
+ }
+
+ String sanitized = Utility.sanitizeFilename(filename);
+
+ File directory = new File(K9.getAttachmentDefaultPath());
+ File file = Utility.createUniqueFile(directory, sanitized);
+ FileOutputStream out = new FileOutputStream(file);
+ try {
+ IOUtils.copy(in, out);
+ out.flush();
+ } finally {
+ out.close();
+ }
+
+ return file.getName();
+
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(String filename) {
+ String text;
+ if (filename == null) {
+ text = getContext().getString(R.string.image_saving_failed);
+ } else {
+ text = getContext().getString(R.string.image_saved_as, filename);
+ }
+
+ Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
+ }
+ }
}