mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 11:51:10 +01:00
Fix mediastore access for Android Q.
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user