Implement badge gifting behind feature flag.

This commit is contained in:
Alex Hart
2022-05-02 14:29:42 -03:00
committed by Greyson Parrelli
parent 5d16d1cd23
commit a4a4665aaa
164 changed files with 4999 additions and 486 deletions

View File

@@ -121,6 +121,9 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract void markSmsStatus(long id, int status);
public abstract void markDownloadState(long messageId, long state);
public abstract void markIncomingNotificationReceived(long threadId);
public abstract void markGiftRedemptionCompleted(long messageId);
public abstract void markGiftRedemptionStarted(long messageId);
public abstract void markGiftRedemptionFailed(long messageId);
public abstract Set<MessageUpdate> incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, boolean storiesOnly);
abstract @NonNull MmsSmsDatabase.TimestampReadResult setTimestampRead(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map<Long, Long> threadToLatestRead);

View File

@@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.database.model.StoryResult;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
@@ -83,6 +84,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -1578,6 +1580,11 @@ public class MmsDatabase extends MessageDatabase {
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
}
GiftBadge giftBadge = null;
if (body != null && Types.isGiftBadge(outboxType)) {
giftBadge = GiftBadge.parseFrom(Base64.decode(body));
}
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient,
body,
attachments,
@@ -1594,7 +1601,8 @@ public class MmsDatabase extends MessageDatabase {
previews,
mentions,
networkFailures,
mismatches);
mismatches,
giftBadge);
if (Types.isSecureType(outboxType)) {
return new OutgoingSecureMediaMessage(message);
@@ -1791,10 +1799,20 @@ public class MmsDatabase extends MessageDatabase {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
boolean hasSpecialType = false;
if (retrieved.isStoryReaction()) {
hasSpecialType = true;
type |= Types.SPECIAL_TYPE_STORY_REACTION;
}
if (retrieved.getGiftBadge() != null) {
if (hasSpecialType) {
throw new MmsException("Cannot insert message with multiple special types.");
}
type |= Types.SPECIAL_TYPE_GIFT_BADGE;
}
return insertMessageInbox(retrieved, "", threadId, type);
}
@@ -1858,6 +1876,55 @@ public class MmsDatabase extends MessageDatabase {
TrimThreadJob.enqueueAsync(threadId);
}
@Override
public void markGiftRedemptionCompleted(long messageId) {
markGiftRedemptionState(messageId, GiftBadge.RedemptionState.REDEEMED);
}
@Override
public void markGiftRedemptionStarted(long messageId) {
markGiftRedemptionState(messageId, GiftBadge.RedemptionState.STARTED);
}
@Override
public void markGiftRedemptionFailed(long messageId) {
markGiftRedemptionState(messageId, GiftBadge.RedemptionState.FAILED);
}
private void markGiftRedemptionState(long messageId, @NonNull GiftBadge.RedemptionState redemptionState) {
String[] projection = SqlUtil.buildArgs(BODY, THREAD_ID);
String where = "(" + MESSAGE_BOX + " & " + Types.SPECIAL_TYPES_MASK + " = " + Types.SPECIAL_TYPE_GIFT_BADGE + ") AND " +
ID + " = ?";
String[] args = SqlUtil.buildArgs(messageId);
boolean updated = false;
long threadId = -1;
getWritableDatabase().beginTransaction();
try (Cursor cursor = getWritableDatabase().query(TABLE_NAME, projection, where, args, null, null, null)) {
if (cursor.moveToFirst()) {
GiftBadge giftBadge = GiftBadge.parseFrom(Base64.decode(CursorUtil.requireString(cursor, BODY)));
GiftBadge updatedBadge = giftBadge.toBuilder().setRedemptionState(redemptionState).build();
ContentValues contentValues = new ContentValues(1);
contentValues.put(BODY, Base64.encodeBytes(updatedBadge.toByteArray()));
updated = getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, args) > 0;
threadId = CursorUtil.requireLong(cursor, THREAD_ID);
getWritableDatabase().setTransactionSuccessful();
}
} catch (IOException e) {
Log.w(TAG, "Failed to mark gift badge " + redemptionState.name(), e, true);
} finally {
getWritableDatabase().endTransaction();
}
if (updated) {
ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(new MessageId(messageId, true));
notifyConversationListeners(threadId);
}
}
@Override
public long insertMessageOutbox(@NonNull OutgoingMediaMessage message,
long threadId,
@@ -1899,10 +1966,20 @@ public class MmsDatabase extends MessageDatabase {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
boolean hasSpecialType = false;
if (message.isStoryReaction()) {
hasSpecialType = true;
type |= Types.SPECIAL_TYPE_STORY_REACTION;
}
if (message.getGiftBadge() != null) {
if (hasSpecialType) {
throw new MmsException("Cannot insert message with multiple special types.");
}
type |= Types.SPECIAL_TYPE_GIFT_BADGE;
}
Map<RecipientId, EarlyReceiptCache.Receipt> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis());
ContentValues contentValues = new ContentValues();
@@ -2421,7 +2498,8 @@ public class MmsDatabase extends MessageDatabase {
-1,
null,
message.getStoryType(),
message.getParentStoryId());
message.getParentStoryId(),
message.getGiftBadge());
}
}
@@ -2476,6 +2554,7 @@ public class MmsDatabase extends MessageDatabase {
long receiptTimestamp = CursorUtil.requireLong(cursor, MmsSmsColumns.RECEIPT_TIMESTAMP);
StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE));
ParentStoryId parentStoryId = ParentStoryId.deserialize(CursorUtil.requireLong(cursor, PARENT_STORY_ID));
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
@@ -2492,13 +2571,21 @@ public class MmsDatabase extends MessageDatabase {
SlideDeck slideDeck = new SlideDeck(context, new MmsNotificationAttachment(status, messageSize));
GiftBadge giftBadge = null;
if (body != null && Types.isGiftBadge(mailbox)) {
try {
giftBadge = GiftBadge.parseFrom(Base64.decode(body));
} catch (IOException e) {
Log.w(TAG, "Error parsing gift badge", e);
}
}
return new NotificationMmsMessageRecord(id, recipient, recipient,
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId,
contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox, subscriptionId, slideDeck,
readReceiptCount, viewedReceiptCount, receiptTimestamp, storyType,
parentStoryId);
parentStoryId, giftBadge);
}
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
@@ -2558,13 +2645,22 @@ public class MmsDatabase extends MessageDatabase {
Log.w(TAG, "Error parsing message ranges", e);
}
GiftBadge giftBadge = null;
if (body != null && Types.isGiftBadge(box)) {
try {
giftBadge = GiftBadge.parseFrom(Base64.decode(body));
} catch (IOException e) {
Log.w(TAG, "Error parsing gift badge", e);
}
}
return new MediaMmsMessageRecord(id, recipient, recipient,
addressDeviceId, dateSent, dateReceived, dateServer, deliveryReceiptCount,
threadId, body, slideDeck, partCount, box, mismatches,
networkFailures, subscriptionId, expiresIn, expireStarted,
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, Collections.emptyList(),
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, messageRanges,
storyType, parentStoryId);
storyType, parentStoryId, giftBadge);
}
private Set<IdentityKeyMismatch> getMismatchedIdentities(String document) {

View File

@@ -138,13 +138,14 @@ public interface MmsSmsColumns {
// Special message types
public static final long SPECIAL_TYPES_MASK = 0xF00000000L;
public static final long SPECIAL_TYPE_STORY_REACTION = 0x100000000L;
public static boolean isSpecialType(long type) {
return (type & SPECIAL_TYPES_MASK) != 0L;
}
public static final long SPECIAL_TYPE_GIFT_BADGE = 0x200000000L;
public static boolean isStoryReaction(long type) {
return (type & SPECIAL_TYPE_STORY_REACTION) == SPECIAL_TYPE_STORY_REACTION;
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_STORY_REACTION;
}
public static boolean isGiftBadge(long type) {
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_GIFT_BADGE;
}
public static boolean isDraftMessageType(long type) {

View File

@@ -1491,6 +1491,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
value = Bitmask.update(value, Capabilities.ANNOUNCEMENT_GROUPS, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isAnnouncementGroup).serialize().toLong())
value = Bitmask.update(value, Capabilities.CHANGE_NUMBER, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isChangeNumber).serialize().toLong())
value = Bitmask.update(value, Capabilities.STORIES, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isStories).serialize().toLong())
value = Bitmask.update(value, Capabilities.GIFT_BADGES, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isGiftBadges).serialize().toLong())
val values = ContentValues(1).apply {
put(CAPABILITIES, value)
@@ -3086,6 +3087,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
announcementGroupCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.ANNOUNCEMENT_GROUPS, Capabilities.BIT_LENGTH).toInt()),
changeNumberCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.CHANGE_NUMBER, Capabilities.BIT_LENGTH).toInt()),
storiesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.STORIES, Capabilities.BIT_LENGTH).toInt()),
giftBadgesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.GIFT_BADGES, Capabilities.BIT_LENGTH).toInt()),
insightsBannerTier = InsightsBannerTier.fromId(cursor.requireInt(SEEN_INVITE_REMINDER)),
storageId = Base64.decodeNullableOrThrow(cursor.requireString(STORAGE_SERVICE_ID)),
mentionSetting = MentionSetting.fromId(cursor.requireInt(MENTION_SETTING)),
@@ -3403,6 +3405,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
const val ANNOUNCEMENT_GROUPS = 3
const val CHANGE_NUMBER = 4
const val STORIES = 5
const val GIFT_BADGES = 6
}
enum class VibrateState(val id: Int) {

View File

@@ -1696,6 +1696,21 @@ public class SmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
@Override
public void markGiftRedemptionCompleted(long messageId) {
throw new UnsupportedOperationException();
}
@Override
public void markGiftRedemptionStarted(long messageId) {
throw new UnsupportedOperationException();
}
@Override
public void markGiftRedemptionFailed(long messageId) {
throw new UnsupportedOperationException();
}
@Override
public MessageDatabase.Reader getMessages(Collection<Long> messageIds) {
throw new UnsupportedOperationException();

View File

@@ -573,10 +573,10 @@ public class ThreadDatabase extends Database {
}
public Cursor getRecentConversationList(int limit, boolean includeInactiveGroups, boolean hideV1Groups) {
return getRecentConversationList(limit, includeInactiveGroups, false, hideV1Groups, false);
return getRecentConversationList(limit, includeInactiveGroups, false, false, hideV1Groups, false);
}
public Cursor getRecentConversationList(int limit, boolean includeInactiveGroups, boolean groupsOnly, boolean hideV1Groups, boolean hideSms) {
public Cursor getRecentConversationList(int limit, boolean includeInactiveGroups, boolean individualsOnly, boolean groupsOnly, boolean hideV1Groups, boolean hideSms) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String query = !includeInactiveGroups ? MEANINGFUL_MESSAGES + " != 0 AND (" + GroupDatabase.TABLE_NAME + "." + GroupDatabase.ACTIVE + " IS NULL OR " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.ACTIVE + " = 1)"
: MEANINGFUL_MESSAGES + " != 0";
@@ -585,6 +585,10 @@ public class ThreadDatabase extends Database {
query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.GROUP_ID + " NOT NULL";
}
if (individualsOnly) {
query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.GROUP_ID + " IS NULL";
}
if (hideV1Groups) {
query += " AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.GROUP_TYPE + " != " + RecipientDatabase.GroupType.SIGNAL_V1.getId();
}

View File

@@ -13,7 +13,8 @@ data class DonationReceiptRecord(
) {
enum class Type(val code: String) {
RECURRING("recurring"),
BOOST("boost");
BOOST("boost"),
GIFT("gift");
companion object {
fun fromCode(code: String): Type {
@@ -46,5 +47,15 @@ data class DonationReceiptRecord(
type = Type.BOOST
)
}
fun createForGift(amount: FiatMoney): DonationReceiptRecord {
return DonationReceiptRecord(
id = -1L,
amount = amount,
timestamp = System.currentTimeMillis(),
subscriptionLevel = -1,
type = Type.GIFT
)
}
}
}

View File

@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -93,13 +94,14 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
long receiptTimestamp,
@Nullable BodyRangeList messageRanges,
@NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId)
@Nullable ParentStoryId parentStoryId,
@Nullable GiftBadge giftBadge)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp,
storyType, parentStoryId);
storyType, parentStoryId, giftBadge);
this.partCount = partCount;
this.mentionsSelf = mentionsSelf;
this.messageRanges = messageRanges;
@@ -153,7 +155,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge());
}
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
@@ -174,7 +176,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge());
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View File

@@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
@@ -24,6 +25,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
private final @NonNull List<LinkPreview> linkPreviews = new LinkedList<>();
private final @NonNull StoryType storyType;
private final @Nullable ParentStoryId parentStoryId;
private final @Nullable GiftBadge giftBadge;
private final boolean viewOnce;
@@ -38,7 +40,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
@NonNull List<LinkPreview> linkPreviews, boolean unidentified,
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId)
@Nullable ParentStoryId parentStoryId, @Nullable GiftBadge giftBadge)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount,
@@ -50,6 +52,7 @@ public abstract class MmsMessageRecord extends MessageRecord {
this.viewOnce = viewOnce;
this.storyType = storyType;
this.parentStoryId = parentStoryId;
this.giftBadge = giftBadge;
this.contacts.addAll(contacts);
this.linkPreviews.addAll(linkPreviews);
@@ -104,4 +107,8 @@ public abstract class MmsMessageRecord extends MessageRecord {
public @NonNull List<LinkPreview> getLinkPreviews() {
return linkPreviews;
}
public @Nullable GiftBadge getGiftBadge() {
return giftBadge;
}
}

View File

@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -54,13 +55,13 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
long expiry, int status, byte[] transactionId, long mailbox,
int subscriptionId, SlideDeck slideDeck, int readReceiptCount,
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId)
@Nullable ParentStoryId parentStoryId, @Nullable GiftBadge giftBadge)
{
super(id, "", conversationRecipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, -1, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
new HashSet<>(), new HashSet<>(), subscriptionId,
0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false,
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId);
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId, giftBadge);
this.contentLocation = contentLocation;
this.messageSize = messageSize;

View File

@@ -70,6 +70,7 @@ data class RecipientRecord(
val announcementGroupCapability: Recipient.Capability,
val changeNumberCapability: Recipient.Capability,
val storiesCapability: Recipient.Capability,
val giftBadgesCapability: Recipient.Capability,
val insightsBannerTier: InsightsBannerTier,
val storageId: ByteArray?,
val mentionSetting: MentionSetting,