mirror of
https://github.com/moparisthebest/k-9
synced 2025-02-17 07:30:16 -05:00
Merge branch 'open_attachment_improvements'
Conflicts: src/com/fsck/k9/helper/Utility.java
This commit is contained in:
commit
98b5d63909
@ -226,7 +226,7 @@ K-9 Mail — почтовый клиент для Android.
|
|||||||
<string name="message_view_show_attachments_action">Вложения</string>
|
<string name="message_view_show_attachments_action">Вложения</string>
|
||||||
<string name="message_view_show_more_attachments_action">Показать вложения</string>
|
<string name="message_view_show_more_attachments_action">Показать вложения</string>
|
||||||
<string name="message_view_fetching_attachment_toast">Извлечение вложений</string>
|
<string name="message_view_fetching_attachment_toast">Извлечение вложений</string>
|
||||||
<string name="message_view_no_viewer">Отсутствует просмотрщик %s <xliff:g id="mimetype">%s</xliff:g></string>
|
<string name="message_view_no_viewer">Отсутствует просмотрщик <xliff:g id="mimetype">%s</xliff:g></string>
|
||||||
<string name="message_view_download_remainder">Загрузить полностью</string>
|
<string name="message_view_download_remainder">Загрузить полностью</string>
|
||||||
<string name="message_view_downloading">Загрузка…</string>
|
<string name="message_view_downloading">Загрузка…</string>
|
||||||
<!--NOTE: The following message refers to strings with id account_setup_incoming_save_all_headers_label and account_setup_incoming_title-->
|
<!--NOTE: The following message refers to strings with id account_setup_incoming_save_all_headers_label and account_setup_incoming_title-->
|
||||||
|
@ -283,33 +283,6 @@ public class K9 extends Application {
|
|||||||
*/
|
*/
|
||||||
private static boolean sDatabasesUpToDate = false;
|
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
|
* For use when displaying that no folder is selected
|
||||||
*/
|
*/
|
||||||
|
65
src/com/fsck/k9/cache/TemporaryAttachmentStore.java
vendored
Normal file
65
src/com/fsck/k9/cache/TemporaryAttachmentStore.java
vendored
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -206,7 +206,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
|
|||||||
mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() {
|
mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showFileBrowser(final AttachmentView caller) {
|
public void pickDirectoryToSaveAttachmentTo(final AttachmentView caller) {
|
||||||
FileBrowserHelper.getInstance()
|
FileBrowserHelper.getInstance()
|
||||||
.showFileBrowserActivity(MessageViewFragment.this,
|
.showFileBrowserActivity(MessageViewFragment.this,
|
||||||
null,
|
null,
|
||||||
@ -498,15 +498,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
switch (view.getId()) {
|
if (view.getId() == R.id.download_remainder) {
|
||||||
case R.id.download: {
|
onDownloadRemainder();
|
||||||
((AttachmentView)view).saveFile();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case R.id.download_remainder: {
|
|
||||||
onDownloadRemainder();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
166
src/com/fsck/k9/helper/FileHelper.java
Normal file
166
src/com/fsck/k9/helper/FileHelper.java
Normal file
@ -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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Allowed are:
|
||||||
|
* <ul>
|
||||||
|
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
|
||||||
|
* <li>spaces: {@code " "}</li>
|
||||||
|
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
|
||||||
|
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>{</code>,
|
||||||
|
* <code>}</code>, {@code ~}, {@code .}, {@code ,}</li>
|
||||||
|
* </ul></p>
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +1,15 @@
|
|||||||
package com.fsck.k9.helper;
|
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 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) {
|
public class MediaScannerNotifier {
|
||||||
mFile = file;
|
public static void notify(Context context, File file) {
|
||||||
mConnection = new MediaScannerConnection(context, this);
|
String[] paths = { file.getAbsolutePath() };
|
||||||
mConnection.connect();
|
MediaScannerConnection.scanFile(context, paths, null, null);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,43 +17,13 @@ import android.widget.TextView;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.mail.filter.Base64;
|
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.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class Utility {
|
public class Utility {
|
||||||
/**
|
|
||||||
* Regular expression that represents characters we won't allow in file names.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Allowed are:
|
|
||||||
* <ul>
|
|
||||||
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
|
|
||||||
* <li>spaces: {@code " "}</li>
|
|
||||||
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
|
|
||||||
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>{</code>,
|
|
||||||
* <code>}</code>, {@code ~}, {@code .}, {@code ,}</li>
|
|
||||||
* </ul></p>
|
|
||||||
*
|
|
||||||
* @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
|
// \u00A0 (non-breaking space) happens to be used by French MUA
|
||||||
|
|
||||||
@ -471,139 +441,6 @@ public class Utility {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param parentDir
|
|
||||||
* @param name
|
|
||||||
* Never <code>null</code>.
|
|
||||||
*/
|
|
||||||
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:<img[^>]+src\\s*=\\s*['\"]?([a-z]+)\\:)";
|
private static final String IMG_SRC_REGEX = "(?is:<img[^>]+src\\s*=\\s*['\"]?([a-z]+)\\:)";
|
||||||
private static final Pattern IMG_PATTERN = Pattern.compile(IMG_SRC_REGEX);
|
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.
|
* Check to see if we have network connectivity.
|
||||||
* @param app Current application (Hint: see if your base class has a getApplication() method.)
|
* @param app Current application (Hint: see if your base class has a getApplication() method.)
|
||||||
|
@ -1111,20 +1111,8 @@ public class MimeUtility {
|
|||||||
return p.matcher(mimeType).matches();
|
return p.matcher(mimeType).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static boolean isDefaultMimeType(String mimeType) {
|
||||||
* Returns true if the given mimeType matches any of the matchAgainst specifications.
|
return DEFAULT_ATTACHMENT_MIME_TYPE.equalsIgnoreCase(mimeType);
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@ import android.os.Build;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.FileHelper;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
|
||||||
public class LockableDatabase {
|
public class LockableDatabase {
|
||||||
@ -337,9 +337,10 @@ public class LockableDatabase {
|
|||||||
prepareStorage(newProviderId);
|
prepareStorage(newProviderId);
|
||||||
|
|
||||||
// move all database files
|
// move all database files
|
||||||
Utility.moveRecursive(oldDatabase, storageManager.getDatabase(uUid, newProviderId));
|
FileHelper.moveRecursive(oldDatabase, storageManager.getDatabase(uUid, newProviderId));
|
||||||
// move all attachment files
|
// 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
|
// remove any remaining old journal files
|
||||||
deleteDatabase(oldDatabase);
|
deleteDatabase(oldDatabase);
|
||||||
|
|
||||||
@ -421,14 +422,14 @@ public class LockableDatabase {
|
|||||||
// Android seems to be unmounting the storage...
|
// Android seems to be unmounting the storage...
|
||||||
throw new UnavailableStorageException("Unable to access: " + databaseParentDir);
|
throw new UnavailableStorageException("Unable to access: " + databaseParentDir);
|
||||||
}
|
}
|
||||||
Utility.touchFile(databaseParentDir, ".nomedia");
|
FileHelper.touchFile(databaseParentDir, ".nomedia");
|
||||||
}
|
}
|
||||||
|
|
||||||
final File attachmentDir = storageManager.getAttachmentDirectory(uUid, providerId);
|
final File attachmentDir = storageManager.getAttachmentDirectory(uUid, providerId);
|
||||||
final File attachmentParentDir = attachmentDir.getParentFile();
|
final File attachmentParentDir = attachmentDir.getParentFile();
|
||||||
if (!attachmentParentDir.exists()) {
|
if (!attachmentParentDir.exists()) {
|
||||||
attachmentParentDir.mkdirs();
|
attachmentParentDir.mkdirs();
|
||||||
Utility.touchFile(attachmentParentDir, ".nomedia");
|
FileHelper.touchFile(attachmentParentDir, ".nomedia");
|
||||||
}
|
}
|
||||||
if (!attachmentDir.exists()) {
|
if (!attachmentDir.exists()) {
|
||||||
attachmentDir.mkdirs();
|
attachmentDir.mkdirs();
|
||||||
|
@ -12,6 +12,8 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import com.fsck.k9.helper.FileHelper;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -23,7 +25,6 @@ import android.util.Xml;
|
|||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.helper.Utility;
|
|
||||||
import com.fsck.k9.mail.Store;
|
import com.fsck.k9.mail.Store;
|
||||||
import com.fsck.k9.mail.ServerSettings;
|
import com.fsck.k9.mail.ServerSettings;
|
||||||
import com.fsck.k9.mail.Transport;
|
import com.fsck.k9.mail.Transport;
|
||||||
@ -87,7 +88,7 @@ public class SettingsExporter {
|
|||||||
File dir = new File(Environment.getExternalStorageDirectory() + File.separator
|
File dir = new File(Environment.getExternalStorageDirectory() + File.separator
|
||||||
+ context.getPackageName());
|
+ context.getPackageName());
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
File file = Utility.createUniqueFile(dir, EXPORT_FILENAME);
|
File file = FileHelper.createUniqueFile(dir, EXPORT_FILENAME);
|
||||||
filename = file.getAbsolutePath();
|
filename = file.getAbsolutePath();
|
||||||
os = new FileOutputStream(filename);
|
os = new FileOutputStream(filename);
|
||||||
|
|
||||||
|
@ -51,11 +51,21 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
|
|
||||||
|
|
||||||
public static Uri getAttachmentUri(Account account, long id) {
|
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) {
|
public static Uri getAttachmentUriForViewing(Account account, long id, String mimeType, String filename) {
|
||||||
return getAttachmentUri(account.getUuid(), id, false);
|
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) {
|
public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height) {
|
||||||
@ -68,14 +78,6 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
.build();
|
.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) {
|
public static void clear(Context context) {
|
||||||
/*
|
/*
|
||||||
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
|
* 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 dbName = segments.get(0);
|
||||||
String id = segments.get(1);
|
String id = segments.get(1);
|
||||||
String format = segments.get(2);
|
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
|
@Override
|
||||||
@ -165,7 +168,7 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
|
|
||||||
file = getThumbnailFile(getContext(), accountUuid, attachmentId);
|
file = getThumbnailFile(getContext(), accountUuid, attachmentId);
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
String type = getType(accountUuid, attachmentId, FORMAT_VIEW);
|
String type = getType(accountUuid, attachmentId, FORMAT_VIEW, null);
|
||||||
try {
|
try {
|
||||||
FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
|
FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
|
||||||
try {
|
try {
|
||||||
@ -258,7 +261,7 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getType(String dbName, String id, String format) {
|
private String getType(String dbName, String id, String format, String mimeType) {
|
||||||
String type;
|
String type;
|
||||||
if (FORMAT_THUMBNAIL.equals(format)) {
|
if (FORMAT_THUMBNAIL.equals(format)) {
|
||||||
type = "image/png";
|
type = "image/png";
|
||||||
@ -269,10 +272,9 @@ public class AttachmentProvider extends ContentProvider {
|
|||||||
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
|
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
|
||||||
|
|
||||||
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
|
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
|
||||||
if (FORMAT_VIEW.equals(format)) {
|
if (FORMAT_VIEW.equals(format) && mimeType != null) {
|
||||||
type = MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
|
type = mimeType;
|
||||||
} else {
|
} else {
|
||||||
// When accessing the "raw" message we deliver the original MIME type.
|
|
||||||
type = attachmentInfo.type;
|
type = attachmentInfo.type;
|
||||||
}
|
}
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package com.fsck.k9.view;
|
package com.fsck.k9.view;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -30,11 +32,12 @@ import android.widget.Toast;
|
|||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.cache.TemporaryAttachmentStore;
|
||||||
import com.fsck.k9.controller.MessagingController;
|
import com.fsck.k9.controller.MessagingController;
|
||||||
import com.fsck.k9.controller.MessagingListener;
|
import com.fsck.k9.controller.MessagingListener;
|
||||||
|
import com.fsck.k9.helper.FileHelper;
|
||||||
import com.fsck.k9.helper.MediaScannerNotifier;
|
import com.fsck.k9.helper.MediaScannerNotifier;
|
||||||
import com.fsck.k9.helper.SizeFormatter;
|
import com.fsck.k9.helper.SizeFormatter;
|
||||||
import com.fsck.k9.helper.Utility;
|
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Part;
|
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.internet.MimeUtility;
|
||||||
import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
|
import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
|
||||||
import com.fsck.k9.provider.AttachmentProvider;
|
import com.fsck.k9.provider.AttachmentProvider;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
|
||||||
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
|
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
|
||||||
private Context mContext;
|
private Context context;
|
||||||
public Button viewButton;
|
private Message message;
|
||||||
public Button downloadButton;
|
private LocalAttachmentBodyPart part;
|
||||||
public LocalAttachmentBodyPart part;
|
private Account account;
|
||||||
private Message mMessage;
|
private MessagingController controller;
|
||||||
private Account mAccount;
|
private MessagingListener listener;
|
||||||
private MessagingController mController;
|
|
||||||
private MessagingListener mListener;
|
|
||||||
public String name;
|
|
||||||
public String contentType;
|
|
||||||
public long size;
|
|
||||||
public ImageView iconView;
|
|
||||||
|
|
||||||
private AttachmentFileDownloadCallback callback;
|
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) {
|
public AttachmentView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
mContext = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AttachmentView(Context context, AttributeSet attrs) {
|
public AttachmentView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
mContext = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AttachmentView(Context context) {
|
public AttachmentView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
mContext = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setButtonsEnabled(boolean enabled) {
|
||||||
public interface AttachmentFileDownloadCallback {
|
viewButton.setEnabled(enabled);
|
||||||
/**
|
downloadButton.setEnabled(enabled);
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates this view with information about the attachment.
|
* Populates this view with information about the attachment.
|
||||||
*
|
|
||||||
* <p>
|
* <p>
|
||||||
* This method also decides which attachments are displayed when the "show attachments" button
|
* 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"
|
* 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.
|
* Inline attachments with content ID and unnamed attachments fall into the second category.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param inputPart
|
* @return {@code true} for a regular attachment. {@code false} for attachments that should be initially hidden.
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
public boolean populateFromPart(Part inputPart, Message message, Account account,
|
public boolean populateFromPart(Part inputPart, Message message, Account account,
|
||||||
MessagingController controller, MessagingListener listener) throws MessagingException {
|
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());
|
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
|
||||||
|
|
||||||
name = MimeUtility.getHeaderParameter(contentType, "name");
|
name = MimeUtility.getHeaderParameter(contentTypeHeader, "name");
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
|
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
|
||||||
}
|
}
|
||||||
@ -135,11 +140,6 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
|
|||||||
firstClassAttachment = false;
|
firstClassAttachment = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mAccount = account;
|
|
||||||
mMessage = message;
|
|
||||||
mController = controller;
|
|
||||||
mListener = listener;
|
|
||||||
|
|
||||||
String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size");
|
String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size");
|
||||||
if (sizeParam != null) {
|
if (sizeParam != null) {
|
||||||
try {
|
try {
|
||||||
@ -147,20 +147,15 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
|
|||||||
} catch (NumberFormatException e) { /* ignore */ }
|
} catch (NumberFormatException e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name);
|
return firstClassAttachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayAttachmentInformation() {
|
||||||
TextView attachmentName = (TextView) findViewById(R.id.attachment_name);
|
TextView attachmentName = (TextView) findViewById(R.id.attachment_name);
|
||||||
TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info);
|
TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info);
|
||||||
final ImageView attachmentIcon = (ImageView) findViewById(R.id.attachment_icon);
|
|
||||||
viewButton = (Button) findViewById(R.id.view);
|
viewButton = (Button) findViewById(R.id.view);
|
||||||
downloadButton = (Button) findViewById(R.id.download);
|
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) {
|
if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
|
||||||
viewButton.setVisibility(View.GONE);
|
viewButton.setVisibility(View.GONE);
|
||||||
downloadButton.setVisibility(View.GONE);
|
downloadButton.setVisibility(View.GONE);
|
||||||
@ -171,23 +166,10 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
|
|||||||
downloadButton.setOnLongClickListener(this);
|
downloadButton.setOnLongClickListener(this);
|
||||||
|
|
||||||
attachmentName.setText(name);
|
attachmentName.setText(name);
|
||||||
attachmentInfo.setText(SizeFormatter.formatSize(mContext, size));
|
attachmentInfo.setText(SizeFormatter.formatSize(context, size));
|
||||||
new AsyncTask<Void, Void, Bitmap>() {
|
|
||||||
protected Bitmap doInBackground(Void... asyncTaskArgs) {
|
|
||||||
Bitmap previewIcon = getPreviewIcon();
|
|
||||||
return previewIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onPostExecute(Bitmap previewIcon) {
|
ImageView thumbnail = (ImageView) findViewById(R.id.attachment_icon);
|
||||||
if (previewIcon != null) {
|
new LoadAndDisplayThumbnailAsyncTask(thumbnail).execute();
|
||||||
attachmentIcon.setImageBitmap(previewIcon);
|
|
||||||
} else {
|
|
||||||
attachmentIcon.setImageResource(R.drawable.attached_image_placeholder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
|
|
||||||
return firstClassAttachment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -207,156 +189,291 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
|
|||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View view) {
|
public boolean onLongClick(View view) {
|
||||||
if (view.getId() == R.id.download) {
|
if (view.getId() == R.id.download) {
|
||||||
callback.showFileBrowser(this);
|
callback.pickDirectoryToSaveAttachmentTo(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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() {
|
private void onViewButtonClicked() {
|
||||||
if (mMessage != null) {
|
if (message != null) {
|
||||||
mController.loadAttachment(mAccount, mMessage, part, new Object[] { false, this }, mListener);
|
controller.loadAttachment(account, message, part, new Object[] {false, this}, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onSaveButtonClicked() {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (message != null) {
|
||||||
* Writes the attachment onto the given path
|
controller.loadAttachment(account, message, part, new Object[] {true, this}, listener);
|
||||||
* @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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* saves the file to the defaultpath setting in the config, or if the config
|
|
||||||
* is not set => to the Environment
|
|
||||||
*/
|
|
||||||
public void writeFile() {
|
public void writeFile() {
|
||||||
writeFile(new File(K9.getAttachmentDefaultPath()));
|
writeFile(new File(K9.getAttachmentDefaultPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveFile() {
|
/**
|
||||||
//TODO: Can the user save attachments on the internal filesystem or sd card only?
|
* Saves the attachment as file in the given directory
|
||||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
*/
|
||||||
/*
|
public void writeFile(File directory) {
|
||||||
* Abort early if there's no place to save the attachment. We don't want to spend
|
try {
|
||||||
* the time downloading it and then abort.
|
File file = saveAttachmentWithUniqueFileName(directory);
|
||||||
*/
|
|
||||||
Toast.makeText(mContext,
|
displayAttachmentSavedMessage(file.toString());
|
||||||
mContext.getString(R.string.message_view_status_attachment_not_saved),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
MediaScannerNotifier.notify(context, file);
|
||||||
return;
|
} catch (IOException ioe) {
|
||||||
}
|
if (K9.DEBUG) {
|
||||||
if (mMessage != null) {
|
Log.e(K9.LOG_TAG, "Error saving attachment", ioe);
|
||||||
mController.loadAttachment(mAccount, mMessage, part, new Object[] {true, this}, mListener);
|
}
|
||||||
|
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() {
|
public void showFile() {
|
||||||
Uri uri = AttachmentProvider.getAttachmentUriForViewing(mAccount, part.getAttachmentId());
|
new ViewAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private Intent getBestViewIntentAndSaveFileIfNecessary() {
|
||||||
* Check the {@link PackageManager} if the phone has an application
|
String inferredMimeType = MimeUtility.getMimeTypeByExtension(name);
|
||||||
* installed to view this type of attachment.
|
|
||||||
* If not, {@link #viewButton} is disabled.
|
IntentAndResolvedActivitiesCount resolvedIntentInfo;
|
||||||
* This should be done in any place where
|
if (MimeUtility.isDefaultMimeType(contentType)) {
|
||||||
* attachment.viewButton.setEnabled(enabled); is called.
|
resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType);
|
||||||
* This method is safe to be called from the UI-thread.
|
} else {
|
||||||
*/
|
resolvedIntentInfo = getBestViewIntentForMimeType(contentType);
|
||||||
public void checkViewable() {
|
if (!resolvedIntentInfo.hasResolvedActivities() && !inferredMimeType.equals(contentType)) {
|
||||||
if (viewButton.getVisibility() == View.GONE) {
|
resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
// 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) {
|
private IntentAndResolvedActivitiesCount getBestViewIntentForMimeType(String mimeType) {
|
||||||
Toast.makeText(mContext, String.format(
|
Intent contentUriIntent = createViewIntentForAttachmentProviderUri(mimeType);
|
||||||
mContext.getString(R.string.message_view_status_attachment_saved), filename),
|
int contentUriActivitiesCount = getResolvedIntentActivitiesCount(contentUriIntent);
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
|
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() {
|
private Intent createViewIntentForAttachmentProviderUri(String mimeType) {
|
||||||
Toast.makeText(mContext,
|
Uri uri = AttachmentProvider.getAttachmentUriForViewing(account, part.getAttachmentId(), mimeType, name);
|
||||||
mContext.getString(R.string.message_view_status_attachment_not_saved),
|
|
||||||
Toast.LENGTH_LONG).show();
|
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<ResolveInfo> 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) {
|
public void setCallback(AttachmentFileDownloadCallback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface AttachmentFileDownloadCallback {
|
||||||
|
/**
|
||||||
|
* This method is called to ask the user to pick a directory to save the attachment to.
|
||||||
|
* <p/>
|
||||||
|
* 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<Void, Void, Bitmap> {
|
||||||
|
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<Void, Void, Intent> {
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ import com.fsck.k9.crypto.PgpData;
|
|||||||
import com.fsck.k9.fragment.MessageViewFragment;
|
import com.fsck.k9.fragment.MessageViewFragment;
|
||||||
import com.fsck.k9.helper.ClipboardManager;
|
import com.fsck.k9.helper.ClipboardManager;
|
||||||
import com.fsck.k9.helper.Contacts;
|
import com.fsck.k9.helper.Contacts;
|
||||||
|
import com.fsck.k9.helper.FileHelper;
|
||||||
import com.fsck.k9.helper.HtmlConverter;
|
import com.fsck.k9.helper.HtmlConverter;
|
||||||
import com.fsck.k9.helper.UrlEncodingHelper;
|
import com.fsck.k9.helper.UrlEncodingHelper;
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
@ -598,8 +599,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
public void setAttachmentsEnabled(boolean enabled) {
|
public void setAttachmentsEnabled(boolean enabled) {
|
||||||
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
||||||
AttachmentView attachment = (AttachmentView) mAttachments.getChildAt(i);
|
AttachmentView attachment = (AttachmentView) mAttachments.getChildAt(i);
|
||||||
attachment.viewButton.setEnabled(enabled);
|
attachment.setButtonsEnabled(enabled);
|
||||||
attachment.downloadButton.setEnabled(enabled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -834,10 +834,10 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
filename += "." + extension;
|
filename += "." + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
String sanitized = Utility.sanitizeFilename(filename);
|
String sanitized = FileHelper.sanitizeFilename(filename);
|
||||||
|
|
||||||
File directory = new File(K9.getAttachmentDefaultPath());
|
File directory = new File(K9.getAttachmentDefaultPath());
|
||||||
File file = Utility.createUniqueFile(directory, sanitized);
|
File file = FileHelper.createUniqueFile(directory, sanitized);
|
||||||
FileOutputStream out = new FileOutputStream(file);
|
FileOutputStream out = new FileOutputStream(file);
|
||||||
try {
|
try {
|
||||||
IOUtils.copy(in, out);
|
IOUtils.copy(in, out);
|
||||||
|
32
tests/src/com/fsck/k9/helper/FileHelperTest.java
Normal file
32
tests/src/com/fsck/k9/helper/FileHelperTest.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user