Fix mediastore access for Android Q.

This commit is contained in:
Alex Hart
2020-10-19 18:16:29 -03:00
committed by GitHub
parent 3163e09b98
commit dc64a186d5
14 changed files with 247 additions and 200 deletions

View File

@@ -244,12 +244,12 @@ public class BackupUtil {
private final long timestamp;
private final long size;
private final Uri uri;
private final Uri uri;
BackupInfo(long timestamp, long size, Uri uri) {
this.timestamp = timestamp;
this.size = size;
this.uri = uri;
this.size = size;
this.uri = uri;
}
public long getTimestamp() {

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.util;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.media.MediaScannerConnection;
@@ -7,6 +8,10 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.documentfile.provider.DocumentFile;
import android.os.Build;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
@@ -59,7 +64,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
Context context = contextReference.get();
String directory = null;
if (!StorageUtil.canWriteInSignalStorageDir()) {
if (!StorageUtil.canWriteToMediaStore()) {
return new Pair<>(WRITE_ACCESS_FAILURE, null);
}
@@ -76,14 +81,13 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
if (attachments.length > 1) return new Pair<>(SUCCESS, null);
else return new Pair<>(SUCCESS, directory);
} catch (NoExternalStorageException|IOException ioe) {
} catch (IOException ioe) {
Log.w(TAG, ioe);
return new Pair<>(FAILURE, null);
}
}
private @Nullable String saveAttachment(Context context, Attachment attachment)
throws NoExternalStorageException, IOException
private @Nullable String saveAttachment(Context context, Attachment attachment) throws IOException
{
String contentType = MediaUtil.getCorrectedMimeType(attachment.contentType);
String fileName = attachment.fileName;
@@ -91,40 +95,46 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
if (fileName == null) fileName = generateOutputFileName(contentType, attachment.date);
fileName = sanitizeOutputFileName(fileName);
File outputDirectory = createOutputDirectoryFromContentType(contentType);
File mediaFile = createOutputFile(outputDirectory, fileName);
InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.uri);
Uri outputUri = getMediaStoreContentUriForType(contentType);
Uri mediaUri = createOutputUri(outputUri, fileName);
if (inputStream == null) {
return null;
try (InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.uri)) {
if (inputStream == null) {
return null;
}
if (outputUri.equals(StorageUtil.getLegacyDownloadUri())) {
try (OutputStream outputStream = new FileOutputStream(mediaUri.getPath())) {
Util.copy(inputStream, outputStream);
MediaScannerConnection.scanFile(context, new String[]{mediaUri.getPath()}, new String[]{contentType}, null);
}
}
try (OutputStream outputStream = context.getContentResolver().openOutputStream(mediaUri)) {
Util.copy(inputStream, outputStream);
}
}
OutputStream outputStream = new FileOutputStream(mediaFile);
Util.copy(inputStream, outputStream);
if (Build.VERSION.SDK_INT > 28) {
ContentValues updatePendingValues = new ContentValues();
updatePendingValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
getContext().getContentResolver().update(mediaUri, updatePendingValues, null, null);
}
MediaScannerConnection.scanFile(context, new String[]{mediaFile.getAbsolutePath()},
new String[]{contentType}, null);
return outputDirectory.getName();
return outputUri.getLastPathSegment();
}
private File createOutputDirectoryFromContentType(@NonNull String contentType)
throws NoExternalStorageException
{
File outputDirectory;
private @NonNull Uri getMediaStoreContentUriForType(@NonNull String contentType) {
if (contentType.startsWith("video/")) {
outputDirectory = StorageUtil.getVideoDir();
return StorageUtil.getVideoUri();
} else if (contentType.startsWith("audio/")) {
outputDirectory = StorageUtil.getAudioDir();
return StorageUtil.getAudioUri();
} else if (contentType.startsWith("image/")) {
outputDirectory = StorageUtil.getImageDir();
return StorageUtil.getImageUri();
} else {
outputDirectory = StorageUtil.getDownloadDir();
return StorageUtil.getDownloadUri();
}
if (!outputDirectory.mkdirs()) Log.w(TAG, "mkdirs() returned false, attempting to continue");
return outputDirectory;
}
private String generateOutputFileName(@NonNull String contentType, long timestamp) {
@@ -142,25 +152,34 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
return new File(fileName).getName();
}
private File createOutputFile(@NonNull File outputDirectory, @NonNull String fileName)
throws IOException
{
String[] fileParts = getFileNameParts(fileName);
String base = fileParts[0];
String extension = fileParts[1];
private Uri createOutputUri(@NonNull Uri outputUri, @NonNull String fileName) throws IOException {
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
File outputFile = new File(outputDirectory, base + "." + extension);
int i = 0;
while (outputFile.exists()) {
outputFile = new File(outputDirectory, base + "-" + (++i) + "." + extension);
if (Build.VERSION.SDK_INT > 28) {
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
}
if (outputFile.isHidden()) {
throw new IOException("Specified name would not be visible");
if (Build.VERSION.SDK_INT <= 28 && outputUri.equals(StorageUtil.getLegacyDownloadUri())) {
String[] fileParts = getFileNameParts(fileName);
String base = fileParts[0];
String extension = fileParts[1];
File outputDirectory = new File(outputUri.getPath());
File outputFile = new File(outputDirectory, base + "." + extension);
int i = 0;
while (outputFile.exists()) {
outputFile = new File(outputDirectory, base + "-" + (++i) + "." + extension);
}
if (outputFile.isHidden()) {
throw new IOException("Specified name would not be visible");
}
return Uri.fromFile(outputFile);
}
return outputFile;
return getContext().getContentResolver().insert(outputUri, contentValues);
}
private String[] getFileNameParts(String fileName) {

View File

@@ -1,12 +1,19 @@
package org.thoughtcrime.securesms.util;
import android.Manifest;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.permissions.Permissions;
import java.io.File;
@@ -68,20 +75,37 @@ public class StorageUtil {
return getSignalStorageDir();
}
public static File getVideoDir() throws NoExternalStorageException {
return new File(getSignalStorageDir(), Environment.DIRECTORY_MOVIES);
public static boolean canWriteToMediaStore() {
return Build.VERSION.SDK_INT > 28 ||
Permissions.hasAll(ApplicationDependencies.getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
public static File getAudioDir() throws NoExternalStorageException {
return new File(getSignalStorageDir(), Environment.DIRECTORY_MUSIC);
public static boolean canReadFromMediaStore() {
return Permissions.hasAll(ApplicationDependencies.getApplication(), Manifest.permission.READ_EXTERNAL_STORAGE);
}
public static File getImageDir() throws NoExternalStorageException {
return new File(getSignalStorageDir(), Environment.DIRECTORY_PICTURES);
public static @NonNull Uri getVideoUri() {
return MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}
public static File getDownloadDir() throws NoExternalStorageException {
return new File(getSignalStorageDir(), Environment.DIRECTORY_DOWNLOADS);
public static @NonNull Uri getAudioUri() {
return MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
public static @NonNull Uri getImageUri() {
return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
public static @NonNull Uri getDownloadUri() {
if (Build.VERSION.SDK_INT > 28) {
return MediaStore.Downloads.EXTERNAL_CONTENT_URI;
} else {
return getLegacyDownloadUri();
}
}
public static @NonNull Uri getLegacyDownloadUri() {
return Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
}
public static @Nullable String getCleanFileName(@Nullable String fileName) {