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