From 9fea7d9bc10c17d8983df91d05056714d123a8d0 Mon Sep 17 00:00:00 2001 From: Philip Whitehouse Date: Sat, 21 Jan 2017 22:35:30 +0000 Subject: [PATCH 1/2] Runtime permissions: Request permissions at runtime for attachment saving - k9mail/k-9 #2110 --- .../ui/messageview/AttachmentController.java | 10 +++- .../ui/messageview/MessageViewFragment.java | 53 +++++++++++++++---- k9mail/src/main/res/values/strings.xml | 2 + 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java index e4f5f2bc97c..16bf50cdaef 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java @@ -8,6 +8,7 @@ import java.io.OutputStream; import java.util.List; +import android.Manifest; import android.app.DownloadManager; import android.content.ActivityNotFoundException; import android.content.Context; @@ -16,6 +17,7 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Environment; import android.support.annotation.WorkerThread; import timber.log.Timber; @@ -119,7 +121,7 @@ private void viewLocalAttachment() { 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); + String message = context.getString(R.string.message_view_status_external_storage_not_mounted); displayMessageToUser(message); return; } @@ -142,6 +144,11 @@ private void saveLocalAttachmentTo(File directory) { } private File saveAttachmentWithUniqueFileName(File directory) throws IOException { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + messageViewFragment.getActivity().checkSelfPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + return null; + } String filename = FileHelper.sanitizeFilename(attachment.displayName); File file = FileHelper.createUniqueFile(directory, filename); @@ -154,6 +161,7 @@ private File saveAttachmentWithUniqueFileName(File directory) throws IOException private void writeAttachmentToStorage(File file) throws IOException { InputStream in = context.getContentResolver().openInputStream(attachment.internalUri); + try { OutputStream out = new FileOutputStream(file); try { diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java index da52ab50789..07038647f6f 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.Locale; +import android.Manifest; import android.app.Activity; import android.app.DialogFragment; import android.app.DownloadManager; @@ -13,7 +14,9 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; @@ -65,6 +68,8 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF public static final int REQUEST_MASK_LOADER_HELPER = (1 << 8); public static final int REQUEST_MASK_CRYPTO_PRESENTER = (1 << 9); + private static final int ATTACHMENT_SAVE_PERMISSION_DEFAULT = 400; + private static final int ATTACHMENT_SAVE_PERMISSION_CHOOSE = 401; public static final int PROGRESS_THRESHOLD_MILLIS = 500 * 1000; @@ -826,27 +831,55 @@ public void onViewAttachment(AttachmentViewInfo attachment) { getAttachmentController(attachment).viewAttachment(); } + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] result){ + super.onRequestPermissionsResult(requestCode, permissions, result); + if(requestCode == ATTACHMENT_SAVE_PERMISSION_DEFAULT && result[0] == PackageManager.PERMISSION_GRANTED){ + saveAttachment(currentAttachmentViewInfo); + } else if (requestCode == ATTACHMENT_SAVE_PERMISSION_CHOOSE && result[0] == PackageManager.PERMISSION_GRANTED){ + saveAttachmentChoose(currentAttachmentViewInfo); + } + } + @Override public void onSaveAttachment(AttachmentViewInfo attachment) { currentAttachmentViewInfo = attachment; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && getActivity().checkSelfPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ATTACHMENT_SAVE_PERMISSION_DEFAULT); + } else { + saveAttachment(attachment); + } + } + + private void saveAttachment(AttachmentViewInfo attachment) { getAttachmentController(attachment).saveAttachment(); } @Override public void onSaveAttachmentToUserProvidedDirectory(final AttachmentViewInfo attachment) { currentAttachmentViewInfo = attachment; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && getActivity().checkSelfPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ATTACHMENT_SAVE_PERMISSION_CHOOSE); + } else { + saveAttachmentChoose(attachment); + } + } + + private void saveAttachmentChoose(final AttachmentViewInfo attachment) { FileBrowserHelper.getInstance().showFileBrowserActivity(MessageViewFragment.this, null, - ACTIVITY_CHOOSE_DIRECTORY, new FileBrowserFailOverCallback() { - @Override - public void onPathEntered(String path) { - getAttachmentController(attachment).saveAttachmentTo(path); - } + ACTIVITY_CHOOSE_DIRECTORY, new FileBrowserFailOverCallback() { + @Override + public void onPathEntered(String path) { + getAttachmentController(attachment).saveAttachmentTo(path); + } - @Override - public void onCancel() { - // Do nothing - } - }); + @Override + public void onCancel() { + // Do nothing + } + }); } private AttachmentController getAttachmentController(AttachmentViewInfo attachment) { diff --git a/k9mail/src/main/res/values/strings.xml b/k9mail/src/main/res/values/strings.xml index ccef8e3d0e4..078b5af8501 100644 --- a/k9mail/src/main/res/values/strings.xml +++ b/k9mail/src/main/res/values/strings.xml @@ -289,6 +289,8 @@ Please submit bug reports, contribute new features and ask questions at Save Unable to save attachment to SD card. The attachment could not be saved as there is not enough space. + An error occurred while saving the attachment + Unable to access device storage. Show pictures Unable to find viewer for %s. Download complete message From a5f8fccc531b71047c585f31d5b6f70e79f43cdc Mon Sep 17 00:00:00 2001 From: Philip Whitehouse Date: Sun, 22 Jan 2017 00:20:17 +0000 Subject: [PATCH 2/2] Runtime permissions: Request permissions at runtime for exporting settings - k9mail/k-9 #2110 --- .../java/com/fsck/k9/activity/Accounts.java | 53 ++++++++++++++----- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/activity/Accounts.java b/k9mail/src/main/java/com/fsck/k9/activity/Accounts.java index b2bd2e6e48f..3482578ed41 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/Accounts.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/Accounts.java @@ -15,6 +15,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import android.Manifest; +import android.annotation.SuppressLint; import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; @@ -115,6 +117,19 @@ public class Accounts extends K9ListActivity implements OnItemClickListener { private static final int DIALOG_RECREATE_ACCOUNT = 3; private static final int DIALOG_NO_FILE_MANAGER = 4; + private static final int ACTIVITY_REQUEST_PICK_SETTINGS_FILE = 1; + private static final int ACTIVITY_REQUEST_SAVE_SETTINGS_FILE = 2; + private static final int EXPORT_SAVE_PERMISSION = 100; + + private static String ACCOUNT_STATS = "accountStats"; + private static String STATE_UNREAD_COUNT = "unreadCount"; + private static String SELECTED_CONTEXT_ACCOUNT = "selectedContextAccount"; + private static final String STATE_EXPORT_GLOBAL_SETTINGS = "exportGlobalSettings"; + private static final String STATE_EXPORT_ACCOUNTS = "exportAccountUuids"; + + public static final String EXTRA_STARTUP = "startup"; + public static final String ACTION_IMPORT_SETTINGS = "importSettings"; + /* * Must be serializable hence implementation class used for declaration. */ @@ -147,10 +162,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener { * @see #onRetainNonConfigurationInstance() */ private NonConfigurationInstance mNonConfigurationInstance; + private boolean exportIncludeGlobals; + private Account exportAccount; - private static final int ACTIVITY_REQUEST_PICK_SETTINGS_FILE = 1; - private static final int ACTIVITY_REQUEST_SAVE_SETTINGS_FILE = 2; class AccountsHandler extends Handler { private void setViewTitle() { @@ -209,7 +224,8 @@ public void run() { stats.size = newSize; } String toastText = getString(R.string.account_size_changed, account.getDescription(), - SizeFormatter.formatSize(getApplication(), oldSize), SizeFormatter.formatSize(getApplication(), newSize)); + SizeFormatter.formatSize(getApplication(), oldSize), + SizeFormatter.formatSize(getApplication(), newSize)); Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG); toast.show(); @@ -330,17 +346,6 @@ public void synchronizeMailboxFailed(Account account, String folder, }; - private static String ACCOUNT_STATS = "accountStats"; - private static String STATE_UNREAD_COUNT = "unreadCount"; - private static String SELECTED_CONTEXT_ACCOUNT = "selectedContextAccount"; - private static final String STATE_EXPORT_GLOBAL_SETTINGS = "exportGlobalSettings"; - private static final String STATE_EXPORT_ACCOUNTS = "exportAccountUuids"; - - - public static final String EXTRA_STARTUP = "startup"; - - public static final String ACTION_IMPORT_SETTINGS = "importSettings"; - public static void listAccounts(Context context) { Intent intent = new Intent(context, Accounts.class); @@ -1918,7 +1923,27 @@ public void onClick(View v) { } + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] result){ + super.onRequestPermissionsResult(requestCode, permissions, result); + + if(requestCode == EXPORT_SAVE_PERMISSION && result[0] == PackageManager.PERMISSION_GRANTED){ + performExport(exportIncludeGlobals, exportAccount); + } + } + public void onExport(final boolean includeGlobals, final Account account) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && this.checkSelfPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + exportIncludeGlobals = includeGlobals; + exportAccount = account; + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXPORT_SAVE_PERMISSION); + } else { + performExport(includeGlobals, account); + } + } + + private void performExport(final boolean includeGlobals, final Account account) { // TODO, prompt to allow a user to choose which accounts to export ArrayList accountUuids = null; if (account != null) {