Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd0a126b authored by Seweryn Fornalik's avatar Seweryn Fornalik
Browse files

Fix saving mail attachment error

parent 1c9e8888
Loading
Loading
Loading
Loading
Loading
+8 −66
Original line number Diff line number Diff line
package foundation.e.mail.activity.setup;


import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.widget.Toast;

import org.openintents.openpgp.util.OpenPgpAppPreference;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import foundation.e.mail.K9;
import foundation.e.mail.K9.NotificationHideSubject;
import foundation.e.mail.K9.NotificationQuickDelete;
@@ -31,8 +30,6 @@ import foundation.e.mail.Preferences;
import foundation.e.mail.R;
import foundation.e.mail.activity.ColorPickerDialog;
import foundation.e.mail.activity.K9PreferenceActivity;
import foundation.e.mail.helper.FileBrowserHelper;
import foundation.e.mail.helper.FileBrowserHelper.FileBrowserFailOverCallback;
import foundation.e.mail.notification.NotificationController;
import foundation.e.mail.preferences.CheckBoxListPreference;
import foundation.e.mail.preferences.Storage;
@@ -41,8 +38,6 @@ import foundation.e.mail.preferences.TimePickerPreference;
import foundation.e.mail.service.MailService;
import foundation.e.mail.ui.dialog.ApgDeprecationWarningDialog;

import org.openintents.openpgp.util.OpenPgpAppPreference;


public class Prefs extends K9PreferenceActivity {

@@ -165,7 +160,6 @@ public class Prefs extends K9PreferenceActivity {
    private foundation.e.mail.preferences.TimePickerPreference mQuietTimeEnds;
    private ListPreference mNotificationQuickDelete;
    private ListPreference mLockScreenNotificationVisibility;
    private Preference mAttachmentPathPreference;

    private CheckBoxPreference mBackgroundAsUnreadIndicator;
    private CheckBoxPreference mThreadedView;
@@ -407,36 +401,6 @@ public class Prefs extends K9PreferenceActivity {
        mOpenPgpSupportSignOnly = (CheckBoxPreference) findPreference(PREFERENCE_OPENPGP_SUPPORT_SIGN_ONLY);
        mOpenPgpSupportSignOnly.setChecked(K9.getOpenPgpSupportSignOnly());

        mAttachmentPathPreference = findPreference(PREFERENCE_ATTACHMENT_DEF_PATH);
        mAttachmentPathPreference.setSummary(K9.getAttachmentDefaultPath());
        mAttachmentPathPreference
        .setOnPreferenceClickListener(new OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                FileBrowserHelper
                .getInstance()
                .showFileBrowserActivity(Prefs.this,
                                         new File(K9.getAttachmentDefaultPath()),
                                         ACTIVITY_CHOOSE_FOLDER, callback);

                return true;
            }

            FileBrowserFailOverCallback callback = new FileBrowserFailOverCallback() {

                @Override
                public void onPathEntered(String path) {
                    mAttachmentPathPreference.setSummary(path);
                    K9.setAttachmentDefaultPath(path);
                }

                @Override
                public void onCancel() {
                    // canceled, do nothing
                }
            };
        });

        mWrapFolderNames = (CheckBoxPreference)findPreference(PREFERENCE_FOLDERLIST_WRAP_NAME);
        mWrapFolderNames.setChecked(K9.wrapFolderNames());

@@ -550,7 +514,6 @@ public class Prefs extends K9PreferenceActivity {
        }

        K9.setSplitViewMode(SplitViewMode.valueOf(mSplitViewMode.getValue()));
        K9.setAttachmentDefaultPath(mAttachmentPathPreference.getSummary().toString());
        boolean needsRefresh = K9.setBackgroundOps(mBackgroundOps.getValue());

        if (!K9.isDebug() && mDebugLogging.isChecked()) {
@@ -611,25 +574,4 @@ public class Prefs extends K9PreferenceActivity {
        }
        return dialog;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case ACTIVITY_CHOOSE_FOLDER:
            if (resultCode == RESULT_OK && data != null) {
                // obtain the filename
                Uri fileUri = data.getData();
                if (fileUri != null) {
                    String filePath = fileUri.getPath();
                    if (filePath != null) {
                        mAttachmentPathPreference.setSummary(filePath.toString());
                        K9.setAttachmentDefaultPath(filePath.toString());
                    }
                }
            }
            break;
        }

        super.onActivityResult(requestCode, resultCode, data);
    }
}
+0 −62
Original line number Diff line number Diff line
package foundation.e.mail.cache;


import java.io.File;
import java.io.IOException;

import android.content.Context;
import timber.log.Timber;

import foundation.e.mail.helper.FileHelper;


public class TemporaryAttachmentStore {
    private static final String TEMPORARY_ATTACHMENT_DIRECTORY = "attachments";
    private static final 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()) {
                    Timber.d("Deleted from temporary attachment store: %s", file.getName());
                } else {
                    Timber.w("Couldn't delete from temporary attachment store: %s", file.getName());
                }
            }
        }
    }
}
+0 −167
Original line number Diff line number Diff line
package foundation.e.mail.helper;

import java.io.File;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.text.InputType;
import android.widget.EditText;

import foundation.e.mail.K9;
import foundation.e.mail.R;

public class FileBrowserHelper {
    /**
     * A string array that specifies the name of the intent to use, and the scheme to use with it
     * when setting the data for the intent.
     */
    private static final String[][] PICK_DIRECTORY_INTENTS = {
        { "org.openintents.action.PICK_DIRECTORY", "file://" },   // OI File Manager (maybe others)
        { "com.estrongs.action.PICK_DIRECTORY", "file://" },      // ES File Explorer
        { Intent.ACTION_PICK, "folder://" },                      // Blackmoon File Browser (maybe others)
        { "com.androidworkz.action.PICK_DIRECTORY", "file://" }
    }; // SystemExplorer

    private static FileBrowserHelper sInstance;

    /**
     * callback class to provide the result of the fallback textedit path dialog
     */
    public interface FileBrowserFailOverCallback {
        /**
         * the user has entered a path
         * @param path the path as String
         */
        public void onPathEntered(String path);
        /**
         * the user has cancel the inputtext dialog
         */
        public void onCancel();
    }
    /**
     * factory method
     *
     */
    private FileBrowserHelper() {
    }
    public synchronized static FileBrowserHelper getInstance() {
        if (sInstance == null) {
            sInstance = new FileBrowserHelper();
        }
        return sInstance;
    }


    /**
     * tries to open known filebrowsers.
     * If no filebrowser is found and fallback textdialog is shown
     * @param c the context as activity
     * @param startPath: the default value, where the filebrowser will start.
     *      if startPath = null => the default path is used
     * @param requestcode: the int you will get as requestcode in onActivityResult
     *      (only used if there is a filebrowser installed)
     * @param callback: the callback (only used when no filebrowser is installed.
     *      if a filebrowser is installed => override the onActivtyResult Method
     *
     * @return true: if a filebrowser has been found (the result will be in the onActivityResult
     *          false: a fallback textinput has been shown. The Result will be sent with the callback method
     *
     *
     */
    public boolean showFileBrowserActivity(Activity c, File startPath, int requestcode, FileBrowserFailOverCallback callback) {
        boolean success = false;

        if (startPath == null) {
            startPath = new File(K9.getAttachmentDefaultPath());
        }

        int listIndex = 0;
        do {
            String intentAction = PICK_DIRECTORY_INTENTS[listIndex][0];
            String uriPrefix = PICK_DIRECTORY_INTENTS[listIndex][1];
            Intent intent = new Intent(intentAction);
            intent.setData(Uri.parse(uriPrefix + startPath.getPath()));

            try {
                c.startActivityForResult(intent, requestcode);
                success = true;
            } catch (ActivityNotFoundException e) {
                // Try the next intent in the list
                listIndex++;
            }
        } while (!success && (listIndex < PICK_DIRECTORY_INTENTS.length));

        if (listIndex == PICK_DIRECTORY_INTENTS.length) {
            //No Filebrowser is installed => show a fallback textdialog
            showPathTextInput(c, startPath, callback);
            success = false;
        }

        return success;
    }

    public boolean showFileBrowserActivity(Fragment c, File startPath, int requestcode, FileBrowserFailOverCallback callback) {
        boolean success = false;

        if (startPath == null) {
            startPath = new File(K9.getAttachmentDefaultPath());
        }

        int listIndex = 0;
        do {
            String intentAction = PICK_DIRECTORY_INTENTS[listIndex][0];
            String uriPrefix = PICK_DIRECTORY_INTENTS[listIndex][1];
            Intent intent = new Intent(intentAction);
            intent.setData(Uri.parse(uriPrefix + startPath.getPath()));

            try {
                c.startActivityForResult(intent, requestcode);
                success = true;
            } catch (ActivityNotFoundException e) {
                // Try the next intent in the list
                listIndex++;
            }
        } while (!success && (listIndex < PICK_DIRECTORY_INTENTS.length));

        if (listIndex == PICK_DIRECTORY_INTENTS.length) {
            //No Filebrowser is installed => show a fallback textdialog
            showPathTextInput(c.getActivity(), startPath, callback);
            success = false;
        }

        return success;
    }

    private void showPathTextInput(final Activity c, final File startPath, final FileBrowserFailOverCallback callback) {
        AlertDialog.Builder alert = new AlertDialog.Builder(c);

        alert.setTitle(c.getString(R.string.attachment_save_title));
        alert.setMessage(c.getString(R.string.attachment_save_desc));
        final EditText input = new EditText(c);
        input.setInputType(InputType.TYPE_CLASS_TEXT);
        if (startPath != null)
            input.setText(startPath.toString());
        alert.setView(input);

        alert.setPositiveButton(c.getString(R.string.okay_action), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                String path = input.getText().toString();
                callback.onPathEntered(path);
            }
        });

        alert.setNegativeButton(c.getString(R.string.cancel_action),
        new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                callback.onCancel();
            }
        });

        alert.show();
    }
}
+44 −114
Original line number Diff line number Diff line
package foundation.e.mail.ui.messageview;


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 android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.support.annotation.WorkerThread;
import timber.log.Timber;
import android.widget.Toast;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import foundation.e.mail.Account;
import foundation.e.mail.K9;
import foundation.e.mail.Preferences;
import foundation.e.mail.R;
import foundation.e.mail.cache.TemporaryAttachmentStore;
import foundation.e.mail.controller.MessagingController;
import foundation.e.mail.controller.SimpleMessagingListener;
import foundation.e.mail.helper.FileHelper;
import foundation.e.mail.mail.Message;
import foundation.e.mail.mail.Part;
import foundation.e.mail.mail.internet.MimeUtility;
@@ -36,7 +32,7 @@ import foundation.e.mail.mailstore.AttachmentViewInfo;
import foundation.e.mail.mailstore.LocalMessage;
import foundation.e.mail.mailstore.LocalPart;
import foundation.e.mail.provider.AttachmentTempFileProvider;
import org.apache.commons.io.IOUtils;
import timber.log.Timber;


public class AttachmentController {
@@ -44,14 +40,11 @@ public class AttachmentController {
    private final MessagingController controller;
    private final MessageViewFragment messageViewFragment;
    private final AttachmentViewInfo attachment;
    private final DownloadManager downloadManager;


    AttachmentController(MessagingController controller, DownloadManager downloadManager,
            MessageViewFragment messageViewFragment, AttachmentViewInfo attachment) {
    AttachmentController(MessagingController controller, MessageViewFragment messageViewFragment,
                         AttachmentViewInfo attachment) {
        this.context = messageViewFragment.getApplicationContext();
        this.controller = controller;
        this.downloadManager = downloadManager;
        this.messageViewFragment = messageViewFragment;
        this.attachment = attachment;
    }
@@ -64,12 +57,16 @@ public class AttachmentController {
        }
    }

    public void saveAttachment() {
    /*public void saveAttachment() {
        saveAttachmentTo(K9.getAttachmentDefaultPath());
    }
    }*/

    public void saveAttachmentTo(String directory) {
        saveAttachmentTo(new File(directory));
    public void saveAttachmentTo(Uri documentUri) {
        if (!attachment.isContentAvailable()) {
            downloadAndSaveAttachmentTo((LocalPart) attachment.part, documentUri);
        } else {
            saveLocalAttachmentTo(documentUri);
        }
    }

    private void downloadAndViewAttachment(LocalPart localPart) {
@@ -81,12 +78,12 @@ public class AttachmentController {
        });
    }

    private void downloadAndSaveAttachmentTo(LocalPart localPart, final File directory) {
    private void downloadAndSaveAttachmentTo(LocalPart localPart, final Uri documentUri) {
        downloadAttachment(localPart, new Runnable() {
            @Override
            public void run() {
                messageViewFragment.refreshAttachmentThumbnail(attachment);
                saveLocalAttachmentTo(directory);
                saveLocalAttachmentTo(documentUri);
            }
        });
    }
@@ -116,46 +113,15 @@ public class AttachmentController {
        new ViewAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    private void saveAttachmentTo(File directory) {
        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 (attachment.size > directory.getFreeSpace()) {
            String message = context.getString(R.string.message_view_status_no_space);
            displayMessageToUser(message);
            return;
        }

        if (!attachment.isContentAvailable()) {
            downloadAndSaveAttachmentTo((LocalPart) attachment.part, directory);
        } else {
            saveLocalAttachmentTo(directory);
        }
    }

    private void saveLocalAttachmentTo(File directory) {
        new SaveAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, directory);
    }

    private File saveAttachmentWithUniqueFileName(File directory) throws IOException {
        String filename = FileHelper.sanitizeFilename(attachment.displayName);
        File file = FileHelper.createUniqueFile(directory, filename);

        writeAttachmentToStorage(file);

        addSavedAttachmentToDownloadsDatabase(file);

        return file;
    private void saveLocalAttachmentTo(Uri documentUri) {
        new SaveAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, documentUri);
    }

    private void writeAttachmentToStorage(File file) throws IOException {
        InputStream in = context.getContentResolver().openInputStream(attachment.internalUri);
    private void writeAttachment(Uri documentUri) throws IOException {
        ContentResolver contentResolver = context.getContentResolver();
        InputStream in = contentResolver.openInputStream(attachment.internalUri);
        try {
            OutputStream out = new FileOutputStream(file);
            OutputStream out = contentResolver.openOutputStream(documentUri);
            try {
                IOUtils.copy(in, out);
                out.flush();
@@ -167,17 +133,8 @@ public class AttachmentController {
        }
    }

    private void addSavedAttachmentToDownloadsDatabase(File file) {
        String fileName = file.getName();
        String path = file.getAbsolutePath();
        long fileLength = file.length();
        String mimeType = attachment.mimeType;

        downloadManager.addCompletedDownload(fileName, fileName, true, mimeType, path, fileLength, true);
    }

    @WorkerThread
    private Intent getBestViewIntentAndSaveFile() {
    private Intent getBestViewIntent() {
        Uri intentDataUri;
        try {
            intentDataUri = AttachmentTempFileProvider.createTempUriForContentUri(context, attachment.internalUri);
@@ -192,53 +149,25 @@ public class AttachmentController {
        IntentAndResolvedActivitiesCount resolvedIntentInfo;
        String mimeType = attachment.mimeType;
        if (MimeUtility.isDefaultMimeType(mimeType)) {
            resolvedIntentInfo = getBestViewIntentForMimeType(intentDataUri, inferredMimeType);
            resolvedIntentInfo = getViewIntentForMimeType(intentDataUri, inferredMimeType);
        } else {
            resolvedIntentInfo = getBestViewIntentForMimeType(intentDataUri, mimeType);
            resolvedIntentInfo = getViewIntentForMimeType(intentDataUri, mimeType);
            if (!resolvedIntentInfo.hasResolvedActivities() && !inferredMimeType.equals(mimeType)) {
                resolvedIntentInfo = getBestViewIntentForMimeType(intentDataUri, inferredMimeType);
                resolvedIntentInfo = getViewIntentForMimeType(intentDataUri, inferredMimeType);
            }
        }

        if (!resolvedIntentInfo.hasResolvedActivities()) {
            resolvedIntentInfo = getBestViewIntentForMimeType(
                    intentDataUri, MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE);
        }

        Intent viewIntent;
        if (resolvedIntentInfo.hasResolvedActivities() && resolvedIntentInfo.containsFileUri()) {
            try {
                File tempFile = TemporaryAttachmentStore.getFileForWriting(context, displayName);
                writeAttachmentToStorage(tempFile);
                viewIntent = createViewIntentForFileUri(resolvedIntentInfo.getMimeType(), Uri.fromFile(tempFile));
            } catch (IOException e) {
                Timber.e(e, "Error while saving attachment to use file:// URI with ACTION_VIEW Intent");
                viewIntent = createViewIntentForAttachmentProviderUri(intentDataUri, MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE);
            }
        } else {
            viewIntent = resolvedIntentInfo.getIntent();
            resolvedIntentInfo = getViewIntentForMimeType(intentDataUri, MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE);
        }

        return viewIntent;
        return resolvedIntentInfo.getIntent();
    }

    private IntentAndResolvedActivitiesCount getBestViewIntentForMimeType(Uri contentUri, String mimeType) {
    private IntentAndResolvedActivitiesCount getViewIntentForMimeType(Uri contentUri, String mimeType) {
        Intent contentUriIntent = createViewIntentForAttachmentProviderUri(contentUri, mimeType);
        int contentUriActivitiesCount = getResolvedIntentActivitiesCount(contentUriIntent);

        if (contentUriActivitiesCount > 0) {
            return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount);
        }

        File tempFile = TemporaryAttachmentStore.getFile(context, attachment.displayName);
        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);
    }

@@ -318,7 +247,7 @@ public class AttachmentController {

        @Override
        protected Intent doInBackground(Void... params) {
            return getBestViewIntentAndSaveFile();
            return getBestViewIntent();
        }

        @Override
@@ -339,7 +268,7 @@ public class AttachmentController {
        }
    }

    private class SaveAttachmentAsyncTask extends AsyncTask<File, Void, File> {
    private class SaveAttachmentAsyncTask extends AsyncTask<Uri, Void, Boolean> {

        @Override
        protected void onPreExecute() {
@@ -347,20 +276,21 @@ public class AttachmentController {
        }

        @Override
        protected File doInBackground(File... params) {
        protected Boolean doInBackground(Uri... params) {
            try {
                File directory = params[0];
                return saveAttachmentWithUniqueFileName(directory);
                Uri documentUri = params[0];
                writeAttachment(documentUri);
                return true;
            } catch (IOException e) {
                Timber.e(e, "Error saving attachment");
                return null;
                return false;
            }
        }

        @Override
        protected void onPostExecute(File file) {
        protected void onPostExecute(Boolean success) {
            messageViewFragment.enableAttachmentButtons(attachment);
            if (file == null) {
            if (!success) {
                displayAttachmentNotSavedMessage();
            }
        }
+1 −16

File changed.

Preview size limit exceeded, changes collapsed.

Loading