diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 1d0dba7f1..aa573b580 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -226,7 +226,7 @@ K-9 Mail — почтовый клиент для Android.
Вложения
Показать вложения
Извлечение вложений
- Отсутствует просмотрщик %s %s
+ Отсутствует просмотрщик %s
Загрузить полностью
Загрузка…
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index 6b5d83d56..d46dc09d7 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -283,33 +283,6 @@ public class K9 extends Application {
*/
private static boolean sDatabasesUpToDate = false;
-
- /**
- * The MIME type(s) of attachments we're willing to view.
- */
- public static final String[] ACCEPTABLE_ATTACHMENT_VIEW_TYPES = new String[] {
- "*/*",
- };
-
- /**
- * The MIME type(s) of attachments we're not willing to view.
- */
- public static final String[] UNACCEPTABLE_ATTACHMENT_VIEW_TYPES = new String[] {
- };
-
- /**
- * The MIME type(s) of attachments we're willing to download to SD.
- */
- public static final String[] ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
- "*/*",
- };
-
- /**
- * The MIME type(s) of attachments we're not willing to download to SD.
- */
- public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
- };
-
/**
* For use when displaying that no folder is selected
*/
diff --git a/src/com/fsck/k9/cache/TemporaryAttachmentStore.java b/src/com/fsck/k9/cache/TemporaryAttachmentStore.java
new file mode 100644
index 000000000..098e919e9
--- /dev/null
+++ b/src/com/fsck/k9/cache/TemporaryAttachmentStore.java
@@ -0,0 +1,65 @@
+package com.fsck.k9.cache;
+
+
+import java.io.File;
+import java.io.IOException;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.fsck.k9.K9;
+import com.fsck.k9.helper.FileHelper;
+
+
+public class TemporaryAttachmentStore {
+ private static String TEMPORARY_ATTACHMENT_DIRECTORY = "attachments";
+ private static long MAX_FILE_AGE = 12 * 60 * 60 * 1000; // 12h
+
+ public static File getFile(Context context, String attachmentName) {
+ File directory = getTemporaryAttachmentDirectory(context);
+ String filename = FileHelper.sanitizeFilename(attachmentName);
+ return new File(directory, filename);
+ }
+
+ public static File getFileForWriting(Context context, String attachmentName) throws IOException {
+ File directory = createOrCleanAttachmentDirectory(context);
+ String filename = FileHelper.sanitizeFilename(attachmentName);
+ return new File(directory, filename);
+ }
+
+ private static File createOrCleanAttachmentDirectory(Context context) throws IOException {
+ File directory = getTemporaryAttachmentDirectory(context);
+ if (directory.exists()) {
+ cleanOldFiles(directory);
+ } else {
+ if (!directory.mkdir()) {
+ throw new IOException("Couldn't create temporary attachment store: " + directory.getAbsolutePath());
+ }
+ }
+ return directory;
+ }
+
+ private static File getTemporaryAttachmentDirectory(Context context) {
+ return new File(context.getExternalCacheDir(), TEMPORARY_ATTACHMENT_DIRECTORY);
+ }
+
+ private static void cleanOldFiles(File directory) {
+ File[] files = directory.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ long cutOffTime = System.currentTimeMillis() - MAX_FILE_AGE;
+ for (File file : files) {
+ if (file.lastModified() < cutOffTime) {
+ if (file.delete()) {
+ if (K9.DEBUG) {
+ Log.d(K9.LOG_TAG, "Deleted from temporary attachment store: " + file.getName());
+ }
+ } else {
+ Log.w(K9.LOG_TAG, "Couldn't delete from temporary attachment store: " + file.getName());
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/fsck/k9/fragment/MessageViewFragment.java b/src/com/fsck/k9/fragment/MessageViewFragment.java
index ebecda26a..3a8bb4ddd 100644
--- a/src/com/fsck/k9/fragment/MessageViewFragment.java
+++ b/src/com/fsck/k9/fragment/MessageViewFragment.java
@@ -206,7 +206,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() {
@Override
- public void showFileBrowser(final AttachmentView caller) {
+ public void pickDirectoryToSaveAttachmentTo(final AttachmentView caller) {
FileBrowserHelper.getInstance()
.showFileBrowserActivity(MessageViewFragment.this,
null,
@@ -498,15 +498,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override
public void onClick(View view) {
- switch (view.getId()) {
- case R.id.download: {
- ((AttachmentView)view).saveFile();
- break;
- }
- case R.id.download_remainder: {
- onDownloadRemainder();
- break;
- }
+ if (view.getId() == R.id.download_remainder) {
+ onDownloadRemainder();
}
}
diff --git a/src/com/fsck/k9/helper/FileHelper.java b/src/com/fsck/k9/helper/FileHelper.java
new file mode 100644
index 000000000..e4f98c322
--- /dev/null
+++ b/src/com/fsck/k9/helper/FileHelper.java
@@ -0,0 +1,166 @@
+package com.fsck.k9.helper;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Locale;
+
+import android.util.Log;
+
+import com.fsck.k9.K9;
+
+
+public class FileHelper {
+
+ /**
+ * Regular expression that represents characters we won't allow in file names.
+ *
+ *
+ * Allowed are:
+ *
+ * - word characters (letters, digits, and underscores): {@code \w}
+ * - spaces: {@code " "}
+ * - special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
+ * {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `},
{
,
+ * }
, {@code ~}, {@code .}, {@code ,}
+ *
+ *
+ * @see #sanitizeFilename(String)
+ */
+ private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]";
+
+ /**
+ * Invalid characters in a file name are replaced by this character.
+ *
+ * @see #sanitizeFilename(String)
+ */
+ private static final String REPLACEMENT_CHARACTER = "_";
+
+
+ /**
+ * Creates a unique file in the given directory by appending a hyphen
+ * and a number to the given filename.
+ */
+ public static File createUniqueFile(File directory, String filename) {
+ File file = new File(directory, filename);
+ if (!file.exists()) {
+ return file;
+ }
+ // Get the extension of the file, if any.
+ int index = filename.lastIndexOf('.');
+ String format;
+ if (index != -1) {
+ String name = filename.substring(0, index);
+ String extension = filename.substring(index);
+ format = name + "-%d" + extension;
+ } else {
+ format = filename + "-%d";
+ }
+ for (int i = 2; i < Integer.MAX_VALUE; i++) {
+ file = new File(directory, String.format(Locale.US, format, i));
+ if (!file.exists()) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ public static void touchFile(final File parentDir, final String name) {
+ final File file = new File(parentDir, name);
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ } else {
+ file.setLastModified(System.currentTimeMillis());
+ }
+ } catch (Exception e) {
+ Log.d(K9.LOG_TAG, "Unable to touch file: " + file.getAbsolutePath(), e);
+ }
+ }
+
+ public static boolean move(final File from, final File to) {
+ if (to.exists()) {
+ to.delete();
+ }
+ to.getParentFile().mkdirs();
+
+ try {
+ FileInputStream in = new FileInputStream(from);
+ try {
+ FileOutputStream out = new FileOutputStream(to);
+ try {
+ byte[] buffer = new byte[1024];
+ int count = -1;
+ while ((count = in.read(buffer)) > 0) {
+ out.write(buffer, 0, count);
+ }
+ } finally {
+ out.close();
+ }
+ } finally {
+ try { in.close(); } catch (Throwable ignore) {}
+ }
+ from.delete();
+ return true;
+ } catch (Exception e) {
+ Log.w(K9.LOG_TAG, "cannot move " + from.getAbsolutePath() + " to " + to.getAbsolutePath(), e);
+ return false;
+ }
+
+ }
+
+ public static void moveRecursive(final File fromDir, final File toDir) {
+ if (!fromDir.exists()) {
+ return;
+ }
+ if (!fromDir.isDirectory()) {
+ if (toDir.exists()) {
+ if (!toDir.delete()) {
+ Log.w(K9.LOG_TAG, "cannot delete already existing file/directory " + toDir.getAbsolutePath());
+ }
+ }
+ if (!fromDir.renameTo(toDir)) {
+ Log.w(K9.LOG_TAG, "cannot rename " + fromDir.getAbsolutePath() + " to " + toDir.getAbsolutePath() + " - moving instead");
+ move(fromDir, toDir);
+ }
+ return;
+ }
+ if (!toDir.exists() || !toDir.isDirectory()) {
+ if (toDir.exists()) {
+ toDir.delete();
+ }
+ if (!toDir.mkdirs()) {
+ Log.w(K9.LOG_TAG, "cannot create directory " + toDir.getAbsolutePath());
+ }
+ }
+ File[] files = fromDir.listFiles();
+ for (File file : files) {
+ if (file.isDirectory()) {
+ moveRecursive(file, new File(toDir, file.getName()));
+ file.delete();
+ } else {
+ File target = new File(toDir, file.getName());
+ if (!file.renameTo(target)) {
+ Log.w(K9.LOG_TAG, "cannot rename " + file.getAbsolutePath() + " to " + target.getAbsolutePath() + " - moving instead");
+ move(file, target);
+ }
+ }
+ }
+ if (!fromDir.delete()) {
+ Log.w(K9.LOG_TAG, "cannot delete " + fromDir.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Replace characters we don't allow in file names with a replacement character.
+ *
+ * @param filename
+ * The original file name.
+ *
+ * @return The sanitized file name containing only allowed characters.
+ */
+ public static String sanitizeFilename(String filename) {
+ return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
+ }
+}
diff --git a/src/com/fsck/k9/helper/MediaScannerNotifier.java b/src/com/fsck/k9/helper/MediaScannerNotifier.java
index 8229b8297..9dbed11ce 100644
--- a/src/com/fsck/k9/helper/MediaScannerNotifier.java
+++ b/src/com/fsck/k9/helper/MediaScannerNotifier.java
@@ -1,40 +1,15 @@
package com.fsck.k9.helper;
-import android.content.Context;
-import android.content.Intent;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.net.Uri;
import java.io.File;
+import android.content.Context;
+import android.media.MediaScannerConnection;
-public class MediaScannerNotifier implements MediaScannerConnectionClient {
- private MediaScannerConnection mConnection;
- private File mFile;
- private Context mContext;
- public MediaScannerNotifier(Context context, File file) {
- mFile = file;
- mConnection = new MediaScannerConnection(context, this);
- mConnection.connect();
- mContext = context;
-
- }
-
- public void onMediaScannerConnected() {
- mConnection.scanFile(mFile.getAbsolutePath(), null);
- }
-
- public void onScanCompleted(String path, Uri uri) {
- try {
- if (uri != null) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(uri);
- mContext.startActivity(intent);
- }
- } finally {
- mConnection.disconnect();
- }
+public class MediaScannerNotifier {
+ public static void notify(Context context, File file) {
+ String[] paths = { file.getAbsolutePath() };
+ MediaScannerConnection.scanFile(context, paths, null, null);
}
}
diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java
index 370920d59..50ca99e3a 100644
--- a/src/com/fsck/k9/helper/Utility.java
+++ b/src/com/fsck/k9/helper/Utility.java
@@ -17,43 +17,13 @@ import android.widget.TextView;
import com.fsck.k9.K9;
import com.fsck.k9.mail.filter.Base64;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Utility {
- /**
- * Regular expression that represents characters we won't allow in file names.
- *
- *
- * Allowed are:
- *
- * - word characters (letters, digits, and underscores): {@code \w}
- * - spaces: {@code " "}
- * - special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
- * {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `},
{
,
- * }
, {@code ~}, {@code .}, {@code ,}
- *
- *
- * @see #sanitizeFilename(String)
- */
- private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]+";
-
- /**
- * Invalid characters in a file name are replaced by this character.
- *
- * @see #sanitizeFilename(String)
- */
- private static final String REPLACEMENT_CHARACTER = "_";
// \u00A0 (non-breaking space) happens to be used by French MUA
@@ -471,139 +441,6 @@ public class Utility {
}
}
- /**
- * @param parentDir
- * @param name
- * Never null
.
- */
- public static void touchFile(final File parentDir, final String name) {
- final File file = new File(parentDir, name);
- try {
- if (!file.exists()) {
- file.createNewFile();
- } else {
- file.setLastModified(System.currentTimeMillis());
- }
- } catch (Exception e) {
- Log.d(K9.LOG_TAG, "Unable to touch file: " + file.getAbsolutePath(), e);
- }
- }
-
- /**
- * Creates a unique file in the given directory by appending a hyphen
- * and a number to the given filename.
- *
- * @param directory
- * @param filename
- * @return
- */
- public static File createUniqueFile(File directory, String filename) {
- File file = new File(directory, filename);
- if (!file.exists()) {
- return file;
- }
- // Get the extension of the file, if any.
- int index = filename.lastIndexOf('.');
- String format;
- if (index != -1) {
- String name = filename.substring(0, index);
- String extension = filename.substring(index);
- format = name + "-%d" + extension;
- } else {
- format = filename + "-%d";
- }
- for (int i = 2; i < Integer.MAX_VALUE; i++) {
- file = new File(directory, String.format(Locale.US, format, i));
- if (!file.exists()) {
- return file;
- }
- }
- return null;
- }
-
-
-
- /**
- * @param from
- * @param to
- * @return
- */
- public static boolean move(final File from, final File to) {
- if (to.exists()) {
- to.delete();
- }
- to.getParentFile().mkdirs();
-
- try {
- FileInputStream in = new FileInputStream(from);
- try {
- FileOutputStream out = new FileOutputStream(to);
- try {
- byte[] buffer = new byte[1024];
- int count = -1;
- while ((count = in.read(buffer)) > 0) {
- out.write(buffer, 0, count);
- }
- } finally {
- out.close();
- }
- } finally {
- try { in.close(); } catch (Throwable ignore) {}
- }
- from.delete();
- return true;
- } catch (Exception e) {
- Log.w(K9.LOG_TAG, "cannot move " + from.getAbsolutePath() + " to " + to.getAbsolutePath(), e);
- return false;
- }
-
- }
-
- /**
- * @param fromDir
- * @param toDir
- */
- public static void moveRecursive(final File fromDir, final File toDir) {
- if (!fromDir.exists()) {
- return;
- }
- if (!fromDir.isDirectory()) {
- if (toDir.exists()) {
- if (!toDir.delete()) {
- Log.w(K9.LOG_TAG, "cannot delete already existing file/directory " + toDir.getAbsolutePath());
- }
- }
- if (!fromDir.renameTo(toDir)) {
- Log.w(K9.LOG_TAG, "cannot rename " + fromDir.getAbsolutePath() + " to " + toDir.getAbsolutePath() + " - moving instead");
- move(fromDir, toDir);
- }
- return;
- }
- if (!toDir.exists() || !toDir.isDirectory()) {
- if (toDir.exists()) {
- toDir.delete();
- }
- if (!toDir.mkdirs()) {
- Log.w(K9.LOG_TAG, "cannot create directory " + toDir.getAbsolutePath());
- }
- }
- File[] files = fromDir.listFiles();
- for (File file : files) {
- if (file.isDirectory()) {
- moveRecursive(file, new File(toDir, file.getName()));
- file.delete();
- } else {
- File target = new File(toDir, file.getName());
- if (!file.renameTo(target)) {
- Log.w(K9.LOG_TAG, "cannot rename " + file.getAbsolutePath() + " to " + target.getAbsolutePath() + " - moving instead");
- move(file, target);
- }
- }
- }
- if (!fromDir.delete()) {
- Log.w(K9.LOG_TAG, "cannot delete " + fromDir.getAbsolutePath());
- }
- }
private static final String IMG_SRC_REGEX = "(?is:]+src\\s*=\\s*['\"]?([a-z]+)\\:)";
private static final Pattern IMG_PATTERN = Pattern.compile(IMG_SRC_REGEX);
@@ -641,18 +478,6 @@ public class Utility {
}
}
- /**
- * Replace characters we don't allow in file names with a replacement character.
- *
- * @param filename
- * The original file name.
- *
- * @return The sanitized file name containing only allowed characters.
- */
- public static String sanitizeFilename(String filename) {
- return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
- }
-
/**
* Check to see if we have network connectivity.
* @param app Current application (Hint: see if your base class has a getApplication() method.)
diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java
index 21dad28c7..c6308a313 100644
--- a/src/com/fsck/k9/mail/internet/MimeUtility.java
+++ b/src/com/fsck/k9/mail/internet/MimeUtility.java
@@ -1111,20 +1111,8 @@ public class MimeUtility {
return p.matcher(mimeType).matches();
}
- /**
- * Returns true if the given mimeType matches any of the matchAgainst specifications.
- * @param mimeType A MIME type to check.
- * @param matchAgainst An array of MIME types to check against. May include wildcards such
- * as image/* or * /*.
- * @return
- */
- public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
- for (String matchType : matchAgainst) {
- if (mimeTypeMatches(mimeType, matchType)) {
- return true;
- }
- }
- return false;
+ public static boolean isDefaultMimeType(String mimeType) {
+ return DEFAULT_ATTACHMENT_MIME_TYPE.equalsIgnoreCase(mimeType);
}
/**
diff --git a/src/com/fsck/k9/mail/store/LockableDatabase.java b/src/com/fsck/k9/mail/store/LockableDatabase.java
index f08076087..b9aaa8b84 100644
--- a/src/com/fsck/k9/mail/store/LockableDatabase.java
+++ b/src/com/fsck/k9/mail/store/LockableDatabase.java
@@ -14,7 +14,7 @@ import android.os.Build;
import android.util.Log;
import com.fsck.k9.K9;
-import com.fsck.k9.helper.Utility;
+import com.fsck.k9.helper.FileHelper;
import com.fsck.k9.mail.MessagingException;
public class LockableDatabase {
@@ -337,9 +337,10 @@ public class LockableDatabase {
prepareStorage(newProviderId);
// move all database files
- Utility.moveRecursive(oldDatabase, storageManager.getDatabase(uUid, newProviderId));
+ FileHelper.moveRecursive(oldDatabase, storageManager.getDatabase(uUid, newProviderId));
// move all attachment files
- Utility.moveRecursive(storageManager.getAttachmentDirectory(uUid, oldProviderId), storageManager.getAttachmentDirectory(uUid, newProviderId));
+ FileHelper.moveRecursive(storageManager.getAttachmentDirectory(uUid, oldProviderId),
+ storageManager.getAttachmentDirectory(uUid, newProviderId));
// remove any remaining old journal files
deleteDatabase(oldDatabase);
@@ -421,14 +422,14 @@ public class LockableDatabase {
// Android seems to be unmounting the storage...
throw new UnavailableStorageException("Unable to access: " + databaseParentDir);
}
- Utility.touchFile(databaseParentDir, ".nomedia");
+ FileHelper.touchFile(databaseParentDir, ".nomedia");
}
final File attachmentDir = storageManager.getAttachmentDirectory(uUid, providerId);
final File attachmentParentDir = attachmentDir.getParentFile();
if (!attachmentParentDir.exists()) {
attachmentParentDir.mkdirs();
- Utility.touchFile(attachmentParentDir, ".nomedia");
+ FileHelper.touchFile(attachmentParentDir, ".nomedia");
}
if (!attachmentDir.exists()) {
attachmentDir.mkdirs();
diff --git a/src/com/fsck/k9/preferences/SettingsExporter.java b/src/com/fsck/k9/preferences/SettingsExporter.java
index d7d77f6dc..6085f3014 100644
--- a/src/com/fsck/k9/preferences/SettingsExporter.java
+++ b/src/com/fsck/k9/preferences/SettingsExporter.java
@@ -12,6 +12,8 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
+
+import com.fsck.k9.helper.FileHelper;
import org.xmlpull.v1.XmlSerializer;
import android.content.Context;
@@ -23,7 +25,6 @@ import android.util.Xml;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
-import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Transport;
@@ -87,7 +88,7 @@ public class SettingsExporter {
File dir = new File(Environment.getExternalStorageDirectory() + File.separator
+ context.getPackageName());
dir.mkdirs();
- File file = Utility.createUniqueFile(dir, EXPORT_FILENAME);
+ File file = FileHelper.createUniqueFile(dir, EXPORT_FILENAME);
filename = file.getAbsolutePath();
os = new FileOutputStream(filename);
diff --git a/src/com/fsck/k9/provider/AttachmentProvider.java b/src/com/fsck/k9/provider/AttachmentProvider.java
index c0bd8b0f7..6f5bf32ac 100644
--- a/src/com/fsck/k9/provider/AttachmentProvider.java
+++ b/src/com/fsck/k9/provider/AttachmentProvider.java
@@ -51,11 +51,21 @@ public class AttachmentProvider extends ContentProvider {
public static Uri getAttachmentUri(Account account, long id) {
- return getAttachmentUri(account.getUuid(), id, true);
+ return CONTENT_URI.buildUpon()
+ .appendPath(account.getUuid())
+ .appendPath(Long.toString(id))
+ .appendPath(FORMAT_RAW)
+ .build();
}
- public static Uri getAttachmentUriForViewing(Account account, long id) {
- return getAttachmentUri(account.getUuid(), id, false);
+ public static Uri getAttachmentUriForViewing(Account account, long id, String mimeType, String filename) {
+ return CONTENT_URI.buildUpon()
+ .appendPath(account.getUuid())
+ .appendPath(Long.toString(id))
+ .appendPath(FORMAT_VIEW)
+ .appendPath(mimeType)
+ .appendPath(filename)
+ .build();
}
public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height) {
@@ -68,14 +78,6 @@ public class AttachmentProvider extends ContentProvider {
.build();
}
- private static Uri getAttachmentUri(String db, long id, boolean raw) {
- return CONTENT_URI.buildUpon()
- .appendPath(db)
- .appendPath(Long.toString(id))
- .appendPath(raw ? FORMAT_RAW : FORMAT_VIEW)
- .build();
- }
-
public static void clear(Context context) {
/*
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
@@ -146,8 +148,9 @@ public class AttachmentProvider extends ContentProvider {
String dbName = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
+ String mimeType = (segments.size() < 4) ? null : segments.get(3);
- return getType(dbName, id, format);
+ return getType(dbName, id, format, mimeType);
}
@Override
@@ -165,7 +168,7 @@ public class AttachmentProvider extends ContentProvider {
file = getThumbnailFile(getContext(), accountUuid, attachmentId);
if (!file.exists()) {
- String type = getType(accountUuid, attachmentId, FORMAT_VIEW);
+ String type = getType(accountUuid, attachmentId, FORMAT_VIEW, null);
try {
FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
try {
@@ -258,7 +261,7 @@ public class AttachmentProvider extends ContentProvider {
return null;
}
- private String getType(String dbName, String id, String format) {
+ private String getType(String dbName, String id, String format, String mimeType) {
String type;
if (FORMAT_THUMBNAIL.equals(format)) {
type = "image/png";
@@ -269,10 +272,9 @@ public class AttachmentProvider extends ContentProvider {
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
- if (FORMAT_VIEW.equals(format)) {
- type = MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
+ if (FORMAT_VIEW.equals(format) && mimeType != null) {
+ type = mimeType;
} else {
- // When accessing the "raw" message we deliver the original MIME type.
type = attachmentInfo.type;
}
} catch (MessagingException e) {
diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java
index cffb37c2a..ea917f334 100644
--- a/src/com/fsck/k9/view/AttachmentView.java
+++ b/src/com/fsck/k9/view/AttachmentView.java
@@ -1,16 +1,18 @@
package com.fsck.k9.view;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.List;
-import org.apache.commons.io.IOUtils;
-
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
@@ -30,11 +32,12 @@ import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
+import com.fsck.k9.cache.TemporaryAttachmentStore;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
+import com.fsck.k9.helper.FileHelper;
import com.fsck.k9.helper.MediaScannerNotifier;
import com.fsck.k9.helper.SizeFormatter;
-import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
@@ -42,53 +45,48 @@ import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
+import org.apache.commons.io.IOUtils;
+
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
- private Context mContext;
- public Button viewButton;
- public Button downloadButton;
- public LocalAttachmentBodyPart part;
- private Message mMessage;
- private Account mAccount;
- private MessagingController mController;
- private MessagingListener mListener;
- public String name;
- public String contentType;
- public long size;
- public ImageView iconView;
-
+ private Context context;
+ private Message message;
+ private LocalAttachmentBodyPart part;
+ private Account account;
+ private MessagingController controller;
+ private MessagingListener listener;
private AttachmentFileDownloadCallback callback;
+ private Button viewButton;
+ private Button downloadButton;
+
+ private String name;
+ private String contentType;
+ private long size;
+
+
public AttachmentView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mContext = context;
+ this.context = context;
}
+
public AttachmentView(Context context, AttributeSet attrs) {
super(context, attrs);
- mContext = context;
+ this.context = context;
}
+
public AttachmentView(Context context) {
super(context);
- mContext = context;
+ this.context = context;
}
-
- public interface AttachmentFileDownloadCallback {
- /**
- * this method i called by the attachmentview when
- * he wants to show a filebrowser
- * the provider should show the filebrowser activity
- * and save the reference to the attachment view for later.
- * in his onActivityResult he can get the saved reference and
- * call the saveFile method of AttachmentView
- * @param view
- */
- public void showFileBrowser(AttachmentView caller);
+ public void setButtonsEnabled(boolean enabled) {
+ viewButton.setEnabled(enabled);
+ downloadButton.setEnabled(enabled);
}
/**
* Populates this view with information about the attachment.
- *
*
* This method also decides which attachments are displayed when the "show attachments" button
* is pressed, and which attachments are only displayed after the "show more attachments"
@@ -96,26 +94,33 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
* Inline attachments with content ID and unnamed attachments fall into the second category.
*
*
- * @param inputPart
- * @param message
- * @param account
- * @param controller
- * @param listener
- *
- * @return {@code true} for a regular attachment. {@code false}, otherwise.
- *
- * @throws MessagingException
- * In case of an error
+ * @return {@code true} for a regular attachment. {@code false} for attachments that should be initially hidden.
*/
public boolean populateFromPart(Part inputPart, Message message, Account account,
MessagingController controller, MessagingListener listener) throws MessagingException {
- boolean firstClassAttachment = true;
- part = (LocalAttachmentBodyPart) inputPart;
- contentType = MimeUtility.unfoldAndDecode(part.getContentType());
+ part = (LocalAttachmentBodyPart) inputPart;
+ this.message = message;
+ this.account = account;
+ this.controller = controller;
+ this.listener = listener;
+
+ boolean firstClassAttachment = extractAttachmentInformation(part);
+
+ displayAttachmentInformation();
+
+ return firstClassAttachment;
+ }
+
+ //TODO: extract this code to a helper class
+ private boolean extractAttachmentInformation(Part part) throws MessagingException {
+ boolean firstClassAttachment = true;
+
+ contentType = part.getMimeType();
+ String contentTypeHeader = MimeUtility.unfoldAndDecode(part.getContentType());
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
- name = MimeUtility.getHeaderParameter(contentType, "name");
+ name = MimeUtility.getHeaderParameter(contentTypeHeader, "name");
if (name == null) {
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
}
@@ -135,11 +140,6 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
firstClassAttachment = false;
}
- mAccount = account;
- mMessage = message;
- mController = controller;
- mListener = listener;
-
String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size");
if (sizeParam != null) {
try {
@@ -147,20 +147,15 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
} catch (NumberFormatException e) { /* ignore */ }
}
- contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name);
+ return firstClassAttachment;
+ }
+
+ private void displayAttachmentInformation() {
TextView attachmentName = (TextView) findViewById(R.id.attachment_name);
TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info);
- final ImageView attachmentIcon = (ImageView) findViewById(R.id.attachment_icon);
viewButton = (Button) findViewById(R.id.view);
downloadButton = (Button) findViewById(R.id.download);
- if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
- || (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
- viewButton.setVisibility(View.GONE);
- }
- if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))
- || (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) {
- downloadButton.setVisibility(View.GONE);
- }
+
if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
viewButton.setVisibility(View.GONE);
downloadButton.setVisibility(View.GONE);
@@ -171,23 +166,10 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
downloadButton.setOnLongClickListener(this);
attachmentName.setText(name);
- attachmentInfo.setText(SizeFormatter.formatSize(mContext, size));
- new AsyncTask() {
- protected Bitmap doInBackground(Void... asyncTaskArgs) {
- Bitmap previewIcon = getPreviewIcon();
- return previewIcon;
- }
+ attachmentInfo.setText(SizeFormatter.formatSize(context, size));
- protected void onPostExecute(Bitmap previewIcon) {
- if (previewIcon != null) {
- attachmentIcon.setImageBitmap(previewIcon);
- } else {
- attachmentIcon.setImageResource(R.drawable.attached_image_placeholder);
- }
- }
- }.execute();
-
- return firstClassAttachment;
+ ImageView thumbnail = (ImageView) findViewById(R.id.attachment_icon);
+ new LoadAndDisplayThumbnailAsyncTask(thumbnail).execute();
}
@Override
@@ -207,156 +189,291 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
@Override
public boolean onLongClick(View view) {
if (view.getId() == R.id.download) {
- callback.showFileBrowser(this);
+ callback.pickDirectoryToSaveAttachmentTo(this);
return true;
}
return false;
}
- private Bitmap getPreviewIcon() {
- Bitmap icon = null;
- try {
- InputStream input = mContext.getContentResolver().openInputStream(
- AttachmentProvider.getAttachmentThumbnailUri(mAccount,
- part.getAttachmentId(),
- 62,
- 62));
- icon = BitmapFactory.decodeStream(input);
- input.close();
- } catch (Exception e) {
- /*
- * We don't care what happened, we just return null for the preview icon.
- */
- }
- return icon;
- }
-
private void onViewButtonClicked() {
- if (mMessage != null) {
- mController.loadAttachment(mAccount, mMessage, part, new Object[] { false, this }, mListener);
+ if (message != null) {
+ controller.loadAttachment(account, message, part, new Object[] {false, this}, listener);
}
}
-
private void onSaveButtonClicked() {
- saveFile();
- }
+ boolean isExternalStorageMounted = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+ if (!isExternalStorageMounted) {
+ String message = context.getString(R.string.message_view_status_attachment_not_saved);
+ displayMessageToUser(message);
+ return;
+ }
- /**
- * Writes the attachment onto the given path
- * @param directory: the base dir where the file should be saved.
- */
- public void writeFile(File directory) {
- try {
- String filename = Utility.sanitizeFilename(name);
- File file = Utility.createUniqueFile(directory, filename);
- Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
- InputStream in = mContext.getContentResolver().openInputStream(uri);
- OutputStream out = new FileOutputStream(file);
- IOUtils.copy(in, out);
- out.flush();
- out.close();
- in.close();
- attachmentSaved(file.toString());
- new MediaScannerNotifier(mContext, file);
- } catch (IOException ioe) {
- if (K9.DEBUG) {
- Log.e(K9.LOG_TAG, "Error saving attachment", ioe);
- }
- attachmentNotSaved();
+ if (message != null) {
+ controller.loadAttachment(account, message, part, new Object[] {true, this}, listener);
}
}
- /**
- * saves the file to the defaultpath setting in the config, or if the config
- * is not set => to the Environment
- */
public void writeFile() {
writeFile(new File(K9.getAttachmentDefaultPath()));
}
- public void saveFile() {
- //TODO: Can the user save attachments on the internal filesystem or sd card only?
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- /*
- * Abort early if there's no place to save the attachment. We don't want to spend
- * the time downloading it and then abort.
- */
- Toast.makeText(mContext,
- mContext.getString(R.string.message_view_status_attachment_not_saved),
- Toast.LENGTH_SHORT).show();
- return;
- }
- if (mMessage != null) {
- mController.loadAttachment(mAccount, mMessage, part, new Object[] {true, this}, mListener);
+ /**
+ * Saves the attachment as file in the given directory
+ */
+ public void writeFile(File directory) {
+ try {
+ File file = saveAttachmentWithUniqueFileName(directory);
+
+ displayAttachmentSavedMessage(file.toString());
+
+ MediaScannerNotifier.notify(context, file);
+ } catch (IOException ioe) {
+ if (K9.DEBUG) {
+ Log.e(K9.LOG_TAG, "Error saving attachment", ioe);
+ }
+ displayAttachmentNotSavedMessage();
}
}
+ private File saveAttachmentWithUniqueFileName(File directory) throws IOException {
+ String filename = FileHelper.sanitizeFilename(name);
+ File file = FileHelper.createUniqueFile(directory, filename);
+
+ writeAttachmentToStorage(file);
+
+ return file;
+ }
+
+ private void writeAttachmentToStorage(File file) throws IOException {
+ Uri uri = AttachmentProvider.getAttachmentUri(account, part.getAttachmentId());
+ InputStream in = context.getContentResolver().openInputStream(uri);
+ try {
+ OutputStream out = new FileOutputStream(file);
+ try {
+ IOUtils.copy(in, out);
+ out.flush();
+ } finally {
+ out.close();
+ }
+ } finally {
+ in.close();
+ }
+ }
public void showFile() {
- Uri uri = AttachmentProvider.getAttachmentUriForViewing(mAccount, part.getAttachmentId());
- Intent intent = new Intent(Intent.ACTION_VIEW);
- // We explicitly set the ContentType in addition to the URI because some attachment viewers (such as Polaris office 3.0.x) choke on documents without a mime type
- intent.setDataAndType(uri, contentType);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-
- try {
- mContext.startActivity(intent);
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Could not display attachment of type " + contentType, e);
- Toast toast = Toast.makeText(mContext, mContext.getString(R.string.message_view_no_viewer, contentType), Toast.LENGTH_LONG);
- toast.show();
- }
+ new ViewAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- /**
- * Check the {@link PackageManager} if the phone has an application
- * installed to view this type of attachment.
- * If not, {@link #viewButton} is disabled.
- * This should be done in any place where
- * attachment.viewButton.setEnabled(enabled); is called.
- * This method is safe to be called from the UI-thread.
- */
- public void checkViewable() {
- if (viewButton.getVisibility() == View.GONE) {
- // nothing to do
- return;
- }
- if (!viewButton.isEnabled()) {
- // nothing to do
- return;
- }
- try {
- Uri uri = AttachmentProvider.getAttachmentUriForViewing(mAccount, part.getAttachmentId());
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(uri);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- if (intent.resolveActivity(mContext.getPackageManager()) == null) {
- viewButton.setEnabled(false);
+ private Intent getBestViewIntentAndSaveFileIfNecessary() {
+ String inferredMimeType = MimeUtility.getMimeTypeByExtension(name);
+
+ IntentAndResolvedActivitiesCount resolvedIntentInfo;
+ if (MimeUtility.isDefaultMimeType(contentType)) {
+ resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType);
+ } else {
+ resolvedIntentInfo = getBestViewIntentForMimeType(contentType);
+ if (!resolvedIntentInfo.hasResolvedActivities() && !inferredMimeType.equals(contentType)) {
+ resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType);
}
- // currently we do not cache re result.
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Cannot resolve activity to determine if we shall show the 'view'-button for an attachment", e);
}
+
+ if (!resolvedIntentInfo.hasResolvedActivities()) {
+ resolvedIntentInfo = getBestViewIntentForMimeType(MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE);
+ }
+
+ Intent viewIntent;
+ if (resolvedIntentInfo.hasResolvedActivities() && resolvedIntentInfo.containsFileUri()) {
+ try {
+ File tempFile = TemporaryAttachmentStore.getFileForWriting(context, name);
+ writeAttachmentToStorage(tempFile);
+ viewIntent = createViewIntentForFileUri(resolvedIntentInfo.getMimeType(), Uri.fromFile(tempFile));
+ } catch (IOException e) {
+ if (K9.DEBUG) {
+ Log.e(K9.LOG_TAG, "Error while saving attachment to use file:// URI with ACTION_VIEW Intent", e);
+ }
+ viewIntent = createViewIntentForAttachmentProviderUri(MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE);
+ }
+ } else {
+ viewIntent = resolvedIntentInfo.getIntent();
+ }
+
+ return viewIntent;
}
- public void attachmentSaved(final String filename) {
- Toast.makeText(mContext, String.format(
- mContext.getString(R.string.message_view_status_attachment_saved), filename),
- Toast.LENGTH_LONG).show();
+ private IntentAndResolvedActivitiesCount getBestViewIntentForMimeType(String mimeType) {
+ Intent contentUriIntent = createViewIntentForAttachmentProviderUri(mimeType);
+ int contentUriActivitiesCount = getResolvedIntentActivitiesCount(contentUriIntent);
+
+ if (contentUriActivitiesCount > 0) {
+ return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount);
+ }
+
+ File tempFile = TemporaryAttachmentStore.getFile(context, name);
+ Uri tempFileUri = Uri.fromFile(tempFile);
+ Intent fileUriIntent = createViewIntentForFileUri(mimeType, tempFileUri);
+ int fileUriActivitiesCount = getResolvedIntentActivitiesCount(fileUriIntent);
+
+ if (fileUriActivitiesCount > 0) {
+ return new IntentAndResolvedActivitiesCount(fileUriIntent, fileUriActivitiesCount);
+ }
+
+ return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount);
}
- public void attachmentNotSaved() {
- Toast.makeText(mContext,
- mContext.getString(R.string.message_view_status_attachment_not_saved),
- Toast.LENGTH_LONG).show();
+ private Intent createViewIntentForAttachmentProviderUri(String mimeType) {
+ Uri uri = AttachmentProvider.getAttachmentUriForViewing(account, part.getAttachmentId(), mimeType, name);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(uri, mimeType);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ addUiIntentFlags(intent);
+
+ return intent;
}
- public AttachmentFileDownloadCallback getCallback() {
- return callback;
+
+ private Intent createViewIntentForFileUri(String mimeType, Uri uri) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(uri, mimeType);
+ addUiIntentFlags(intent);
+
+ return intent;
}
+
+ private void addUiIntentFlags(Intent intent) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ }
+
+ private int getResolvedIntentActivitiesCount(Intent intent) {
+ PackageManager packageManager = context.getPackageManager();
+
+ List resolveInfos =
+ packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+
+ return resolveInfos.size();
+ }
+
+ private void displayAttachmentSavedMessage(final String filename) {
+ String message = context.getString(R.string.message_view_status_attachment_saved, filename);
+ displayMessageToUser(message);
+ }
+
+ private void displayAttachmentNotSavedMessage() {
+ String message = context.getString(R.string.message_view_status_attachment_not_saved);
+ displayMessageToUser(message);
+ }
+
+ private void displayMessageToUser(String message) {
+ Toast.makeText(context, message, Toast.LENGTH_LONG).show();
+ }
+
public void setCallback(AttachmentFileDownloadCallback callback) {
this.callback = callback;
}
+
+ public interface AttachmentFileDownloadCallback {
+ /**
+ * This method is called to ask the user to pick a directory to save the attachment to.
+ *
+ * After the user has selected a directory, the implementation of this interface has to call
+ * {@link #writeFile(File)} on the object supplied as argument in order for the attachment to be saved.
+ */
+ public void pickDirectoryToSaveAttachmentTo(AttachmentView caller);
+ }
+
+
+ private static class IntentAndResolvedActivitiesCount {
+ private Intent intent;
+ private int activitiesCount;
+
+ IntentAndResolvedActivitiesCount(Intent intent, int activitiesCount) {
+ this.intent = intent;
+ this.activitiesCount = activitiesCount;
+ }
+
+ public Intent getIntent() {
+ return intent;
+ }
+
+ public boolean hasResolvedActivities() {
+ return activitiesCount > 0;
+ }
+
+ public String getMimeType() {
+ return intent.getType();
+ }
+
+ public boolean containsFileUri() {
+ return "file".equals(intent.getData().getScheme());
+ }
+ }
+
+ private class LoadAndDisplayThumbnailAsyncTask extends AsyncTask {
+ private final ImageView thumbnail;
+
+ public LoadAndDisplayThumbnailAsyncTask(ImageView thumbnail) {
+ this.thumbnail = thumbnail;
+ }
+
+ protected Bitmap doInBackground(Void... asyncTaskArgs) {
+ return getPreviewIcon();
+ }
+
+ private Bitmap getPreviewIcon() {
+ Bitmap icon = null;
+ try {
+ InputStream input = context.getContentResolver().openInputStream(
+ AttachmentProvider.getAttachmentThumbnailUri(account,
+ part.getAttachmentId(),
+ 62,
+ 62));
+ icon = BitmapFactory.decodeStream(input);
+ input.close();
+ } catch (Exception e) {
+ // We don't care what happened, we just return null for the preview icon.
+ }
+
+ return icon;
+ }
+
+ protected void onPostExecute(Bitmap previewIcon) {
+ if (previewIcon != null) {
+ thumbnail.setImageBitmap(previewIcon);
+ } else {
+ thumbnail.setImageResource(R.drawable.attached_image_placeholder);
+ }
+ }
+ }
+
+ private class ViewAttachmentAsyncTask extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ viewButton.setEnabled(false);
+ }
+
+ @Override
+ protected Intent doInBackground(Void... params) {
+ return getBestViewIntentAndSaveFileIfNecessary();
+ }
+
+ @Override
+ protected void onPostExecute(Intent intent) {
+ viewAttachment(intent);
+ viewButton.setEnabled(true);
+ }
+
+ private void viewAttachment(Intent intent) {
+ try {
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(K9.LOG_TAG, "Could not display attachment of type " + contentType, e);
+
+ String message = context.getString(R.string.message_view_no_viewer, contentType);
+ displayMessageToUser(message);
+ }
+ }
+ }
}
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index 2ed03fda4..1506bd4dd 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -46,6 +46,7 @@ import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.helper.ClipboardManager;
import com.fsck.k9.helper.Contacts;
+import com.fsck.k9.helper.FileHelper;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
@@ -598,8 +599,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
public void setAttachmentsEnabled(boolean enabled) {
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
AttachmentView attachment = (AttachmentView) mAttachments.getChildAt(i);
- attachment.viewButton.setEnabled(enabled);
- attachment.downloadButton.setEnabled(enabled);
+ attachment.setButtonsEnabled(enabled);
}
}
@@ -834,10 +834,10 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
filename += "." + extension;
}
- String sanitized = Utility.sanitizeFilename(filename);
+ String sanitized = FileHelper.sanitizeFilename(filename);
File directory = new File(K9.getAttachmentDefaultPath());
- File file = Utility.createUniqueFile(directory, sanitized);
+ File file = FileHelper.createUniqueFile(directory, sanitized);
FileOutputStream out = new FileOutputStream(file);
try {
IOUtils.copy(in, out);
diff --git a/tests/src/com/fsck/k9/helper/FileHelperTest.java b/tests/src/com/fsck/k9/helper/FileHelperTest.java
new file mode 100644
index 000000000..1e8260256
--- /dev/null
+++ b/tests/src/com/fsck/k9/helper/FileHelperTest.java
@@ -0,0 +1,32 @@
+package com.fsck.k9.helper;
+
+import junit.framework.TestCase;
+
+import java.lang.String;
+
+public class FileHelperTest extends TestCase {
+
+ public void testSanitize1() {
+ checkSanitization(".._bla_", "../bla_");
+ }
+
+ public void testSanitize2() {
+ checkSanitization("_etc_bla", "/etc/bla");
+ }
+
+ public void testSanitize3() {
+ checkSanitization("_пPп", "+пPп");
+ }
+
+ public void testSanitize4() {
+ checkSanitization(".東京_!", ".東京?!");
+ }
+
+ public void testSanitize5() {
+ checkSanitization("Plan 9", "Plan 9");
+ }
+
+ private void checkSanitization(String expected, String actual) {
+ assertEquals(expected, FileHelper.sanitizeFilename(actual));
+ }
+}