From 8ebdbf2af756d0111686f3a7ad7e00dbbb4a1f56 Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Fri, 4 Sep 2020 17:27:57 +1000 Subject: [PATCH 1/4] Move away from deprecated Environment external file access API. --- res/xml/file_provider_paths.xml | 2 + .../securesms/RegistrationActivity.java | 3 +- .../securesms/avatar/AvatarSelection.java | 37 ++++---- .../securesms/backup/BackupDialog.java | 2 +- .../database/AttachmentDatabase.java | 6 +- .../securesms/jobs/LocalBackupJob.java | 8 +- .../jobs/RetrieveProfileAvatarJob.java | 4 +- .../securesms/mms/DocumentSlide.java | 4 +- .../securesms/util/BackupUtil.java | 14 +-- .../securesms/util/ExternalStorageUtil.java | 66 ++++++++++++++ .../securesms/util/IntentUtils.java | 10 +-- .../securesms/util/SaveAttachmentTask.java | 12 +-- .../securesms/util/StorageUtil.java | 86 ------------------- 13 files changed, 115 insertions(+), 139 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/util/ExternalStorageUtil.java delete mode 100644 src/org/thoughtcrime/securesms/util/StorageUtil.java diff --git a/res/xml/file_provider_paths.xml b/res/xml/file_provider_paths.xml index c913a472c9..c05fbc994e 100644 --- a/res/xml/file_provider_paths.xml +++ b/res/xml/file_provider_paths.xml @@ -8,4 +8,6 @@ + + \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index 29ca7d34d9..c96e433013 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -76,6 +76,7 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.internal.push.LockedException; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -278,7 +279,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif @Override protected @Nullable BackupUtil.BackupInfo doInBackground(Void... voids) { try { - return BackupUtil.getLatestBackup(); + return BackupUtil.getLatestBackup(RegistrationActivity.this); } catch (NoExternalStorageException e) { Log.w(TAG, e); return null; diff --git a/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java b/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java index b54c9e6cc4..5f1e26fc6f 100644 --- a/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java +++ b/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java @@ -6,17 +6,22 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; +import android.os.Environment; import android.provider.MediaStore; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.core.app.ShareCompat; import androidx.core.content.ContextCompat; import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImageView; import network.loki.messenger.R; + +import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.util.ExternalStorageUtil; import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.IntentUtils; @@ -31,12 +36,12 @@ public final class AvatarSelection { private static final String TAG = AvatarSelection.class.getSimpleName(); + public static final int REQUEST_CODE_CROP_IMAGE = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE; + public static final int REQUEST_CODE_AVATAR = REQUEST_CODE_CROP_IMAGE + 1; + private AvatarSelection() { } - public static final int REQUEST_CODE_CROP_IMAGE = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE; - public static final int REQUEST_CODE_AVATAR = REQUEST_CODE_CROP_IMAGE + 1; - /** * Returns result on {@link #REQUEST_CODE_CROP_IMAGE} */ @@ -63,16 +68,12 @@ public final class AvatarSelection { * @return Temporary capture file if created. */ public static File startAvatarSelection(Activity activity, boolean includeClear, boolean attemptToIncludeCamera) { - File captureFile = null; - + File captureFile = null; if (attemptToIncludeCamera) { - if (Permissions.hasAll(activity, Manifest.permission.CAMERA)) { - try { - captureFile = File.createTempFile("capture", "jpg", activity.getExternalCacheDir()); - } catch (IOException e) { - Log.w(TAG, e); - captureFile = null; - } + try { + captureFile = File.createTempFile("avatar-capture", ".jpg", ExternalStorageUtil.getImageDir(activity)); + } catch (IOException | NoExternalStorageException e) { + Log.e("Cannot reserve a temporary avatar capture file.", e); } } @@ -84,7 +85,6 @@ public final class AvatarSelection { private static Intent createAvatarSelectionIntent(Context context, @Nullable File tempCaptureFile, boolean includeClear) { List extraIntents = new LinkedList<>(); Intent galleryIntent = new Intent(Intent.ACTION_PICK); - galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*"); if (!IntentUtils.isResolvable(context, galleryIntent)) { @@ -93,12 +93,11 @@ public final class AvatarSelection { } if (tempCaptureFile != null) { + Uri uri = FileProviderUtil.getUriFor(context, tempCaptureFile); Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - - if (cameraIntent.resolveActivity(context.getPackageManager()) != null) { - cameraIntent.putExtra(EXTRA_OUTPUT, FileProviderUtil.getUriFor(context, tempCaptureFile)); - extraIntents.add(cameraIntent); - } + cameraIntent.putExtra(EXTRA_OUTPUT, uri); + cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + extraIntents.add(cameraIntent); } if (includeClear) { @@ -113,4 +112,4 @@ public final class AvatarSelection { return chooserIntent; } -} +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/backup/BackupDialog.java b/src/org/thoughtcrime/securesms/backup/BackupDialog.java index 11ca52a688..1da08039f8 100644 --- a/src/org/thoughtcrime/securesms/backup/BackupDialog.java +++ b/src/org/thoughtcrime/securesms/backup/BackupDialog.java @@ -77,7 +77,7 @@ public class BackupDialog { .setPositiveButton(R.string.BackupDialog_delete_backups_statement, (dialog, which) -> { BackupPassphrase.set(context, null); TextSecurePreferences.setBackupEnabled(context, false); - BackupUtil.deleteAllBackups(); + BackupUtil.deleteAllBackups(context); preference.setChecked(false); }) .create() diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 8f6f663fe6..63bdd4ca70 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -54,7 +54,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData; -import org.thoughtcrime.securesms.util.StorageUtil; +import org.thoughtcrime.securesms.util.ExternalStorageUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.video.EncryptedMediaDataSource; @@ -473,7 +473,7 @@ public class AttachmentDatabase extends Database { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(1); - contentValues.put(FILE_NAME, StorageUtil.getCleanFileName(fileName)); + contentValues.put(FILE_NAME, ExternalStorageUtil.getCleanFileName(fileName)); database.update(TABLE_NAME, contentValues, PART_ID_WHERE, attachmentId.toStrings()); } @@ -724,7 +724,7 @@ public class AttachmentDatabase extends Database { contentValues.put(DIGEST, attachment.getDigest()); contentValues.put(CONTENT_DISPOSITION, attachment.getKey()); contentValues.put(NAME, attachment.getRelay()); - contentValues.put(FILE_NAME, StorageUtil.getCleanFileName(attachment.getFileName())); + contentValues.put(FILE_NAME, ExternalStorageUtil.getCleanFileName(attachment.getFileName())); contentValues.put(SIZE, attachment.getSize()); contentValues.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId()); contentValues.put(VOICE_NOTE, attachment.isVoiceNote() ? 1 : 0); diff --git a/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java b/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java index dc0d8b1f4b..4110699e67 100644 --- a/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java +++ b/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.util.BackupUtil; -import org.thoughtcrime.securesms.util.StorageUtil; +import org.thoughtcrime.securesms.util.ExternalStorageUtil; import java.io.File; import java.io.IOException; @@ -71,7 +71,7 @@ public class LocalBackupJob extends BaseJob { try { String backupPassword = BackupPassphrase.get(context); - File backupDirectory = StorageUtil.getBackupDirectory(); + File backupDirectory = ExternalStorageUtil.getBackupDir(context); String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(new Date()); String fileName = String.format("session-%s.backup", timestamp); File backupFile = new File(backupDirectory, fileName); @@ -84,7 +84,7 @@ public class LocalBackupJob extends BaseJob { throw new IOException("Backup password is null"); } - File tempFile = File.createTempFile("backup", "tmp", StorageUtil.getBackupCacheDirectory(context)); + File tempFile = File.createTempFile("backup", "tmp", ExternalStorageUtil.getCacheDir(context)); FullBackupExporter.export(context, AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), @@ -97,7 +97,7 @@ public class LocalBackupJob extends BaseJob { throw new IOException("Renaming temporary backup file failed!"); } - BackupUtil.deleteOldBackups(); + BackupUtil.deleteOldBackups(context); } finally { GenericForegroundService.stopForegroundTask(context); } diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index 48bc091e97..168a6630a3 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -96,11 +96,11 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType return; } - File downloadDestination = File.createTempFile("avatar", "jpg", context.getCacheDir()); + File downloadDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); try { InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, profileKey, MAX_PROFILE_SIZE_BYTES); - File decryptDestination = File.createTempFile("avatar", "jpg", context.getCacheDir()); + File decryptDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); Util.copy(avatarStream, new FileOutputStream(decryptDestination)); decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getAddress())); diff --git a/src/org/thoughtcrime/securesms/mms/DocumentSlide.java b/src/org/thoughtcrime/securesms/mms/DocumentSlide.java index 24d6ceb133..968e8dd441 100644 --- a/src/org/thoughtcrime/securesms/mms/DocumentSlide.java +++ b/src/org/thoughtcrime/securesms/mms/DocumentSlide.java @@ -7,7 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.util.StorageUtil; +import org.thoughtcrime.securesms.util.ExternalStorageUtil; public class DocumentSlide extends Slide { @@ -19,7 +19,7 @@ public class DocumentSlide extends Slide { @NonNull String contentType, long size, @Nullable String fileName) { - super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), null, null, false, false)); + super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, ExternalStorageUtil.getCleanFileName(fileName), null, null, false, false)); } @Override diff --git a/src/org/thoughtcrime/securesms/util/BackupUtil.java b/src/org/thoughtcrime/securesms/util/BackupUtil.java index d3b4f4a112..d60660da4f 100644 --- a/src/org/thoughtcrime/securesms/util/BackupUtil.java +++ b/src/org/thoughtcrime/securesms/util/BackupUtil.java @@ -22,7 +22,7 @@ public class BackupUtil { public static @NonNull String getLastBackupTime(@NonNull Context context, @NonNull Locale locale) { try { - BackupInfo backup = getLatestBackup(); + BackupInfo backup = getLatestBackup(context); if (backup == null) return context.getString(R.string.BackupUtil_never); else return DateUtils.getExtendedRelativeTimeSpanString(context, locale, backup.getTimestamp()); @@ -32,8 +32,8 @@ public class BackupUtil { } } - public static @Nullable BackupInfo getLatestBackup() throws NoExternalStorageException { - File backupDirectory = StorageUtil.getBackupDirectory(); + public static @Nullable BackupInfo getLatestBackup(Context context) throws NoExternalStorageException { + File backupDirectory = ExternalStorageUtil.getBackupDir(context); File[] backups = backupDirectory.listFiles(); BackupInfo latestBackup = null; @@ -49,9 +49,9 @@ public class BackupUtil { } @SuppressWarnings("ResultOfMethodCallIgnored") - public static void deleteAllBackups() { + public static void deleteAllBackups(Context context) { try { - File backupDirectory = StorageUtil.getBackupDirectory(); + File backupDirectory = ExternalStorageUtil.getBackupDir(context); File[] backups = backupDirectory.listFiles(); for (File backup : backups) { @@ -62,9 +62,9 @@ public class BackupUtil { } } - public static void deleteOldBackups() { + public static void deleteOldBackups(Context context) { try { - File backupDirectory = StorageUtil.getBackupDirectory(); + File backupDirectory = ExternalStorageUtil.getBackupDir(context); File[] backups = backupDirectory.listFiles(); if (backups != null && backups.length > 2) { diff --git a/src/org/thoughtcrime/securesms/util/ExternalStorageUtil.java b/src/org/thoughtcrime/securesms/util/ExternalStorageUtil.java new file mode 100644 index 0000000000..6c701b4501 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/ExternalStorageUtil.java @@ -0,0 +1,66 @@ +package org.thoughtcrime.securesms.util; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.database.NoExternalStorageException; + +import java.io.File; + +public class ExternalStorageUtil { + + public static final String DIRECTORY_BACKUPS = "Backups"; + + /** @see Context#getExternalFilesDir(String) */ + @NonNull + public static File getDir(Context context, @Nullable String type) throws NoExternalStorageException { + final File dir = context.getExternalFilesDir(type); + if (dir == null) { + throw new NoExternalStorageException("External storage dir is currently unavailable: " + type); + } + return dir; + } + + @NonNull + public static File getBackupDir(Context context) throws NoExternalStorageException { + return getDir(context, DIRECTORY_BACKUPS); + } + + @NonNull + public static File getVideoDir(Context context) throws NoExternalStorageException { + return getDir(context, Environment.DIRECTORY_MOVIES); + } + + @NonNull + public static File getAudioDir(Context context) throws NoExternalStorageException { + return getDir(context, Environment.DIRECTORY_MUSIC); + } + + @NonNull + public static File getImageDir(Context context) throws NoExternalStorageException { + return getDir(context, Environment.DIRECTORY_PICTURES); + } + + @NonNull + public static File getDownloadDir(Context context) throws NoExternalStorageException { + return getDir(context, Environment.DIRECTORY_DOWNLOADS); + } + + @Nullable + public static File getCacheDir(Context context) { + return context.getExternalCacheDir(); + } + + @Nullable + public static String getCleanFileName(@Nullable String fileName) { + if (fileName == null) return null; + + fileName = fileName.replace('\u202D', '\uFFFD'); + fileName = fileName.replace('\u202E', '\uFFFD'); + + return fileName; + } +} diff --git a/src/org/thoughtcrime/securesms/util/IntentUtils.java b/src/org/thoughtcrime/securesms/util/IntentUtils.java index 0eb110ebdc..c01f42c2c2 100644 --- a/src/org/thoughtcrime/securesms/util/IntentUtils.java +++ b/src/org/thoughtcrime/securesms/util/IntentUtils.java @@ -1,18 +1,16 @@ package org.thoughtcrime.securesms.util; - import android.content.Context; import android.content.Intent; -import android.content.pm.ResolveInfo; -import androidx.annotation.NonNull; +import android.content.pm.PackageManager; -import java.util.List; +import androidx.annotation.NonNull; public class IntentUtils { public static boolean isResolvable(@NonNull Context context, @NonNull Intent intent) { - List resolveInfoList = context.getPackageManager().queryIntentActivities(intent, 0); - return resolveInfoList != null && resolveInfoList.size() > 1; + return context.getPackageManager() + .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; } } diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java index f1afec8de8..c4daadf36e 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java @@ -59,10 +59,6 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask(WRITE_ACCESS_FAILURE, null); - } - if (context == null) { return new Pair<>(FAILURE, null); } @@ -114,13 +110,13 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask Date: Fri, 4 Sep 2020 17:34:22 +1000 Subject: [PATCH 2/4] Media repository deprecation note. --- .../thoughtcrime/securesms/mediasend/MediaRepository.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaRepository.java b/src/org/thoughtcrime/securesms/mediasend/MediaRepository.java index d63bfbdb38..f9c4486e3b 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaRepository.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaRepository.java @@ -33,6 +33,11 @@ import java.util.Map; /** * Handles the retrieval of media present on the user's device. + * @deprecated Usage of this class is unsafe on Android API 30 and up, + * the public external directory is no longer exposed to the apps. + *

+ * The functionality of this class should be refactored to use + * MediaStore. */ class MediaRepository { From e352869aeb6ff83f8a8eb5587c4c57463ee48041 Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Mon, 7 Sep 2020 21:03:15 +1000 Subject: [PATCH 3/4] Restored camera permission check for avatar change action. --- .../securesms/avatar/AvatarSelection.java | 15 +++--- .../loki/activities/SettingsActivity.kt | 15 +++++- .../securesms/mms/AttachmentManager.java | 49 +++++++++++-------- .../securesms/permissions/Permissions.java | 10 ++-- 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java b/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java index 5f1e26fc6f..4a4f65883e 100644 --- a/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java +++ b/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java @@ -4,23 +4,20 @@ import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; -import android.os.Environment; import android.provider.MediaStore; + import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.core.app.ShareCompat; import androidx.core.content.ContextCompat; import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImageView; -import network.loki.messenger.R; - import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.ExternalStorageUtil; import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.IntentUtils; @@ -30,6 +27,8 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; +import network.loki.messenger.R; + import static android.provider.MediaStore.EXTRA_OUTPUT; public final class AvatarSelection { @@ -69,7 +68,9 @@ public final class AvatarSelection { */ public static File startAvatarSelection(Activity activity, boolean includeClear, boolean attemptToIncludeCamera) { File captureFile = null; - if (attemptToIncludeCamera) { + boolean hasCameraPermission = ContextCompat + .checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; + if (attemptToIncludeCamera && hasCameraPermission) { try { captureFile = File.createTempFile("avatar-capture", ".jpg", ExternalStorageUtil.getImageDir(activity)); } catch (IOException | NoExternalStorageException e) { @@ -84,7 +85,7 @@ public final class AvatarSelection { private static Intent createAvatarSelectionIntent(Context context, @Nullable File tempCaptureFile, boolean includeClear) { List extraIntents = new LinkedList<>(); - Intent galleryIntent = new Intent(Intent.ACTION_PICK); + Intent galleryIntent = new Intent(Intent.ACTION_PICK); galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*"); if (!IntentUtils.isResolvable(context, galleryIntent)) { diff --git a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index 1a88113784..45092b0c06 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.loki.activities +import android.Manifest import android.app.Activity import android.content.ClipData import android.content.ClipboardManager @@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.profiles.AvatarHelper import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints import org.thoughtcrime.securesms.util.BitmapDecodingException @@ -153,6 +155,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } } } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) + } // endregion // region Updating @@ -246,7 +253,13 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } private fun showEditProfilePictureUI() { - tempFile = AvatarSelection.startAvatarSelection(this, false, true) + // Ask for an optional camera permission. + Permissions.with(this) + .request(Manifest.permission.CAMERA) + .onAnyResult { + tempFile = AvatarSelection.startAvatarSelection(this, false, true) + } + .execute() } private fun copyPublicKey() { diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 5f13c13e6c..53e4ca54cc 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.components.RemovableEditableMediaView; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.location.SignalMapView; import org.thoughtcrime.securesms.components.location.SignalPlace; +import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mediasend.MediaSendActivity; @@ -54,6 +55,8 @@ import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.BitmapUtil; +import org.thoughtcrime.securesms.util.ExternalStorageUtil; +import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; @@ -65,6 +68,7 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.views.Stub; import org.whispersystems.libsignal.util.guava.Optional; +import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; @@ -73,6 +77,8 @@ import java.util.concurrent.ExecutionException; import network.loki.messenger.R; +import static android.provider.MediaStore.EXTRA_OUTPUT; + public class AttachmentManager { @@ -112,7 +118,6 @@ public class AttachmentManager { thumbnail.setOnClickListener(new ThumbnailClickListener()); documentView.getBackground().setColorFilter(ThemeUtil.getThemedColor(context, R.attr.conversation_item_bubble_background), PorterDuff.Mode.MULTIPLY); } - } public void clear(@NonNull GlideRequests glideRequests, boolean animate) { @@ -438,25 +443,29 @@ public class AttachmentManager { public void capturePhoto(Activity activity, int requestCode) { Permissions.with(activity) - .request(Manifest.permission.CAMERA) - .ifNecessary() - .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied)) - .onAllGranted(() -> { - try { - Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { - if (captureUri == null) { - captureUri = DeprecatedPersistentBlobProvider.getInstance(context).createForExternal(context, MediaUtil.IMAGE_JPEG); - } - Log.d(TAG, "captureUri path is " + captureUri.getPath()); - captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri); - activity.startActivityForResult(captureIntent, requestCode); - } - } catch (IOException ioe) { - Log.w(TAG, ioe); - } - }) - .execute(); + .request(Manifest.permission.CAMERA) + .ifNecessary() + .withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied)) + .onAllGranted(() -> { + try { + File captureFile = File.createTempFile( + "conversation-capture", + ".jpg", + ExternalStorageUtil.getImageDir(activity)); + Uri captureUri = FileProviderUtil.getUriFor(context, captureFile); + Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + captureIntent.putExtra(EXTRA_OUTPUT, captureUri); + captureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { + Log.d(TAG, "captureUri path is " + captureUri.getPath()); + this.captureUri = captureUri; + activity.startActivityForResult(captureIntent, requestCode); + } + } catch (IOException | NoExternalStorageException e) { + throw new RuntimeException("Error creating image capture intent.", e); + } + }) + .execute(); } private static void selectMediaType(Activity activity, @NonNull String type, @Nullable String[] extraMimeType, int requestCode) { diff --git a/src/org/thoughtcrime/securesms/permissions/Permissions.java b/src/org/thoughtcrime/securesms/permissions/Permissions.java index 70e2da80fb..f647da6db4 100644 --- a/src/org/thoughtcrime/securesms/permissions/Permissions.java +++ b/src/org/thoughtcrime/securesms/permissions/Permissions.java @@ -171,7 +171,7 @@ public class Permissions { } for (String permission : requestedPermissions) { - request.addMapping(permission, permissionObject.shouldShouldPermissionRationale(permission)); + request.addMapping(permission, permissionObject.shouldShowPermissionRationale(permission)); } permissionObject.requestPermissions(requestCode, requestedPermissions); @@ -240,7 +240,7 @@ public class Permissions { for (int i=0;i Date: Tue, 8 Sep 2020 15:33:19 +1000 Subject: [PATCH 4/4] Fixed missing "ifNecessary" permission request flag. --- .../thoughtcrime/securesms/loki/activities/SettingsActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index 45092b0c06..c61ddc94a3 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -256,6 +256,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { // Ask for an optional camera permission. Permissions.with(this) .request(Manifest.permission.CAMERA) + .ifNecessary() .onAnyResult { tempFile = AvatarSelection.startAvatarSelection(this, false, true) }