|
|
|
|
@@ -19,11 +19,8 @@ package org.thoughtcrime.securesms.database;
|
|
|
|
|
import android.content.ContentValues;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.database.Cursor;
|
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
|
import android.media.MediaDataSource;
|
|
|
|
|
import android.media.MediaMetadataRetriever;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.Build;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.util.Pair;
|
|
|
|
|
|
|
|
|
|
@@ -58,14 +55,10 @@ import org.thoughtcrime.securesms.mms.MmsException;
|
|
|
|
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
|
|
|
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
|
|
|
|
import org.thoughtcrime.securesms.util.Base64;
|
|
|
|
|
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
|
|
|
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
|
|
|
|
import org.thoughtcrime.securesms.util.CursorUtil;
|
|
|
|
|
import org.thoughtcrime.securesms.util.FileUtils;
|
|
|
|
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
|
|
|
|
import org.thoughtcrime.securesms.util.MediaMetadataRetrieverUtil;
|
|
|
|
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
|
|
|
|
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
|
|
|
|
|
import org.thoughtcrime.securesms.util.SetUtil;
|
|
|
|
|
import org.thoughtcrime.securesms.util.StorageUtil;
|
|
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
|
|
|
@@ -74,7 +67,6 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|
|
|
|
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.io.OutputStream;
|
|
|
|
|
@@ -90,9 +82,6 @@ import java.util.List;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.concurrent.Callable;
|
|
|
|
|
import java.util.concurrent.ExecutionException;
|
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
|
|
|
|
public class AttachmentDatabase extends Database {
|
|
|
|
|
|
|
|
|
|
@@ -111,8 +100,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
private static final String TRANSFER_FILE = "transfer_file";
|
|
|
|
|
public static final String SIZE = "data_size";
|
|
|
|
|
static final String FILE_NAME = "file_name";
|
|
|
|
|
public static final String THUMBNAIL = "thumbnail";
|
|
|
|
|
static final String THUMBNAIL_ASPECT_RATIO = "aspect_ratio";
|
|
|
|
|
public static final String UNIQUE_ID = "unique_id";
|
|
|
|
|
static final String DIGEST = "digest";
|
|
|
|
|
static final String VOICE_NOTE = "voice_note";
|
|
|
|
|
@@ -124,7 +111,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
static final String STICKER_EMOJI = "sticker_emoji";
|
|
|
|
|
static final String FAST_PREFLIGHT_ID = "fast_preflight_id";
|
|
|
|
|
public static final String DATA_RANDOM = "data_random";
|
|
|
|
|
private static final String THUMBNAIL_RANDOM = "thumbnail_random";
|
|
|
|
|
static final String WIDTH = "width";
|
|
|
|
|
static final String HEIGHT = "height";
|
|
|
|
|
static final String CAPTION = "caption";
|
|
|
|
|
@@ -149,11 +135,10 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
|
|
|
|
|
private static final String[] PROJECTION = new String[] {ROW_ID,
|
|
|
|
|
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
|
|
|
|
|
CDN_NUMBER, CONTENT_LOCATION, DATA, THUMBNAIL,
|
|
|
|
|
TRANSFER_STATE, SIZE, FILE_NAME, THUMBNAIL,
|
|
|
|
|
THUMBNAIL_ASPECT_RATIO, UNIQUE_ID, DIGEST,
|
|
|
|
|
CDN_NUMBER, CONTENT_LOCATION, DATA,
|
|
|
|
|
TRANSFER_STATE, SIZE, FILE_NAME, UNIQUE_ID, DIGEST,
|
|
|
|
|
FAST_PREFLIGHT_ID, VOICE_NOTE, BORDERLESS, QUOTE, DATA_RANDOM,
|
|
|
|
|
THUMBNAIL_RANDOM, WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID,
|
|
|
|
|
WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID,
|
|
|
|
|
STICKER_PACK_KEY, STICKER_ID, STICKER_EMOJI, DATA_HASH, VISUAL_HASH,
|
|
|
|
|
TRANSFORM_PROPERTIES, TRANSFER_FILE, DISPLAY_ORDER,
|
|
|
|
|
UPLOAD_TIMESTAMP };
|
|
|
|
|
@@ -175,15 +160,12 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
DATA + " TEXT, " +
|
|
|
|
|
SIZE + " INTEGER, " +
|
|
|
|
|
FILE_NAME + " TEXT, " +
|
|
|
|
|
THUMBNAIL + " TEXT, " +
|
|
|
|
|
THUMBNAIL_ASPECT_RATIO + " REAL, " +
|
|
|
|
|
UNIQUE_ID + " INTEGER NOT NULL, " +
|
|
|
|
|
DIGEST + " BLOB, " +
|
|
|
|
|
FAST_PREFLIGHT_ID + " TEXT, " +
|
|
|
|
|
VOICE_NOTE + " INTEGER DEFAULT 0, " +
|
|
|
|
|
BORDERLESS + " INTEGER DEFAULT 0, " +
|
|
|
|
|
DATA_RANDOM + " BLOB, " +
|
|
|
|
|
THUMBNAIL_RANDOM + " BLOB, " +
|
|
|
|
|
QUOTE + " INTEGER DEFAULT 0, " +
|
|
|
|
|
WIDTH + " INTEGER DEFAULT 0, " +
|
|
|
|
|
HEIGHT + " INTEGER DEFAULT 0, " +
|
|
|
|
|
@@ -208,10 +190,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS part_data_index ON " + TABLE_NAME + " (" + DATA + ");"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static final long STANDARD_THUMB_TIME = 1000;
|
|
|
|
|
|
|
|
|
|
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
|
|
|
|
|
|
|
|
|
|
private final AttachmentSecret attachmentSecret;
|
|
|
|
|
|
|
|
|
|
public AttachmentDatabase(Context context, SQLCipherOpenHelper databaseHelper, AttachmentSecret attachmentSecret) {
|
|
|
|
|
@@ -228,29 +206,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
else return dataStream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public @NonNull InputStream getThumbnailStream(@NonNull AttachmentId attachmentId)
|
|
|
|
|
throws IOException
|
|
|
|
|
{
|
|
|
|
|
Log.d(TAG, "getThumbnailStream(" + attachmentId + ")");
|
|
|
|
|
InputStream dataStream = getDataStream(attachmentId, THUMBNAIL, 0);
|
|
|
|
|
|
|
|
|
|
if (dataStream != null) {
|
|
|
|
|
return dataStream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
InputStream generatedStream = thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId, STANDARD_THUMB_TIME)).get();
|
|
|
|
|
|
|
|
|
|
if (generatedStream == null) throw new FileNotFoundException("No thumbnail stream available: " + attachmentId);
|
|
|
|
|
else return generatedStream;
|
|
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
|
throw new AssertionError("interrupted");
|
|
|
|
|
} catch (ExecutionException ee) {
|
|
|
|
|
Log.w(TAG, ee);
|
|
|
|
|
throw new IOException(ee);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean containsStickerPackId(@NonNull String stickerPackId) {
|
|
|
|
|
String selection = STICKER_PACK_ID + " = ?";
|
|
|
|
|
String[] args = new String[] { stickerPackId };
|
|
|
|
|
@@ -365,12 +320,11 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
Cursor cursor = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?",
|
|
|
|
|
cursor = database.query(TABLE_NAME, new String[] {DATA, CONTENT_TYPE, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?",
|
|
|
|
|
new String[] {mmsId+""}, null, null, null);
|
|
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
|
deleteAttachmentOnDisk(cursor.getString(cursor.getColumnIndex(DATA)),
|
|
|
|
|
cursor.getString(cursor.getColumnIndex(THUMBNAIL)),
|
|
|
|
|
cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)),
|
|
|
|
|
new AttachmentId(cursor.getLong(cursor.getColumnIndex(ROW_ID)),
|
|
|
|
|
cursor.getLong(cursor.getColumnIndex(UNIQUE_ID))));
|
|
|
|
|
@@ -418,12 +372,11 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
Cursor cursor = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?",
|
|
|
|
|
cursor = database.query(TABLE_NAME, new String[] {DATA, CONTENT_TYPE, ROW_ID, UNIQUE_ID}, MMS_ID + " = ?",
|
|
|
|
|
new String[] {mmsId+""}, null, null, null);
|
|
|
|
|
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
|
deleteAttachmentOnDisk(cursor.getString(cursor.getColumnIndex(DATA)),
|
|
|
|
|
cursor.getString(cursor.getColumnIndex(THUMBNAIL)),
|
|
|
|
|
cursor.getString(cursor.getColumnIndex(CONTENT_TYPE)),
|
|
|
|
|
new AttachmentId(cursor.getLong(cursor.getColumnIndex(ROW_ID)),
|
|
|
|
|
cursor.getLong(cursor.getColumnIndex(UNIQUE_ID))));
|
|
|
|
|
@@ -437,8 +390,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
values.put(DATA, (String) null);
|
|
|
|
|
values.put(DATA_RANDOM, (byte[]) null);
|
|
|
|
|
values.put(DATA_HASH, (String) null);
|
|
|
|
|
values.put(THUMBNAIL, (String) null);
|
|
|
|
|
values.put(THUMBNAIL_RANDOM, (byte[]) null);
|
|
|
|
|
values.put(FILE_NAME, (String) null);
|
|
|
|
|
values.put(CAPTION, (String) null);
|
|
|
|
|
values.put(SIZE, 0);
|
|
|
|
|
@@ -463,7 +414,7 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
|
|
|
|
|
|
try (Cursor cursor = database.query(TABLE_NAME,
|
|
|
|
|
new String[]{DATA, THUMBNAIL, CONTENT_TYPE},
|
|
|
|
|
new String[]{DATA, CONTENT_TYPE},
|
|
|
|
|
PART_ID_WHERE,
|
|
|
|
|
id.toStrings(),
|
|
|
|
|
null,
|
|
|
|
|
@@ -475,11 +426,10 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
String data = cursor.getString(cursor.getColumnIndex(DATA));
|
|
|
|
|
String thumbnail = cursor.getString(cursor.getColumnIndex(THUMBNAIL));
|
|
|
|
|
String contentType = cursor.getString(cursor.getColumnIndex(CONTENT_TYPE));
|
|
|
|
|
|
|
|
|
|
database.delete(TABLE_NAME, PART_ID_WHERE, id.toStrings());
|
|
|
|
|
deleteAttachmentOnDisk(data, thumbnail, contentType, id);
|
|
|
|
|
deleteAttachmentOnDisk(data, contentType, id);
|
|
|
|
|
notifyAttachmentListeners();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -502,10 +452,9 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
filesOnDisk.add(file.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try (Cursor cursor = databaseHelper.getReadableDatabase().query(true, TABLE_NAME, new String[] { DATA, THUMBNAIL }, null, null, null, null, null, null)) {
|
|
|
|
|
try (Cursor cursor = databaseHelper.getReadableDatabase().query(true, TABLE_NAME, new String[] { DATA }, null, null, null, null, null, null)) {
|
|
|
|
|
while (cursor != null && cursor.moveToNext()) {
|
|
|
|
|
filesInDb.add(CursorUtil.requireString(cursor, DATA));
|
|
|
|
|
filesInDb.add(CursorUtil.requireString(cursor, THUMBNAIL));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -530,7 +479,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void deleteAttachmentOnDisk(@Nullable String data,
|
|
|
|
|
@Nullable String thumbnail,
|
|
|
|
|
@Nullable String contentType,
|
|
|
|
|
@NonNull AttachmentId attachmentId)
|
|
|
|
|
{
|
|
|
|
|
@@ -561,8 +509,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
values.putNull(DATA);
|
|
|
|
|
values.putNull(DATA_RANDOM);
|
|
|
|
|
values.putNull(DATA_HASH);
|
|
|
|
|
values.putNull(THUMBNAIL);
|
|
|
|
|
values.putNull(THUMBNAIL_RANDOM);
|
|
|
|
|
deletedCount += database.update(TABLE_NAME, values, PART_ID_WHERE, weakReference.toStrings());
|
|
|
|
|
}
|
|
|
|
|
database.setTransactionSuccessful();
|
|
|
|
|
@@ -581,15 +527,7 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(thumbnail)) {
|
|
|
|
|
if (new File(thumbnail).delete()) {
|
|
|
|
|
Log.i(TAG, "[deleteAttachmentOnDisk] Deleted thumbnail. " + data + " " + attachmentId);
|
|
|
|
|
} else {
|
|
|
|
|
Log.w(TAG, "[deleteAttachmentOnDisk] Failed to delete attachment. " + data + " " + attachmentId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (MediaUtil.isImageType(contentType) || thumbnail != null) {
|
|
|
|
|
if (MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType)) {
|
|
|
|
|
Glide.get(context).clearDiskCache();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -623,22 +561,17 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
DataInfo oldInfo = getAttachmentDataFileInfo(attachmentId, DATA);
|
|
|
|
|
DataInfo dataInfo = setAttachmentData(inputStream, false, attachmentId);
|
|
|
|
|
DataInfo dataInfo = setAttachmentData(inputStream, attachmentId);
|
|
|
|
|
File transferFile = getTransferFile(databaseHelper.getReadableDatabase(), attachmentId);
|
|
|
|
|
|
|
|
|
|
if (oldInfo != null) {
|
|
|
|
|
updateAttachmentDataHash(database, oldInfo.hash, dataInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (placeholder != null && placeholder.isQuote() && !placeholder.getContentType().startsWith("image")) {
|
|
|
|
|
values.put(THUMBNAIL, dataInfo.file.getAbsolutePath());
|
|
|
|
|
values.put(THUMBNAIL_RANDOM, dataInfo.random);
|
|
|
|
|
} else {
|
|
|
|
|
values.put(DATA, dataInfo.file.getAbsolutePath());
|
|
|
|
|
values.put(SIZE, dataInfo.length);
|
|
|
|
|
values.put(DATA_RANDOM, dataInfo.random);
|
|
|
|
|
values.put(DATA_HASH, dataInfo.hash);
|
|
|
|
|
}
|
|
|
|
|
values.put(DATA, dataInfo.file.getAbsolutePath());
|
|
|
|
|
values.put(SIZE, dataInfo.length);
|
|
|
|
|
values.put(DATA_RANDOM, dataInfo.random);
|
|
|
|
|
values.put(DATA_HASH, dataInfo.hash);
|
|
|
|
|
|
|
|
|
|
String visualHashString = getVisualHashStringOrNull(placeholder);
|
|
|
|
|
if (visualHashString != null) {
|
|
|
|
|
@@ -662,8 +595,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
//noinspection ResultOfMethodCallIgnored
|
|
|
|
|
transferFile.delete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId, STANDARD_THUMB_TIME));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static @Nullable String getVisualHashStringOrNull(@Nullable Attachment attachment) {
|
|
|
|
|
@@ -846,7 +777,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
|
|
|
|
|
DataInfo dataInfo = setAttachmentData(destination,
|
|
|
|
|
mediaStream.getStream(),
|
|
|
|
|
false,
|
|
|
|
|
databaseAttachment.getAttachmentId());
|
|
|
|
|
|
|
|
|
|
ContentValues contentValues = new ContentValues();
|
|
|
|
|
@@ -1055,16 +985,8 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
|
|
|
|
Cursor cursor = null;
|
|
|
|
|
|
|
|
|
|
String randomColumn;
|
|
|
|
|
|
|
|
|
|
switch (dataType) {
|
|
|
|
|
case DATA: randomColumn = DATA_RANDOM; break;
|
|
|
|
|
case THUMBNAIL: randomColumn = THUMBNAIL_RANDOM; break;
|
|
|
|
|
default:throw new AssertionError("Unknown data type: " + dataType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
cursor = database.query(TABLE_NAME, new String[]{dataType, SIZE, randomColumn, DATA_HASH}, PART_ID_WHERE, attachmentId.toStrings(),
|
|
|
|
|
cursor = database.query(TABLE_NAME, new String[]{dataType, SIZE, DATA_RANDOM, DATA_HASH}, PART_ID_WHERE, attachmentId.toStrings(),
|
|
|
|
|
null, null, null);
|
|
|
|
|
|
|
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
|
@@ -1074,7 +996,7 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
|
|
|
|
|
return new DataInfo(new File(cursor.getString(cursor.getColumnIndexOrThrow(dataType))),
|
|
|
|
|
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
|
|
|
|
cursor.getBlob(cursor.getColumnIndexOrThrow(randomColumn)),
|
|
|
|
|
cursor.getBlob(cursor.getColumnIndexOrThrow(DATA_RANDOM)),
|
|
|
|
|
cursor.getString(cursor.getColumnIndexOrThrow(DATA_HASH)));
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
@@ -1087,26 +1009,24 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private @NonNull DataInfo setAttachmentData(@NonNull Uri uri,
|
|
|
|
|
boolean isThumbnail,
|
|
|
|
|
@Nullable AttachmentId attachmentId)
|
|
|
|
|
throws MmsException
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
|
|
|
|
return setAttachmentData(inputStream, isThumbnail, attachmentId);
|
|
|
|
|
return setAttachmentData(inputStream, attachmentId);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
throw new MmsException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private @NonNull DataInfo setAttachmentData(@NonNull InputStream in,
|
|
|
|
|
boolean isThumbnail,
|
|
|
|
|
@Nullable AttachmentId attachmentId)
|
|
|
|
|
throws MmsException
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
File dataFile = newFile();
|
|
|
|
|
return setAttachmentData(dataFile, in, isThumbnail, attachmentId);
|
|
|
|
|
return setAttachmentData(dataFile, in, attachmentId);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
throw new MmsException(e);
|
|
|
|
|
}
|
|
|
|
|
@@ -1119,7 +1039,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
|
|
|
|
|
private @NonNull DataInfo setAttachmentData(@NonNull File destination,
|
|
|
|
|
@NonNull InputStream in,
|
|
|
|
|
boolean isThumbnail,
|
|
|
|
|
@Nullable AttachmentId attachmentId)
|
|
|
|
|
throws MmsException
|
|
|
|
|
{
|
|
|
|
|
@@ -1130,18 +1049,16 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
long length = Util.copy(digestInputStream, out.second);
|
|
|
|
|
String hash = Base64.encodeBytes(digestInputStream.getMessageDigest().digest());
|
|
|
|
|
|
|
|
|
|
if (!isThumbnail) {
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
|
Optional<DataInfo> sharedDataInfo = findDuplicateDataFileInfo(database, hash, attachmentId);
|
|
|
|
|
if (sharedDataInfo.isPresent()) {
|
|
|
|
|
Log.i(TAG, "[setAttachmentData] Duplicate data file found! " + sharedDataInfo.get().file.getAbsolutePath());
|
|
|
|
|
if (!destination.equals(sharedDataInfo.get().file) && destination.delete()) {
|
|
|
|
|
Log.i(TAG, "[setAttachmentData] Deleted original file. " + destination);
|
|
|
|
|
}
|
|
|
|
|
return sharedDataInfo.get();
|
|
|
|
|
} else {
|
|
|
|
|
Log.i(TAG, "[setAttachmentData] No matching attachment data found. " + destination.getAbsolutePath());
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
|
Optional<DataInfo> sharedDataInfo = findDuplicateDataFileInfo(database, hash, attachmentId);
|
|
|
|
|
if (sharedDataInfo.isPresent()) {
|
|
|
|
|
Log.i(TAG, "[setAttachmentData] Duplicate data file found! " + sharedDataInfo.get().file.getAbsolutePath());
|
|
|
|
|
if (!destination.equals(sharedDataInfo.get().file) && destination.delete()) {
|
|
|
|
|
Log.i(TAG, "[setAttachmentData] Deleted original file. " + destination);
|
|
|
|
|
}
|
|
|
|
|
return sharedDataInfo.get();
|
|
|
|
|
} else {
|
|
|
|
|
Log.i(TAG, "[setAttachmentData] No matching attachment data found. " + destination.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new DataInfo(destination, length, out.first, hash);
|
|
|
|
|
@@ -1216,7 +1133,7 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
result.add(new DatabaseAttachment(new AttachmentId(object.getLong(ROW_ID), object.getLong(UNIQUE_ID)),
|
|
|
|
|
object.getLong(MMS_ID),
|
|
|
|
|
!TextUtils.isEmpty(object.getString(DATA)),
|
|
|
|
|
!TextUtils.isEmpty(object.getString(THUMBNAIL)),
|
|
|
|
|
MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType),
|
|
|
|
|
contentType,
|
|
|
|
|
object.getInt(TRANSFER_STATE),
|
|
|
|
|
object.getLong(SIZE),
|
|
|
|
|
@@ -1254,7 +1171,7 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
|
|
|
|
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
|
|
|
|
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
|
|
|
|
|
!cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)),
|
|
|
|
|
MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType),
|
|
|
|
|
contentType,
|
|
|
|
|
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
|
|
|
|
|
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
|
|
|
|
@@ -1296,10 +1213,9 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
|
DataInfo dataInfo = null;
|
|
|
|
|
long uniqueId = System.currentTimeMillis();
|
|
|
|
|
long thumbnailTimeUs;
|
|
|
|
|
|
|
|
|
|
if (attachment.getDataUri() != null) {
|
|
|
|
|
dataInfo = setAttachmentData(attachment.getDataUri(), false, null);
|
|
|
|
|
if (attachment.getUri() != null) {
|
|
|
|
|
dataInfo = setAttachmentData(attachment.getUri(), null);
|
|
|
|
|
Log.d(TAG, "Wrote part to file: " + dataInfo.file.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1342,11 +1258,9 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
if (attachment.getTransformProperties().isVideoEdited()) {
|
|
|
|
|
contentValues.putNull(VISUAL_HASH);
|
|
|
|
|
contentValues.put(TRANSFORM_PROPERTIES, attachment.getTransformProperties().serialize());
|
|
|
|
|
thumbnailTimeUs = Math.max(STANDARD_THUMB_TIME, attachment.getTransformProperties().videoTrimStartTimeUs);
|
|
|
|
|
} else {
|
|
|
|
|
contentValues.put(VISUAL_HASH, getVisualHashStringOrNull(template));
|
|
|
|
|
contentValues.put(TRANSFORM_PROPERTIES, template.getTransformProperties().serialize());
|
|
|
|
|
thumbnailTimeUs = STANDARD_THUMB_TIME;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attachment.isSticker()) {
|
|
|
|
|
@@ -1370,38 +1284,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
boolean notifyPacks = attachment.isSticker() && !hasStickerAttachments();
|
|
|
|
|
long rowId = database.insert(TABLE_NAME, null, contentValues);
|
|
|
|
|
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
|
|
|
|
|
Uri thumbnailUri = attachment.getThumbnailUri();
|
|
|
|
|
boolean hasThumbnail = false;
|
|
|
|
|
|
|
|
|
|
if (thumbnailUri != null) {
|
|
|
|
|
try (InputStream attachmentStream = PartAuthority.getAttachmentStream(context, thumbnailUri)) {
|
|
|
|
|
Pair<Integer, Integer> dimens = BitmapUtil.getDimensions(attachmentStream);
|
|
|
|
|
updateAttachmentThumbnail(attachmentId,
|
|
|
|
|
PartAuthority.getAttachmentStream(context, thumbnailUri),
|
|
|
|
|
(float) dimens.first / (float) dimens.second);
|
|
|
|
|
hasThumbnail = true;
|
|
|
|
|
} catch (IOException | BitmapDecodingException e) {
|
|
|
|
|
Log.w(TAG, "Failed to save existing thumbnail.", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!hasThumbnail && dataInfo != null) {
|
|
|
|
|
if (MediaUtil.hasVideoThumbnail(attachment.getDataUri()) && thumbnailTimeUs == STANDARD_THUMB_TIME) {
|
|
|
|
|
Bitmap bitmap = MediaUtil.getVideoThumbnail(context, attachment.getDataUri(), thumbnailTimeUs);
|
|
|
|
|
|
|
|
|
|
if (bitmap != null) {
|
|
|
|
|
try (ThumbnailData thumbnailData = new ThumbnailData(bitmap)) {
|
|
|
|
|
updateAttachmentThumbnail(attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.w(TAG, "Retrieving video thumbnail failed, submitting thumbnail generation job...");
|
|
|
|
|
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId, thumbnailTimeUs));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.i(TAG, "Submitting thumbnail generation job...");
|
|
|
|
|
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId, thumbnailTimeUs));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (notifyPacks) {
|
|
|
|
|
notifyStickerPackListeners();
|
|
|
|
|
@@ -1423,35 +1305,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("WeakerAccess")
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
protected void updateAttachmentThumbnail(AttachmentId attachmentId, InputStream in, float aspectRatio)
|
|
|
|
|
throws MmsException
|
|
|
|
|
{
|
|
|
|
|
Log.i(TAG, "updating part thumbnail for #" + attachmentId);
|
|
|
|
|
|
|
|
|
|
DataInfo thumbnailFile = setAttachmentData(in, true, attachmentId);
|
|
|
|
|
|
|
|
|
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
|
|
|
|
ContentValues values = new ContentValues(2);
|
|
|
|
|
|
|
|
|
|
values.put(THUMBNAIL, thumbnailFile.file.getAbsolutePath());
|
|
|
|
|
values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio);
|
|
|
|
|
values.put(THUMBNAIL_RANDOM, thumbnailFile.random);
|
|
|
|
|
|
|
|
|
|
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
|
|
|
|
|
|
|
|
|
|
Cursor cursor = database.query(TABLE_NAME, new String[] {MMS_ID}, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
|
|
|
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID))));
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
if (cursor != null) cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@WorkerThread
|
|
|
|
|
public void writeAudioHash(@NonNull AttachmentId attachmentId, @Nullable AudioWaveFormData audioWaveForm) {
|
|
|
|
|
Log.i(TAG, "updating part audio wave form for #" + attachmentId);
|
|
|
|
|
@@ -1468,66 +1321,6 @@ public class AttachmentDatabase extends Database {
|
|
|
|
|
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
class ThumbnailFetchCallable implements Callable<InputStream> {
|
|
|
|
|
|
|
|
|
|
private final AttachmentId attachmentId;
|
|
|
|
|
private final long timeUs;
|
|
|
|
|
|
|
|
|
|
ThumbnailFetchCallable(AttachmentId attachmentId, long timeUs) {
|
|
|
|
|
this.attachmentId = attachmentId;
|
|
|
|
|
this.timeUs = timeUs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public @Nullable InputStream call() throws Exception {
|
|
|
|
|
Log.d(TAG, "Executing thumbnail job...");
|
|
|
|
|
final InputStream stream = getDataStream(attachmentId, THUMBNAIL, 0);
|
|
|
|
|
|
|
|
|
|
if (stream != null) {
|
|
|
|
|
return stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DatabaseAttachment attachment = getAttachment(attachmentId);
|
|
|
|
|
|
|
|
|
|
if (attachment == null || !attachment.hasData()) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (MediaUtil.isVideoType(attachment.getContentType())) {
|
|
|
|
|
|
|
|
|
|
try (ThumbnailData data = generateVideoThumbnail(attachmentId, timeUs)) {
|
|
|
|
|
|
|
|
|
|
if (data != null) {
|
|
|
|
|
updateAttachmentThumbnail(attachmentId, data.toDataStream(), data.getAspectRatio());
|
|
|
|
|
|
|
|
|
|
return getDataStream(attachmentId, THUMBNAIL, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ThumbnailData generateVideoThumbnail(AttachmentId attachmentId, long timeUs) throws IOException {
|
|
|
|
|
if (Build.VERSION.SDK_INT < 23) {
|
|
|
|
|
Log.w(TAG, "Video thumbnails not supported...");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try (MediaDataSource dataSource = mediaDataSourceFor(attachmentId)) {
|
|
|
|
|
if (dataSource == null) return null;
|
|
|
|
|
|
|
|
|
|
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
|
|
|
MediaMetadataRetrieverUtil.setDataSource(retriever, dataSource);
|
|
|
|
|
|
|
|
|
|
Bitmap bitmap = retriever.getFrameAtTime(timeUs);
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "Generated video thumbnail...");
|
|
|
|
|
return bitmap != null ? new ThumbnailData(bitmap) : null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@RequiresApi(23)
|
|
|
|
|
public @Nullable MediaDataSource mediaDataSourceFor(@NonNull AttachmentId attachmentId) {
|
|
|
|
|
|