mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Show per-member delivery/read status on message info in groups
// FREEBIE
This commit is contained in:
parent
2b4064f3b7
commit
285947eb66
@ -6,14 +6,13 @@
|
||||
android:gravity="center_vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp">
|
||||
android:padding="16dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/contact_photo_image"
|
||||
android:foreground="@drawable/contact_photo_background"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginTop="3dp"
|
||||
@ -28,6 +27,7 @@
|
||||
android:layout_marginTop="4dip"
|
||||
android:layout_marginBottom="4dip"
|
||||
android:layout_toRightOf="@id/contact_photo_image"
|
||||
android:layout_toEndOf="@id/contact_photo_image"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
android:drawableLeft="@drawable/ic_error_white_18dp"
|
||||
android:text="@string/message_recipients_list_item__view"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="gone" />
|
||||
|
||||
<Button android:id="@+id/resend_button"
|
||||
android:layout_width="wrap_content"
|
||||
@ -91,8 +91,13 @@
|
||||
android:drawableLeft="@drawable/ic_refresh_white_18dp"
|
||||
android:text="@string/message_recipients_list_item__resend"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="gone" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.DeliveryStatusView
|
||||
android:id="@+id/delivery_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -34,11 +34,14 @@ import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
@ -153,12 +156,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void onModified(final Recipient recipient) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setActionBarColor(recipient.getColor());
|
||||
}
|
||||
});
|
||||
Util.runOnMain(() -> setActionBarColor(recipient.getColor()));
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
@ -241,7 +239,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void updateRecipients(MessageRecord messageRecord, Recipient recipient, List<Recipient> recipients) {
|
||||
private void updateRecipients(MessageRecord messageRecord, Recipient recipient, List<RecipientDeliveryStatus> recipients) {
|
||||
final int toFromRes;
|
||||
if (messageRecord.isMms() && !messageRecord.isPush() && !messageRecord.isOutgoing()) {
|
||||
toFromRes = R.string.message_details_header__with;
|
||||
@ -251,8 +249,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
toFromRes = R.string.message_details_header__from;
|
||||
}
|
||||
toFrom.setText(toFromRes);
|
||||
conversationItem.bind(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(),
|
||||
new HashSet<MessageRecord>(), recipient);
|
||||
conversationItem.bind(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient);
|
||||
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, messageRecord,
|
||||
recipients, isPushGroup));
|
||||
}
|
||||
@ -319,11 +316,12 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
return false;
|
||||
}
|
||||
|
||||
private class MessageRecipientAsyncTask extends AsyncTask<Void,Void,List<Recipient>> {
|
||||
private WeakReference<Context> weakContext;
|
||||
private MessageRecord messageRecord;
|
||||
private class MessageRecipientAsyncTask extends AsyncTask<Void,Void,List<RecipientDeliveryStatus>> {
|
||||
|
||||
public MessageRecipientAsyncTask(@NonNull Context context, @NonNull MessageRecord messageRecord) {
|
||||
private final WeakReference<Context> weakContext;
|
||||
private final MessageRecord messageRecord;
|
||||
|
||||
MessageRecipientAsyncTask(@NonNull Context context, @NonNull MessageRecord messageRecord) {
|
||||
this.weakContext = new WeakReference<>(context);
|
||||
this.messageRecord = messageRecord;
|
||||
}
|
||||
@ -333,27 +331,41 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipient> doInBackground(Void... voids) {
|
||||
public List<RecipientDeliveryStatus> doInBackground(Void... voids) {
|
||||
Context context = getContext();
|
||||
|
||||
if (context == null) {
|
||||
Log.w(TAG, "associated context is destroyed, finishing early");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Recipient> recipients = new LinkedList<>();
|
||||
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
|
||||
|
||||
if (!messageRecord.getRecipient().isGroupRecipient()) {
|
||||
Log.w(TAG, "Recipient is not a group, resolving members immediately.");
|
||||
recipients.add(messageRecord.getRecipient());
|
||||
recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), -1));
|
||||
} else {
|
||||
recipients.addAll(DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false));
|
||||
List<GroupReceiptInfo> receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId());
|
||||
|
||||
if (receiptInfoList.isEmpty()) {
|
||||
List<Recipient> group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false);
|
||||
|
||||
for (Recipient recipient : group) {
|
||||
recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, -1));
|
||||
}
|
||||
} else {
|
||||
for (GroupReceiptInfo info : receiptInfoList) {
|
||||
recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true),
|
||||
getStatusFor(info.getStatus(), messageRecord.isPending()),
|
||||
info.getTimestamp()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(List<Recipient> recipients) {
|
||||
public void onPostExecute(List<RecipientDeliveryStatus> recipients) {
|
||||
if (getContext() == null) {
|
||||
Log.w(TAG, "AsyncTask finished with a destroyed context, leaving early.");
|
||||
return;
|
||||
@ -373,5 +385,22 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
metadataContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private RecipientDeliveryStatus.Status getStatusFor(int deliveryReceiptCount, int readReceiptCount, boolean pending) {
|
||||
if (readReceiptCount > 0) return RecipientDeliveryStatus.Status.READ;
|
||||
else if (deliveryReceiptCount > 0) return RecipientDeliveryStatus.Status.DELIVERED;
|
||||
else if (!pending) return RecipientDeliveryStatus.Status.SENT;
|
||||
else return RecipientDeliveryStatus.Status.PENDING;
|
||||
}
|
||||
|
||||
private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending) {
|
||||
if (groupStatus == GroupReceiptDatabase.STATUS_READ) return RecipientDeliveryStatus.Status.READ;
|
||||
else if (groupStatus == GroupReceiptDatabase.STATUS_DELIVERED) return RecipientDeliveryStatus.Status.DELIVERED;
|
||||
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && !pending) return RecipientDeliveryStatus.Status.SENT;
|
||||
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED) return RecipientDeliveryStatus.Status.PENDING;
|
||||
else if (groupStatus == GroupReceiptDatabase.STATUS_UNKNOWN) return RecipientDeliveryStatus.Status.UNKNOWN;
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,39 +16,38 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
public class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
|
||||
class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final MessageRecord record;
|
||||
private final List<Recipient> recipients;
|
||||
private final boolean isPushGroup;
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final MessageRecord record;
|
||||
private final List<RecipientDeliveryStatus> members;
|
||||
private final boolean isPushGroup;
|
||||
|
||||
public MessageDetailsRecipientAdapter(Context context, MasterSecret masterSecret,
|
||||
MessageRecord record, List<Recipient> recipients,
|
||||
boolean isPushGroup)
|
||||
MessageDetailsRecipientAdapter(Context context, MasterSecret masterSecret, MessageRecord record,
|
||||
List<RecipientDeliveryStatus> members, boolean isPushGroup)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.record = record;
|
||||
this.recipients = recipients;
|
||||
this.isPushGroup = isPushGroup;
|
||||
this.members = members;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return recipients.size();
|
||||
return members.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return recipients.get(position);
|
||||
return members.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
try {
|
||||
return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(recipients.get(position).getAddress().serialize().getBytes()));
|
||||
return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(members.get(position).recipient.getAddress().serialize().getBytes()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
@ -60,8 +59,9 @@ public class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsLi
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.message_recipient_list_item, parent, false);
|
||||
}
|
||||
|
||||
Recipient recipient = recipients.get(position);
|
||||
((MessageRecipientListItem)convertView).set(masterSecret, record, recipient, isPushGroup);
|
||||
RecipientDeliveryStatus member = members.get(position);
|
||||
|
||||
((MessageRecipientListItem)convertView).set(masterSecret, record, member, isPushGroup);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@ -70,4 +70,35 @@ public class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsLi
|
||||
((MessageRecipientListItem)view).unbind();
|
||||
}
|
||||
|
||||
|
||||
static class RecipientDeliveryStatus {
|
||||
|
||||
enum Status {
|
||||
UNKNOWN, PENDING, SENT, DELIVERED, READ
|
||||
}
|
||||
|
||||
private final Recipient recipient;
|
||||
private final Status deliveryStatus;
|
||||
private final long timestamp;
|
||||
|
||||
RecipientDeliveryStatus(Recipient recipient, Status deliveryStatus, long timestamp) {
|
||||
this.recipient = recipient;
|
||||
this.deliveryStatus = deliveryStatus;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
Status getDeliveryStatus() {
|
||||
return deliveryStatus;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public Recipient getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -25,7 +25,9 @@ import android.widget.Button;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -48,13 +50,14 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
{
|
||||
private final static String TAG = MessageRecipientListItem.class.getSimpleName();
|
||||
|
||||
private Recipient recipient;
|
||||
private FromTextView fromView;
|
||||
private TextView errorDescription;
|
||||
private TextView actionDescription;
|
||||
private Button conflictButton;
|
||||
private Button resendButton;
|
||||
private AvatarImageView contactPhotoImage;
|
||||
private RecipientDeliveryStatus member;
|
||||
private FromTextView fromView;
|
||||
private TextView errorDescription;
|
||||
private TextView actionDescription;
|
||||
private Button conflictButton;
|
||||
private Button resendButton;
|
||||
private AvatarImageView contactPhotoImage;
|
||||
private DeliveryStatusView deliveryStatusView;
|
||||
|
||||
public MessageRecipientListItem(Context context) {
|
||||
super(context);
|
||||
@ -67,24 +70,25 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.fromView = (FromTextView) findViewById(R.id.from);
|
||||
this.errorDescription = (TextView) findViewById(R.id.error_description);
|
||||
this.actionDescription = (TextView) findViewById(R.id.action_description);
|
||||
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
|
||||
this.conflictButton = (Button) findViewById(R.id.conflict_button);
|
||||
this.resendButton = (Button) findViewById(R.id.resend_button);
|
||||
this.fromView = (FromTextView) findViewById(R.id.from);
|
||||
this.errorDescription = (TextView) findViewById(R.id.error_description);
|
||||
this.actionDescription = (TextView) findViewById(R.id.action_description);
|
||||
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
|
||||
this.conflictButton = (Button) findViewById(R.id.conflict_button);
|
||||
this.resendButton = (Button) findViewById(R.id.resend_button);
|
||||
this.deliveryStatusView = (DeliveryStatusView) findViewById(R.id.delivery_status);
|
||||
}
|
||||
|
||||
public void set(final MasterSecret masterSecret,
|
||||
final MessageRecord record,
|
||||
final Recipient recipient,
|
||||
final RecipientDeliveryStatus member,
|
||||
final boolean isPushGroup)
|
||||
{
|
||||
this.recipient = recipient;
|
||||
this.member = member;
|
||||
|
||||
recipient.addListener(this);
|
||||
fromView.setText(recipient);
|
||||
contactPhotoImage.setAvatar(recipient, false);
|
||||
member.getRecipient().addListener(this);
|
||||
fromView.setText(member.getRecipient());
|
||||
contactPhotoImage.setAvatar(member.getRecipient(), false);
|
||||
setIssueIndicators(masterSecret, record, isPushGroup);
|
||||
}
|
||||
|
||||
@ -102,12 +106,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
conflictButton.setVisibility(View.VISIBLE);
|
||||
|
||||
errorText = getContext().getString(R.string.MessageDetailsRecipient_new_safety_number);
|
||||
conflictButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new ConfirmIdentityDialog(getContext(), masterSecret, record, keyMismatch).show();
|
||||
}
|
||||
});
|
||||
conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), masterSecret, record, keyMismatch).show());
|
||||
} else if (networkFailure != null || (!isPushGroup && record.isFailed())) {
|
||||
resendButton.setVisibility(View.VISIBLE);
|
||||
resendButton.setEnabled(true);
|
||||
@ -115,17 +114,27 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
conflictButton.setVisibility(View.GONE);
|
||||
|
||||
errorText = getContext().getString(R.string.MessageDetailsRecipient_failed_to_send);
|
||||
resendButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
resendButton.setVisibility(View.GONE);
|
||||
errorDescription.setVisibility(View.GONE);
|
||||
actionDescription.setVisibility(View.VISIBLE);
|
||||
actionDescription.setText(R.string.message_recipients_list_item__resending);
|
||||
new ResendAsyncTask(masterSecret, record, networkFailure).execute();
|
||||
}
|
||||
resendButton.setOnClickListener(v -> {
|
||||
resendButton.setVisibility(View.GONE);
|
||||
errorDescription.setVisibility(View.GONE);
|
||||
actionDescription.setVisibility(View.VISIBLE);
|
||||
actionDescription.setText(R.string.message_recipients_list_item__resending);
|
||||
new ResendAsyncTask(masterSecret, record, networkFailure).execute();
|
||||
});
|
||||
} else {
|
||||
if (member.getDeliveryStatus() == RecipientDeliveryStatus.Status.PENDING || member.getDeliveryStatus() == RecipientDeliveryStatus.Status.UNKNOWN) {
|
||||
deliveryStatusView.setVisibility(View.GONE);
|
||||
} else if (member.getDeliveryStatus() == RecipientDeliveryStatus.Status.READ) {
|
||||
deliveryStatusView.setRead();
|
||||
deliveryStatusView.setVisibility(View.VISIBLE);
|
||||
} else if (member.getDeliveryStatus() == RecipientDeliveryStatus.Status.DELIVERED) {
|
||||
deliveryStatusView.setDelivered();
|
||||
deliveryStatusView.setVisibility(View.VISIBLE);
|
||||
} else if (member.getDeliveryStatus() == RecipientDeliveryStatus.Status.SENT) {
|
||||
deliveryStatusView.setSent();
|
||||
deliveryStatusView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
resendButton.setVisibility(View.GONE);
|
||||
conflictButton.setVisibility(View.GONE);
|
||||
}
|
||||
@ -137,7 +146,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
private NetworkFailure getNetworkFailure(final MessageRecord record) {
|
||||
if (record.hasNetworkFailures()) {
|
||||
for (final NetworkFailure failure : record.getNetworkFailures()) {
|
||||
if (failure.getAddress().equals(recipient.getAddress())) {
|
||||
if (failure.getAddress().equals(member.getRecipient().getAddress())) {
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
@ -148,7 +157,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
private IdentityKeyMismatch getKeyMismatch(final MessageRecord record) {
|
||||
if (record.isIdentityMismatchFailure()) {
|
||||
for (final IdentityKeyMismatch mismatch : record.getIdentityKeyMismatches()) {
|
||||
if (mismatch.getAddress().equals(recipient.getAddress())) {
|
||||
if (mismatch.getAddress().equals(member.getRecipient().getAddress())) {
|
||||
return mismatch;
|
||||
}
|
||||
}
|
||||
@ -157,7 +166,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
if (this.recipient != null) this.recipient.removeListener(this);
|
||||
if (this.member != null && this.member.getRecipient() != null) this.member.getRecipient().removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -107,7 +107,8 @@ public class DatabaseFactory {
|
||||
private static final int PROFILE_SHARING_APPROVAL = 42;
|
||||
private static final int UNSEEN_NUMBER_OFFER = 43;
|
||||
private static final int READ_RECEIPTS = 44;
|
||||
private static final int DATABASE_VERSION = 44;
|
||||
private static final int GROUP_RECEIPT_TRACKING = 45;
|
||||
private static final int DATABASE_VERSION = 45;
|
||||
|
||||
private static final String DATABASE_NAME = "messages.db";
|
||||
private static final Object lock = new Object();
|
||||
@ -129,6 +130,7 @@ public class DatabaseFactory {
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final RecipientDatabase recipientDatabase;
|
||||
private final ContactsDatabase contactsDatabase;
|
||||
private final GroupReceiptDatabase groupReceiptDatabase;
|
||||
|
||||
public static DatabaseFactory getInstance(Context context) {
|
||||
synchronized (lock) {
|
||||
@ -191,21 +193,26 @@ public class DatabaseFactory {
|
||||
return getInstance(context).contactsDatabase;
|
||||
}
|
||||
|
||||
public static GroupReceiptDatabase getGroupReceiptDatabase(Context context) {
|
||||
return getInstance(context).groupReceiptDatabase;
|
||||
}
|
||||
|
||||
private DatabaseFactory(Context context) {
|
||||
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
this.sms = new SmsDatabase(context, databaseHelper);
|
||||
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
|
||||
this.mms = new MmsDatabase(context, databaseHelper);
|
||||
this.attachments = new AttachmentDatabase(context, databaseHelper);
|
||||
this.media = new MediaDatabase(context, databaseHelper);
|
||||
this.thread = new ThreadDatabase(context, databaseHelper);
|
||||
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
|
||||
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
|
||||
this.draftDatabase = new DraftDatabase(context, databaseHelper);
|
||||
this.pushDatabase = new PushDatabase(context, databaseHelper);
|
||||
this.groupDatabase = new GroupDatabase(context, databaseHelper);
|
||||
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
|
||||
this.contactsDatabase = new ContactsDatabase(context);
|
||||
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
this.sms = new SmsDatabase(context, databaseHelper);
|
||||
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
|
||||
this.mms = new MmsDatabase(context, databaseHelper);
|
||||
this.attachments = new AttachmentDatabase(context, databaseHelper);
|
||||
this.media = new MediaDatabase(context, databaseHelper);
|
||||
this.thread = new ThreadDatabase(context, databaseHelper);
|
||||
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
|
||||
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
|
||||
this.draftDatabase = new DraftDatabase(context, databaseHelper);
|
||||
this.pushDatabase = new PushDatabase(context, databaseHelper);
|
||||
this.groupDatabase = new GroupDatabase(context, databaseHelper);
|
||||
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
|
||||
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
|
||||
this.contactsDatabase = new ContactsDatabase(context);
|
||||
}
|
||||
|
||||
public void reset(Context context) {
|
||||
@ -223,6 +230,7 @@ public class DatabaseFactory {
|
||||
this.pushDatabase.reset(databaseHelper);
|
||||
this.groupDatabase.reset(databaseHelper);
|
||||
this.recipientDatabase.reset(databaseHelper);
|
||||
this.groupReceiptDatabase.reset(databaseHelper);
|
||||
old.close();
|
||||
}
|
||||
|
||||
@ -536,6 +544,7 @@ public class DatabaseFactory {
|
||||
db.execSQL(PushDatabase.CREATE_TABLE);
|
||||
db.execSQL(GroupDatabase.CREATE_TABLE);
|
||||
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
||||
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
|
||||
|
||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||
@ -543,6 +552,7 @@ public class DatabaseFactory {
|
||||
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, DraftDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1328,6 +1338,11 @@ public class DatabaseFactory {
|
||||
db.execSQL("ALTER TABLE thread ADD COLUMN read_receipt_count INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
if (oldVersion < GROUP_RECEIPT_TRACKING) {
|
||||
db.execSQL("CREATE TABLE group_receipts (_id INTEGER PRIMARY KEY, mms_id INTEGER, address TEXT, status INTEGER, timestamp INTEGER)");
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON group_receipts (mms_id)");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
@ -0,0 +1,112 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class GroupReceiptDatabase extends Database {
|
||||
|
||||
public static final String TABLE_NAME = "group_receipts";
|
||||
|
||||
private static final String ID = "_id";
|
||||
private static final String MMS_ID = "mms_id";
|
||||
private static final String ADDRESS = "address";
|
||||
private static final String STATUS = "status";
|
||||
private static final String TIMESTAMP = "timestamp";
|
||||
|
||||
public static final int STATUS_UNKNOWN = -1;
|
||||
public static final int STATUS_UNDELIVERED = 0;
|
||||
public static final int STATUS_DELIVERED = 1;
|
||||
public static final int STATUS_READ = 2;
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
MMS_ID + " INTEGER, " + ADDRESS + " TEXT, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER);";
|
||||
|
||||
public static final String[] CREATE_INDEXES = {
|
||||
"CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
|
||||
};
|
||||
|
||||
public GroupReceiptDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void insert(List<Address> addresses, long mmsId, int status, long timestamp) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
for (Address address : addresses) {
|
||||
ContentValues values = new ContentValues(4);
|
||||
values.put(MMS_ID, mmsId);
|
||||
values.put(ADDRESS, address.serialize());
|
||||
values.put(STATUS, status);
|
||||
values.put(TIMESTAMP, timestamp);
|
||||
|
||||
db.insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(Address address, long mmsId, int status, long timestamp) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(STATUS, status);
|
||||
values.put(TIMESTAMP, timestamp);
|
||||
|
||||
db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + ADDRESS + " = ? AND " + STATUS + " < ?",
|
||||
new String[] {String.valueOf(mmsId), address.serialize(), String.valueOf(status)});
|
||||
}
|
||||
|
||||
public @NonNull List<GroupReceiptInfo> getGroupReceiptInfo(long mmsId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
List<GroupReceiptInfo> results = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)}, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.add(new GroupReceiptInfo(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP))));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void deleteRowsForMessage(long mmsId) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)});
|
||||
}
|
||||
|
||||
void deleteAllRows() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, null, null);
|
||||
}
|
||||
|
||||
public static class GroupReceiptInfo {
|
||||
private final Address address;
|
||||
private final int status;
|
||||
private final long timestamp;
|
||||
|
||||
public GroupReceiptInfo(Address address, int status, long timestamp) {
|
||||
this.address = address;
|
||||
this.status = status;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.mms.pdu_alt.NotificationInd;
|
||||
import com.google.android.mms.pdu_alt.PduHeaders;
|
||||
|
||||
@ -125,7 +126,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE,
|
||||
MESSAGE_SIZE, STATUS, TRANSACTION_ID,
|
||||
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||
DELIVERY_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
||||
DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED,
|
||||
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
@ -194,7 +195,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) {
|
||||
public void incrementReceiptCount(SyncMessageId messageId, long timestamp, boolean deliveryReceipt, boolean readReceipt) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
boolean found = false;
|
||||
@ -211,6 +212,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||
int status = deliveryReceipt ? GroupReceiptDatabase.STATUS_DELIVERED : GroupReceiptDatabase.STATUS_READ;
|
||||
|
||||
found = true;
|
||||
|
||||
@ -218,6 +220,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
columnName + " = " + columnName + " + 1 WHERE " + ID + " = ?",
|
||||
new String[] {String.valueOf(id)});
|
||||
|
||||
DatabaseFactory.getGroupReceiptDatabase(context).update(ourAddress, id, status, timestamp);
|
||||
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
@ -814,6 +817,14 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener);
|
||||
|
||||
if (message.getRecipient().getAddress().isGroup()) {
|
||||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().getAddress().toGroupString(), false);
|
||||
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
||||
|
||||
receiptDatabase.insert(Stream.of(members).map(Recipient::getAddress).toList(),
|
||||
messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getSentTimeMillis());
|
||||
}
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
|
||||
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
|
||||
jobManager.add(new TrimThreadJob(context, threadId));
|
||||
@ -891,6 +902,9 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
attachmentDatabase.deleteAttachmentsForMessage(messageId);
|
||||
|
||||
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
||||
groupReceiptDatabase.deleteRowsForMessage(messageId);
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
|
||||
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
||||
@ -972,6 +986,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
public void deleteAllThreads() {
|
||||
DatabaseFactory.getAttachmentDatabase(context).deleteAllAttachments();
|
||||
DatabaseFactory.getGroupReceiptDatabase(context).deleteAllRows();
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, null, null);
|
||||
|
@ -137,14 +137,14 @@ public class MmsSmsDatabase extends Database {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void incrementDeliveryReceiptCount(SyncMessageId syncMessageId) {
|
||||
public void incrementDeliveryReceiptCount(SyncMessageId syncMessageId, long timestamp) {
|
||||
DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, true, false);
|
||||
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, true, false);
|
||||
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, true, false);
|
||||
}
|
||||
|
||||
public void incrementReadReceiptCount(SyncMessageId syncMessageId) {
|
||||
public void incrementReadReceiptCount(SyncMessageId syncMessageId, long timestamp) {
|
||||
DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, false, true);
|
||||
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, false, true);
|
||||
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, false, true);
|
||||
}
|
||||
|
||||
private Cursor queryTables(String[] projection, String selection, String order, String limit) {
|
||||
|
@ -836,7 +836,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
for (long timestamp : message.getTimestamps()) {
|
||||
Log.w(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp));
|
||||
DatabaseFactory.getMmsSmsDatabase(context)
|
||||
.incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp));
|
||||
.incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@ -848,7 +848,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
Log.w(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp));
|
||||
|
||||
DatabaseFactory.getMmsSmsDatabase(context)
|
||||
.incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp));
|
||||
.incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp), envelope.getTimestamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,15 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
@ -137,7 +141,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
{
|
||||
String groupId = message.getRecipient().getAddress().toGroupString();
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
||||
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
|
||||
List<Address> recipients = getGroupMessageRecipients(groupId, messageId);
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments);
|
||||
@ -181,13 +185,15 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
private List<SignalServiceAddress> getPushAddresses(List<Recipient> recipients) {
|
||||
List<SignalServiceAddress> addresses = new LinkedList<>();
|
||||
private List<SignalServiceAddress> getPushAddresses(List<Address> addresses) {
|
||||
return Stream.of(addresses).map(this::getPushAddress).toList();
|
||||
}
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
addresses.add(getPushAddress(recipient.getAddress()));
|
||||
}
|
||||
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
|
||||
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
|
||||
if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList();
|
||||
|
||||
return addresses;
|
||||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
|
||||
return Stream.of(members).map(Recipient::getAddress).toList();
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public abstract class PushReceivedJob extends ContextJob {
|
||||
private void handleReceipt(SignalServiceEnvelope envelope) {
|
||||
Log.w(TAG, String.format("Received receipt: (XXXXX, %d)", envelope.getTimestamp()));
|
||||
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()),
|
||||
envelope.getTimestamp()));
|
||||
envelope.getTimestamp()), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private boolean isActiveNumber(@NonNull Recipient recipient) {
|
||||
|
Loading…
Reference in New Issue
Block a user