From b89163bb14c2e8226399d06ab3e246307c33149e Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 23 Sep 2019 11:37:01 -0400 Subject: [PATCH] Migrate avatars to use recipientId filenames. --- protobuf/Backups.proto | 5 +- .../securesms/ConversationListActivity.java | 2 +- .../securesms/CreateProfileActivity.java | 9 +- .../RecipientPreferenceActivity.java | 2 +- .../securesms/backup/BackupProtos.java | 200 ++++++++++++++++-- .../securesms/backup/FullBackupExporter.java | 2 +- .../securesms/backup/FullBackupImporter.java | 7 +- .../contacts/avatars/ProfileContactPhoto.java | 23 +- .../securesms/jobs/JobManagerFactories.java | 2 + .../jobs/RetrieveProfileAvatarJob.java | 6 +- .../securesms/jobs/RotateProfileKeyJob.java | 2 +- .../migrations/ApplicationMigrations.java | 7 +- .../migrations/AvatarMigrationJob.java | 79 +++++++ .../securesms/migrations/MigrationJob.java | 3 +- .../widgets/ProfilePreference.java | 9 +- .../securesms/profiles/AvatarHelper.java | 17 +- .../securesms/recipients/Recipient.java | 2 +- 17 files changed, 315 insertions(+), 62 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java diff --git a/protobuf/Backups.proto b/protobuf/Backups.proto index c7efc3914..3169da84b 100644 --- a/protobuf/Backups.proto +++ b/protobuf/Backups.proto @@ -40,8 +40,9 @@ message Sticker { } message Avatar { - optional string name = 1; - optional uint32 length = 2; + optional string name = 1; + optional string recipientId = 3; + optional uint32 length = 2; } message DatabaseVersion { diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 9e31e25a1..7ca96a086 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -188,7 +188,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit Drawable fallback = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(this, fallbackColor.toAvatarColor(this)); GlideApp.with(this) - .load(new ProfileContactPhoto(recipient.requireAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))) + .load(new ProfileContactPhoto(recipient.getId(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))) .error(fallback) .circleCrop() .diskCacheStrategy(DiskCacheStrategy.ALL) diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java index 6f638a348..1ff82aece 100644 --- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java +++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java @@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints; import org.thoughtcrime.securesms.profiles.SystemProfileUtil; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DynamicLanguage; @@ -263,14 +264,14 @@ public class CreateProfileActivity extends BaseActionBarActivity { } private void initializeProfileAvatar(boolean excludeSystem) { - Address ourAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)); + RecipientId selfId = Recipient.self().getId(); - if (AvatarHelper.getAvatarFile(this, ourAddress).exists() && AvatarHelper.getAvatarFile(this, ourAddress).length() > 0) { + if (AvatarHelper.getAvatarFile(this, selfId).exists() && AvatarHelper.getAvatarFile(this, selfId).length() > 0) { new AsyncTask() { @Override protected byte[] doInBackground(Void... params) { try { - return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, ourAddress)); + return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, selfId)); } catch (IOException e) { Log.w(TAG, e); return null; @@ -373,7 +374,7 @@ public class CreateProfileActivity extends BaseActionBarActivity { try { accountManager.setProfileAvatar(profileKey, avatar); - AvatarHelper.setAvatar(CreateProfileActivity.this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), avatarBytes); + AvatarHelper.setAvatar(CreateProfileActivity.this, Recipient.self().getId(), avatarBytes); TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt()); } catch (IOException e) { Log.w(TAG, e); diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index d3050c6fe..89f28664a 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -206,7 +206,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi } private void setHeader(@NonNull Recipient recipient) { - ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient.requireAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this))) + ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient.getId(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this))) : recipient.getContactPhoto(); FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_default, R.drawable.ic_person_large) : recipient.getFallbackContactPhoto(); diff --git a/src/org/thoughtcrime/securesms/backup/BackupProtos.java b/src/org/thoughtcrime/securesms/backup/BackupProtos.java index f3b78606f..5193aa15b 100644 --- a/src/org/thoughtcrime/securesms/backup/BackupProtos.java +++ b/src/org/thoughtcrime/securesms/backup/BackupProtos.java @@ -3475,6 +3475,21 @@ public final class BackupProtos { com.google.protobuf.ByteString getNameBytes(); + // optional string recipientId = 3; + /** + * optional string recipientId = 3; + */ + boolean hasRecipientId(); + /** + * optional string recipientId = 3; + */ + java.lang.String getRecipientId(); + /** + * optional string recipientId = 3; + */ + com.google.protobuf.ByteString + getRecipientIdBytes(); + // optional uint32 length = 2; /** * optional uint32 length = 2; @@ -3542,10 +3557,15 @@ public final class BackupProtos { break; } case 16: { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; length_ = input.readUInt32(); break; } + case 26: { + bitField0_ |= 0x00000002; + recipientId_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -3629,6 +3649,49 @@ public final class BackupProtos { } } + // optional string recipientId = 3; + public static final int RECIPIENTID_FIELD_NUMBER = 3; + private java.lang.Object recipientId_; + /** + * optional string recipientId = 3; + */ + public boolean hasRecipientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string recipientId = 3; + */ + public java.lang.String getRecipientId() { + java.lang.Object ref = recipientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + recipientId_ = s; + } + return s; + } + } + /** + * optional string recipientId = 3; + */ + public com.google.protobuf.ByteString + getRecipientIdBytes() { + java.lang.Object ref = recipientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + recipientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + // optional uint32 length = 2; public static final int LENGTH_FIELD_NUMBER = 2; private int length_; @@ -3636,7 +3699,7 @@ public final class BackupProtos { * optional uint32 length = 2; */ public boolean hasLength() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 length = 2; @@ -3647,6 +3710,7 @@ public final class BackupProtos { private void initFields() { name_ = ""; + recipientId_ = ""; length_ = 0; } private byte memoizedIsInitialized = -1; @@ -3664,9 +3728,12 @@ public final class BackupProtos { if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeBytes(1, getNameBytes()); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeUInt32(2, length_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(3, getRecipientIdBytes()); + } getUnknownFields().writeTo(output); } @@ -3680,10 +3747,14 @@ public final class BackupProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(1, getNameBytes()); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(2, length_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getRecipientIdBytes()); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -3802,8 +3873,10 @@ public final class BackupProtos { super.clear(); name_ = ""; bitField0_ = (bitField0_ & ~0x00000001); - length_ = 0; + recipientId_ = ""; bitField0_ = (bitField0_ & ~0x00000002); + length_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -3839,6 +3912,10 @@ public final class BackupProtos { if (((from_bitField0_ & 0x00000002) == 0x00000002)) { to_bitField0_ |= 0x00000002; } + result.recipientId_ = recipientId_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } result.length_ = length_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -3861,6 +3938,11 @@ public final class BackupProtos { name_ = other.name_; onChanged(); } + if (other.hasRecipientId()) { + bitField0_ |= 0x00000002; + recipientId_ = other.recipientId_; + onChanged(); + } if (other.hasLength()) { setLength(other.getLength()); } @@ -3965,13 +4047,87 @@ public final class BackupProtos { return this; } + // optional string recipientId = 3; + private java.lang.Object recipientId_ = ""; + /** + * optional string recipientId = 3; + */ + public boolean hasRecipientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string recipientId = 3; + */ + public java.lang.String getRecipientId() { + java.lang.Object ref = recipientId_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + recipientId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string recipientId = 3; + */ + public com.google.protobuf.ByteString + getRecipientIdBytes() { + java.lang.Object ref = recipientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + recipientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string recipientId = 3; + */ + public Builder setRecipientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + recipientId_ = value; + onChanged(); + return this; + } + /** + * optional string recipientId = 3; + */ + public Builder clearRecipientId() { + bitField0_ = (bitField0_ & ~0x00000002); + recipientId_ = getDefaultInstance().getRecipientId(); + onChanged(); + return this; + } + /** + * optional string recipientId = 3; + */ + public Builder setRecipientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + recipientId_ = value; + onChanged(); + return this; + } + // optional uint32 length = 2; private int length_ ; /** * optional uint32 length = 2; */ public boolean hasLength() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 length = 2; @@ -3983,7 +4139,7 @@ public final class BackupProtos { * optional uint32 length = 2; */ public Builder setLength(int value) { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; length_ = value; onChanged(); return this; @@ -3992,7 +4148,7 @@ public final class BackupProtos { * optional uint32 length = 2; */ public Builder clearLength() { - bitField0_ = (bitField0_ & ~0x00000002); + bitField0_ = (bitField0_ & ~0x00000004); length_ = 0; onChanged(); return this; @@ -6692,19 +6848,19 @@ public final class BackupProtos { "\030\001 \001(\t\022\013\n\003key\030\002 \001(\t\022\r\n\005value\030\003 \001(\t\"A\n\nAt" + "tachment\022\r\n\005rowId\030\001 \001(\004\022\024\n\014attachmentId\030" + "\002 \001(\004\022\016\n\006length\030\003 \001(\r\"(\n\007Sticker\022\r\n\005rowI", - "d\030\001 \001(\004\022\016\n\006length\030\002 \001(\r\"&\n\006Avatar\022\014\n\004nam" + - "e\030\001 \001(\t\022\016\n\006length\030\002 \001(\r\"\"\n\017DatabaseVersi" + - "on\022\017\n\007version\030\001 \001(\r\"\"\n\006Header\022\n\n\002iv\030\001 \001(" + - "\014\022\014\n\004salt\030\002 \001(\014\"\245\002\n\013BackupFrame\022\036\n\006heade" + - "r\030\001 \001(\0132\016.signal.Header\022\'\n\tstatement\030\002 \001" + - "(\0132\024.signal.SqlStatement\022,\n\npreference\030\003" + - " \001(\0132\030.signal.SharedPreference\022&\n\nattach" + - "ment\030\004 \001(\0132\022.signal.Attachment\022(\n\007versio" + - "n\030\005 \001(\0132\027.signal.DatabaseVersion\022\013\n\003end\030" + - "\006 \001(\010\022\036\n\006avatar\030\007 \001(\0132\016.signal.Avatar\022 \n", - "\007sticker\030\010 \001(\0132\017.signal.StickerB1\n!org.t" + - "houghtcrime.securesms.backupB\014BackupProt" + - "os" + "d\030\001 \001(\004\022\016\n\006length\030\002 \001(\r\";\n\006Avatar\022\014\n\004nam" + + "e\030\001 \001(\t\022\023\n\013recipientId\030\003 \001(\t\022\016\n\006length\030\002" + + " \001(\r\"\"\n\017DatabaseVersion\022\017\n\007version\030\001 \001(\r" + + "\"\"\n\006Header\022\n\n\002iv\030\001 \001(\014\022\014\n\004salt\030\002 \001(\014\"\245\002\n" + + "\013BackupFrame\022\036\n\006header\030\001 \001(\0132\016.signal.He" + + "ader\022\'\n\tstatement\030\002 \001(\0132\024.signal.SqlStat" + + "ement\022,\n\npreference\030\003 \001(\0132\030.signal.Share" + + "dPreference\022&\n\nattachment\030\004 \001(\0132\022.signal" + + ".Attachment\022(\n\007version\030\005 \001(\0132\027.signal.Da" + + "tabaseVersion\022\013\n\003end\030\006 \001(\010\022\036\n\006avatar\030\007 \001", + "(\0132\016.signal.Avatar\022 \n\007sticker\030\010 \001(\0132\017.si" + + "gnal.StickerB1\n!org.thoughtcrime.secures" + + "ms.backupB\014BackupProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -6746,7 +6902,7 @@ public final class BackupProtos { internal_static_signal_Avatar_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signal_Avatar_descriptor, - new java.lang.String[] { "Name", "Length", }); + new java.lang.String[] { "Name", "RecipientId", "Length", }); internal_static_signal_DatabaseVersion_descriptor = getDescriptor().getMessageTypes().get(5); internal_static_signal_DatabaseVersion_fieldAccessorTable = new diff --git a/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java b/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java index 0ffe1a40a..1f3f0f463 100644 --- a/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java +++ b/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java @@ -326,7 +326,7 @@ public class FullBackupExporter extends FullBackupBase { public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException { write(outputStream, BackupProtos.BackupFrame.newBuilder() .setAvatar(BackupProtos.Avatar.newBuilder() - .setName(avatarName) + .setRecipientId(avatarName) .setLength(Util.toIntExact(size)) .build()) .build()); diff --git a/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java b/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java index 6baf2fa27..fa5ecd6a4 100644 --- a/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java +++ b/src/org/thoughtcrime/securesms/backup/FullBackupImporter.java @@ -27,6 +27,8 @@ import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.profiles.AvatarHelper; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.kdf.HKDFv3; @@ -163,7 +165,10 @@ public class FullBackupImporter extends FullBackupBase { } private static void processAvatar(@NonNull Context context, @NonNull BackupProtos.Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException { - inputStream.readAttachmentTo(new FileOutputStream(AvatarHelper.getAvatarFile(context, Address.fromSerialized(PhoneNumberFormatter.get(context).format(avatar.getName())))), avatar.getLength()); + Recipient recipient = avatar.hasRecipientId() ? Recipient.resolved(RecipientId.from(avatar.getRecipientId())) + : Recipient.external(context, avatar.getName()); + + inputStream.readAttachmentTo(new FileOutputStream(AvatarHelper.getAvatarFile(context, recipient.getId())), avatar.getLength()); } @SuppressLint("ApplySharedPref") diff --git a/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java b/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java index 480b7dc3d..bca3e003d 100644 --- a/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java +++ b/src/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java @@ -6,8 +6,9 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.profiles.AvatarHelper; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import java.io.File; import java.io.IOException; @@ -16,22 +17,22 @@ import java.security.MessageDigest; public class ProfileContactPhoto implements ContactPhoto { - private final @NonNull Address address; - private final @NonNull String avatarObject; + private final @NonNull RecipientId recipient; + private final @NonNull String avatarObject; - public ProfileContactPhoto(@NonNull Address address, @NonNull String avatarObject) { - this.address = address; + public ProfileContactPhoto(@NonNull RecipientId recipient, @NonNull String avatarObject) { + this.recipient = recipient; this.avatarObject = avatarObject; } @Override - public InputStream openInputStream(Context context) throws IOException { - return AvatarHelper.getInputStreamFor(context, address); + public @NonNull InputStream openInputStream(Context context) throws IOException { + return AvatarHelper.getInputStreamFor(context, recipient); } @Override public @Nullable Uri getUri(@NonNull Context context) { - File avatarFile = AvatarHelper.getAvatarFile(context, address); + File avatarFile = AvatarHelper.getAvatarFile(context, recipient); return avatarFile.exists() ? Uri.fromFile(avatarFile) : null; } @@ -42,7 +43,7 @@ public class ProfileContactPhoto implements ContactPhoto { @Override public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(address.serialize().getBytes()); + messageDigest.update(recipient.serialize().getBytes()); messageDigest.update(avatarObject.getBytes()); } @@ -52,11 +53,11 @@ public class ProfileContactPhoto implements ContactPhoto { ProfileContactPhoto that = (ProfileContactPhoto)other; - return this.address.equals(that.address) && this.avatarObject.equals(that.avatarObject); + return this.recipient.equals(that.recipient) && this.avatarObject.equals(that.avatarObject); } @Override public int hashCode() { - return address.hashCode() ^ avatarObject.hashCode(); + return recipient.hashCode() ^ avatarObject.hashCode(); } } diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index e062e2f79..11a0bcc20 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintOb import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration; +import org.thoughtcrime.securesms.migrations.AvatarMigrationJob; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; @@ -82,6 +83,7 @@ public final class JobManagerFactories { put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); // Migrations + put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory()); put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index 0e4ea1d9e..f32b3fadd 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -86,7 +86,7 @@ public class RetrieveProfileAvatarJob extends BaseJob { if (TextUtils.isEmpty(profileAvatar)) { Log.w(TAG, "Removing profile avatar (no url) for: " + recipient.requireAddress().serialize()); - AvatarHelper.delete(context, recipient.requireAddress()); + AvatarHelper.delete(context, recipient.getId()); database.setProfileAvatar(recipient.getId(), profileAvatar); return; } @@ -104,11 +104,11 @@ public class RetrieveProfileAvatarJob extends BaseJob { throw new IOException("Failed to copy stream. Likely a Conscrypt issue.", e); } - decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.requireAddress())); + decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getId())); } catch (PushNetworkException e) { if (e.getCause() instanceof NonSuccessfulResponseCodeException) { Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.requireAddress().serialize()); - AvatarHelper.delete(context, recipient.requireAddress()); + AvatarHelper.delete(context, recipient.getId()); } else { throw e; } diff --git a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java index 0256cdd6d..f25f10354 100644 --- a/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java @@ -73,7 +73,7 @@ public class RotateProfileKeyJob extends BaseJob { private @Nullable StreamDetails getProfileAvatar() { try { - File avatarFile = AvatarHelper.getAvatarFile(context, Recipient.self().requireAddress()); + File avatarFile = AvatarHelper.getAvatarFile(context, Recipient.self().getId()); if (avatarFile.exists()) { return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length()); diff --git a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 13d98febb..326778ace 100644 --- a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -38,13 +38,14 @@ public class ApplicationMigrations { private static final int LEGACY_CANONICAL_VERSION = 455; - public static final int CURRENT_VERSION = 4; + public static final int CURRENT_VERSION = 5; private static final class Version { static final int LEGACY = 1; static final int RECIPIENT_ID = 2; static final int RECIPIENT_SEARCH = 3; static final int RECIPIENT_CLEANUP = 4; + static final int AVATAR_MIGRATION = 5; } /** @@ -173,6 +174,10 @@ public class ApplicationMigrations { jobs.put(Version.RECIPIENT_CLEANUP, new DatabaseMigrationJob()); } + if (lastSeenVersion < Version.AVATAR_MIGRATION) { + jobs.put(Version.AVATAR_MIGRATION, new AvatarMigrationJob()); + } + return jobs; } diff --git a/src/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java b/src/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java new file mode 100644 index 000000000..0e5355187 --- /dev/null +++ b/src/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java @@ -0,0 +1,79 @@ +package org.thoughtcrime.securesms.migrations; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.profiles.AvatarHelper; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.Util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * Previously, we used a recipient's address as the filename for their avatar. We want to use + * recipientId's instead in preparation for UUIDs. + */ +public class AvatarMigrationJob extends MigrationJob { + + public static final String KEY = "AvatarMigrationJob"; + + private static final String TAG = Log.tag(AvatarMigrationJob.class); + + AvatarMigrationJob() { + this(new Parameters.Builder().build()); + } + + private AvatarMigrationJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public boolean isUiBlocking() { + return true; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void performMigration() { + File oldDirectory = new File(context.getFilesDir(), "avatars"); + File[] files = oldDirectory.listFiles(); + + Log.i(TAG, "Preparing to move " + files.length + " avatars."); + + for (File file : files) { + try { + Recipient recipient = Recipient.external(context, file.getName()); + byte[] data = Util.readFully(new FileInputStream(file)); + + AvatarHelper.setAvatar(context, recipient.getId(), data); + } catch (IOException e) { + Log.w(TAG, "Failed to copy avatar file. Skipping it.", e); + } finally { + file.delete(); + } + } + + oldDirectory.delete(); + } + + @Override + boolean shouldRetry(@NonNull Exception e) { + return false; + } + + public static class Factory implements Job.Factory { + @Override + public @NonNull AvatarMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new AvatarMigrationJob(parameters); + } + } +} diff --git a/src/org/thoughtcrime/securesms/migrations/MigrationJob.java b/src/org/thoughtcrime/securesms/migrations/MigrationJob.java index 299210114..f2eea63b8 100644 --- a/src/org/thoughtcrime/securesms/migrations/MigrationJob.java +++ b/src/org/thoughtcrime/securesms/migrations/MigrationJob.java @@ -36,6 +36,7 @@ abstract class MigrationJob extends Job { @Override public @NonNull Result run() { try { + Log.i(TAG, "About to run " + getClass().getSimpleName()); performMigration(); return Result.success(); } catch (RuntimeException e) { @@ -54,7 +55,7 @@ abstract class MigrationJob extends Job { @Override public void onCanceled() { - throw new AssertionError("This job should never fail."); + throw new AssertionError("This job should never fail. " + getClass().getSimpleName()); } /** diff --git a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java index b87551536..fc3c1bf6c 100644 --- a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java +++ b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; public class ProfilePreference extends Preference { @@ -64,11 +65,11 @@ public class ProfilePreference extends Preference { public void refresh() { if (profileNumberView == null) return; - final Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(getContext())); - final String profileName = TextSecurePreferences.getProfileName(getContext()); + final Recipient self = Recipient.self(); + final String profileName = TextSecurePreferences.getProfileName(getContext()); GlideApp.with(getContext().getApplicationContext()) - .load(new ProfileContactPhoto(localAddress, String.valueOf(TextSecurePreferences.getProfileAvatarId(getContext())))) + .load(new ProfileContactPhoto(self.getId(), String.valueOf(TextSecurePreferences.getProfileAvatarId(getContext())))) .error(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(getContext(), getContext().getResources().getColor(R.color.grey_400))) .circleCrop() .diskCacheStrategy(DiskCacheStrategy.ALL) @@ -78,6 +79,6 @@ public class ProfilePreference extends Preference { profileNameView.setText(profileName); } - profileNumberView.setText(localAddress.toPhoneString()); + profileNumberView.setText(self.requireAddress().toPhoneString()); } } diff --git a/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java b/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java index 572ae4ab6..9fcd7f732 100644 --- a/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java +++ b/src/org/thoughtcrime/securesms/profiles/AvatarHelper.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.recipients.RecipientId; import java.io.File; import java.io.FileInputStream; @@ -21,7 +22,7 @@ public class AvatarHelper { private static final String AVATAR_DIRECTORY = "avatars"; - public static InputStream getInputStreamFor(@NonNull Context context, @NonNull Address address) + public static InputStream getInputStreamFor(@NonNull Context context, @NonNull RecipientId address) throws IOException { return new FileInputStream(getAvatarFile(context, address)); @@ -35,24 +36,24 @@ public class AvatarHelper { else return Stream.of(results).toList(); } - public static void delete(@NonNull Context context, @NonNull Address address) { - getAvatarFile(context, address).delete(); + public static void delete(@NonNull Context context, @NonNull RecipientId recipientId) { + getAvatarFile(context, recipientId).delete(); } - public static @NonNull File getAvatarFile(@NonNull Context context, @NonNull Address address) { + public static @NonNull File getAvatarFile(@NonNull Context context, @NonNull RecipientId recipientId) { File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY); avatarDirectory.mkdirs(); - return new File(avatarDirectory, new File(address.serialize()).getName()); + return new File(avatarDirectory, new File(recipientId.serialize()).getName()); } - public static void setAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data) + public static void setAvatar(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable byte[] data) throws IOException { if (data == null) { - delete(context, address); + delete(context, recipientId); } else { - FileOutputStream out = new FileOutputStream(getAvatarFile(context, address)); + FileOutputStream out = new FileOutputStream(getAvatarFile(context, recipientId)); out.write(data); out.close(); } diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index f39b12dbc..ad1c6ffc6 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -302,7 +302,7 @@ public class Recipient { if (localNumber) return null; else if (isGroupInternal() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(address, groupAvatarId.get()); else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); - else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar); + else if (profileAvatar != null) return new ProfileContactPhoto(id, profileAvatar); else return null; }