Convert StickerTable to kotlin.
This commit is contained in:
parent
1509e3ed79
commit
465c852e8b
23 changed files with 574 additions and 819 deletions
|
@ -483,7 +483,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||
long estimatedCount)
|
||||
throws IOException
|
||||
{
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerTable._ID));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerTable.ID));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerTable.FILE_LENGTH));
|
||||
|
||||
String data = cursor.getString(cursor.getColumnIndexOrThrow(StickerTable.FILE_PATH));
|
||||
|
|
|
@ -239,7 +239,7 @@ public class FullBackupImporter extends FullBackupBase {
|
|||
contentValues.put(StickerTable.FILE_RANDOM, output.first);
|
||||
|
||||
db.update(StickerTable.TABLE_NAME, contentValues,
|
||||
StickerTable._ID + " = ?",
|
||||
StickerTable.ID + " = ?",
|
||||
new String[] {String.valueOf(sticker.rowId)});
|
||||
}
|
||||
|
||||
|
|
|
@ -24,14 +24,14 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
|
|||
*/
|
||||
object StickerArchiveProcessor {
|
||||
fun export(db: SignalDatabase, emitter: BackupFrameEmitter) {
|
||||
StickerPackRecordReader(db.stickerTable.allStickerPacks).use { reader ->
|
||||
var record: StickerPackRecord? = reader.next
|
||||
StickerPackRecordReader(db.stickerTable.getAllStickerPacks()).use { reader ->
|
||||
var record: StickerPackRecord? = reader.getNext()
|
||||
while (record != null) {
|
||||
if (record.isInstalled) {
|
||||
val frame = record.toBackupFrame()
|
||||
emitter.emit(frame)
|
||||
}
|
||||
record = reader.next
|
||||
record = reader.getNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class ConversationStickerSuggestionAdapter extends RecyclerView.Adapter<C
|
|||
}
|
||||
|
||||
void bind(@NonNull RequestManager requestManager, @NonNull EventListener eventListener, @NonNull StickerRecord sticker) {
|
||||
requestManager.load(new DecryptableUri(sticker.getUri()))
|
||||
requestManager.load(new DecryptableUri(sticker.uri))
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.fitCenter()
|
||||
.into(image);
|
||||
|
|
|
@ -930,7 +930,7 @@ class AttachmentTable(
|
|||
cursor.requireString(THUMBNAIL_FILE)?.let { filesInDb += it }
|
||||
}
|
||||
|
||||
filesInDb += SignalDatabase.stickers.allStickerFiles
|
||||
filesInDb += SignalDatabase.stickers.getAllStickerFiles()
|
||||
|
||||
val onDiskButNotInDatabase: Set<String> = filesOnDisk - filesInDb
|
||||
|
||||
|
|
|
@ -1,518 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.model.IncomingSticker;
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
|
||||
import org.signal.core.util.CursorUtil;
|
||||
import org.signal.core.util.SqlUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class StickerTable extends DatabaseTable {
|
||||
|
||||
private static final String TAG = Log.tag(StickerTable.class);
|
||||
|
||||
public static final String TABLE_NAME = "sticker";
|
||||
public static final String _ID = "_id";
|
||||
public static final String PACK_ID = "pack_id";
|
||||
public static final String PACK_KEY = "pack_key";
|
||||
public static final String PACK_TITLE = "pack_title";
|
||||
public static final String PACK_AUTHOR = "pack_author";
|
||||
private static final String STICKER_ID = "sticker_id";
|
||||
public static final String EMOJI = "emoji";
|
||||
public static final String CONTENT_TYPE = "content_type";
|
||||
public static final String COVER = "cover";
|
||||
private static final String PACK_ORDER = "pack_order";
|
||||
public static final String INSTALLED = "installed";
|
||||
private static final String LAST_USED = "last_used";
|
||||
public static final String FILE_PATH = "file_path";
|
||||
public static final String FILE_LENGTH = "file_length";
|
||||
public static final String FILE_RANDOM = "file_random";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
PACK_ID + " TEXT NOT NULL, " +
|
||||
PACK_KEY + " TEXT NOT NULL, " +
|
||||
PACK_TITLE + " TEXT NOT NULL, " +
|
||||
PACK_AUTHOR + " TEXT NOT NULL, " +
|
||||
STICKER_ID + " INTEGER, " +
|
||||
COVER + " INTEGER, " +
|
||||
PACK_ORDER + " INTEGER, " +
|
||||
EMOJI + " TEXT NOT NULL, " +
|
||||
CONTENT_TYPE + " TEXT DEFAULT NULL, " +
|
||||
LAST_USED + " INTEGER, " +
|
||||
INSTALLED + " INTEGER," +
|
||||
FILE_PATH + " TEXT NOT NULL, " +
|
||||
FILE_LENGTH + " INTEGER, " +
|
||||
FILE_RANDOM + " BLOB, " +
|
||||
"UNIQUE(" + PACK_ID + ", " + STICKER_ID + ", " + COVER + ") ON CONFLICT IGNORE)";
|
||||
|
||||
public static final String[] CREATE_INDEXES = {
|
||||
"CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON " + TABLE_NAME + " (" + PACK_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON " + TABLE_NAME + " (" + STICKER_ID + ");"
|
||||
};
|
||||
|
||||
public static final String DIRECTORY = "stickers";
|
||||
|
||||
private final AttachmentSecret attachmentSecret;
|
||||
|
||||
public StickerTable(Context context, SignalDatabase databaseHelper, AttachmentSecret attachmentSecret) {
|
||||
super(context, databaseHelper);
|
||||
this.attachmentSecret = attachmentSecret;
|
||||
}
|
||||
|
||||
public void insertSticker(@NonNull IncomingSticker sticker, @NonNull InputStream dataStream, boolean notify) throws IOException {
|
||||
FileInfo fileInfo = saveStickerImage(dataStream);
|
||||
ContentValues contentValues = new ContentValues();
|
||||
|
||||
contentValues.put(PACK_ID, sticker.getPackId());
|
||||
contentValues.put(PACK_KEY, sticker.getPackKey());
|
||||
contentValues.put(PACK_TITLE, sticker.getPackTitle());
|
||||
contentValues.put(PACK_AUTHOR, sticker.getPackAuthor());
|
||||
contentValues.put(STICKER_ID, sticker.getStickerId());
|
||||
contentValues.put(EMOJI, sticker.getEmoji());
|
||||
contentValues.put(CONTENT_TYPE, sticker.getContentType());
|
||||
contentValues.put(COVER, sticker.isCover() ? 1 : 0);
|
||||
contentValues.put(INSTALLED, sticker.isInstalled() ? 1 : 0);
|
||||
contentValues.put(FILE_PATH, fileInfo.getFile().getAbsolutePath());
|
||||
contentValues.put(FILE_LENGTH, fileInfo.getLength());
|
||||
contentValues.put(FILE_RANDOM, fileInfo.getRandom());
|
||||
|
||||
long id = databaseHelper.getSignalWritableDatabase().insert(TABLE_NAME, null, contentValues);
|
||||
if (id == -1) {
|
||||
String selection = PACK_ID + " = ? AND " + STICKER_ID + " = ? AND " + COVER + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(sticker.getPackId(), sticker.getStickerId(), (sticker.isCover() ? 1 : 0));
|
||||
|
||||
id = databaseHelper.getSignalWritableDatabase().update(TABLE_NAME, contentValues, selection, args);
|
||||
}
|
||||
|
||||
if (id > 0) {
|
||||
notifyStickerListeners();
|
||||
|
||||
if (sticker.isCover()) {
|
||||
notifyStickerPackListeners();
|
||||
|
||||
if (sticker.isInstalled() && notify) {
|
||||
broadcastInstallEvent(sticker.getPackId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable StickerRecord getSticker(@NonNull String packId, int stickerId, boolean isCover) {
|
||||
String selection = PACK_ID + " = ? AND " + STICKER_ID + " = ? AND " + COVER + " = ?";
|
||||
String[] args = new String[] { packId, String.valueOf(stickerId), String.valueOf(isCover ? 1 : 0) };
|
||||
|
||||
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) {
|
||||
return new StickerRecordReader(cursor).getNext();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable StickerPackRecord getStickerPack(@NonNull String packId) {
|
||||
String query = PACK_ID + " = ? AND " + COVER + " = ?";
|
||||
String[] args = new String[] { packId, "1" };
|
||||
|
||||
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null, "1")) {
|
||||
return new StickerPackRecordReader(cursor).getNext();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Cursor getInstalledStickerPacks() {
|
||||
String selection = COVER + " = ? AND " + INSTALLED + " = ?";
|
||||
String[] args = new String[] { "1", "1" };
|
||||
|
||||
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, PACK_ORDER + " ASC");
|
||||
}
|
||||
|
||||
public @Nullable Cursor getStickersByEmoji(@NonNull String emoji) {
|
||||
String selection = EMOJI + " LIKE ? AND " + COVER + " = ?";
|
||||
String[] args = new String[] { "%"+emoji+"%", "0" };
|
||||
|
||||
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
|
||||
}
|
||||
|
||||
public @Nullable Cursor getAllStickerPacks() {
|
||||
return getAllStickerPacks(null);
|
||||
}
|
||||
|
||||
public @Nullable Cursor getAllStickerPacks(@Nullable String limit) {
|
||||
String query = COVER + " = ?";
|
||||
String[] args = new String[] { "1" };
|
||||
|
||||
return databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, query, args, null, null, PACK_ORDER + " ASC", limit);
|
||||
}
|
||||
|
||||
public @Nullable Cursor getStickersForPack(@NonNull String packId) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
|
||||
String[] args = new String[] { packId, "0" };
|
||||
|
||||
return db.query(TABLE_NAME, null, selection, args, null, null, STICKER_ID + " ASC");
|
||||
}
|
||||
|
||||
public @Nullable Cursor getRecentlyUsedStickers(int limit) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String selection = LAST_USED + " > ? AND " + COVER + " = ?";
|
||||
String[] args = new String[] { "0", "0" };
|
||||
|
||||
return db.query(TABLE_NAME, null, selection, args, null, null, LAST_USED + " DESC", String.valueOf(limit));
|
||||
}
|
||||
|
||||
public @NonNull Set<String> getAllStickerFiles() {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
|
||||
Set<String> files = new HashSet<>();
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] { FILE_PATH }, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
files.add(CursorUtil.requireString(cursor, FILE_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public @Nullable InputStream getStickerStream(long rowId) throws IOException {
|
||||
String selection = _ID + " = ?";
|
||||
String[] args = new String[] { String.valueOf(rowId) };
|
||||
|
||||
try (Cursor cursor = databaseHelper.getSignalReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
String path = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
|
||||
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(FILE_RANDOM));
|
||||
|
||||
if (path != null) {
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(path), 0);
|
||||
} else {
|
||||
Log.w(TAG, "getStickerStream("+rowId+") - No sticker data");
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "getStickerStream("+rowId+") - Sticker not found.");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isPackInstalled(@NonNull String packId) {
|
||||
StickerPackRecord record = getStickerPack(packId);
|
||||
|
||||
return (record != null && record.isInstalled());
|
||||
}
|
||||
|
||||
public boolean isPackAvailableAsReference(@NonNull String packId) {
|
||||
return getStickerPack(packId) != null;
|
||||
}
|
||||
|
||||
public void updateStickerLastUsedTime(long rowId, long lastUsed) {
|
||||
String selection = _ID + " = ?";
|
||||
String[] args = new String[] { String.valueOf(rowId) };
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(LAST_USED, lastUsed);
|
||||
|
||||
databaseHelper.getSignalWritableDatabase().update(TABLE_NAME, values, selection, args);
|
||||
|
||||
notifyStickerListeners();
|
||||
notifyStickerPackListeners();
|
||||
}
|
||||
|
||||
public void markPackAsInstalled(@NonNull String packKey, boolean notify) {
|
||||
updatePackInstalled(databaseHelper.getSignalWritableDatabase(), packKey, true, notify);
|
||||
notifyStickerPackListeners();
|
||||
}
|
||||
|
||||
public void deleteOrphanedPacks() {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
String query = "SELECT " + PACK_ID + " FROM " + TABLE_NAME + " WHERE " + INSTALLED + " = ? AND " +
|
||||
PACK_ID + " NOT IN (" +
|
||||
"SELECT DISTINCT " + AttachmentTable.STICKER_PACK_ID + " FROM " + AttachmentTable.TABLE_NAME + " " +
|
||||
"WHERE " + AttachmentTable.STICKER_PACK_ID + " NOT NULL" +
|
||||
")";
|
||||
String[] args = new String[] { "0" };
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
boolean performedDelete = false;
|
||||
|
||||
try (Cursor cursor = db.rawQuery(query, args)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String packId = cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID));
|
||||
|
||||
if (!BlessedPacks.contains(packId)) {
|
||||
deletePack(db, packId);
|
||||
performedDelete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
|
||||
if (performedDelete) {
|
||||
notifyStickerPackListeners();
|
||||
notifyStickerListeners();
|
||||
}
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public void uninstallPack(@NonNull String packId) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
updatePackInstalled(db, packId, false, false);
|
||||
deleteStickersInPackExceptCover(db, packId);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
notifyStickerPackListeners();
|
||||
notifyStickerListeners();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePackOrder(@NonNull List<StickerPackRecord> packsInOrder) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
|
||||
|
||||
for (int i = 0; i < packsInOrder.size(); i++) {
|
||||
String[] args = new String[]{ packsInOrder.get(i).getPackId(), "1" };
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(PACK_ORDER, i);
|
||||
|
||||
db.update(TABLE_NAME, values, selection, args);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
notifyStickerPackListeners();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePackInstalled(@NonNull SQLiteDatabase db, @NonNull String packId, boolean installed, boolean notify) {
|
||||
StickerPackRecord existing = getStickerPack(packId);
|
||||
|
||||
if (existing != null && existing.isInstalled() == installed) {
|
||||
return;
|
||||
}
|
||||
|
||||
String selection = PACK_ID + " = ?";
|
||||
String[] args = new String[]{ packId };
|
||||
ContentValues values = new ContentValues(1);
|
||||
|
||||
values.put(INSTALLED, installed ? 1 : 0);
|
||||
db.update(TABLE_NAME, values, selection, args);
|
||||
|
||||
if (installed && notify) {
|
||||
broadcastInstallEvent(packId);
|
||||
}
|
||||
}
|
||||
|
||||
private FileInfo saveStickerImage(@NonNull InputStream inputStream) throws IOException {
|
||||
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
File file = File.createTempFile("sticker", ".mms", partsDirectory);
|
||||
Pair<byte[], OutputStream> out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, false);
|
||||
long length = StreamUtil.copy(inputStream, out.second);
|
||||
|
||||
return new FileInfo(file, length, out.first);
|
||||
}
|
||||
|
||||
private void deleteSticker(@NonNull SQLiteDatabase db, long rowId, @Nullable String filePath) {
|
||||
String selection = _ID + " = ?";
|
||||
String[] args = new String[] { String.valueOf(rowId) };
|
||||
|
||||
db.delete(TABLE_NAME, selection, args);
|
||||
|
||||
if (!TextUtils.isEmpty(filePath)) {
|
||||
new File(filePath).delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void deletePack(@NonNull SQLiteDatabase db, @NonNull String packId) {
|
||||
String selection = PACK_ID + " = ?";
|
||||
String[] args = new String[] { packId };
|
||||
|
||||
db.delete(TABLE_NAME, selection, args);
|
||||
|
||||
deleteStickersInPack(db, packId);
|
||||
}
|
||||
|
||||
private void deleteStickersInPack(@NonNull SQLiteDatabase db, @NonNull String packId) {
|
||||
String selection = PACK_ID + " = ?";
|
||||
String[] args = new String[] { packId };
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
|
||||
|
||||
deleteSticker(db, rowId, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
db.delete(TABLE_NAME, selection, args);
|
||||
}
|
||||
|
||||
private void deleteStickersInPackExceptCover(@NonNull SQLiteDatabase db, @NonNull String packId) {
|
||||
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
|
||||
String[] args = new String[] { packId, "0" };
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
|
||||
String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
|
||||
|
||||
deleteSticker(db, rowId, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastInstallEvent(@NonNull String packId) {
|
||||
StickerPackRecord pack = getStickerPack(packId);
|
||||
|
||||
if (pack != null) {
|
||||
EventBus.getDefault().postSticky(new StickerPackInstallEvent(new DecryptableUri(pack.getCover().getUri())));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FileInfo {
|
||||
private final File file;
|
||||
private final long length;
|
||||
private final byte[] random;
|
||||
|
||||
private FileInfo(@NonNull File file, long length, @NonNull byte[] random) {
|
||||
this.file = file;
|
||||
this.length = length;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public byte[] getRandom() {
|
||||
return random;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StickerRecordReader implements Closeable {
|
||||
|
||||
private final Cursor cursor;
|
||||
|
||||
public StickerRecordReader(@Nullable Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public @Nullable StickerRecord getNext() {
|
||||
if (cursor == null || !cursor.moveToNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getCurrent();
|
||||
}
|
||||
|
||||
public @NonNull StickerRecord getCurrent() {
|
||||
return new StickerRecord(cursor.getLong(cursor.getColumnIndexOrThrow(_ID)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(EMOJI)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LENGTH)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(COVER)) == 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StickerPackRecordReader implements Closeable {
|
||||
|
||||
private final Cursor cursor;
|
||||
|
||||
public StickerPackRecordReader(@Nullable Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public @Nullable StickerPackRecord getNext() {
|
||||
if (cursor == null || !cursor.moveToNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getCurrent();
|
||||
}
|
||||
|
||||
public @NonNull StickerPackRecord getCurrent() {
|
||||
StickerRecord cover = new StickerRecordReader(cursor).getCurrent();
|
||||
|
||||
return new StickerPackRecord(cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_TITLE)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(PACK_AUTHOR)),
|
||||
cover,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(INSTALLED)) == 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.forEach
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.Log.tag
|
||||
import org.signal.core.util.readToSet
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.requireString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.toInt
|
||||
import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
|
||||
import org.thoughtcrime.securesms.database.model.IncomingSticker
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
||||
import org.thoughtcrime.securesms.stickers.BlessedPacks
|
||||
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class StickerTable(
|
||||
context: Context?,
|
||||
databaseHelper: SignalDatabase?,
|
||||
private val attachmentSecret: AttachmentSecret
|
||||
) : DatabaseTable(context, databaseHelper) {
|
||||
|
||||
companion object {
|
||||
private val TAG = tag(StickerTable::class.java)
|
||||
|
||||
const val TABLE_NAME: String = "sticker"
|
||||
const val ID: String = "_id"
|
||||
const val PACK_ID: String = "pack_id"
|
||||
const val PACK_KEY: String = "pack_key"
|
||||
const val PACK_TITLE: String = "pack_title"
|
||||
const val PACK_AUTHOR: String = "pack_author"
|
||||
private const val STICKER_ID = "sticker_id"
|
||||
const val EMOJI: String = "emoji"
|
||||
const val CONTENT_TYPE: String = "content_type"
|
||||
const val COVER: String = "cover"
|
||||
private const val PACK_ORDER = "pack_order"
|
||||
const val INSTALLED: String = "installed"
|
||||
private const val LAST_USED = "last_used"
|
||||
const val FILE_PATH: String = "file_path"
|
||||
const val FILE_LENGTH: String = "file_length"
|
||||
const val FILE_RANDOM: String = "file_random"
|
||||
|
||||
val CREATE_TABLE: String = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$PACK_ID TEXT NOT NULL,
|
||||
$PACK_KEY TEXT NOT NULL,
|
||||
$PACK_TITLE TEXT NOT NULL,
|
||||
$PACK_AUTHOR TEXT NOT NULL,
|
||||
$STICKER_ID INTEGER,
|
||||
$COVER INTEGER,
|
||||
$PACK_ORDER INTEGER,
|
||||
$EMOJI TEXT NOT NULL,
|
||||
$CONTENT_TYPE TEXT DEFAULT NULL,
|
||||
$LAST_USED INTEGER,
|
||||
$INSTALLED INTEGER,
|
||||
$FILE_PATH TEXT NOT NULL,
|
||||
$FILE_LENGTH INTEGER,
|
||||
$FILE_RANDOM BLOB,
|
||||
UNIQUE($PACK_ID, $STICKER_ID, $COVER) ON CONFLICT IGNORE
|
||||
)
|
||||
"""
|
||||
|
||||
val CREATE_INDEXES: Array<String> = arrayOf(
|
||||
"CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON $TABLE_NAME ($PACK_ID);",
|
||||
"CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON $TABLE_NAME ($STICKER_ID);"
|
||||
)
|
||||
|
||||
const val DIRECTORY: String = "stickers"
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun insertSticker(sticker: IncomingSticker, dataStream: InputStream, notify: Boolean) {
|
||||
val fileInfo: FileInfo = saveStickerImage(dataStream)
|
||||
|
||||
writableDatabase
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(
|
||||
PACK_ID to sticker.packId,
|
||||
PACK_KEY to sticker.packKey,
|
||||
PACK_TITLE to sticker.packTitle,
|
||||
PACK_AUTHOR to sticker.packAuthor,
|
||||
STICKER_ID to sticker.stickerId,
|
||||
EMOJI to sticker.emoji,
|
||||
CONTENT_TYPE to sticker.contentType,
|
||||
COVER to if (sticker.isCover) 1 else 0,
|
||||
INSTALLED to if (sticker.isInstalled) 1 else 0,
|
||||
FILE_PATH to fileInfo.file.absolutePath,
|
||||
FILE_LENGTH to fileInfo.length,
|
||||
FILE_RANDOM to fileInfo.random
|
||||
)
|
||||
.run(SQLiteDatabase.CONFLICT_REPLACE)
|
||||
|
||||
notifyStickerListeners()
|
||||
|
||||
if (sticker.isCover) {
|
||||
notifyStickerPackListeners()
|
||||
|
||||
if (sticker.isInstalled && notify) {
|
||||
broadcastInstallEvent(sticker.packId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getSticker(packId: String, stickerId: Int, isCover: Boolean): StickerRecord? {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$PACK_ID = ? AND $STICKER_ID = ? AND $COVER = ?", packId, stickerId.toString(), isCover.toInt())
|
||||
.run()
|
||||
.readToSingleObject { it.readStickerRecord() }
|
||||
}
|
||||
|
||||
fun getStickerPack(packId: String): StickerPackRecord? {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$PACK_ID = ? AND $COVER = 1", packId)
|
||||
.run()
|
||||
.readToSingleObject { it.readStickerPackRecord() }
|
||||
}
|
||||
|
||||
fun getInstalledStickerPacks(): Cursor {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$COVER = 1 AND $INSTALLED = 1")
|
||||
.orderBy("$PACK_ORDER ASC")
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getStickersByEmoji(emoji: String): Cursor {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$EMOJI LIKE ? AND $COVER = 0", "%$emoji%")
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getAllStickerPacks(): Cursor {
|
||||
return getAllStickerPacks(null)
|
||||
}
|
||||
|
||||
fun getAllStickerPacks(limit: String?): Cursor {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$COVER = 1")
|
||||
.orderBy("$PACK_ORDER ASC")
|
||||
.limit(limit ?: "")
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getStickersForPack(packId: String): Cursor {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$PACK_ID = ? AND $COVER = 0", packId)
|
||||
.orderBy("$STICKER_ID ASC")
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getRecentlyUsedStickers(limit: Int): Cursor {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$LAST_USED > 0 AND $COVER = 0")
|
||||
.orderBy("$LAST_USED DESC")
|
||||
.limit(limit)
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getAllStickerFiles(): Set<String> {
|
||||
return readableDatabase
|
||||
.select(FILE_PATH)
|
||||
.from(TABLE_NAME)
|
||||
.run()
|
||||
.readToSet { it.requireNonNullString(FILE_PATH) }
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getStickerStream(rowId: Long): InputStream? {
|
||||
return readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$ID = ?", rowId)
|
||||
.run()
|
||||
.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val path = cursor.requireString(FILE_PATH)
|
||||
val random = cursor.requireBlob(FILE_RANDOM)
|
||||
|
||||
if (path != null && random != null) {
|
||||
ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(path), 0)
|
||||
} else {
|
||||
Log.w(TAG, "getStickerStream($rowId) - No sticker data")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "getStickerStream($rowId) - Sticker not found")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isPackInstalled(packId: String): Boolean {
|
||||
return getStickerPack(packId)?.isInstalled ?: false
|
||||
}
|
||||
|
||||
fun isPackAvailableAsReference(packId: String): Boolean {
|
||||
return readableDatabase
|
||||
.exists(TABLE_NAME)
|
||||
.where("$PACK_ID = ? AND $COVER = 1", packId)
|
||||
.run()
|
||||
}
|
||||
|
||||
fun updateStickerLastUsedTime(rowId: Long, lastUsed: Long) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(LAST_USED to lastUsed)
|
||||
.where("$ID = ?", rowId)
|
||||
.run()
|
||||
|
||||
notifyStickerListeners()
|
||||
notifyStickerPackListeners()
|
||||
}
|
||||
|
||||
fun markPackAsInstalled(packKey: String, notify: Boolean) {
|
||||
updatePackInstalled(
|
||||
db = databaseHelper.signalWritableDatabase,
|
||||
packId = packKey,
|
||||
installed = true,
|
||||
notify = notify
|
||||
)
|
||||
notifyStickerPackListeners()
|
||||
}
|
||||
|
||||
fun deleteOrphanedPacks() {
|
||||
var performedDelete = false
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.rawQuery(
|
||||
"""
|
||||
SELECT $PACK_ID
|
||||
FROM $TABLE_NAME
|
||||
WHERE
|
||||
$INSTALLED = 0 AND
|
||||
$PACK_ID NOT IN (
|
||||
SELECT DISTINCT ${AttachmentTable.STICKER_PACK_ID}
|
||||
FROM ${AttachmentTable.TABLE_NAME}
|
||||
WHERE ${AttachmentTable.STICKER_PACK_ID} NOT NULL
|
||||
)
|
||||
""",
|
||||
null
|
||||
).forEach { cursor ->
|
||||
val packId = cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID))
|
||||
|
||||
if (!BlessedPacks.contains(packId)) {
|
||||
deletePack(db, packId)
|
||||
performedDelete = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (performedDelete) {
|
||||
notifyStickerPackListeners()
|
||||
notifyStickerListeners()
|
||||
}
|
||||
}
|
||||
|
||||
fun uninstallPack(packId: String) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
updatePackInstalled(db = db, packId = packId, installed = false, notify = false)
|
||||
deleteStickersInPackExceptCover(db, packId)
|
||||
}
|
||||
|
||||
notifyStickerPackListeners()
|
||||
notifyStickerListeners()
|
||||
}
|
||||
|
||||
fun updatePackOrder(packsInOrder: MutableList<StickerPackRecord>) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
for ((i, pack) in packsInOrder.withIndex()) {
|
||||
db.update(TABLE_NAME)
|
||||
.values(PACK_ORDER to i)
|
||||
.where("$PACK_ID = ? AND $COVER = 1", pack.packId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
notifyStickerPackListeners()
|
||||
}
|
||||
|
||||
private fun updatePackInstalled(db: SQLiteDatabase, packId: String, installed: Boolean, notify: Boolean) {
|
||||
val existing = getStickerPack(packId)
|
||||
|
||||
if (existing != null && existing.isInstalled == installed) {
|
||||
return
|
||||
}
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(INSTALLED to installed.toInt())
|
||||
.where("$PACK_ID = ?", packId)
|
||||
.run()
|
||||
|
||||
if (installed && notify) {
|
||||
broadcastInstallEvent(packId)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun saveStickerImage(inputStream: InputStream): FileInfo {
|
||||
val partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE)
|
||||
val file = File.createTempFile("sticker", ".mms", partsDirectory)
|
||||
val out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, false)
|
||||
val length = StreamUtil.copy(inputStream, out.second)
|
||||
|
||||
return FileInfo(file, length, out.first!!)
|
||||
}
|
||||
|
||||
private fun deleteSticker(db: SQLiteDatabase, rowId: Long, filePath: String?) {
|
||||
db.delete(TABLE_NAME)
|
||||
.where("$ID = ?", rowId)
|
||||
.run()
|
||||
|
||||
if (filePath.isNotNullOrBlank()) {
|
||||
File(filePath).delete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deletePack(db: SQLiteDatabase, packId: String) {
|
||||
db.delete(TABLE_NAME)
|
||||
.where("$PACK_ID = ?", packId)
|
||||
.run()
|
||||
|
||||
deleteStickersInPack(db, packId)
|
||||
}
|
||||
|
||||
private fun deleteStickersInPack(database: SQLiteDatabase, packId: String) {
|
||||
database.withinTransaction { db ->
|
||||
db.select(ID, FILE_PATH)
|
||||
.from(TABLE_NAME)
|
||||
.where("$PACK_ID = ?", packId)
|
||||
.run()
|
||||
.forEach { cursor ->
|
||||
val rowId = cursor.requireLong(ID)
|
||||
val filePath = cursor.requireString(FILE_PATH)
|
||||
|
||||
deleteSticker(db, rowId, filePath)
|
||||
}
|
||||
|
||||
db.delete(TABLE_NAME)
|
||||
.where("$PACK_ID = ?", packId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteStickersInPackExceptCover(database: SQLiteDatabase, packId: String) {
|
||||
database.withinTransaction { db ->
|
||||
db.select(ID, FILE_PATH)
|
||||
.from(TABLE_NAME)
|
||||
.where("$PACK_ID = ? AND $COVER = 0", packId)
|
||||
.run()
|
||||
.forEach { cursor ->
|
||||
val rowId = cursor.requireLong(ID)
|
||||
val filePath = cursor.requireString(FILE_PATH)
|
||||
|
||||
deleteSticker(db, rowId, filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun broadcastInstallEvent(packId: String) {
|
||||
val pack = getStickerPack(packId)
|
||||
|
||||
if (pack != null) {
|
||||
EventBus.getDefault().postSticky(StickerPackInstallEvent(DecryptableUri(pack.cover.uri)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Cursor.readStickerRecord(): StickerRecord {
|
||||
return StickerRecordReader(this).getCurrent()
|
||||
}
|
||||
|
||||
private fun Cursor.readStickerPackRecord(): StickerPackRecord {
|
||||
return StickerPackRecordReader(this).getCurrent()
|
||||
}
|
||||
|
||||
private class FileInfo(
|
||||
val file: File,
|
||||
val length: Long,
|
||||
val random: ByteArray
|
||||
)
|
||||
|
||||
class StickerRecordReader(private val cursor: Cursor) : Closeable {
|
||||
|
||||
fun getNext(): StickerRecord? {
|
||||
if (!cursor.moveToNext()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return getCurrent()
|
||||
}
|
||||
|
||||
fun getCurrent(): StickerRecord {
|
||||
return StickerRecord(
|
||||
rowId = cursor.requireLong(ID),
|
||||
packId = cursor.requireNonNullString(PACK_ID),
|
||||
packKey = cursor.requireNonNullString(PACK_KEY),
|
||||
stickerId = cursor.requireInt(STICKER_ID),
|
||||
emoji = cursor.requireNonNullString(EMOJI),
|
||||
contentType = cursor.requireString(CONTENT_TYPE) ?: MediaUtil.IMAGE_WEBP,
|
||||
size = cursor.requireLong(FILE_LENGTH),
|
||||
isCover = cursor.requireBoolean(COVER)
|
||||
)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
class StickerPackRecordReader(private val cursor: Cursor) : Closeable {
|
||||
|
||||
fun getNext(): StickerPackRecord? {
|
||||
if (!cursor.moveToNext()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return getCurrent()
|
||||
}
|
||||
|
||||
fun getCurrent(): StickerPackRecord {
|
||||
val cover = StickerRecordReader(cursor).getCurrent()
|
||||
|
||||
return StickerPackRecord(
|
||||
packId = cursor.requireNonNullString(PACK_ID),
|
||||
packKey = cursor.requireNonNullString(PACK_KEY),
|
||||
title = cursor.requireNonNullString(PACK_TITLE),
|
||||
author = cursor.requireNonNullString(PACK_AUTHOR),
|
||||
cover = cover,
|
||||
isInstalled = cursor.requireBoolean(INSTALLED)
|
||||
)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class IncomingSticker {
|
||||
|
||||
private final String packKey;
|
||||
private final String packId;
|
||||
private final String packTitle;
|
||||
private final String packAuthor;
|
||||
private final int stickerId;
|
||||
private final String emoji;
|
||||
private final String contentType;
|
||||
private final boolean isCover;
|
||||
private final boolean isInstalled;
|
||||
|
||||
public IncomingSticker(@NonNull String packId,
|
||||
@NonNull String packKey,
|
||||
@NonNull String packTitle,
|
||||
@NonNull String packAuthor,
|
||||
int stickerId,
|
||||
@NonNull String emoji,
|
||||
@Nullable String contentType,
|
||||
boolean isCover,
|
||||
boolean isInstalled)
|
||||
{
|
||||
this.packId = packId;
|
||||
this.packKey = packKey;
|
||||
this.packTitle = packTitle;
|
||||
this.packAuthor = packAuthor;
|
||||
this.stickerId = stickerId;
|
||||
this.emoji = emoji;
|
||||
this.contentType = contentType;
|
||||
this.isCover = isCover;
|
||||
this.isInstalled = isInstalled;
|
||||
}
|
||||
|
||||
public @NonNull String getPackKey() {
|
||||
return packKey;
|
||||
}
|
||||
|
||||
public @NonNull String getPackId() {
|
||||
return packId;
|
||||
}
|
||||
|
||||
public @NonNull String getPackTitle() {
|
||||
return packTitle;
|
||||
}
|
||||
|
||||
public @NonNull String getPackAuthor() {
|
||||
return packAuthor;
|
||||
}
|
||||
|
||||
public int getStickerId() {
|
||||
return stickerId;
|
||||
}
|
||||
|
||||
public @NonNull String getEmoji() {
|
||||
return emoji;
|
||||
}
|
||||
|
||||
public @Nullable String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public boolean isCover() {
|
||||
return isCover;
|
||||
}
|
||||
|
||||
public boolean isInstalled() {
|
||||
return isInstalled;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
data class IncomingSticker(
|
||||
val packId: String,
|
||||
val packKey: String,
|
||||
val packTitle: String,
|
||||
val packAuthor: String,
|
||||
val stickerId: Int,
|
||||
val emoji: String,
|
||||
val contentType: String?,
|
||||
val isCover: Boolean,
|
||||
val isInstalled: Boolean
|
||||
)
|
|
@ -1,80 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.StickerTable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a record for a sticker pack in the {@link StickerTable}.
|
||||
*/
|
||||
public final class StickerPackRecord {
|
||||
|
||||
private final String packId;
|
||||
private final String packKey;
|
||||
private final Optional<String> title;
|
||||
private final Optional<String> author;
|
||||
private final StickerRecord cover;
|
||||
private final boolean installed;
|
||||
|
||||
public StickerPackRecord(@NonNull String packId,
|
||||
@NonNull String packKey,
|
||||
@NonNull String title,
|
||||
@NonNull String author,
|
||||
@NonNull StickerRecord cover,
|
||||
boolean installed)
|
||||
{
|
||||
this.packId = packId;
|
||||
this.packKey = packKey;
|
||||
this.title = TextUtils.isEmpty(title) ? Optional.empty() : Optional.of(title);
|
||||
this.author = TextUtils.isEmpty(author) ? Optional.empty() : Optional.of(author);
|
||||
this.cover = cover;
|
||||
this.installed = installed;
|
||||
}
|
||||
|
||||
public @NonNull String getPackId() {
|
||||
return packId;
|
||||
}
|
||||
|
||||
public @NonNull String getPackKey() {
|
||||
return packKey;
|
||||
}
|
||||
|
||||
public @NonNull Optional<String> getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public @NonNull Optional<String> getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public @NonNull StickerRecord getCover() {
|
||||
return cover;
|
||||
}
|
||||
|
||||
public boolean isInstalled() {
|
||||
return installed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
StickerPackRecord record = (StickerPackRecord) o;
|
||||
return installed == record.installed &&
|
||||
packId.equals(record.packId) &&
|
||||
packKey.equals(record.packKey) &&
|
||||
title.equals(record.title) &&
|
||||
author.equals(record.author) &&
|
||||
cover.equals(record.cover);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(packId, packKey, title, author, cover, installed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Represents a record for a sticker pack in the [StickerTable].
|
||||
*/
|
||||
data class StickerPackRecord(
|
||||
@JvmField val packId: String,
|
||||
@JvmField val packKey: String,
|
||||
@JvmField val title: String,
|
||||
@JvmField val author: String,
|
||||
@JvmField val cover: StickerRecord,
|
||||
@JvmField val isInstalled: Boolean
|
||||
) {
|
||||
@JvmField
|
||||
val titleOptional: Optional<String> = if (title.isBlank()) Optional.empty() else Optional.of(title)
|
||||
|
||||
@JvmField
|
||||
val authorOptional: Optional<String> = if (author.isBlank()) Optional.empty() else Optional.of(author)
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.StickerTable;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a record for a sticker pack in the {@link StickerTable}.
|
||||
*/
|
||||
public final class StickerRecord {
|
||||
|
||||
private final long rowId;
|
||||
private final String packId;
|
||||
private final String packKey;
|
||||
private final int stickerId;
|
||||
private final String emoji;
|
||||
private final String contentType;
|
||||
private final long size;
|
||||
private final boolean isCover;
|
||||
|
||||
public StickerRecord(long rowId,
|
||||
@NonNull String packId,
|
||||
@NonNull String packKey,
|
||||
int stickerId,
|
||||
@NonNull String emoji,
|
||||
@Nullable String contentType,
|
||||
long size,
|
||||
boolean isCover)
|
||||
{
|
||||
this.rowId = rowId;
|
||||
this.packId = packId;
|
||||
this.packKey = packKey;
|
||||
this.stickerId = stickerId;
|
||||
this.emoji = emoji;
|
||||
this.contentType = contentType;
|
||||
this.size = size;
|
||||
this.isCover = isCover;
|
||||
}
|
||||
|
||||
public long getRowId() {
|
||||
return rowId;
|
||||
}
|
||||
|
||||
public @NonNull String getPackId() {
|
||||
return packId;
|
||||
}
|
||||
|
||||
public @NonNull String getPackKey() {
|
||||
return packKey;
|
||||
}
|
||||
|
||||
public int getStickerId() {
|
||||
return stickerId;
|
||||
}
|
||||
|
||||
public @NonNull Uri getUri() {
|
||||
return PartAuthority.getStickerUri(rowId);
|
||||
}
|
||||
|
||||
public @NonNull String getEmoji() {
|
||||
return emoji;
|
||||
}
|
||||
|
||||
public @NonNull String getContentType() {
|
||||
return Util.isEmpty(contentType) ? MediaUtil.IMAGE_WEBP : contentType;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean isCover() {
|
||||
return isCover;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
StickerRecord that = (StickerRecord) o;
|
||||
return rowId == that.rowId &&
|
||||
stickerId == that.stickerId &&
|
||||
size == that.size &&
|
||||
isCover == that.isCover &&
|
||||
packId.equals(that.packId) &&
|
||||
packKey.equals(that.packKey) &&
|
||||
emoji.equals(that.emoji) &&
|
||||
Objects.equals(contentType, that.contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(rowId, packId, packKey, stickerId, emoji, contentType, size, isCover);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import android.net.Uri
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
|
||||
/**
|
||||
* Represents a record for a sticker pack in the [StickerTable].
|
||||
*/
|
||||
data class StickerRecord(
|
||||
@JvmField val rowId: Long,
|
||||
@JvmField val packId: String,
|
||||
@JvmField val packKey: String,
|
||||
@JvmField val stickerId: Int,
|
||||
@JvmField val emoji: String,
|
||||
@JvmField val contentType: String,
|
||||
@JvmField val size: Long,
|
||||
@JvmField val isCover: Boolean
|
||||
) {
|
||||
@JvmField
|
||||
val uri: Uri = PartAuthority.getStickerUri(rowId)
|
||||
}
|
|
@ -72,8 +72,8 @@ public class MultiDeviceStickerPackSyncJob extends BaseJob {
|
|||
try (StickerPackRecordReader reader = new StickerPackRecordReader(SignalDatabase.stickers().getInstalledStickerPacks())) {
|
||||
StickerPackRecord pack;
|
||||
while ((pack = reader.getNext()) != null) {
|
||||
byte[] packIdBytes = Hex.fromStringCondensed(pack.getPackId());
|
||||
byte[] packKeyBytes = Hex.fromStringCondensed(pack.getPackKey());
|
||||
byte[] packIdBytes = Hex.fromStringCondensed(pack.packId);
|
||||
byte[] packKeyBytes = Hex.fromStringCondensed(pack.packKey);
|
||||
|
||||
operations.add(new StickerPackOperationMessage(packIdBytes, packKeyBytes, StickerPackOperationMessage.Type.INSTALL));
|
||||
}
|
||||
|
|
|
@ -426,7 +426,7 @@ public abstract class PushSendJob extends SendJob {
|
|||
byte[] packKey = Hex.fromStringCondensed(stickerAttachment.stickerLocator.packKey);
|
||||
int stickerId = stickerAttachment.stickerLocator.stickerId;
|
||||
StickerRecord record = SignalDatabase.stickers().getSticker(stickerAttachment.stickerLocator.packId, stickerId, false);
|
||||
String emoji = record != null ? record.getEmoji() : null;
|
||||
String emoji = record != null ? record.emoji : null;
|
||||
SignalServiceAttachment attachment = getAttachmentPointerFor(stickerAttachment);
|
||||
|
||||
return Optional.of(new SignalServiceDataMessage.Sticker(packId, packKey, stickerId, emoji, attachment));
|
||||
|
|
|
@ -82,7 +82,7 @@ public class StickerDownloadJob extends BaseJob {
|
|||
|
||||
StickerRecord stickerRecord = db.getSticker(sticker.getPackId(), sticker.getStickerId(), sticker.isCover());
|
||||
if (stickerRecord != null) {
|
||||
try (InputStream stream = PartAuthority.getAttachmentStream(context, stickerRecord.getUri())) {
|
||||
try (InputStream stream = PartAuthority.getAttachmentStream(context, stickerRecord.uri)) {
|
||||
if (stream != null) {
|
||||
Log.w(TAG, "Sticker already downloaded.");
|
||||
return;
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.keyboard.sticker
|
|||
|
||||
import android.net.Uri
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.nullIfBlank
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.StickerTable
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerPackRecordReader
|
||||
|
@ -18,11 +19,11 @@ class StickerKeyboardRepository(private val stickerTable: StickerTable) {
|
|||
SignalExecutors.BOUNDED.execute {
|
||||
val packs: MutableList<KeyboardStickerPack> = mutableListOf()
|
||||
|
||||
StickerPackRecordReader(stickerTable.installedStickerPacks).use { reader ->
|
||||
var pack: StickerPackRecord? = reader.next
|
||||
StickerPackRecordReader(stickerTable.getInstalledStickerPacks()).use { reader ->
|
||||
var pack: StickerPackRecord? = reader.getNext()
|
||||
while (pack != null) {
|
||||
packs += KeyboardStickerPack(packId = pack.packId, title = pack.title.orElse(null), coverUri = pack.cover.uri)
|
||||
pack = reader.next
|
||||
packs += KeyboardStickerPack(packId = pack.packId, title = pack.title.nullIfBlank(), coverUri = pack.cover.uri)
|
||||
pack = reader.getNext()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,10 +31,10 @@ class StickerKeyboardRepository(private val stickerTable: StickerTable) {
|
|||
val stickers: MutableList<StickerRecord> = mutableListOf()
|
||||
|
||||
StickerRecordReader(stickerTable.getStickersForPack(p.packId)).use { reader ->
|
||||
var sticker: StickerRecord? = reader.next
|
||||
var sticker: StickerRecord? = reader.getNext()
|
||||
while (sticker != null) {
|
||||
stickers.add(sticker)
|
||||
sticker = reader.next
|
||||
sticker = reader.getNext()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,10 +53,10 @@ class StickerKeyboardRepository(private val stickerTable: StickerTable) {
|
|||
val recentStickers: MutableList<StickerRecord> = mutableListOf()
|
||||
|
||||
StickerRecordReader(stickerTable.getRecentlyUsedStickers(RECENT_LIMIT)).use { reader ->
|
||||
var recentSticker: StickerRecord? = reader.next
|
||||
var recentSticker: StickerRecord? = reader.getNext()
|
||||
while (recentSticker != null) {
|
||||
recentStickers.add(recentSticker)
|
||||
recentSticker = reader.next
|
||||
recentSticker = reader.getNext()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,10 +44,10 @@ class StickerSearchRepository {
|
|||
private fun StickerRecordReader.readAll(): List<StickerRecord> {
|
||||
val stickers: MutableList<StickerRecord> = mutableListOf()
|
||||
use { reader ->
|
||||
var record: StickerRecord? = reader.next
|
||||
var record: StickerRecord? = reader.getNext()
|
||||
while (record != null) {
|
||||
stickers.add(record)
|
||||
record = reader.next
|
||||
record = reader.getNext()
|
||||
}
|
||||
}
|
||||
return stickers
|
||||
|
|
|
@ -56,10 +56,10 @@ public final class ImageEditorStickerSelectActivity extends AppCompatActivity im
|
|||
@Override
|
||||
public void onStickerSelected(@NonNull StickerRecord sticker) {
|
||||
Intent intent = new Intent();
|
||||
intent.setData(sticker.getUri());
|
||||
intent.setData(sticker.uri);
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> SignalDatabase.stickers().updateStickerLastUsedTime(sticker.getRowId(), System.currentTimeMillis()));
|
||||
SignalExecutors.BOUNDED.execute(() -> SignalDatabase.stickers().updateStickerLastUsedTime(sticker.rowId, System.currentTimeMillis()));
|
||||
ViewUtil.hideKeyboard(this, findViewById(android.R.id.content));
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String
|
|||
} else if (records.isEmpty()) {
|
||||
return idGenerator.getId(tag + "_" + STABLE_ID_TEXT);
|
||||
} else {
|
||||
return idGenerator.getId(records.get(localPosition - 1).getPackId());
|
||||
return idGenerator.getId(records.get(localPosition - 1).packId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,38 +264,38 @@ final class StickerManagementAdapter extends SectionedRecyclerViewAdapter<String
|
|||
boolean lastInList,
|
||||
boolean allowApngAnimation)
|
||||
{
|
||||
SpannableStringBuilder titleBuilder = new SpannableStringBuilder(stickerPack.getTitle().orElse(itemView.getResources().getString(R.string.StickerManagementAdapter_untitled)));
|
||||
if (BlessedPacks.contains(stickerPack.getPackId())) {
|
||||
SpannableStringBuilder titleBuilder = new SpannableStringBuilder(stickerPack.titleOptional.orElse(itemView.getResources().getString(R.string.StickerManagementAdapter_untitled)));
|
||||
if (BlessedPacks.contains(stickerPack.packId)) {
|
||||
titleBuilder.append(blessedBadge);
|
||||
}
|
||||
|
||||
title.setText(titleBuilder);
|
||||
author.setText(stickerPack.getAuthor().orElse(itemView.getResources().getString(R.string.StickerManagementAdapter_unknown)));
|
||||
author.setText(stickerPack.authorOptional.orElse(itemView.getResources().getString(R.string.StickerManagementAdapter_unknown)));
|
||||
divider.setVisibility(lastInList ? View.GONE : View.VISIBLE);
|
||||
|
||||
requestManager.load(new DecryptableUri(stickerPack.getCover().getUri()))
|
||||
requestManager.load(new DecryptableUri(stickerPack.cover.uri))
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.fitCenter()
|
||||
.set(ApngOptions.ANIMATE, allowApngAnimation)
|
||||
.into(cover);
|
||||
|
||||
if (stickerPack.isInstalled()) {
|
||||
if (stickerPack.isInstalled) {
|
||||
actionButtonImage.setImageResource(R.drawable.ic_x);
|
||||
actionButton.setOnClickListener(v -> eventListener.onStickerPackUninstallClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
||||
actionButton.setOnClickListener(v -> eventListener.onStickerPackUninstallClicked(stickerPack.packId, stickerPack.packKey));
|
||||
|
||||
shareButton.setVisibility(View.VISIBLE);
|
||||
shareButtonImage.setVisibility(View.VISIBLE);
|
||||
shareButton.setOnClickListener(v -> eventListener.onStickerPackShareClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
||||
shareButton.setOnClickListener(v -> eventListener.onStickerPackShareClicked(stickerPack.packId, stickerPack.packKey));
|
||||
} else {
|
||||
actionButtonImage.setImageResource(R.drawable.symbol_arrow_down_24);
|
||||
actionButton.setOnClickListener(v -> eventListener.onStickerPackInstallClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
||||
actionButton.setOnClickListener(v -> eventListener.onStickerPackInstallClicked(stickerPack.packId, stickerPack.packKey));
|
||||
|
||||
shareButton.setVisibility(View.GONE);
|
||||
shareButtonImage.setVisibility(View.GONE);
|
||||
shareButton.setOnClickListener(null);
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(v -> eventListener.onStickerPackClicked(stickerPack.getPackId(), stickerPack.getPackKey()));
|
||||
itemView.setOnClickListener(v -> eventListener.onStickerPackClicked(stickerPack.packId, stickerPack.packKey));
|
||||
}
|
||||
|
||||
void recycle() {
|
||||
|
|
|
@ -61,9 +61,9 @@ final class StickerManagementRepository {
|
|||
try (StickerPackRecordReader reader = new StickerPackRecordReader(stickerDatabase.getAllStickerPacks())) {
|
||||
StickerPackRecord record;
|
||||
while ((record = reader.getNext()) != null) {
|
||||
if (record.isInstalled()) {
|
||||
if (record.isInstalled) {
|
||||
installedPacks.add(record);
|
||||
} else if (BlessedPacks.contains(record.getPackId())) {
|
||||
} else if (BlessedPacks.contains(record.packId)) {
|
||||
blessedPacks.add(record);
|
||||
} else {
|
||||
availablePacks.add(record);
|
||||
|
|
|
@ -58,18 +58,18 @@ public final class StickerPackPreviewRepository {
|
|||
private Optional<StickerManifestResult> getManifestFromDatabase(@NonNull String packId) {
|
||||
StickerPackRecord record = stickerDatabase.getStickerPack(packId);
|
||||
|
||||
if (record != null && record.isInstalled()) {
|
||||
StickerManifest.Sticker cover = toSticker(record.getCover());
|
||||
if (record != null && record.isInstalled) {
|
||||
StickerManifest.Sticker cover = toSticker(record.cover);
|
||||
List<StickerManifest.Sticker> stickers = getStickersFromDatabase(packId);
|
||||
|
||||
StickerManifest manifest = new StickerManifest(record.getPackId(),
|
||||
record.getPackKey(),
|
||||
record.getTitle(),
|
||||
record.getAuthor(),
|
||||
StickerManifest manifest = new StickerManifest(record.packId,
|
||||
record.packKey,
|
||||
record.titleOptional,
|
||||
record.authorOptional,
|
||||
Optional.of(cover),
|
||||
stickers);
|
||||
|
||||
return Optional.of(new StickerManifestResult(manifest, record.isInstalled()));
|
||||
return Optional.of(new StickerManifestResult(manifest, record.isInstalled));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
|
@ -131,7 +131,7 @@ public final class StickerPackPreviewRepository {
|
|||
}
|
||||
|
||||
private StickerManifest.Sticker toSticker(@NonNull StickerRecord record) {
|
||||
return new StickerManifest.Sticker(record.getPackId(), record.getPackKey(), record.getStickerId(), record.getEmoji(), record.getContentType(), record.getUri());
|
||||
return new StickerManifest.Sticker(record.packId, record.packKey, record.stickerId, record.emoji, record.contentType, record.uri);
|
||||
}
|
||||
|
||||
static class StickerManifestResult {
|
||||
|
|
|
@ -81,7 +81,7 @@ public class StickerRolloverTouchListener implements RecyclerView.OnItemTouchLis
|
|||
|
||||
public void enterHoverMode(@NonNull RecyclerView recyclerView, @NonNull KeyboardStickerListAdapter.Sticker sticker) {
|
||||
this.hoverMode = true;
|
||||
showSticker(recyclerView, sticker.getUri(), sticker.getStickerRecord().getEmoji());
|
||||
showSticker(recyclerView, sticker.getUri(), sticker.getStickerRecord().emoji);
|
||||
}
|
||||
|
||||
private void exitHoverMode() {
|
||||
|
|
Loading…
Add table
Reference in a new issue