mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
import of Signal's code to deal with attachments saving
This commit is contained in:
parent
9f26436041
commit
a5e376b616
@ -1,26 +1,30 @@
|
|||||||
package org.thoughtcrime.securesms.util
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface.OnClickListener
|
import android.content.DialogInterface.OnClickListener
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.task.ProgressDialogAsyncTask
|
||||||
|
import org.session.libsignal.utilities.externalstorage.ExternalStorageUtil
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||||
import org.session.libsession.utilities.task.ProgressDialogAsyncTask
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import kotlin.jvm.Throws
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import org.session.libsession.utilities.Util
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves attachment files to an external storage using [MediaStore] API.
|
* Saves attachment files to an external storage using [MediaStore] API.
|
||||||
@ -93,13 +97,14 @@ class SaveAttachmentTask : ProgressDialogAsyncTask<SaveAttachmentTask.Attachment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
/*@Throws(IOException::class)
|
||||||
private fun saveAttachment(context: Context, attachment: Attachment): String? {
|
private fun saveAttachment(context: Context, attachment: Attachment): String? {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
|
|
||||||
val contentType = MediaUtil.getCorrectedMimeType(attachment.contentType)!!
|
val contentType = MediaUtil.getCorrectedMimeType(attachment.contentType)!!
|
||||||
val fileName = attachment.fileName
|
val fileName = if (attachment.fileName != null) sanitizeOutputFileName(attachment.fileName)
|
||||||
?: sanitizeOutputFileName(generateOutputFileName(contentType, attachment.date))
|
else sanitizeOutputFileName(generateOutputFileName(contentType, attachment.date))
|
||||||
|
|
||||||
|
|
||||||
val mediaRecord = ContentValues()
|
val mediaRecord = ContentValues()
|
||||||
val mediaVolume = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
val mediaVolume = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||||
@ -121,10 +126,14 @@ class SaveAttachmentTask : ProgressDialogAsyncTask<SaveAttachmentTask.Attachment
|
|||||||
}
|
}
|
||||||
contentType.startsWith("audio/") -> {
|
contentType.startsWith("audio/") -> {
|
||||||
collectionUri = MediaStore.Audio.Media.getContentUri(mediaVolume)
|
collectionUri = MediaStore.Audio.Media.getContentUri(mediaVolume)
|
||||||
mediaRecord.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName)
|
mediaRecord.put(MediaStore.Audio.Media.TITLE, "test")
|
||||||
|
mediaRecord.put(MediaStore.Audio.Media.DISPLAY_NAME, "test")
|
||||||
mediaRecord.put(MediaStore.Audio.Media.MIME_TYPE, contentType)
|
mediaRecord.put(MediaStore.Audio.Media.MIME_TYPE, contentType)
|
||||||
mediaRecord.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis())
|
mediaRecord.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis())
|
||||||
mediaRecord.put(MediaStore.Audio.Media.DATE_TAKEN, System.currentTimeMillis())
|
mediaRecord.put(MediaStore.Audio.Media.DATE_TAKEN, System.currentTimeMillis())
|
||||||
|
val directory = context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)
|
||||||
|
mediaRecord.put(MediaStore.Audio.Media.DATA, String.format("%s/%s", directory, fileName))
|
||||||
|
//collectionUri = directory?.toUri()!!
|
||||||
|
|
||||||
}
|
}
|
||||||
contentType.startsWith("image/") -> {
|
contentType.startsWith("image/") -> {
|
||||||
@ -160,6 +169,124 @@ class SaveAttachmentTask : ProgressDialogAsyncTask<SaveAttachmentTask.Attachment
|
|||||||
}
|
}
|
||||||
|
|
||||||
return mediaFileUri.toString()
|
return mediaFileUri.toString()
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun saveAttachment(context: Context, attachment: Attachment): String? {
|
||||||
|
val contentType = Objects.requireNonNull(MediaUtil.getCorrectedMimeType(attachment.contentType))!!
|
||||||
|
var fileName = attachment.fileName
|
||||||
|
if (fileName == null) fileName = generateOutputFileName(contentType, attachment.date)
|
||||||
|
fileName = sanitizeOutputFileName(fileName)
|
||||||
|
val outputUri: Uri = getMediaStoreContentUriForType(contentType)
|
||||||
|
val mediaUri = createOutputUri(outputUri, contentType, fileName)
|
||||||
|
val updateValues = ContentValues()
|
||||||
|
PartAuthority.getAttachmentStream(context, attachment.uri).use { inputStream ->
|
||||||
|
if (inputStream == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (outputUri.scheme == ContentResolver.SCHEME_FILE) {
|
||||||
|
FileOutputStream(mediaUri!!.path).use { outputStream ->
|
||||||
|
StreamUtil.copy(inputStream, outputStream)
|
||||||
|
MediaScannerConnection.scanFile(context, arrayOf(mediaUri.path), arrayOf(contentType), null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.contentResolver.openOutputStream(mediaUri!!, "w").use { outputStream ->
|
||||||
|
val total: Long = StreamUtil.copy(inputStream, outputStream)
|
||||||
|
if (total > 0) {
|
||||||
|
updateValues.put(MediaStore.MediaColumns.SIZE, total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT > 28) {
|
||||||
|
updateValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
||||||
|
}
|
||||||
|
if (updateValues.size() > 0) {
|
||||||
|
getContext().contentResolver.update(mediaUri!!, updateValues, null, null)
|
||||||
|
}
|
||||||
|
return outputUri.lastPathSegment
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMediaStoreContentUriForType(contentType: String): Uri {
|
||||||
|
return when {
|
||||||
|
contentType.startsWith("video/") ->
|
||||||
|
ExternalStorageUtil.getVideoUri()
|
||||||
|
contentType.startsWith("audio/") ->
|
||||||
|
ExternalStorageUtil.getAudioUri()
|
||||||
|
contentType.startsWith("image/") ->
|
||||||
|
ExternalStorageUtil.getImageUri()
|
||||||
|
else ->
|
||||||
|
ExternalStorageUtil.getDownloadUri()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun createOutputUri(outputUri: Uri, contentType: String, fileName: String): Uri? {
|
||||||
|
val fileParts: Array<String> = getFileNameParts(fileName)
|
||||||
|
val base = fileParts[0]
|
||||||
|
val extension = fileParts[1]
|
||||||
|
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||||
|
val contentValues = ContentValues()
|
||||||
|
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
|
||||||
|
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
||||||
|
contentValues.put(MediaStore.MediaColumns.DATE_ADDED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))
|
||||||
|
contentValues.put(MediaStore.MediaColumns.DATE_MODIFIED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))
|
||||||
|
if (Build.VERSION.SDK_INT > 28) {
|
||||||
|
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
|
} else if (Objects.equals(outputUri.scheme, ContentResolver.SCHEME_FILE)) {
|
||||||
|
val outputDirectory = File(outputUri.path)
|
||||||
|
var outputFile = File(outputDirectory, "$base.$extension")
|
||||||
|
var i = 0
|
||||||
|
while (outputFile.exists()) {
|
||||||
|
outputFile = File(outputDirectory, base + "-" + ++i + "." + extension)
|
||||||
|
}
|
||||||
|
if (outputFile.isHidden) {
|
||||||
|
throw IOException("Specified name would not be visible")
|
||||||
|
}
|
||||||
|
return Uri.fromFile(outputFile)
|
||||||
|
} else {
|
||||||
|
var outputFileName = fileName
|
||||||
|
var dataPath = java.lang.String.format("%s/%s", getMediaStoreContentUriForType(contentType), outputFileName)
|
||||||
|
var i = 0
|
||||||
|
while (pathTaken(outputUri, dataPath)) {
|
||||||
|
Log.d(TAG, "The content exists. Rename and check again.")
|
||||||
|
outputFileName = base + "-" + ++i + "." + extension
|
||||||
|
dataPath = java.lang.String.format("%s/%s", getMediaStoreContentUriForType(contentType), outputFileName)
|
||||||
|
}
|
||||||
|
contentValues.put(MediaStore.MediaColumns.DATA, dataPath)
|
||||||
|
}
|
||||||
|
return context.contentResolver.insert(outputUri, contentValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileNameParts(fileName: String): Array<String> {
|
||||||
|
val tokens = fileName.split("\\.(?=[^\\.]+$)".toRegex()).toTypedArray()
|
||||||
|
return arrayOf(tokens[0], if (tokens.size > 1) tokens[1] else "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getExternalPathToFileForType(contentType: String): String {
|
||||||
|
val storage: File = when {
|
||||||
|
contentType.startsWith("video/") ->
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
|
||||||
|
contentType.startsWith("audio/") ->
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
|
||||||
|
contentType.startsWith("image/") ->
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||||
|
else ->
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
|
||||||
|
}
|
||||||
|
return storage.absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun pathTaken(outputUri: Uri, dataPath: String): Boolean {
|
||||||
|
context.contentResolver.query(outputUri, arrayOf(MediaStore.MediaColumns.DATA),
|
||||||
|
MediaStore.MediaColumns.DATA + " = ?", arrayOf(dataPath),
|
||||||
|
null).use { cursor ->
|
||||||
|
if (cursor == null) {
|
||||||
|
throw IOException("Something is wrong with the filename to save")
|
||||||
|
}
|
||||||
|
return cursor.moveToFirst()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateOutputFileName(contentType: String, timestamp: Long): String {
|
private fun generateOutputFileName(contentType: String, timestamp: Long): String {
|
||||||
@ -177,8 +304,7 @@ class SaveAttachmentTask : ProgressDialogAsyncTask<SaveAttachmentTask.Attachment
|
|||||||
|
|
||||||
override fun onPostExecute(result: Pair<Int, String?>) {
|
override fun onPostExecute(result: Pair<Int, String?>) {
|
||||||
super.onPostExecute(result)
|
super.onPostExecute(result)
|
||||||
val context = contextReference.get()
|
val context = contextReference.get() ?: return
|
||||||
if (context == null) return
|
|
||||||
|
|
||||||
when (result.first) {
|
when (result.first) {
|
||||||
RESULT_FAILURE -> {
|
RESULT_FAILURE -> {
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.session.libsignal.utilities.logging.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for input and output streams.
|
||||||
|
*/
|
||||||
|
public final class StreamUtil {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(StreamUtil.class);
|
||||||
|
|
||||||
|
private StreamUtil() {}
|
||||||
|
|
||||||
|
public static void close(@Nullable Closeable closeable) {
|
||||||
|
if (closeable == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getStreamLength(InputStream in) throws IOException {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int totalSize = 0;
|
||||||
|
|
||||||
|
int read;
|
||||||
|
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
totalSize += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||||
|
readFully(in, buffer, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readFully(InputStream in, byte[] buffer, int len) throws IOException {
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int read = in.read(buffer, offset, len - offset);
|
||||||
|
if (read == -1) throw new EOFException("Stream ended early");
|
||||||
|
|
||||||
|
if (read + offset < len) offset += read;
|
||||||
|
else return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] readFully(InputStream in) throws IOException {
|
||||||
|
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
bout.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
return bout.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readFullyAsString(InputStream in) throws IOException {
|
||||||
|
return new String(readFully(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long copy(InputStream in, OutputStream out) throws IOException {
|
||||||
|
byte[] buffer = new byte[64 * 1024];
|
||||||
|
int read;
|
||||||
|
long total = 0;
|
||||||
|
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
total += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
package org.session.libsignal.utilities.externalstorage
|
package org.session.libsignal.utilities.externalstorage
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object ExternalStorageUtil {
|
object ExternalStorageUtil {
|
||||||
@ -45,6 +48,30 @@ object ExternalStorageUtil {
|
|||||||
return context.externalCacheDir
|
return context.externalCacheDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getVideoUri(): Uri {
|
||||||
|
return MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAudioUri(): Uri {
|
||||||
|
return MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImageUri(): Uri {
|
||||||
|
return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDownloadUri(): Uri {
|
||||||
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
|
return getLegacyUri(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
} else {
|
||||||
|
return MediaStore.Downloads.EXTERNAL_CONTENT_URI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLegacyUri(directory: String): Uri {
|
||||||
|
return Uri.fromFile(Environment.getExternalStoragePublicDirectory(directory))
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getCleanFileName(fileName: String?): String? {
|
fun getCleanFileName(fileName: String?): String? {
|
||||||
var fileName = fileName ?: return null
|
var fileName = fileName ?: return null
|
||||||
|
Loading…
Reference in New Issue
Block a user