mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Add support for persisting wallpaper selection.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user