That fuck shit the fascists are using
at master 249 lines 8.8 kB view raw
1package org.tm.archive.profiles; 2 3 4import android.content.Context; 5 6import androidx.annotation.NonNull; 7import androidx.annotation.Nullable; 8 9import org.signal.core.util.StreamUtil; 10import org.signal.core.util.logging.Log; 11import org.tm.archive.crypto.AttachmentSecret; 12import org.tm.archive.crypto.AttachmentSecretProvider; 13import org.tm.archive.crypto.ModernDecryptingPartInputStream; 14import org.tm.archive.crypto.ModernEncryptingPartOutputStream; 15import org.tm.archive.database.model.ProfileAvatarFileDetails; 16import org.tm.archive.keyvalue.SignalStore; 17import org.tm.archive.recipients.Recipient; 18import org.tm.archive.recipients.RecipientId; 19import org.tm.archive.util.ByteUnit; 20import org.tm.archive.util.MediaUtil; 21import org.whispersystems.signalservice.api.util.StreamDetails; 22 23import java.io.File; 24import java.io.IOException; 25import java.io.InputStream; 26import java.io.OutputStream; 27import java.util.Collections; 28import java.util.Iterator; 29 30public class AvatarHelper { 31 32 private static final String TAG = Log.tag(AvatarHelper.class); 33 34 public static int AVATAR_DIMENSIONS = 1024; 35 public static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = ByteUnit.MEGABYTES.toBytes(10); 36 37 private static final String AVATAR_DIRECTORY = "avatars"; 38 39 private static File avatarDirectory; 40 41 private static File getAvatarDirectory(@NonNull Context context) { 42 if (avatarDirectory == null) { 43 avatarDirectory = context.getDir(AVATAR_DIRECTORY, Context.MODE_PRIVATE); 44 } 45 return avatarDirectory; 46 } 47 48 public static long getAvatarCount(@NonNull Context context) { 49 File avatarDirectory = getAvatarDirectory(context); 50 String[] results = avatarDirectory.list(); 51 52 return results == null ? 0 : results.length; 53 } 54 55 /** 56 * Retrieves an iterable set of avatars. Only intended to be used during backup. 57 */ 58 public static Iterable<Avatar> getAvatars(@NonNull Context context) { 59 File avatarDirectory = getAvatarDirectory(context); 60 File[] results = avatarDirectory.listFiles(); 61 62 if (results == null) { 63 return Collections.emptyList(); 64 } 65 66 return () -> { 67 return new Iterator<Avatar>() { 68 int i = 0; 69 @Override 70 public boolean hasNext() { 71 return i < results.length; 72 } 73 74 @Override 75 public Avatar next() { 76 File file = results[i]; 77 try { 78 return new Avatar(getAvatar(context, RecipientId.from(file.getName())), 79 file.getName(), 80 ModernEncryptingPartOutputStream.getPlaintextLength(file.length())); 81 } catch (IOException e) { 82 return null; 83 } finally { 84 i++; 85 } 86 } 87 }; 88 }; 89 } 90 91 /** 92 * Deletes and avatar. 93 */ 94 public static void delete(@NonNull Context context, @NonNull RecipientId recipientId) { 95 getAvatarFile(context, recipientId).delete(); 96 Recipient.live(recipientId).refresh(); 97 } 98 99 /** 100 * Whether or not an avatar is present for the given recipient. 101 */ 102 public static boolean hasAvatar(@NonNull Context context, @NonNull RecipientId recipientId) { 103 File avatarFile = getAvatarFile(context, recipientId); 104 return avatarFile.exists() && avatarFile.length() > 0; 105 } 106 107 public static @NonNull ProfileAvatarFileDetails getAvatarFileDetails(@NonNull Context context, @NonNull RecipientId recipientId) { 108 File avatarFile = getAvatarFile(context, recipientId); 109 if (avatarFile.exists() && avatarFile.length() > 0) { 110 return new ProfileAvatarFileDetails(avatarFile.hashCode(), avatarFile.lastModified()); 111 } 112 return ProfileAvatarFileDetails.NO_DETAILS; 113 } 114 115 /** 116 * Retrieves a stream for an avatar. If there is no avatar, the stream will likely throw an 117 * IOException. It is recommended to call {@link #hasAvatar(Context, RecipientId)} first. 118 */ 119 public static @NonNull InputStream getAvatar(@NonNull Context context, @NonNull RecipientId recipientId) throws IOException { 120 AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); 121 File avatarFile = getAvatarFile(context, recipientId); 122 123 return ModernDecryptingPartInputStream.createFor(attachmentSecret, avatarFile, 0); 124 } 125 126 public static byte[] getAvatarBytes(@NonNull Context context, @NonNull RecipientId recipientId) throws IOException { 127 return hasAvatar(context, recipientId) ? StreamUtil.readFully(getAvatar(context, recipientId)) 128 : null; 129 } 130 131 /** 132 * Returns the size of the avatar on disk. 133 */ 134 public static long getAvatarLength(@NonNull Context context, @NonNull RecipientId recipientId) { 135 return ModernEncryptingPartOutputStream.getPlaintextLength(getAvatarFile(context, recipientId).length()); 136 } 137 138 /** 139 * Saves the contents of the input stream as the avatar for the specified recipient. If you pass 140 * in null for the stream, the avatar will be deleted. 141 */ 142 public static void setAvatar(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable InputStream inputStream) 143 throws IOException 144 { 145 if (inputStream == null) { 146 delete(context, recipientId); 147 return; 148 } 149 150 OutputStream outputStream = null; 151 try { 152 outputStream = getOutputStream(context, recipientId, false); 153 StreamUtil.copy(inputStream, outputStream); 154 } finally { 155 StreamUtil.close(outputStream); 156 } 157 } 158 159 public static void setSyncAvatar(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable InputStream inputStream) 160 throws IOException 161 { 162 if (inputStream == null) { 163 delete(context, recipientId); 164 return; 165 } 166 167 OutputStream outputStream = null; 168 try { 169 outputStream = getOutputStream(context, recipientId, true); 170 StreamUtil.copy(inputStream, outputStream); 171 } finally { 172 StreamUtil.close(outputStream); 173 } 174 } 175 176 /** 177 * Retrieves an output stream you can write to that will be saved as the avatar for the specified 178 * recipient. Only intended to be used for backup. Otherwise, use {@link #setAvatar(Context, RecipientId, InputStream)}. 179 */ 180 public static @NonNull OutputStream getOutputStream(@NonNull Context context, @NonNull RecipientId recipientId, boolean isSyncAvatar) throws IOException { 181 AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); 182 File targetFile = getAvatarFile(context, recipientId, isSyncAvatar); 183 return ModernEncryptingPartOutputStream.createFor(attachmentSecret, targetFile, true).second; 184 } 185 186 /** 187 * Returns a {@link StreamDetails} for the local user's own avatar, or null if one does not exist. 188 */ 189 public static @Nullable StreamDetails getSelfProfileAvatarStream(@NonNull Context context) { 190 RecipientId selfId = Recipient.self().getId(); 191 192 if (!hasAvatar(context, selfId)) { 193 return null; 194 } 195 196 try { 197 InputStream stream = getAvatar(context, selfId); 198 return new StreamDetails(stream, MediaUtil.IMAGE_JPEG, getAvatarLength(context, selfId)); 199 } catch (IOException e) { 200 Log.w(TAG, "Failed to read own avatar!", e); 201 return null; 202 } 203 } 204 205 public static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId) { 206 File profileAvatar = getAvatarFile(context, recipientId, false); 207 boolean profileAvatarExists = profileAvatar.exists() && profileAvatar.length() > 0; 208 File syncAvatar = getAvatarFile(context, recipientId, true); 209 boolean syncAvatarExists = syncAvatar.exists() && syncAvatar.length() > 0; 210 211 if (SignalStore.settings().isPreferSystemContactPhotos() && syncAvatarExists) { 212 return syncAvatar; 213 } else if (profileAvatarExists) { 214 return profileAvatar; 215 } else if (syncAvatarExists) { 216 return syncAvatar; 217 } 218 219 return profileAvatar; 220 } 221 222 private static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId, boolean isSyncAvatar) { 223 return new File(getAvatarDirectory(context), recipientId.serialize() + (isSyncAvatar ? "-sync" : "")); 224 } 225 226 public static class Avatar { 227 private final InputStream inputStream; 228 private final String filename; 229 private final long length; 230 231 public Avatar(@NonNull InputStream inputStream, @NonNull String filename, long length) { 232 this.inputStream = inputStream; 233 this.filename = filename; 234 this.length = length; 235 } 236 237 public @NonNull InputStream getInputStream() { 238 return inputStream; 239 } 240 241 public @NonNull String getFilename() { 242 return filename; 243 } 244 245 public long getLength() { 246 return length; 247 } 248 } 249}