Add support for persisting wallpaper selection.

fork-5.53.8
Greyson Parrelli 2021-01-20 09:03:21 -05:00
rodzic 80651d2425
commit 6bcb0de43d
13 zmienionych plików z 447 dodań i 37 usunięć

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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();

Wyświetl plik

@ -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());
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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];
}
};
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;
}