Add support for persisting wallpaper selection.
This commit is contained in:
parent
80651d2425
commit
6bcb0de43d
13 changed files with 447 additions and 37 deletions
|
@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
|||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileKeyCredentialColumnData;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
|
||||
|
@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor;
|
|||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -53,6 +55,9 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
|
|||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory;
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
@ -131,6 +136,8 @@ public class RecipientDatabase extends Database {
|
|||
private static final String STORAGE_PROTO = "storage_proto";
|
||||
private static final String LAST_GV1_MIGRATE_REMINDER = "last_gv1_migrate_reminder";
|
||||
private static final String LAST_SESSION_RESET = "last_session_reset";
|
||||
private static final String WALLPAPER = "wallpaper";
|
||||
private static final String WALLPAPER_URI = "wallpaper_file";
|
||||
|
||||
public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
|
||||
private static final String SORT_NAME = "sort_name";
|
||||
|
@ -155,7 +162,7 @@ public class RecipientDatabase extends Database {
|
|||
FORCE_SMS_SELECTION,
|
||||
CAPABILITIES,
|
||||
STORAGE_SERVICE_ID, DIRTY,
|
||||
MENTION_SETTING
|
||||
MENTION_SETTING, WALLPAPER, WALLPAPER_URI
|
||||
};
|
||||
|
||||
private static final String[] ID_PROJECTION = new String[]{ID};
|
||||
|
@ -350,7 +357,9 @@ public class RecipientDatabase extends Database {
|
|||
STORAGE_PROTO + " TEXT DEFAULT NULL, " +
|
||||
CAPABILITIES + " INTEGER DEFAULT 0, " +
|
||||
LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0, " +
|
||||
LAST_SESSION_RESET + " BLOB DEFAULT NULL);";
|
||||
LAST_SESSION_RESET + " BLOB DEFAULT NULL, " +
|
||||
WALLPAPER + " BLOB DEFAULT NULL, " +
|
||||
WALLPAPER_URI + " TEXT DEFAULT NULL);";
|
||||
|
||||
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
|
||||
" FROM " + TABLE_NAME +
|
||||
|
@ -1264,6 +1273,7 @@ public class RecipientDatabase extends Database {
|
|||
long capabilities = CursorUtil.requireLong(cursor, CAPABILITIES);
|
||||
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
|
||||
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
|
||||
byte[] wallpaper = CursorUtil.requireBlob(cursor, WALLPAPER);
|
||||
|
||||
MaterialColor color;
|
||||
byte[] profileKey = null;
|
||||
|
@ -1303,6 +1313,16 @@ public class RecipientDatabase extends Database {
|
|||
|
||||
byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null;
|
||||
|
||||
ChatWallpaper chatWallpaper = null;
|
||||
|
||||
if (wallpaper != null) {
|
||||
try {
|
||||
chatWallpaper = ChatWallpaperFactory.create(Wallpaper.parseFrom(wallpaper));
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w(TAG, "Failed to parse wallpaper.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new RecipientSettings(RecipientId.from(id),
|
||||
uuid,
|
||||
username,
|
||||
|
@ -1338,6 +1358,7 @@ public class RecipientDatabase extends Database {
|
|||
InsightsBannerTier.fromId(insightsBannerTier),
|
||||
storageKey,
|
||||
MentionSetting.fromId(mentionSettingId),
|
||||
chatWallpaper,
|
||||
getSyncExtras(cursor));
|
||||
}
|
||||
|
||||
|
@ -1778,6 +1799,60 @@ public class RecipientDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public void setWallpaper(@NonNull RecipientId id, @NonNull ChatWallpaper chatWallpaper) {
|
||||
Wallpaper wallpaper = chatWallpaper.serialize();
|
||||
Uri existingWallpaperUri = getWallpaperUri(id);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(WALLPAPER, wallpaper.toByteArray());
|
||||
|
||||
if (wallpaper.hasFile()) {
|
||||
values.put(WALLPAPER_URI, wallpaper.getFile().getUri());
|
||||
} else {
|
||||
values.putNull(WALLPAPER_URI);
|
||||
}
|
||||
|
||||
if (update(id, values)) {
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
|
||||
if (existingWallpaperUri != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, existingWallpaperUri);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Uri getWallpaperUri(@NonNull RecipientId id) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] {WALLPAPER_URI}, ID_WHERE, SqlUtil.buildArgs(id), null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
String raw = CursorUtil.requireString(cursor, WALLPAPER_URI);
|
||||
|
||||
if (raw != null) {
|
||||
return Uri.parse(raw);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getWallpaperUriUsageCount(@NonNull Uri uri) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
String query = WALLPAPER_URI + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(uri);
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] { "COUNT(*)"}, query, args, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if setting the phone number resulted in changed recipientId, otherwise false.
|
||||
*/
|
||||
|
@ -2788,6 +2863,7 @@ public class RecipientDatabase extends Database {
|
|||
private final InsightsBannerTier insightsBannerTier;
|
||||
private final byte[] storageId;
|
||||
private final MentionSetting mentionSetting;
|
||||
private final ChatWallpaper wallpaper;
|
||||
private final SyncExtras syncExtras;
|
||||
|
||||
RecipientSettings(@NonNull RecipientId id,
|
||||
|
@ -2825,6 +2901,7 @@ public class RecipientDatabase extends Database {
|
|||
@NonNull InsightsBannerTier insightsBannerTier,
|
||||
@Nullable byte[] storageId,
|
||||
@NonNull MentionSetting mentionSetting,
|
||||
@Nullable ChatWallpaper wallpaper,
|
||||
@NonNull SyncExtras syncExtras)
|
||||
{
|
||||
this.id = id;
|
||||
|
@ -2864,6 +2941,7 @@ public class RecipientDatabase extends Database {
|
|||
this.insightsBannerTier = insightsBannerTier;
|
||||
this.storageId = storageId;
|
||||
this.mentionSetting = mentionSetting;
|
||||
this.wallpaper = wallpaper;
|
||||
this.syncExtras = syncExtras;
|
||||
}
|
||||
|
||||
|
@ -3011,6 +3089,10 @@ public class RecipientDatabase extends Database {
|
|||
return mentionSetting;
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
return wallpaper;
|
||||
}
|
||||
|
||||
public @NonNull SyncExtras getSyncExtras() {
|
||||
return syncExtras;
|
||||
}
|
||||
|
|
|
@ -169,8 +169,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
|||
private static final int GV1_MIGRATION_REFACTOR = 85;
|
||||
private static final int CLEAR_PROFILE_KEY_CREDENTIALS = 86;
|
||||
private static final int LAST_RESET_SESSION_TIME = 87;
|
||||
private static final int WALLPAPER = 88;
|
||||
|
||||
private static final int DATABASE_VERSION = 87;
|
||||
private static final int DATABASE_VERSION = 88;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
|
@ -1246,6 +1247,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
|
|||
db.execSQL("ALTER TABLE recipient ADD COLUMN last_session_reset BLOB DEFAULT NULL");
|
||||
}
|
||||
|
||||
if (oldVersion < WALLPAPER) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN wallpaper BLOB DEFAULT NULL");
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN wallpaper_file TEXT DEFAULT NULL");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.keyvalue;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||
|
||||
|
@ -28,6 +29,7 @@ public final class SignalStore {
|
|||
private final CertificateValues certificateValues;
|
||||
private final PhoneNumberPrivacyValues phoneNumberPrivacyValues;
|
||||
private final OnboardingValues onboardingValues;
|
||||
private final WallpaperValues wallpaperValues;
|
||||
|
||||
private SignalStore() {
|
||||
this.store = new KeyValueStore(ApplicationDependencies.getApplication());
|
||||
|
@ -45,6 +47,7 @@ public final class SignalStore {
|
|||
this.certificateValues = new CertificateValues(store);
|
||||
this.phoneNumberPrivacyValues = new PhoneNumberPrivacyValues(store);
|
||||
this.onboardingValues = new OnboardingValues(store);
|
||||
this.wallpaperValues = new WallpaperValues(store);
|
||||
}
|
||||
|
||||
public static void onFirstEverAppLaunch() {
|
||||
|
@ -61,6 +64,7 @@ public final class SignalStore {
|
|||
certificateValues().onFirstEverAppLaunch();
|
||||
phoneNumberPrivacy().onFirstEverAppLaunch();
|
||||
onboarding().onFirstEverAppLaunch();
|
||||
wallpaper().onFirstEverAppLaunch();
|
||||
}
|
||||
|
||||
public static @NonNull KbsValues kbsValues() {
|
||||
|
@ -119,6 +123,10 @@ public final class SignalStore {
|
|||
return INSTANCE.onboardingValues;
|
||||
}
|
||||
|
||||
public static @NonNull WallpaperValues wallpaper() {
|
||||
return INSTANCE.wallpaperValues;
|
||||
}
|
||||
|
||||
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
||||
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory;
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage;
|
||||
|
||||
public final class WallpaperValues extends SignalStoreValues {
|
||||
|
||||
private static final String TAG = Log.tag(WallpaperValues.class);
|
||||
|
||||
private static final String KEY_WALLPAPER = "wallpaper.wallpaper";
|
||||
|
||||
WallpaperValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onFirstEverAppLaunch() {
|
||||
|
||||
}
|
||||
|
||||
public void setWallpaper(@NonNull Context context, @Nullable ChatWallpaper wallpaper) {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
Uri currentUri = null;
|
||||
|
||||
if (currentWallpaper != null && currentWallpaper.hasFile()) {
|
||||
currentUri = Uri.parse(currentWallpaper.getFile().getUri());
|
||||
}
|
||||
|
||||
if (wallpaper != null) {
|
||||
putBlob(KEY_WALLPAPER, wallpaper.serialize().toByteArray());
|
||||
} else {
|
||||
getStore().beginWrite().remove(KEY_WALLPAPER).apply();
|
||||
}
|
||||
|
||||
WallpaperStorage.onWallpaperDeselected(context, currentUri);
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
|
||||
if (currentWallpaper != null) {
|
||||
return ChatWallpaperFactory.create(currentWallpaper);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Uri getCurrentWallpaperUri() {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
|
||||
if (currentWallpaper != null && currentWallpaper.hasFile()) {
|
||||
return Uri.parse(currentWallpaper.getFile().getUri());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Wallpaper getCurrentWallpaper() {
|
||||
byte[] serialized = getBlob(KEY_WALLPAPER, null);
|
||||
|
||||
if (serialized != null) {
|
||||
try {
|
||||
return Wallpaper.parseFrom(serialized);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w(TAG, "Invalid proto stored for wallpaper!");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,22 +15,26 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class PartAuthority {
|
||||
|
||||
private static final String AUTHORITY = BuildConfig.APPLICATION_ID;
|
||||
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
|
||||
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
|
||||
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
|
||||
private static final String AUTHORITY = BuildConfig.APPLICATION_ID;
|
||||
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
|
||||
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
|
||||
private static final String WALLPAPER_URI_STRING = "content://" + AUTHORITY + "/wallpaper";
|
||||
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
|
||||
private static final Uri WALLPAPER_CONTENT_URI = Uri.parse(WALLPAPER_URI_STRING);
|
||||
|
||||
private static final int PART_ROW = 1;
|
||||
private static final int PERSISTENT_ROW = 2;
|
||||
private static final int BLOB_ROW = 3;
|
||||
private static final int STICKER_ROW = 4;
|
||||
private static final int WALLPAPER_ROW = 5;
|
||||
|
||||
private static final UriMatcher uriMatcher;
|
||||
|
||||
|
@ -38,6 +42,7 @@ public class PartAuthority {
|
|||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(AUTHORITY, "part/*/#", PART_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "sticker/#", STICKER_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "wallpaper/*", WALLPAPER_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
|
||||
|
@ -59,6 +64,7 @@ public class PartAuthority {
|
|||
case STICKER_ROW: return DatabaseFactory.getStickerDatabase(context).getStickerStream(ContentUris.parseId(uri));
|
||||
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||
case WALLPAPER_ROW: return WallpaperStorage.read(context, getWallpaperFilename(uri));
|
||||
default: return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
|
@ -138,6 +144,14 @@ public class PartAuthority {
|
|||
return ContentUris.withAppendedId(STICKER_CONTENT_URI, id);
|
||||
}
|
||||
|
||||
public static Uri getWallpaperUri(String filename) {
|
||||
return Uri.withAppendedPath(WALLPAPER_CONTENT_URI, filename);
|
||||
}
|
||||
|
||||
public static String getWallpaperFilename(Uri uri) {
|
||||
return uri.getPathSegments().get(1);
|
||||
}
|
||||
|
||||
public static boolean isLocalUri(final @NonNull Uri uri) {
|
||||
int match = uriMatcher.match(uri);
|
||||
switch (match) {
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.util.FeatureFlags;
|
|||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.libsignal.util.guava.Preconditions;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
@ -106,6 +107,7 @@ public class Recipient {
|
|||
private final InsightsBannerTier insightsBannerTier;
|
||||
private final byte[] storageId;
|
||||
private final MentionSetting mentionSetting;
|
||||
private final ChatWallpaper wallpaper;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -339,6 +341,7 @@ public class Recipient {
|
|||
this.groupsV1MigrationCapability = Capability.UNKNOWN;
|
||||
this.storageId = null;
|
||||
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
||||
this.wallpaper = null;
|
||||
}
|
||||
|
||||
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
|
||||
|
@ -381,6 +384,7 @@ public class Recipient {
|
|||
this.groupsV1MigrationCapability = details.groupsV1MigrationCapability;
|
||||
this.storageId = details.storageId;
|
||||
this.mentionSetting = details.mentionSetting;
|
||||
this.wallpaper = details.wallpaper;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getId() {
|
||||
|
@ -843,6 +847,10 @@ public class Recipient {
|
|||
return unidentifiedAccessMode;
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
return wallpaper;
|
||||
}
|
||||
|
||||
public boolean isSystemContact() {
|
||||
return contactUri != null;
|
||||
}
|
||||
|
@ -961,7 +969,8 @@ public class Recipient {
|
|||
groupsV1MigrationCapability == other.groupsV1MigrationCapability &&
|
||||
insightsBannerTier == other.insightsBannerTier &&
|
||||
Arrays.equals(storageId, other.storageId) &&
|
||||
mentionSetting == other.mentionSetting;
|
||||
mentionSetting == other.mentionSetting &&
|
||||
Objects.equals(wallpaper, other.wallpaper);
|
||||
}
|
||||
|
||||
private static boolean allContentsAreTheSame(@NonNull List<Recipient> a, @NonNull List<Recipient> b) {
|
||||
|
@ -999,7 +1008,6 @@ public class Recipient {
|
|||
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
|
||||
return new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_profile_outline_48);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MissingAddressError extends AssertionError {
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
|||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
@ -65,6 +66,7 @@ public class RecipientDetails {
|
|||
final InsightsBannerTier insightsBannerTier;
|
||||
final byte[] storageId;
|
||||
final MentionSetting mentionSetting;
|
||||
final ChatWallpaper wallpaper;
|
||||
|
||||
public RecipientDetails(@Nullable String name,
|
||||
@NonNull Optional<Long> groupAvatarId,
|
||||
|
@ -110,6 +112,7 @@ public class RecipientDetails {
|
|||
this.insightsBannerTier = settings.getInsightsBannerTier();
|
||||
this.storageId = settings.getStorageId();
|
||||
this.mentionSetting = settings.getMentionSetting();
|
||||
this.wallpaper = settings.getWallpaper();
|
||||
|
||||
if (name == null) this.name = settings.getSystemDisplayName();
|
||||
else this.name = name;
|
||||
|
@ -157,6 +160,7 @@ public class RecipientDetails {
|
|||
this.groupsV1MigrationCapability = Recipient.Capability.UNKNOWN;
|
||||
this.storageId = null;
|
||||
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
|
||||
this.wallpaper = null;
|
||||
}
|
||||
|
||||
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) {
|
||||
|
|
|
@ -5,6 +5,8 @@ import android.widget.ImageView;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -26,4 +28,6 @@ public interface ChatWallpaper extends Parcelable {
|
|||
GradientChatWallpaper.GRADIENT_2);
|
||||
|
||||
void loadInto(@NonNull ImageView imageView);
|
||||
|
||||
@NonNull Wallpaper serialize();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.thoughtcrime.securesms.wallpaper;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
|
||||
/**
|
||||
* Converts persisted models of wallpaper into usable {@link ChatWallpaper} instances.
|
||||
*/
|
||||
public class ChatWallpaperFactory {
|
||||
|
||||
public static @NonNull ChatWallpaper create(@NonNull Wallpaper model) {
|
||||
if (model.hasSingleColor()) {
|
||||
return new GradientChatWallpaper(model.getSingleColor().getColor());
|
||||
} else if (model.hasLinearGradient()) {
|
||||
return buildForLinearGradinent(model.getLinearGradient());
|
||||
} else if (model.hasFile()) {
|
||||
return buildForFile(model.getFile());
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull ChatWallpaper create(@NonNull Uri uri) {
|
||||
return new UriChatWallpaper(uri);
|
||||
}
|
||||
|
||||
private static @NonNull ChatWallpaper buildForLinearGradinent(@NonNull Wallpaper.LinearGradient gradient) {
|
||||
int[] colors = new int[gradient.getColorsCount()];
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
colors[i] = gradient.getColors(i);
|
||||
}
|
||||
|
||||
float[] positions = new float[gradient.getPositionsCount()];
|
||||
for (int i = 0; i < positions.length; i++) {
|
||||
positions[i] = gradient.getPositions(i);
|
||||
}
|
||||
|
||||
return new GradientChatWallpaper(gradient.getRotation(), colors, positions);
|
||||
}
|
||||
|
||||
private static @NonNull ChatWallpaper buildForFile(@NonNull Wallpaper.File file) {
|
||||
Uri uri = Uri.parse(file.getUri());
|
||||
return new UriChatWallpaper(uri);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ import android.widget.ImageView;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -81,6 +83,25 @@ final class GradientChatWallpaper implements ChatWallpaper, Parcelable {
|
|||
imageView.setImageDrawable(buildDrawable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Wallpaper serialize() {
|
||||
Wallpaper.LinearGradient.Builder builder = Wallpaper.LinearGradient.newBuilder();
|
||||
|
||||
builder.setRotation(degrees);
|
||||
|
||||
for (int color : colors) {
|
||||
builder.addColors(color);
|
||||
}
|
||||
|
||||
for (float position : positions) {
|
||||
builder.addPositions(position);
|
||||
}
|
||||
|
||||
return Wallpaper.newBuilder()
|
||||
.setLinearGradient(builder)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -7,46 +7,50 @@ import android.widget.ImageView;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
|
||||
final class UriChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
|
||||
private final Uri uri;
|
||||
|
||||
UriChatWallpaper(@NonNull Uri uri) {
|
||||
public UriChatWallpaper(@NonNull Uri uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
protected UriChatWallpaper(Parcel in) {
|
||||
uri = in.readParcelable(Uri.class.getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(uri, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<UriChatWallpaper> CREATOR = new Creator<UriChatWallpaper>() {
|
||||
@Override
|
||||
public UriChatWallpaper createFromParcel(Parcel in) {
|
||||
return new UriChatWallpaper(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriChatWallpaper[] newArray(int size) {
|
||||
return new UriChatWallpaper[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void loadInto(@NonNull ImageView imageView) {
|
||||
GlideApp.with(imageView)
|
||||
.load(uri)
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Wallpaper serialize() {
|
||||
return Wallpaper.newBuilder()
|
||||
.setFile(Wallpaper.File.newBuilder().setUri(uri.toString()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(uri.toString());
|
||||
}
|
||||
|
||||
public static final Creator<UriChatWallpaper> CREATOR = new Creator<UriChatWallpaper>() {
|
||||
@Override
|
||||
public UriChatWallpaper createFromParcel(Parcel in) {
|
||||
return new UriChatWallpaper(Uri.parse(in.readString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriChatWallpaper[] newArray(int size) {
|
||||
return new UriChatWallpaper[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
package org.thoughtcrime.securesms.wallpaper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Manages the storage of custom wallpaper files.
|
||||
*/
|
||||
public final class WallpaperStorage {
|
||||
|
||||
private static final String TAG = Log.tag(WallpaperStorage.class);
|
||||
|
||||
private static final String DIRECTORY = "wallpapers";
|
||||
private static final String FILENAME_BASE = "wallpaper";
|
||||
|
||||
/**
|
||||
* Saves the provided input stream as a new wallpaper file.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream) throws IOException {
|
||||
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
File file = File.createTempFile(FILENAME_BASE, "", directory);
|
||||
|
||||
StreamUtil.copy(wallpaperStream, getOutputStream(context, file));
|
||||
|
||||
return ChatWallpaperFactory.create(PartAuthority.getWallpaperUri(file.getName()));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull InputStream read(@NonNull Context context, String filename) throws IOException {
|
||||
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
File wallpaperFile = new File(directory, filename);
|
||||
|
||||
return getInputStream(context, wallpaperFile);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull List<ChatWallpaper> getAll(@NonNull Context context) {
|
||||
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(FILENAME_BASE));
|
||||
|
||||
return Stream.of(allFiles)
|
||||
.map(File::getName)
|
||||
.map(PartAuthority::getWallpaperUri)
|
||||
.map(ChatWallpaperFactory::create)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when wallpaper is deselected. This will check anywhere the wallpaper could be used, and
|
||||
* if we discover it's unused, we'll delete the file.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static void onWallpaperDeselected(@NonNull Context context, @NonNull Uri uri) {
|
||||
Uri globalUri = SignalStore.wallpaper().getCurrentWallpaperUri();
|
||||
if (Objects.equals(uri, globalUri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int recipientCount = DatabaseFactory.getRecipientDatabase(context).getWallpaperUriUsageCount(uri);
|
||||
if (recipientCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = PartAuthority.getWallpaperFilename(uri);
|
||||
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
File wallpaperFile = new File(directory, filename);
|
||||
|
||||
if (!wallpaperFile.delete()) {
|
||||
Log.w(TAG, "Failed to delete " + filename + "!");
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull OutputStream getOutputStream(@NonNull Context context, File outputFile) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second;
|
||||
}
|
||||
|
||||
private static @NonNull InputStream getInputStream(@NonNull Context context, File inputFile) throws IOException {
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0);
|
||||
}
|
||||
}
|
|
@ -90,4 +90,26 @@ message DeviceLastResetTime {
|
|||
}
|
||||
|
||||
repeated Pair resetTime = 1;
|
||||
}
|
||||
|
||||
message Wallpaper {
|
||||
message SingleColor {
|
||||
int32 color = 1;
|
||||
}
|
||||
message LinearGradient {
|
||||
float rotation = 1;
|
||||
repeated int32 colors = 2;
|
||||
repeated float positions = 3;
|
||||
}
|
||||
message File {
|
||||
string uri = 1;
|
||||
}
|
||||
|
||||
oneof wallpaper {
|
||||
SingleColor singleColor = 1;
|
||||
LinearGradient linearGradient = 2;
|
||||
File file = 3;
|
||||
}
|
||||
|
||||
float dimLevelInDarkMode = 4;
|
||||
}
|
Loading…
Add table
Reference in a new issue