Migrate avatars to use recipientId filenames.

master
Greyson Parrelli 2019-09-23 11:37:01 -04:00
parent b7ce220600
commit b89163bb14
17 changed files with 315 additions and 62 deletions

View File

@ -40,8 +40,9 @@ message Sticker {
} }
message Avatar { message Avatar {
optional string name = 1; optional string name = 1;
optional uint32 length = 2; optional string recipientId = 3;
optional uint32 length = 2;
} }
message DatabaseVersion { message DatabaseVersion {

View File

@ -188,7 +188,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
Drawable fallback = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(this, fallbackColor.toAvatarColor(this)); Drawable fallback = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(this, fallbackColor.toAvatarColor(this));
GlideApp.with(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) .error(fallback)
.circleCrop() .circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL)

View File

@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints; import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
import org.thoughtcrime.securesms.profiles.SystemProfileUtil; import org.thoughtcrime.securesms.profiles.SystemProfileUtil;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
@ -263,14 +264,14 @@ public class CreateProfileActivity extends BaseActionBarActivity {
} }
private void initializeProfileAvatar(boolean excludeSystem) { 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<Void, Void, byte[]>() { new AsyncTask<Void, Void, byte[]>() {
@Override @Override
protected byte[] doInBackground(Void... params) { protected byte[] doInBackground(Void... params) {
try { try {
return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, ourAddress)); return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, selfId));
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
return null; return null;
@ -373,7 +374,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
try { try {
accountManager.setProfileAvatar(profileKey, avatar); 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()); TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt());
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);

View File

@ -206,7 +206,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
} }
private void setHeader(@NonNull Recipient recipient) { 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(); : recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_default, R.drawable.ic_person_large) FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_default, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto(); : recipient.getFallbackContactPhoto();

View File

@ -3475,6 +3475,21 @@ public final class BackupProtos {
com.google.protobuf.ByteString com.google.protobuf.ByteString
getNameBytes(); getNameBytes();
// optional string recipientId = 3;
/**
* <code>optional string recipientId = 3;</code>
*/
boolean hasRecipientId();
/**
* <code>optional string recipientId = 3;</code>
*/
java.lang.String getRecipientId();
/**
* <code>optional string recipientId = 3;</code>
*/
com.google.protobuf.ByteString
getRecipientIdBytes();
// optional uint32 length = 2; // optional uint32 length = 2;
/** /**
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
@ -3542,10 +3557,15 @@ public final class BackupProtos {
break; break;
} }
case 16: { case 16: {
bitField0_ |= 0x00000002; bitField0_ |= 0x00000004;
length_ = input.readUInt32(); length_ = input.readUInt32();
break; break;
} }
case 26: {
bitField0_ |= 0x00000002;
recipientId_ = input.readBytes();
break;
}
} }
} }
} catch (com.google.protobuf.InvalidProtocolBufferException e) { } 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_;
/**
* <code>optional string recipientId = 3;</code>
*/
public boolean hasRecipientId() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional string recipientId = 3;</code>
*/
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;
}
}
/**
* <code>optional string recipientId = 3;</code>
*/
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; // optional uint32 length = 2;
public static final int LENGTH_FIELD_NUMBER = 2; public static final int LENGTH_FIELD_NUMBER = 2;
private int length_; private int length_;
@ -3636,7 +3699,7 @@ public final class BackupProtos {
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
*/ */
public boolean hasLength() { public boolean hasLength() {
return ((bitField0_ & 0x00000002) == 0x00000002); return ((bitField0_ & 0x00000004) == 0x00000004);
} }
/** /**
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
@ -3647,6 +3710,7 @@ public final class BackupProtos {
private void initFields() { private void initFields() {
name_ = ""; name_ = "";
recipientId_ = "";
length_ = 0; length_ = 0;
} }
private byte memoizedIsInitialized = -1; private byte memoizedIsInitialized = -1;
@ -3664,9 +3728,12 @@ public final class BackupProtos {
if (((bitField0_ & 0x00000001) == 0x00000001)) { if (((bitField0_ & 0x00000001) == 0x00000001)) {
output.writeBytes(1, getNameBytes()); output.writeBytes(1, getNameBytes());
} }
if (((bitField0_ & 0x00000002) == 0x00000002)) { if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeUInt32(2, length_); output.writeUInt32(2, length_);
} }
if (((bitField0_ & 0x00000002) == 0x00000002)) {
output.writeBytes(3, getRecipientIdBytes());
}
getUnknownFields().writeTo(output); getUnknownFields().writeTo(output);
} }
@ -3680,10 +3747,14 @@ public final class BackupProtos {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeBytesSize(1, getNameBytes()); .computeBytesSize(1, getNameBytes());
} }
if (((bitField0_ & 0x00000002) == 0x00000002)) { if (((bitField0_ & 0x00000004) == 0x00000004)) {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(2, length_); .computeUInt32Size(2, length_);
} }
if (((bitField0_ & 0x00000002) == 0x00000002)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, getRecipientIdBytes());
}
size += getUnknownFields().getSerializedSize(); size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size; memoizedSerializedSize = size;
return size; return size;
@ -3802,8 +3873,10 @@ public final class BackupProtos {
super.clear(); super.clear();
name_ = ""; name_ = "";
bitField0_ = (bitField0_ & ~0x00000001); bitField0_ = (bitField0_ & ~0x00000001);
length_ = 0; recipientId_ = "";
bitField0_ = (bitField0_ & ~0x00000002); bitField0_ = (bitField0_ & ~0x00000002);
length_ = 0;
bitField0_ = (bitField0_ & ~0x00000004);
return this; return this;
} }
@ -3839,6 +3912,10 @@ public final class BackupProtos {
if (((from_bitField0_ & 0x00000002) == 0x00000002)) { if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
to_bitField0_ |= 0x00000002; to_bitField0_ |= 0x00000002;
} }
result.recipientId_ = recipientId_;
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
to_bitField0_ |= 0x00000004;
}
result.length_ = length_; result.length_ = length_;
result.bitField0_ = to_bitField0_; result.bitField0_ = to_bitField0_;
onBuilt(); onBuilt();
@ -3861,6 +3938,11 @@ public final class BackupProtos {
name_ = other.name_; name_ = other.name_;
onChanged(); onChanged();
} }
if (other.hasRecipientId()) {
bitField0_ |= 0x00000002;
recipientId_ = other.recipientId_;
onChanged();
}
if (other.hasLength()) { if (other.hasLength()) {
setLength(other.getLength()); setLength(other.getLength());
} }
@ -3965,13 +4047,87 @@ public final class BackupProtos {
return this; return this;
} }
// optional string recipientId = 3;
private java.lang.Object recipientId_ = "";
/**
* <code>optional string recipientId = 3;</code>
*/
public boolean hasRecipientId() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional string recipientId = 3;</code>
*/
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;
}
}
/**
* <code>optional string recipientId = 3;</code>
*/
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;
}
}
/**
* <code>optional string recipientId = 3;</code>
*/
public Builder setRecipientId(
java.lang.String value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000002;
recipientId_ = value;
onChanged();
return this;
}
/**
* <code>optional string recipientId = 3;</code>
*/
public Builder clearRecipientId() {
bitField0_ = (bitField0_ & ~0x00000002);
recipientId_ = getDefaultInstance().getRecipientId();
onChanged();
return this;
}
/**
* <code>optional string recipientId = 3;</code>
*/
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; // optional uint32 length = 2;
private int length_ ; private int length_ ;
/** /**
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
*/ */
public boolean hasLength() { public boolean hasLength() {
return ((bitField0_ & 0x00000002) == 0x00000002); return ((bitField0_ & 0x00000004) == 0x00000004);
} }
/** /**
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
@ -3983,7 +4139,7 @@ public final class BackupProtos {
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
*/ */
public Builder setLength(int value) { public Builder setLength(int value) {
bitField0_ |= 0x00000002; bitField0_ |= 0x00000004;
length_ = value; length_ = value;
onChanged(); onChanged();
return this; return this;
@ -3992,7 +4148,7 @@ public final class BackupProtos {
* <code>optional uint32 length = 2;</code> * <code>optional uint32 length = 2;</code>
*/ */
public Builder clearLength() { public Builder clearLength() {
bitField0_ = (bitField0_ & ~0x00000002); bitField0_ = (bitField0_ & ~0x00000004);
length_ = 0; length_ = 0;
onChanged(); onChanged();
return this; 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" + "\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" + "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", "\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" + "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" + "e\030\001 \001(\t\022\023\n\013recipientId\030\003 \001(\t\022\016\n\006length\030\002" +
"on\022\017\n\007version\030\001 \001(\r\"\"\n\006Header\022\n\n\002iv\030\001 \001(" + " \001(\r\"\"\n\017DatabaseVersion\022\017\n\007version\030\001 \001(\r" +
"\014\022\014\n\004salt\030\002 \001(\014\"\245\002\n\013BackupFrame\022\036\n\006heade" + "\"\"\n\006Header\022\n\n\002iv\030\001 \001(\014\022\014\n\004salt\030\002 \001(\014\"\245\002\n" +
"r\030\001 \001(\0132\016.signal.Header\022\'\n\tstatement\030\002 \001" + "\013BackupFrame\022\036\n\006header\030\001 \001(\0132\016.signal.He" +
"(\0132\024.signal.SqlStatement\022,\n\npreference\030\003" + "ader\022\'\n\tstatement\030\002 \001(\0132\024.signal.SqlStat" +
" \001(\0132\030.signal.SharedPreference\022&\n\nattach" + "ement\022,\n\npreference\030\003 \001(\0132\030.signal.Share" +
"ment\030\004 \001(\0132\022.signal.Attachment\022(\n\007versio" + "dPreference\022&\n\nattachment\030\004 \001(\0132\022.signal" +
"n\030\005 \001(\0132\027.signal.DatabaseVersion\022\013\n\003end\030" + ".Attachment\022(\n\007version\030\005 \001(\0132\027.signal.Da" +
"\006 \001(\010\022\036\n\006avatar\030\007 \001(\0132\016.signal.Avatar\022 \n", "tabaseVersion\022\013\n\003end\030\006 \001(\010\022\036\n\006avatar\030\007 \001",
"\007sticker\030\010 \001(\0132\017.signal.StickerB1\n!org.t" + "(\0132\016.signal.Avatar\022 \n\007sticker\030\010 \001(\0132\017.si" +
"houghtcrime.securesms.backupB\014BackupProt" + "gnal.StickerB1\n!org.thoughtcrime.secures" +
"os" "ms.backupB\014BackupProtos"
}; };
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -6746,7 +6902,7 @@ public final class BackupProtos {
internal_static_signal_Avatar_fieldAccessorTable = new internal_static_signal_Avatar_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable( com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_signal_Avatar_descriptor, internal_static_signal_Avatar_descriptor,
new java.lang.String[] { "Name", "Length", }); new java.lang.String[] { "Name", "RecipientId", "Length", });
internal_static_signal_DatabaseVersion_descriptor = internal_static_signal_DatabaseVersion_descriptor =
getDescriptor().getMessageTypes().get(5); getDescriptor().getMessageTypes().get(5);
internal_static_signal_DatabaseVersion_fieldAccessorTable = new internal_static_signal_DatabaseVersion_fieldAccessorTable = new

View File

@ -326,7 +326,7 @@ public class FullBackupExporter extends FullBackupBase {
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException { public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
write(outputStream, BackupProtos.BackupFrame.newBuilder() write(outputStream, BackupProtos.BackupFrame.newBuilder()
.setAvatar(BackupProtos.Avatar.newBuilder() .setAvatar(BackupProtos.Avatar.newBuilder()
.setName(avatarName) .setRecipientId(avatarName)
.setLength(Util.toIntExact(size)) .setLength(Util.toIntExact(size))
.build()) .build())
.build()); .build());

View File

@ -27,6 +27,8 @@ import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.AvatarHelper; 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.Conversions;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.kdf.HKDFv3; 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 { 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") @SuppressLint("ApplySharedPref")

View File

@ -6,8 +6,9 @@ import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.profiles.AvatarHelper; 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.File;
import java.io.IOException; import java.io.IOException;
@ -16,22 +17,22 @@ import java.security.MessageDigest;
public class ProfileContactPhoto implements ContactPhoto { public class ProfileContactPhoto implements ContactPhoto {
private final @NonNull Address address; private final @NonNull RecipientId recipient;
private final @NonNull String avatarObject; private final @NonNull String avatarObject;
public ProfileContactPhoto(@NonNull Address address, @NonNull String avatarObject) { public ProfileContactPhoto(@NonNull RecipientId recipient, @NonNull String avatarObject) {
this.address = address; this.recipient = recipient;
this.avatarObject = avatarObject; this.avatarObject = avatarObject;
} }
@Override @Override
public InputStream openInputStream(Context context) throws IOException { public @NonNull InputStream openInputStream(Context context) throws IOException {
return AvatarHelper.getInputStreamFor(context, address); return AvatarHelper.getInputStreamFor(context, recipient);
} }
@Override @Override
public @Nullable Uri getUri(@NonNull Context context) { 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; return avatarFile.exists() ? Uri.fromFile(avatarFile) : null;
} }
@ -42,7 +43,7 @@ public class ProfileContactPhoto implements ContactPhoto {
@Override @Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(address.serialize().getBytes()); messageDigest.update(recipient.serialize().getBytes());
messageDigest.update(avatarObject.getBytes()); messageDigest.update(avatarObject.getBytes());
} }
@ -52,11 +53,11 @@ public class ProfileContactPhoto implements ContactPhoto {
ProfileContactPhoto that = (ProfileContactPhoto)other; 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 @Override
public int hashCode() { public int hashCode() {
return address.hashCode() ^ avatarObject.hashCode(); return recipient.hashCode() ^ avatarObject.hashCode();
} }
} }

View File

@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintOb
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2;
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration; import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration;
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
@ -82,6 +83,7 @@ public final class JobManagerFactories {
put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
// Migrations // Migrations
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory()); put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory()); put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());

View File

@ -86,7 +86,7 @@ public class RetrieveProfileAvatarJob extends BaseJob {
if (TextUtils.isEmpty(profileAvatar)) { if (TextUtils.isEmpty(profileAvatar)) {
Log.w(TAG, "Removing profile avatar (no url) for: " + recipient.requireAddress().serialize()); 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); database.setProfileAvatar(recipient.getId(), profileAvatar);
return; return;
} }
@ -104,11 +104,11 @@ public class RetrieveProfileAvatarJob extends BaseJob {
throw new IOException("Failed to copy stream. Likely a Conscrypt issue.", e); 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) { } catch (PushNetworkException e) {
if (e.getCause() instanceof NonSuccessfulResponseCodeException) { if (e.getCause() instanceof NonSuccessfulResponseCodeException) {
Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.requireAddress().serialize()); Log.w(TAG, "Removing profile avatar (no image available) for: " + recipient.requireAddress().serialize());
AvatarHelper.delete(context, recipient.requireAddress()); AvatarHelper.delete(context, recipient.getId());
} else { } else {
throw e; throw e;
} }

View File

@ -73,7 +73,7 @@ public class RotateProfileKeyJob extends BaseJob {
private @Nullable StreamDetails getProfileAvatar() { private @Nullable StreamDetails getProfileAvatar() {
try { try {
File avatarFile = AvatarHelper.getAvatarFile(context, Recipient.self().requireAddress()); File avatarFile = AvatarHelper.getAvatarFile(context, Recipient.self().getId());
if (avatarFile.exists()) { if (avatarFile.exists()) {
return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length()); return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length());

View File

@ -38,13 +38,14 @@ public class ApplicationMigrations {
private static final int LEGACY_CANONICAL_VERSION = 455; 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 { private static final class Version {
static final int LEGACY = 1; static final int LEGACY = 1;
static final int RECIPIENT_ID = 2; static final int RECIPIENT_ID = 2;
static final int RECIPIENT_SEARCH = 3; static final int RECIPIENT_SEARCH = 3;
static final int RECIPIENT_CLEANUP = 4; 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()); jobs.put(Version.RECIPIENT_CLEANUP, new DatabaseMigrationJob());
} }
if (lastSeenVersion < Version.AVATAR_MIGRATION) {
jobs.put(Version.AVATAR_MIGRATION, new AvatarMigrationJob());
}
return jobs; return jobs;
} }

View File

@ -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<AvatarMigrationJob> {
@Override
public @NonNull AvatarMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new AvatarMigrationJob(parameters);
}
}
}

View File

@ -36,6 +36,7 @@ abstract class MigrationJob extends Job {
@Override @Override
public @NonNull Result run() { public @NonNull Result run() {
try { try {
Log.i(TAG, "About to run " + getClass().getSimpleName());
performMigration(); performMigration();
return Result.success(); return Result.success();
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -54,7 +55,7 @@ abstract class MigrationJob extends Job {
@Override @Override
public void onCanceled() { public void onCanceled() {
throw new AssertionError("This job should never fail."); throw new AssertionError("This job should never fail. " + getClass().getSimpleName());
} }
/** /**

View File

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class ProfilePreference extends Preference { public class ProfilePreference extends Preference {
@ -64,11 +65,11 @@ public class ProfilePreference extends Preference {
public void refresh() { public void refresh() {
if (profileNumberView == null) return; if (profileNumberView == null) return;
final Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(getContext())); final Recipient self = Recipient.self();
final String profileName = TextSecurePreferences.getProfileName(getContext()); final String profileName = TextSecurePreferences.getProfileName(getContext());
GlideApp.with(getContext().getApplicationContext()) 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))) .error(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(getContext(), getContext().getResources().getColor(R.color.grey_400)))
.circleCrop() .circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL)
@ -78,6 +79,6 @@ public class ProfilePreference extends Preference {
profileNameView.setText(profileName); profileNameView.setText(profileName);
} }
profileNumberView.setText(localAddress.toPhoneString()); profileNumberView.setText(self.requireAddress().toPhoneString());
} }
} }

View File

@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -21,7 +22,7 @@ public class AvatarHelper {
private static final String AVATAR_DIRECTORY = "avatars"; 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 throws IOException
{ {
return new FileInputStream(getAvatarFile(context, address)); return new FileInputStream(getAvatarFile(context, address));
@ -35,24 +36,24 @@ public class AvatarHelper {
else return Stream.of(results).toList(); else return Stream.of(results).toList();
} }
public static void delete(@NonNull Context context, @NonNull Address address) { public static void delete(@NonNull Context context, @NonNull RecipientId recipientId) {
getAvatarFile(context, address).delete(); 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); File avatarDirectory = new File(context.getFilesDir(), AVATAR_DIRECTORY);
avatarDirectory.mkdirs(); 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 throws IOException
{ {
if (data == null) { if (data == null) {
delete(context, address); delete(context, recipientId);
} else { } else {
FileOutputStream out = new FileOutputStream(getAvatarFile(context, address)); FileOutputStream out = new FileOutputStream(getAvatarFile(context, recipientId));
out.write(data); out.write(data);
out.close(); out.close();
} }

View File

@ -302,7 +302,7 @@ public class Recipient {
if (localNumber) return null; if (localNumber) return null;
else if (isGroupInternal() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(address, groupAvatarId.get()); else if (isGroupInternal() && groupAvatarId.isPresent()) return new GroupRecordContactPhoto(address, groupAvatarId.get());
else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0); 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; else return null;
} }