Blur avatar photos from unknown senders when in message request state.

fork-5.53.8
Cody Henthorne 2021-04-23 14:42:51 -04:00 zatwierdzone przez GitHub
rodzic bf124b87fa
commit ad81b310e3
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
19 zmienionych plików z 546 dodań i 113 usunięć

Wyświetl plik

@ -17,6 +17,7 @@ import net.sqlcipher.database.SQLiteStatement;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.util.Hex;
import java.lang.reflect.Field;
import java.util.ArrayList;
@ -237,7 +238,12 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
case Cursor.FIELD_TYPE_FLOAT:
return cursor.getDouble(column);
case Cursor.FIELD_TYPE_BLOB:
return cursor.getBlob(column);
byte[] blob = cursor.getBlob(column);
String bytes = blob != null ? "(blob) " + Hex.toStringCondensed(Arrays.copyOf(blob, Math.min(blob.length, 32))) : null;
if (bytes != null && bytes.length() == 32 && blob.length > 32) {
bytes += "...";
}
return bytes;
case Cursor.FIELD_TYPE_STRING:
default:
return cursor.getString(column);

Wyświetl plik

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@ -14,7 +15,11 @@ import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.FragmentActivity;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
@ -23,6 +28,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
@ -30,9 +36,12 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.recipients.ui.managerecipient.ManageRecipientActivity;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.BlurTransformation;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public final class AvatarImageView extends AppCompatImageView {
@ -63,6 +72,7 @@ public final class AvatarImageView extends AppCompatImageView {
private Paint outlinePaint;
private OnClickListener listener;
private Recipient.FallbackPhotoProvider fallbackPhotoProvider;
private boolean blurred;
private @Nullable RecipientContactPhoto recipientContactPhoto;
private @NonNull Drawable unknownRecipientDrawable;
@ -90,15 +100,16 @@ public final class AvatarImageView extends AppCompatImageView {
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted);
blurred = false;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float width = getWidth() - getPaddingRight() - getPaddingLeft();
float width = getWidth() - getPaddingRight() - getPaddingLeft();
float height = getHeight() - getPaddingBottom() - getPaddingTop();
float cx = width / 2f;
float cx = width / 2f;
float cy = height / 2f;
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
@ -160,20 +171,30 @@ public final class AvatarImageView extends AppCompatImageView {
Recipient.self().getProfileAvatar()))
: new RecipientContactPhoto(recipient);
if (!photo.equals(recipientContactPhoto)) {
boolean shouldBlur = recipient.shouldBlurAvatar();
if (!photo.equals(recipientContactPhoto) || shouldBlur != blurred) {
requestManager.clear(this);
recipientContactPhoto = photo;
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL
? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider)
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider);
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider)
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider);
if (photo.contactPhoto != null) {
List<Transformation<Bitmap>> transforms = new ArrayList<>();
if (shouldBlur) {
transforms.add(new BlurTransformation(ApplicationDependencies.getApplication(), 0.25f, BlurTransformation.MAX_RADIUS));
}
transforms.add(new CircleCrop());
blurred = shouldBlur;
requestManager.load(photo.contactPhoto)
.fallback(fallbackContactPhotoDrawable)
.error(fallbackContactPhotoDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.downsample(DownsampleStrategy.CENTER_INSIDE)
.transform(new MultiTransformation<>(transforms))
.into(this);
} else {
setImageDrawable(fallbackContactPhotoDrawable);

Wyświetl plik

@ -1,6 +1,8 @@
package org.thoughtcrime.securesms.conversation;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
@ -9,11 +11,15 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.widget.ImageViewCompat;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -24,6 +30,7 @@ public class ConversationBannerView extends ConstraintLayout {
private TextView contactAbout;
private TextView contactSubtitle;
private TextView contactDescription;
private View tapToView;
public ConversationBannerView(Context context) {
this(context, null);
@ -43,12 +50,28 @@ public class ConversationBannerView extends ConstraintLayout {
contactAbout = findViewById(R.id.message_request_about);
contactSubtitle = findViewById(R.id.message_request_subtitle);
contactDescription = findViewById(R.id.message_request_description);
tapToView = findViewById(R.id.message_request_avatar_tap_to_view);
contactAvatar.setFallbackPhotoProvider(new FallbackPhotoProvider());
}
public void setAvatar(@NonNull GlideRequests requests, @Nullable Recipient recipient) {
contactAvatar.setAvatar(requests, recipient, false);
if (recipient.shouldBlurAvatar() && recipient.getContactPhoto() != null) {
tapToView.setVisibility(VISIBLE);
tapToView.setOnClickListener(v -> {
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getRecipientDatabase(getContext().getApplicationContext())
.manuallyShowAvatar(recipient.getId()));
});
ImageViewCompat.setImageTintList(contactAvatar, ColorStateList.valueOf(ContextCompat.getColor(getContext(), R.color.transparent_black_40)));
ImageViewCompat.setImageTintMode(contactAvatar, PorterDuff.Mode.SRC_ATOP);
} else {
tapToView.setVisibility(GONE);
tapToView.setOnClickListener(null);
ImageViewCompat.setImageTintList(contactAvatar, null);
ImageViewCompat.setImageTintMode(contactAvatar, PorterDuff.Mode.SRC_IN);
}
}
public void setTitle(@Nullable CharSequence title) {

Wyświetl plik

@ -15,11 +15,9 @@ import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.core.util.logging.Log;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@ -447,15 +445,17 @@ public final class GroupDatabase extends Database {
contentValues.put(MMS, groupId.isMms());
List<RecipientId> groupMembers = members;
if (groupMasterKey != null) {
if (groupState == null) {
throw new AssertionError("V2 master key but no group state");
}
groupId.requireV2();
groupMembers = getV2GroupMembers(groupState);
contentValues.put(V2_MASTER_KEY, groupMasterKey.serialize());
contentValues.put(V2_REVISION, groupState.getRevision());
contentValues.put(V2_DECRYPTED_GROUP, groupState.toByteArray());
contentValues.put(MEMBERS, serializeV2GroupMembers(groupState));
contentValues.put(MEMBERS, RecipientId.toSerializedList(groupMembers));
} else {
if (groupId.isV2()) {
throw new AssertionError("V2 group id but no master key");
@ -468,6 +468,10 @@ public final class GroupDatabase extends Database {
recipientDatabase.setExpireMessages(groupRecipientId, groupState.getDisappearingMessagesTimer().getDuration());
}
if (groupMembers != null && (groupId.isMms() || Recipient.resolved(groupRecipientId).isProfileSharing())) {
recipientDatabase.setHasGroupsInCommon(groupMembers);
}
Recipient.live(groupRecipientId).refresh();
notifyConversationListListeners();
@ -585,10 +589,11 @@ public final class GroupDatabase extends Database {
contentValues.put(UNMIGRATED_V1_MEMBERS, unmigratedV1Members.isEmpty() ? null : RecipientId.toSerializedList(unmigratedV1Members));
}
List<RecipientId> groupMembers = getV2GroupMembers(decryptedGroup);
contentValues.put(TITLE, title);
contentValues.put(V2_REVISION, decryptedGroup.getRevision());
contentValues.put(V2_DECRYPTED_GROUP, decryptedGroup.toByteArray());
contentValues.put(MEMBERS, serializeV2GroupMembers(decryptedGroup));
contentValues.put(MEMBERS, RecipientId.toSerializedList(groupMembers));
contentValues.put(ACTIVE, gv2GroupActive(decryptedGroup) ? 1 : 0);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,
@ -599,6 +604,10 @@ public final class GroupDatabase extends Database {
recipientDatabase.setExpireMessages(groupRecipientId, decryptedGroup.getDisappearingMessagesTimer().getDuration());
}
if (groupMembers != null && (groupId.isMms() || Recipient.resolved(groupRecipientId).isProfileSharing())) {
recipientDatabase.setHasGroupsInCommon(groupMembers);
}
Recipient.live(groupRecipientId).refresh();
notifyConversationListListeners();
@ -742,11 +751,11 @@ public final class GroupDatabase extends Database {
return groupMembers;
}
private static String serializeV2GroupMembers(@NonNull DecryptedGroup decryptedGroup) {
private static List<RecipientId> getV2GroupMembers(@NonNull DecryptedGroup decryptedGroup) {
List<UUID> uuids = DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList());
List<RecipientId> recipientIds = uuidsToRecipientIds(uuids);
return RecipientId.toSerializedList(recipientIds);
return recipientIds;
}
public @NonNull List<GroupId.V2> getAllGroupV2Ids() {

Wyświetl plik

@ -8,6 +8,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
import com.annimon.stream.Stream;
import com.google.protobuf.ByteString;
@ -31,6 +32,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.RecipientExtras;
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
@ -43,8 +45,8 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.storage.StorageRecordUpdate;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.storage.StorageSyncModels;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Bitmask;
@ -68,8 +70,6 @@ import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import org.whispersystems.signalservice.api.storage.SignalRecord;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
@ -142,8 +142,10 @@ public class RecipientDatabase extends Database {
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 ABOUT = "about";
public static final String ABOUT_EMOJI = "about_emoji";
public static final String ABOUT = "about";
public static final String ABOUT_EMOJI = "about_emoji";
private static final String EXTRAS = "extras";
private static final String GROUPS_IN_COMMON = "groups_in_common";
public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
private static final String SORT_NAME = "sort_name";
@ -170,12 +172,13 @@ public class RecipientDatabase extends Database {
STORAGE_SERVICE_ID, DIRTY,
MENTION_SETTING, WALLPAPER, WALLPAPER_URI,
MENTION_SETTING,
ABOUT, ABOUT_EMOJI
ABOUT, ABOUT_EMOJI,
EXTRAS, GROUPS_IN_COMMON
};
private static final String[] ID_PROJECTION = new String[]{ID};
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME};
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, EXTRAS, GROUPS_IN_COMMON, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, EXTRAS, GROUPS_IN_COMMON, SEARCH_PROFILE_NAME, SORT_NAME};
private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
.map(columnName -> TABLE_NAME + "." + columnName)
.toList().toArray(new String[0]);
@ -371,7 +374,9 @@ public class RecipientDatabase extends Database {
WALLPAPER + " BLOB DEFAULT NULL, " +
WALLPAPER_URI + " TEXT DEFAULT NULL, " +
ABOUT + " TEXT DEFAULT NULL, " +
ABOUT_EMOJI + " TEXT DEFAULT NULL);";
ABOUT_EMOJI + " TEXT DEFAULT NULL, " +
EXTRAS + " BLOB DEFAULT NULL, " +
GROUPS_IN_COMMON + " INTEGER DEFAULT 0);";
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
" FROM " + TABLE_NAME +
@ -1461,6 +1466,7 @@ public class RecipientDatabase extends Database {
byte[] wallpaper = CursorUtil.requireBlob(cursor, WALLPAPER);
String about = CursorUtil.requireString(cursor, ABOUT);
String aboutEmoji = CursorUtil.requireString(cursor, ABOUT_EMOJI);
boolean hasGroupsInCommon = CursorUtil.requireBoolean(cursor, GROUPS_IN_COMMON);
MaterialColor color;
byte[] profileKey = null;
@ -1549,7 +1555,9 @@ public class RecipientDatabase extends Database {
chatWallpaper,
about,
aboutEmoji,
getSyncExtras(cursor));
getSyncExtras(cursor),
getExtras(cursor),
hasGroupsInCommon);
}
private static @NonNull RecipientSettings.SyncExtras getSyncExtras(@NonNull Cursor cursor) {
@ -1565,6 +1573,23 @@ public class RecipientDatabase extends Database {
return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived, forcedUnread);
}
private static @Nullable Recipient.Extras getExtras(@NonNull Cursor cursor) {
return Recipient.Extras.from(getRecipientExtras(cursor));
}
private static @Nullable RecipientExtras getRecipientExtras(@NonNull Cursor cursor) {
final Optional<byte[]> blob = CursorUtil.getBlob(cursor, EXTRAS);
return blob.transform(b -> {
try {
return RecipientExtras.parseFrom(b);
} catch (InvalidProtocolBufferException e) {
Log.w(TAG, e);
throw new AssertionError(e);
}
}).orNull();
}
public BulkOperationsHandle beginBulkSystemContactUpdate() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.beginTransaction();
@ -1986,6 +2011,14 @@ public class RecipientDatabase extends Database {
boolean profiledUpdated = update(id, contentValues);
boolean colorUpdated = enabled && setColorIfNotSetInternal(id, ContactColors.generateFor(Recipient.resolved(id).getDisplayName(context)));
if (profiledUpdated && enabled) {
Optional<GroupDatabase.GroupRecord> group = DatabaseFactory.getGroupDatabase(context).getGroup(id);
if (group.isPresent()) {
setHasGroupsInCommon(group.get().getMembers());
}
}
if (profiledUpdated || colorUpdated) {
markDirty(id, DirtyState.UPDATE);
Recipient.live(id).refresh();
@ -2841,6 +2874,93 @@ public class RecipientDatabase extends Database {
}
}
public void markPreMessageRequestRecipientsAsProfileSharingEnabled(long messageRequestEnableTime) {
String[] whereArgs = SqlUtil.buildArgs(messageRequestEnableTime, messageRequestEnableTime);
String select = "SELECT r." + ID + " FROM " + TABLE_NAME + " AS r "
+ "INNER JOIN " + ThreadDatabase.TABLE_NAME + " AS t ON t." + ThreadDatabase.RECIPIENT_ID + " = r." + ID + " WHERE "
+ "r." + PROFILE_SHARING + " = 0 AND "
+ "("
+ "EXISTS(SELECT 1 FROM " + SmsDatabase.TABLE_NAME + " WHERE " + SmsDatabase.THREAD_ID + " = t." + ThreadDatabase.ID + " AND " + SmsDatabase.DATE_RECEIVED + " < ?) "
+ "OR "
+ "EXISTS(SELECT 1 FROM " + MmsDatabase.TABLE_NAME + " WHERE " + MmsDatabase.THREAD_ID + " = t." + ThreadDatabase.ID + " AND " + MmsDatabase.DATE_RECEIVED + " < ?) "
+ ")";
List<Long> idsToUpdate = new ArrayList<>();
try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(select, whereArgs)) {
while (cursor.moveToNext()) {
idsToUpdate.add(CursorUtil.requireLong(cursor, ID));
}
}
if (Util.hasItems(idsToUpdate)) {
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, idsToUpdate);
ContentValues values = new ContentValues(1);
values.put(PROFILE_SHARING, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query.getWhere(), query.getWhereArgs());
for (long id : idsToUpdate) {
Recipient.live(RecipientId.from(id)).refresh();
}
}
}
public void setHasGroupsInCommon(@NonNull List<RecipientId> recipientIds) {
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, recipientIds);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
try (Cursor cursor = db.query(TABLE_NAME,
new String[]{ID},
query.getWhere() + " AND " + GROUPS_IN_COMMON + " = 0",
query.getWhereArgs(),
null,
null,
null))
{
List<Long> idsToUpdate = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
idsToUpdate.add(CursorUtil.requireLong(cursor, ID));
}
if (Util.hasItems(idsToUpdate)) {
query = SqlUtil.buildCollectionQuery(ID, idsToUpdate);
ContentValues values = new ContentValues();
values.put(GROUPS_IN_COMMON, 1);
int count = db.update(TABLE_NAME, values, query.getWhere(), query.getWhereArgs());
if (count > 0) {
for (long id : idsToUpdate) {
Recipient.live(RecipientId.from(id)).refresh();
}
}
}
}
}
public void manuallyShowAvatar(@NonNull RecipientId recipientId) {
updateExtras(recipientId, b -> b.setManuallyShownAvatar(true));
}
private void updateExtras(@NonNull RecipientId recipientId, @NonNull Function<RecipientExtras.Builder, RecipientExtras.Builder> updater) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction();
try {
try (Cursor cursor = db.query(TABLE_NAME, new String[]{ID, EXTRAS}, ID_WHERE, SqlUtil.buildArgs(recipientId), null, null, null)) {
if (cursor.moveToNext()) {
RecipientExtras state = getRecipientExtras(cursor);
RecipientExtras.Builder builder = state != null ? state.toBuilder() : RecipientExtras.newBuilder();
byte[] updatedState = updater.apply(builder).build().toByteArray();
ContentValues values = new ContentValues(1);
values.put(EXTRAS, updatedState);
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(CursorUtil.requireLong(cursor, ID)));
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
Recipient.live(recipientId).refresh();
}
void markDirty(@NonNull RecipientId recipientId, @NonNull DirtyState dirtyState) {
Log.d(TAG, "Attempting to mark " + recipientId + " with dirty state " + dirtyState);
@ -3232,7 +3352,9 @@ public class RecipientDatabase extends Database {
private final ChatWallpaper wallpaper;
private final String about;
private final String aboutEmoji;
private final SyncExtras syncExtras;
private final SyncExtras syncExtras;
private final Recipient.Extras extras;
private final boolean hasGroupsInCommon;
RecipientSettings(@NonNull RecipientId id,
@Nullable UUID uuid,
@ -3273,7 +3395,9 @@ public class RecipientDatabase extends Database {
@Nullable ChatWallpaper wallpaper,
@Nullable String about,
@Nullable String aboutEmoji,
@NonNull SyncExtras syncExtras)
@NonNull SyncExtras syncExtras,
@Nullable Recipient.Extras extras,
boolean hasGroupsInCommon)
{
this.id = id;
this.uuid = uuid;
@ -3316,7 +3440,9 @@ public class RecipientDatabase extends Database {
this.wallpaper = wallpaper;
this.about = about;
this.aboutEmoji = aboutEmoji;
this.syncExtras = syncExtras;
this.syncExtras = syncExtras;
this.extras = extras;
this.hasGroupsInCommon = hasGroupsInCommon;
}
public RecipientId getId() {
@ -3483,6 +3609,14 @@ public class RecipientDatabase extends Database {
return syncExtras;
}
public @Nullable Recipient.Extras getExtras() {
return extras;
}
public boolean hasGroupsInCommon() {
return hasGroupsInCommon;
}
long getCapabilities() {
return capabilities;
}

Wyświetl plik

@ -43,8 +43,8 @@ import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.SqlCipherDatabaseHook;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
@ -172,8 +172,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int PAYMENTS = 91;
private static final int CLEAN_STORAGE_IDS = 92;
private static final int MP4_GIF_SUPPORT = 93;
private static final int BLUR_AVATARS = 94;
private static final int DATABASE_VERSION = 93;
private static final int DATABASE_VERSION = 94;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -1304,6 +1305,23 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
db.execSQL("ALTER TABLE part ADD COLUMN video_gif INTEGER DEFAULT 0");
}
if (oldVersion < BLUR_AVATARS) {
db.execSQL("ALTER TABLE recipient ADD COLUMN extras BLOB DEFAULT NULL");
db.execSQL("ALTER TABLE recipient ADD COLUMN groups_in_common INTEGER DEFAULT 0");
String secureOutgoingSms = "EXISTS(SELECT 1 FROM sms WHERE thread_id = t._id AND (type & 31) = 23 AND (type & 10485760) AND (type & 131072 = 0))";
String secureOutgoingMms = "EXISTS(SELECT 1 FROM mms WHERE thread_id = t._id AND (msg_box & 31) = 23 AND (msg_box & 10485760) AND (msg_box & 131072 = 0))";
String selectIdsToUpdateProfileSharing = "SELECT r._id FROM recipient AS r INNER JOIN thread AS t ON r._id = t.recipient_ids WHERE profile_sharing = 0 AND (" + secureOutgoingSms + " OR " + secureOutgoingMms + ")";
db.rawExecSQL("UPDATE recipient SET profile_sharing = 1 WHERE _id IN (" + selectIdsToUpdateProfileSharing + ")");
String selectIdsWithGroupsInCommon = "SELECT r._id FROM recipient AS r WHERE EXISTS("
+ "SELECT 1 FROM groups AS g INNER JOIN recipient AS gr ON (g.recipient_id = gr._id AND gr.profile_sharing = 1) WHERE g.active = 1 AND (g.members LIKE r._id || ',%' OR g.members LIKE '%,' || r._id || ',%' OR g.members LIKE '%,' || r._id)"
+ ")";
db.rawExecSQL("UPDATE recipient SET groups_in_common = 1 WHERE _id IN (" + selectIdsWithGroupsInCommon + ")");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

Wyświetl plik

@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.migrations.PassingMigrationJob;
import org.thoughtcrime.securesms.migrations.PinOptOutMigration;
import org.thoughtcrime.securesms.migrations.PinReminderMigrationJob;
import org.thoughtcrime.securesms.migrations.ProfileMigrationJob;
import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob;
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
@ -167,6 +168,7 @@ public final class JobManagerFactories {
put(PinOptOutMigration.KEY, new PinOptOutMigration.Factory());
put(PinReminderMigrationJob.KEY, new PinReminderMigrationJob.Factory());
put(ProfileMigrationJob.KEY, new ProfileMigrationJob.Factory());
put(ProfileSharingUpdateMigrationJob.KEY, new ProfileSharingUpdateMigrationJob.Factory());
put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory());
put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory());
put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory());

Wyświetl plik

@ -40,39 +40,40 @@ public class ApplicationMigrations {
private static final int LEGACY_CANONICAL_VERSION = 455;
public static final int CURRENT_VERSION = 31;
public static final int CURRENT_VERSION = 32;
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;
static final int UUIDS = 6;
static final int CACHED_ATTACHMENTS = 7;
static final int STICKERS_LAUNCH = 8;
//static final int TEST_ARGON2 = 9;
static final int SWOON_STICKERS = 10;
static final int STORAGE_SERVICE = 11;
//static final int STORAGE_KEY_ROTATE = 12;
static final int REMOVE_AVATAR_ID = 13;
static final int STORAGE_CAPABILITY = 14;
static final int PIN_REMINDER = 15;
static final int VERSIONED_PROFILE = 16;
static final int PIN_OPT_OUT = 17;
static final int TRIM_SETTINGS = 18;
static final int THUMBNAIL_CLEANUP = 19;
static final int GV2 = 20;
static final int GV2_2 = 21;
static final int CDS = 22;
static final int BACKUP_NOTIFICATION = 23;
static final int GV1_MIGRATION = 24;
static final int USER_NOTIFICATION = 25;
static final int DAY_BY_DAY_STICKERS = 26;
static final int BLOB_LOCATION = 27;
static final int SYSTEM_NAME_SPLIT = 28;
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;
static final int UUIDS = 6;
static final int CACHED_ATTACHMENTS = 7;
static final int STICKERS_LAUNCH = 8;
//static final int TEST_ARGON2 = 9;
static final int SWOON_STICKERS = 10;
static final int STORAGE_SERVICE = 11;
//static final int STORAGE_KEY_ROTATE = 12;
static final int REMOVE_AVATAR_ID = 13;
static final int STORAGE_CAPABILITY = 14;
static final int PIN_REMINDER = 15;
static final int VERSIONED_PROFILE = 16;
static final int PIN_OPT_OUT = 17;
static final int TRIM_SETTINGS = 18;
static final int THUMBNAIL_CLEANUP = 19;
static final int GV2 = 20;
static final int GV2_2 = 21;
static final int CDS = 22;
static final int BACKUP_NOTIFICATION = 23;
static final int GV1_MIGRATION = 24;
static final int USER_NOTIFICATION = 25;
static final int DAY_BY_DAY_STICKERS = 26;
static final int BLOB_LOCATION = 27;
static final int SYSTEM_NAME_SPLIT = 28;
// Versions 29, 30 accidentally skipped
static final int MUTE_SYNC = 31;
static final int MUTE_SYNC = 31;
static final int PROFILE_SHARING_UPDATE = 32;
}
/**
@ -307,6 +308,10 @@ public class ApplicationMigrations {
jobs.put(Version.MUTE_SYNC, new StorageServiceMigrationJob());
}
if (lastSeenVersion < Version.PROFILE_SHARING_UPDATE) {
jobs.put(Version.PROFILE_SHARING_UPDATE, new ProfileSharingUpdateMigrationJob());
}
return jobs;
}

Wyświetl plik

@ -0,0 +1,52 @@
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.keyvalue.SignalStore;
/**
* Updates profile sharing flag to true if conversation is pre-message request enable time.
*/
public class ProfileSharingUpdateMigrationJob extends MigrationJob {
public static final String KEY = "ProfileSharingUpdateMigrationJob";
ProfileSharingUpdateMigrationJob() {
this(new Parameters.Builder().build());
}
private ProfileSharingUpdateMigrationJob(@NonNull Parameters parameters) {
super(parameters);
}
@Override
public boolean isUiBlocking() {
return true;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void performMigration() {
long messageRequestEnableTime = SignalStore.misc().getMessageRequestEnableTime();
DatabaseFactory.getRecipientDatabase(context).markPreMessageRequestRecipientsAsProfileSharingEnabled(messageRequestEnableTime);
}
@Override
boolean shouldRetry(@NonNull Exception e) {
return false;
}
public static class Factory implements Job.Factory<ProfileSharingUpdateMigrationJob> {
@Override
public @NonNull ProfileSharingUpdateMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new ProfileSharingUpdateMigrationJob(parameters);
}
}
}

Wyświetl plik

@ -5,16 +5,21 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import com.bumptech.glide.load.MultiTransformation
import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.BlurTransformation
import java.util.concurrent.ExecutionException
fun Drawable?.toLargeBitmap(context: Context): Bitmap? {
@ -32,10 +37,16 @@ fun Recipient.getContactDrawable(context: Context): Drawable? {
val fallbackContactPhoto: FallbackContactPhoto = if (isSelf) getFallback(context) else fallbackContactPhoto
return if (contactPhoto != null) {
try {
val transforms: MutableList<Transformation<Bitmap>> = mutableListOf()
if (shouldBlurAvatar()) {
transforms += BlurTransformation(ApplicationDependencies.getApplication(), 0.25f, BlurTransformation.MAX_RADIUS)
}
transforms += CircleCrop()
GlideApp.with(context.applicationContext)
.load(contactPhoto)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.transform(MultiTransformation(transforms))
.submit(
context.resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
context.resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height)

Wyświetl plik

@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.MentionSetting;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@ -111,7 +112,8 @@ public class Recipient {
private final String aboutEmoji;
private final ProfileName systemProfileName;
private final String systemContactName;
private final Optional<Extras> extras;
private final boolean hasGroupsInCommon;
/**
* Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be
@ -349,6 +351,8 @@ public class Recipient {
this.aboutEmoji = null;
this.systemProfileName = ProfileName.EMPTY;
this.systemContactName = null;
this.extras = Optional.absent();
this.hasGroupsInCommon = false;
}
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
@ -396,6 +400,8 @@ public class Recipient {
this.aboutEmoji = details.aboutEmoji;
this.systemProfileName = details.systemProfileName;
this.systemContactName = details.systemContactName;
this.extras = details.extras;
this.hasGroupsInCommon = details.hasGroupsInCommon;
}
public @NonNull RecipientId getId() {
@ -929,6 +935,18 @@ public class Recipient {
}
}
public boolean shouldBlurAvatar() {
boolean showOverride = false;
if (extras.isPresent()) {
showOverride = extras.get().manuallyShownAvatar();
}
return !showOverride && !isSelf() && !isProfileSharing() && !isSystemContact() && !hasGroupsInCommon && isRegistered();
}
public boolean hasGroupsInCommon() {
return hasGroupsInCommon;
}
/**
* If this recipient is missing crucial data, this will return a populated copy. Otherwise it
* returns itself.
@ -1003,6 +1021,39 @@ public class Recipient {
}
}
public static final class Extras {
private final RecipientExtras recipientExtras;
public static @Nullable Extras from(@Nullable RecipientExtras recipientExtras) {
if (recipientExtras != null) {
return new Extras(recipientExtras);
} else {
return null;
}
}
private Extras(@NonNull RecipientExtras extras) {
this.recipientExtras = extras;
}
public boolean manuallyShownAvatar() {
return recipientExtras.getManuallyShownAvatar();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Extras that = (Extras) o;
return manuallyShownAvatar() == that.manuallyShownAvatar();
}
@Override
public int hashCode() {
return Objects.hash(manuallyShownAvatar());
}
}
public boolean hasSameContent(@NonNull Recipient other) {
return Objects.equals(id, other.id) &&
resolving == other.resolving &&
@ -1047,7 +1098,8 @@ public class Recipient {
mentionSetting == other.mentionSetting &&
Objects.equals(wallpaper, other.wallpaper) &&
Objects.equals(about, other.about) &&
Objects.equals(aboutEmoji, other.aboutEmoji);
Objects.equals(aboutEmoji, other.aboutEmoji) &&
Objects.equals(extras, other.extras);
}
private static boolean allContentsAreTheSame(@NonNull List<Recipient> a, @NonNull List<Recipient> b) {

Wyświetl plik

@ -27,49 +27,51 @@ import java.util.UUID;
public class RecipientDetails {
final UUID uuid;
final String username;
final String e164;
final String email;
final GroupId groupId;
final String groupName;
final String systemContactName;
final String customLabel;
final Uri systemContactPhoto;
final Uri contactUri;
final Optional<Long> groupAvatarId;
final MaterialColor color;
final Uri messageRingtone;
final Uri callRingtone;
final long mutedUntil;
final VibrateState messageVibrateState;
final VibrateState callVibrateState;
final boolean blocked;
final int expireMessages;
final List<Recipient> participants;
final ProfileName profileName;
final Optional<Integer> defaultSubscriptionId;
final RegisteredState registered;
final byte[] profileKey;
final ProfileKeyCredential profileKeyCredential;
final String profileAvatar;
final boolean hasProfileImage;
final boolean profileSharing;
final long lastProfileFetch;
final boolean systemContact;
final boolean isSelf;
final String notificationChannel;
final UnidentifiedAccessMode unidentifiedAccessMode;
final boolean forceSmsSelection;
final Recipient.Capability groupsV2Capability;
final Recipient.Capability groupsV1MigrationCapability;
final InsightsBannerTier insightsBannerTier;
final byte[] storageId;
final MentionSetting mentionSetting;
final ChatWallpaper wallpaper;
final String about;
final String aboutEmoji;
final ProfileName systemProfileName;
final UUID uuid;
final String username;
final String e164;
final String email;
final GroupId groupId;
final String groupName;
final String systemContactName;
final String customLabel;
final Uri systemContactPhoto;
final Uri contactUri;
final Optional<Long> groupAvatarId;
final MaterialColor color;
final Uri messageRingtone;
final Uri callRingtone;
final long mutedUntil;
final VibrateState messageVibrateState;
final VibrateState callVibrateState;
final boolean blocked;
final int expireMessages;
final List<Recipient> participants;
final ProfileName profileName;
final Optional<Integer> defaultSubscriptionId;
final RegisteredState registered;
final byte[] profileKey;
final ProfileKeyCredential profileKeyCredential;
final String profileAvatar;
final boolean hasProfileImage;
final boolean profileSharing;
final long lastProfileFetch;
final boolean systemContact;
final boolean isSelf;
final String notificationChannel;
final UnidentifiedAccessMode unidentifiedAccessMode;
final boolean forceSmsSelection;
final Recipient.Capability groupsV2Capability;
final Recipient.Capability groupsV1MigrationCapability;
final InsightsBannerTier insightsBannerTier;
final byte[] storageId;
final MentionSetting mentionSetting;
final ChatWallpaper wallpaper;
final String about;
final String aboutEmoji;
final ProfileName systemProfileName;
final Optional<Recipient.Extras> extras;
final boolean hasGroupsInCommon;
public RecipientDetails(@Nullable String groupName,
@Nullable String systemContactName,
@ -122,6 +124,8 @@ public class RecipientDetails {
this.systemProfileName = settings.getSystemProfileName();
this.groupName = groupName;
this.systemContactName = systemContactName;
this.extras = Optional.fromNullable(settings.getExtras());
this.hasGroupsInCommon = settings.hasGroupsInCommon();
}
/**
@ -171,6 +175,8 @@ public class RecipientDetails {
this.aboutEmoji = null;
this.systemProfileName = ProfileName.EMPTY;
this.systemContactName = null;
this.extras = Optional.absent();
this.hasGroupsInCommon = false;
}
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) {

Wyświetl plik

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
@ -108,7 +107,11 @@ public final class AvatarUtil {
@WorkerThread
public static @NonNull Icon getIconForShortcut(@NonNull Context context, @NonNull Recipient recipient) {
try {
return Icon.createWithAdaptiveBitmap(GlideApp.with(context).asBitmap().load(new ConversationShortcutPhoto(recipient)).submit().get());
GlideRequest<Bitmap> glideRequest = GlideApp.with(context).asBitmap().load(new ConversationShortcutPhoto(recipient));
if (recipient.shouldBlurAvatar()) {
glideRequest = glideRequest.transform(new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS));
}
return Icon.createWithAdaptiveBitmap(glideRequest.submit().get());
} catch (ExecutionException | InterruptedException e) {
throw new AssertionError("This call should not fail.");
}
@ -117,7 +120,11 @@ public final class AvatarUtil {
@WorkerThread
public static @NonNull IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) {
try {
return IconCompat.createWithAdaptiveBitmap(GlideApp.with(context).asBitmap().load(new ConversationShortcutPhoto(recipient)).submit().get());
GlideRequest<Bitmap> glideRequest = GlideApp.with(context).asBitmap().load(new ConversationShortcutPhoto(recipient));
if (recipient.shouldBlurAvatar()) {
glideRequest = glideRequest.transform(new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS));
}
return IconCompat.createWithAdaptiveBitmap(glideRequest.submit().get());
} catch (ExecutionException | InterruptedException e) {
throw new AssertionError("This call should not fail.");
}
@ -152,9 +159,15 @@ public final class AvatarUtil {
photo = recipient.getContactPhoto();
}
return glideRequest.load(photo)
.error(getFallback(context, recipient))
.diskCacheStrategy(DiskCacheStrategy.ALL);
final GlideRequest<T> request = glideRequest.load(photo)
.error(getFallback(context, recipient))
.diskCacheStrategy(DiskCacheStrategy.ALL);
if (recipient.shouldBlurAvatar()) {
return request.transform(new BlurTransformation(context, 0.25f, BlurTransformation.MAX_RADIUS));
} else {
return request;
}
}
private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient) {

Wyświetl plik

@ -41,7 +41,9 @@ public final class BlurTransformation extends BitmapTransformation {
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(bitmapScaleFactor, bitmapScaleFactor);
Bitmap blurredBitmap = Bitmap.createBitmap(toTransform, 0, 0, outWidth, outHeight, scaleMatrix, true);
int targetWidth = Math.min(outWidth, toTransform.getWidth());
int targetHeight = Math.min(outHeight, toTransform.getHeight());
Bitmap blurredBitmap = Bitmap.createBitmap(toTransform, 0, 0, targetWidth, targetHeight, scaleMatrix, true);
Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED);
Allocation output = Allocation.createTyped(rs, input.getType());
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

Wyświetl plik

@ -124,4 +124,8 @@ message Wallpaper {
}
float dimLevelInDarkTheme = 4;
}
message RecipientExtras {
bool manuallyShownAvatar = 1;
}

Wyświetl plik

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14,13.08L12.4,8.83C12.3377,8.658 12.2416,8.5003 12.1173,8.366C11.993,8.2318 11.8431,8.1238 11.6765,8.0485C11.5098,7.9731 11.3297,7.9319 11.1469,7.9272C10.964,7.9225 10.7821,7.9545 10.6118,8.0213C10.4415,8.0881 10.2863,8.1883 10.1554,8.316C10.0244,8.4438 9.9204,8.5964 9.8494,8.765C9.7784,8.9335 9.7419,9.1146 9.742,9.2976C9.7421,9.4805 9.7788,9.6615 9.85,9.83"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M16.76,12.52L15.44,9.12C15.3802,8.9497 15.2864,8.7934 15.1643,8.6605C15.0422,8.5276 14.8943,8.4209 14.7297,8.347C14.565,8.2731 14.387,8.2335 14.2066,8.2306C14.0261,8.2276 13.847,8.2615 13.68,8.33C13.5093,8.3909 13.3528,8.4857 13.2198,8.6087C13.0869,8.7318 12.9802,8.8806 12.9064,9.046C12.8325,9.2115 12.793,9.3902 12.7902,9.5713C12.7874,9.7525 12.8213,9.9323 12.89,10.1"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M2.39,7.81C2.1523,7.2139 2.0361,6.5762 2.0482,5.9346C2.0602,5.2929 2.2003,4.6601 2.4601,4.0733C2.72,3.4865 3.0945,2.9575 3.5615,2.5173C4.0286,2.0771 4.5788,1.7347 5.18,1.51C5.7757,1.2753 6.4122,1.1614 7.0523,1.1749C7.6925,1.1884 8.3236,1.329 8.9089,1.5886C9.4942,1.8483 10.022,2.2217 10.4617,2.6871C10.9014,3.1526 11.2441,3.7009 11.47,4.3"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M11.49,14.06L8.21,5.56C8.071,5.231 7.809,4.9693 7.4798,4.8307C7.1507,4.692 6.7804,4.6874 6.4479,4.8178C6.1154,4.9482 5.8469,5.2033 5.6997,5.5287C5.5526,5.8542 5.5383,6.2242 5.66,6.56L8.94,15.07L9.43,16.34L9.71,17.07C9.71,17.07 7.34,15.49 5.26,13.9C3.72,12.69 2.55,14.6 3.65,15.55C7.98,19.55 11.53,24.67 17.07,22.19C22.61,19.71 20.79,15.38 20.13,13.68L18.65,9.83L18.49,9.41C18.4277,9.238 18.3316,9.0803 18.2073,8.946C18.083,8.8118 17.9331,8.7038 17.7665,8.6285C17.5998,8.5531 17.4197,8.5119 17.2369,8.5072C17.054,8.5025 16.8721,8.5345 16.7018,8.6013C16.5315,8.6681 16.3763,8.7683 16.2454,8.896C16.1144,9.0238 16.0104,9.1764 15.9394,9.345C15.8684,9.5135 15.8319,9.6946 15.832,9.8776C15.8321,10.0605 15.8688,10.2415 15.94,10.41"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

Wyświetl plik

@ -13,7 +13,37 @@
android:layout_height="112dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<LinearLayout
android:id="@+id/message_request_avatar_tap_to_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone"
android:foreground="?attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="@+id/message_request_avatar"
app:layout_constraintEnd_toEndOf="@+id/message_request_avatar"
app:layout_constraintStart_toStartOf="@+id/message_request_avatar"
app:layout_constraintTop_toTopOf="@+id/message_request_avatar">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
app:tint="@color/core_white"
app:srcCompat="@drawable/ic_tap_outline_24" />
<TextView
android:textAppearance="@style/TextAppearance.Signal.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/core_white"
android:text="@string/MessageRequestProfileView_view" />
</LinearLayout>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/message_request_title"

Wyświetl plik

@ -1234,6 +1234,7 @@
<string name="MessageRequestBottomView_join_this_group_they_wont_know_youve_seen_their_messages_until_you_accept">Join this group? They wont know youve seen their messages until you accept.</string>
<string name="MessageRequestBottomView_unblock_this_group_and_share_your_name_and_photo_with_its_members">Unblock this group and share your name and photo with its members? You won\'t receive any messages until you unblock them.</string>
<string name="MessageRequestBottomView_legacy_learn_more_url" translatable="false">https://support.signal.org/hc/articles/360007459591</string>
<string name="MessageRequestProfileView_view">View</string>
<string name="MessageRequestProfileView_member_of_one_group">Member of %1$s</string>
<string name="MessageRequestProfileView_member_of_two_groups">Member of %1$s and %2$s</string>
<string name="MessageRequestProfileView_member_of_many_groups">Member of %1$s, %2$s, and %3$s</string>

Wyświetl plik

@ -7,6 +7,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.Arrays;
import java.util.Collections;
@ -111,6 +113,14 @@ public final class SqlUtilTest {
assertArrayEquals(new String[] { "1", "2", "3" }, updateQuery.getWhereArgs());
}
@Test
public void buildCollectionQuery_multipleRecipientIds() {
SqlUtil.Query updateQuery = SqlUtil.buildCollectionQuery("a", Arrays.asList(RecipientId.from(1), RecipientId.from(2), RecipientId.from(3)));
assertEquals("a IN (?, ?, ?)", updateQuery.getWhere());
assertArrayEquals(new String[] { "1", "2", "3" }, updateQuery.getWhereArgs());
}
@Test(expected = IllegalArgumentException.class)
public void buildCollectionQuery_none() {
SqlUtil.buildCollectionQuery("a", Collections.emptyList());