mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
Join attachments instead of running an asynchronous query.
No more SlideDeck futures, just SlideDecks. // FREEBIE
This commit is contained in:
parent
25e099a309
commit
d2f44f6584
@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||
@ -305,24 +304,19 @@ public class ConversationFragment extends Fragment
|
||||
private void handleSaveAttachment(final MediaMmsMessageRecord message) {
|
||||
SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
message.fetchMediaSlide(new FutureTaskListener<Slide>() {
|
||||
@Override
|
||||
public void onSuccess(Slide slide) {
|
||||
for (Slide slide : message.getSlideDeck().getSlides()) {
|
||||
if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret);
|
||||
saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
Log.w(TAG, "No slide with attachable media found, failing nicely.");
|
||||
Log.w(TAG, error);
|
||||
Toast.makeText(getActivity(), R.string.ConversationFragment_error_while_saving_attachment_to_sd_card, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
|
@ -240,7 +240,7 @@ public class ConversationItem extends LinearLayout
|
||||
private boolean hasMedia(MessageRecord messageRecord) {
|
||||
return messageRecord.isMms() &&
|
||||
!messageRecord.isMmsNotification() &&
|
||||
((MediaMmsMessageRecord)messageRecord).getPartCount() > 0;
|
||||
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
|
||||
}
|
||||
|
||||
private void setBodyText(MessageRecord messageRecord) {
|
||||
@ -262,8 +262,9 @@ public class ConversationItem extends LinearLayout
|
||||
setNotificationMmsAttributes((NotificationMmsMessageRecord) messageRecord);
|
||||
} else if (hasMedia(messageRecord)) {
|
||||
mediaThumbnail.setVisibility(View.VISIBLE);
|
||||
//noinspection ConstantConditions
|
||||
mediaThumbnail.setImageResource(masterSecret,
|
||||
((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture(),
|
||||
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
|
||||
!messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending()),
|
||||
false);
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
|
@ -29,9 +29,6 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.RoundedCorners;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
@ -46,8 +43,6 @@ public class ThumbnailView extends FrameLayout {
|
||||
private OnClickListener parentClickListener;
|
||||
|
||||
private Optional<TransferControlView> transferControls = Optional.absent();
|
||||
private ListenableFutureTask<SlideDeck> slideDeckFuture = null;
|
||||
private SlideDeckListener slideDeckListener = null;
|
||||
private ThumbnailClickListener thumbnailClickListener = null;
|
||||
private ThumbnailClickListener downloadClickListener = null;
|
||||
private Slide slide = null;
|
||||
@ -117,25 +112,6 @@ public class ThumbnailView extends FrameLayout {
|
||||
this.backgroundColorHint = color;
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull MasterSecret masterSecret,
|
||||
@NonNull ListenableFutureTask<SlideDeck> slideDeckFuture,
|
||||
boolean showControls, boolean showRemove)
|
||||
{
|
||||
if (this.slideDeckFuture != null && this.slideDeckListener != null) {
|
||||
this.slideDeckFuture.removeListener(this.slideDeckListener);
|
||||
}
|
||||
|
||||
if (!slideDeckFuture.equals(this.slideDeckFuture)) {
|
||||
if (transferControls.isPresent()) getTransferControls().clear();
|
||||
image.setImageDrawable(null);
|
||||
this.slide = null;
|
||||
}
|
||||
|
||||
this.slideDeckListener = new SlideDeckListener(masterSecret, showControls, showRemove);
|
||||
this.slideDeckFuture = slideDeckFuture;
|
||||
this.slideDeckFuture.addListener(this.slideDeckListener);
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Slide slide,
|
||||
boolean showControls, boolean showRemove)
|
||||
{
|
||||
@ -182,11 +158,9 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
public void clear() {
|
||||
if (isContextValid()) Glide.clear(image);
|
||||
if (slideDeckFuture != null) slideDeckFuture.removeListener(slideDeckListener);
|
||||
if (transferControls.isPresent()) getTransferControls().clear();
|
||||
|
||||
slide = null;
|
||||
slideDeckFuture = null;
|
||||
slideDeckListener = null;
|
||||
}
|
||||
|
||||
public void showProgressSpinner() {
|
||||
@ -219,54 +193,6 @@ public class ThumbnailView extends FrameLayout {
|
||||
.fitCenter();
|
||||
}
|
||||
|
||||
private class SlideDeckListener implements FutureTaskListener<SlideDeck> {
|
||||
private final MasterSecret masterSecret;
|
||||
private final boolean showControls;
|
||||
private final boolean showRemove;
|
||||
|
||||
public SlideDeckListener(@NonNull MasterSecret masterSecret, boolean showControls, boolean showRemove) {
|
||||
this.masterSecret = masterSecret;
|
||||
this.showControls = showControls;
|
||||
this.showRemove = showRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(final SlideDeck slideDeck) {
|
||||
if (slideDeck == null) return;
|
||||
|
||||
final Slide slide = slideDeck.getThumbnailSlide();
|
||||
|
||||
if (slide != null) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setImageResource(masterSecret, slide, showControls, showRemove);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w(TAG, "Resolved slide was null!");
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
Log.w(TAG, error);
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w(TAG, "onFailure!");
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public interface ThumbnailClickListener {
|
||||
void onClick(View v, Slide slide);
|
||||
}
|
||||
|
@ -61,11 +61,12 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
static final String TABLE_NAME = "part";
|
||||
static final String ROW_ID = "_id";
|
||||
static final String ATTACHMENT_ID_ALIAS = "attachment_id";
|
||||
static final String MMS_ID = "mid";
|
||||
static final String CONTENT_TYPE = "ct";
|
||||
private static final String NAME = "name";
|
||||
private static final String CONTENT_DISPOSITION = "cd";
|
||||
private static final String CONTENT_LOCATION = "cl";
|
||||
static final String NAME = "name";
|
||||
static final String CONTENT_DISPOSITION = "cd";
|
||||
static final String CONTENT_LOCATION = "cl";
|
||||
static final String DATA = "_data";
|
||||
static final String TRANSFER_STATE = "pending_push";
|
||||
static final String SIZE = "data_size";
|
||||
@ -80,6 +81,12 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
|
||||
|
||||
private static final String[] PROJECTION = new String[] {ROW_ID + " AS " + ATTACHMENT_ID_ALIAS,
|
||||
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
|
||||
CONTENT_LOCATION, DATA, TRANSFER_STATE,
|
||||
SIZE, THUMBNAIL, THUMBNAIL_ASPECT_RATIO,
|
||||
UNIQUE_ID};
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
|
||||
MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " +
|
||||
CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + "chset" + " INTEGER, " +
|
||||
@ -148,7 +155,7 @@ public class AttachmentDatabase extends Database {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) return getAttachment(cursor);
|
||||
else return null;
|
||||
@ -165,7 +172,7 @@ public class AttachmentDatabase extends Database {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""},
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, MMS_ID + " = ?", new String[] {mmsId+""},
|
||||
null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
@ -185,7 +192,7 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
attachments.add(getAttachment(cursor));
|
||||
}
|
||||
@ -417,8 +424,8 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseAttachment getAttachment(Cursor cursor) {
|
||||
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
|
||||
DatabaseAttachment getAttachment(Cursor cursor) {
|
||||
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ATTACHMENT_ID_ALIAS)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
|
||||
|
@ -1012,8 +1012,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument);
|
||||
List<NetworkFailure> networkFailures = getFailures(networkDocument);
|
||||
|
||||
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(dateReceived, id);
|
||||
SlideDeck slideDeck = getSlideDeck(cursor);
|
||||
|
||||
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||
addressDeviceId, dateSent, dateReceived, receiptCount,
|
||||
@ -1078,63 +1077,9 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFutureTask<SlideDeck> getSlideDeck(final long timestamp,
|
||||
final long id)
|
||||
{
|
||||
ListenableFutureTask<SlideDeck> future = getCachedSlideDeck(timestamp, id);
|
||||
|
||||
if (future != null) {
|
||||
return future;
|
||||
}
|
||||
|
||||
Callable<SlideDeck> task = new Callable<SlideDeck>() {
|
||||
@Override
|
||||
public SlideDeck call() throws Exception {
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(id));
|
||||
SlideDeck slideDeck = new SlideDeck(context, attachments);
|
||||
boolean progress = false;
|
||||
|
||||
for (Attachment attachment : attachments) {
|
||||
if (attachment.isInProgress()) progress = true;
|
||||
}
|
||||
|
||||
if (!progress) {
|
||||
slideCache.put(timestamp + "::" + id, new SoftReference<>(slideDeck));
|
||||
}
|
||||
|
||||
return slideDeck;
|
||||
}
|
||||
};
|
||||
|
||||
future = new ListenableFutureTask<>(task, timestamp + "::" + id);
|
||||
slideResolver.execute(future);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private ListenableFutureTask<SlideDeck> getCachedSlideDeck(final long timestamp, final long id) {
|
||||
SoftReference<SlideDeck> reference = slideCache.get(timestamp + "::" + id);
|
||||
|
||||
if (reference != null) {
|
||||
final SlideDeck slideDeck = reference.get();
|
||||
|
||||
if (slideDeck != null) {
|
||||
Callable<SlideDeck> task = new Callable<SlideDeck>() {
|
||||
@Override
|
||||
public SlideDeck call() throws Exception {
|
||||
return slideDeck;
|
||||
}
|
||||
};
|
||||
|
||||
ListenableFutureTask<SlideDeck> future = new ListenableFutureTask<>(task);
|
||||
future.run();
|
||||
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
private SlideDeck getSlideDeck(@NonNull Cursor cursor) {
|
||||
Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
|
||||
return new SlideDeck(context, attachment);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
@ -16,11 +16,13 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
@ -34,16 +36,13 @@ import java.util.Set;
|
||||
|
||||
public class MmsSmsDatabase extends Database {
|
||||
|
||||
private static final String TAG = MmsSmsDatabase.class.getSimpleName();
|
||||
|
||||
public static final String TRANSPORT = "transport_type";
|
||||
public static final String MMS_TRANSPORT = "mms";
|
||||
public static final String SMS_TRANSPORT = "sms";
|
||||
|
||||
public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getConversation(long threadId, long limit) {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
|
||||
private static final String[] PROJECTION = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
|
||||
MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
@ -54,13 +53,27 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
|
||||
public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getConversation(long threadId, long limit) {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
|
||||
Cursor cursor = queryTables(projection, selection, selection, order, null, limit > 0 ? String.valueOf(limit) : null);
|
||||
Cursor cursor = queryTables(PROJECTION, selection, order, limit > 0 ? String.valueOf(limit) : null);
|
||||
setNotifyConverationListeners(cursor, threadId);
|
||||
|
||||
return cursor;
|
||||
@ -71,67 +84,27 @@ public class MmsSmsDatabase extends Database {
|
||||
}
|
||||
|
||||
public Cursor getIdentityConflictMessagesForThread(long threadId) {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
|
||||
MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
|
||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.MISMATCHED_IDENTITIES + " IS NOT NULL";
|
||||
|
||||
Cursor cursor = queryTables(projection, selection, selection, order, null, null);
|
||||
Cursor cursor = queryTables(PROJECTION, selection, order, null);
|
||||
setNotifyConverationListeners(cursor, threadId);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public Cursor getConversationSnippet(long threadId) {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.TYPE,
|
||||
MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
|
||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
|
||||
return queryTables(projection, selection, selection, order, null, "1");
|
||||
return queryTables(PROJECTION, selection, order, "1");
|
||||
}
|
||||
|
||||
public Cursor getUnread() {
|
||||
String[] projection = {MmsSmsColumns.ID, SmsDatabase.BODY, SmsDatabase.READ, SmsDatabase.TYPE,
|
||||
SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.STATUS,
|
||||
MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
|
||||
MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
|
||||
MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
String selection = MmsSmsColumns.READ + " = 0";
|
||||
|
||||
return queryTables(projection, selection, selection, order, null, null);
|
||||
return queryTables(PROJECTION, selection, order, null);
|
||||
}
|
||||
|
||||
public int getConversationCount(long threadId) {
|
||||
@ -146,27 +119,47 @@ public class MmsSmsDatabase extends Database {
|
||||
DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
|
||||
}
|
||||
|
||||
private Cursor queryTables(String[] projection, String smsSelection, String mmsSelection, String order, String groupBy, String limit) {
|
||||
private Cursor queryTables(String[] projection, String selection, String order, String limit) {
|
||||
String[] mmsProjection = {MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " AS " + MmsSmsColumns.ID,
|
||||
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
MmsSmsColumns.ID, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
MmsSmsColumns.ID, "NULL AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
|
||||
SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID,
|
||||
SmsDatabase.TYPE, SmsDatabase.ADDRESS, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE,
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT};
|
||||
|
||||
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
|
||||
AttachmentDatabase.UNIQUE_ID,
|
||||
AttachmentDatabase.MMS_ID,
|
||||
AttachmentDatabase.SIZE,
|
||||
AttachmentDatabase.DATA,
|
||||
AttachmentDatabase.CONTENT_TYPE,
|
||||
AttachmentDatabase.CONTENT_LOCATION,
|
||||
AttachmentDatabase.CONTENT_DISPOSITION,
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
|
||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
||||
@ -174,10 +167,10 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsQueryBuilder.setDistinct(true);
|
||||
smsQueryBuilder.setDistinct(true);
|
||||
|
||||
mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME);
|
||||
mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME + " LEFT OUTER JOIN " + AttachmentDatabase.TABLE_NAME + " ON (" + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " = " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + ")");
|
||||
smsQueryBuilder.setTables(SmsDatabase.TABLE_NAME);
|
||||
|
||||
Set<String> mmsColumnsPresent = new HashSet<String>();
|
||||
Set<String> mmsColumnsPresent = new HashSet<>();
|
||||
mmsColumnsPresent.add(MmsSmsColumns.ID);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.READ);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
||||
@ -198,7 +191,16 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsDatabase.STATUS);
|
||||
mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE);
|
||||
|
||||
Set<String> smsColumnsPresent = new HashSet<String>();
|
||||
mmsColumnsPresent.add(AttachmentDatabase.ROW_ID);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.SIZE);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.NAME);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE);
|
||||
|
||||
Set<String> smsColumnsPresent = new HashSet<>();
|
||||
smsColumnsPresent.add(MmsSmsColumns.ID);
|
||||
smsColumnsPresent.add(MmsSmsColumns.BODY);
|
||||
smsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
||||
@ -213,16 +215,16 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
|
||||
smsColumnsPresent.add(SmsDatabase.STATUS);
|
||||
|
||||
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 2, MMS_TRANSPORT, mmsSelection, null, null, null);
|
||||
String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 2, SMS_TRANSPORT, smsSelection, null, null, null);
|
||||
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 3, MMS_TRANSPORT, selection, null, null, null);
|
||||
String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 3, SMS_TRANSPORT, selection, null, null, null);
|
||||
|
||||
SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
|
||||
String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, null);
|
||||
String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, limit);
|
||||
|
||||
SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
|
||||
outerQueryBuilder.setTables("(" + unionQuery + ")");
|
||||
|
||||
String query = outerQueryBuilder.buildQuery(projection, null, null, groupBy, null, null, limit);
|
||||
String query = outerQueryBuilder.buildQuery(projection, null, null, null, null, null, null);
|
||||
|
||||
Log.w("MmsSmsDatabase", "Executing query: " + query);
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
@ -277,14 +279,15 @@ public class MmsSmsDatabase extends Database {
|
||||
return getCurrent();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public MessageRecord getCurrent() {
|
||||
String type = cursor.getString(cursor.getColumnIndexOrThrow(TRANSPORT));
|
||||
|
||||
if (MmsSmsDatabase.MMS_TRANSPORT.equals(type)) {
|
||||
return getMmsReader().getCurrent();
|
||||
} else {
|
||||
return getSmsReader().getCurrent();
|
||||
}
|
||||
Log.w("MmsSmsDatabase", "Type: " + type);
|
||||
|
||||
if (MmsSmsDatabase.MMS_TRANSPORT.equals(type)) return getMmsReader().getCurrent();
|
||||
else if (MmsSmsDatabase.SMS_TRANSPORT.equals(type)) return getSmsReader().getCurrent();
|
||||
else throw new AssertionError("Bad type: " + type);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
@ -17,23 +17,18 @@
|
||||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.SpannableString;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.mms.MediaNotFoundException;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* Represents the message record model for MMS messages that contain
|
||||
@ -48,13 +43,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||
|
||||
private final Context context;
|
||||
private final int partCount;
|
||||
private final ListenableFutureTask<SlideDeck> slideDeckFutureTask;
|
||||
private final @NonNull SlideDeck slideDeck;
|
||||
|
||||
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived, int deliveredCount,
|
||||
long threadId, Body body,
|
||||
ListenableFutureTask<SlideDeck> slideDeck,
|
||||
@NonNull SlideDeck slideDeck,
|
||||
int partCount, long mailbox,
|
||||
List<IdentityKeyMismatch> mismatches,
|
||||
List<NetworkFailure> failures)
|
||||
@ -65,49 +60,15 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.partCount = partCount;
|
||||
this.slideDeckFutureTask = slideDeck;
|
||||
this.slideDeck = slideDeck;
|
||||
}
|
||||
|
||||
public ListenableFutureTask<SlideDeck> getSlideDeckFuture() {
|
||||
return slideDeckFutureTask;
|
||||
}
|
||||
|
||||
private SlideDeck getSlideDeckSync() {
|
||||
try {
|
||||
return slideDeckFutureTask.get();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
public @NonNull SlideDeck getSlideDeck() {
|
||||
return slideDeck;
|
||||
}
|
||||
|
||||
public boolean containsMediaSlide() {
|
||||
SlideDeck deck = getSlideDeckSync();
|
||||
return deck != null && deck.containsMediaSlide();
|
||||
}
|
||||
|
||||
|
||||
public void fetchMediaSlide(final FutureTaskListener<Slide> listener) {
|
||||
slideDeckFutureTask.addListener(new FutureTaskListener<SlideDeck>() {
|
||||
@Override
|
||||
public void onSuccess(SlideDeck deck) {
|
||||
for (Slide slide : deck.getSlides()) {
|
||||
if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) {
|
||||
listener.onSuccess(slide);
|
||||
return;
|
||||
}
|
||||
}
|
||||
listener.onFailure(new MediaNotFoundException("no media slide found"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
listener.onFailure(error);
|
||||
}
|
||||
});
|
||||
return slideDeck.containsMediaSlide();
|
||||
}
|
||||
|
||||
public int getPartCount() {
|
||||
|
@ -17,29 +17,14 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.SmilUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.pdu.CharacterSets;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
public class SlideDeck {
|
||||
|
||||
private final List<Slide> slides = new LinkedList<>();
|
||||
@ -51,6 +36,11 @@ public class SlideDeck {
|
||||
}
|
||||
}
|
||||
|
||||
public SlideDeck(Context context, Attachment attachment) {
|
||||
Slide slide = MediaUtil.getSlideForAttachment(context, attachment);
|
||||
if (slide != null) slides.add(slide);
|
||||
}
|
||||
|
||||
public SlideDeck() {
|
||||
}
|
||||
|
||||
|
@ -352,7 +352,7 @@ public class MessageNotifier {
|
||||
long threadId = record.getThreadId();
|
||||
CharSequence body = record.getDisplayBody();
|
||||
Recipients threadRecipients = null;
|
||||
ListenableFutureTask<SlideDeck> slideDeck = null;
|
||||
SlideDeck slideDeck = null;
|
||||
long timestamp;
|
||||
|
||||
if (record.isPush()) timestamp = record.getDateSent();
|
||||
@ -366,12 +366,12 @@ public class MessageNotifier {
|
||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
|
||||
} else if (record.isMms() && TextUtils.isEmpty(body)) {
|
||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
|
||||
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeckFuture();
|
||||
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
|
||||
} else if (record.isMms() && !record.isMmsNotification()) {
|
||||
String message = context.getString(R.string.MessageNotifier_media_message_with_text, body);
|
||||
int italicLength = message.length() - body.length();
|
||||
body = SpanUtil.italic(message, italicLength);
|
||||
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeckFuture();
|
||||
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
|
||||
}
|
||||
|
||||
if (threadRecipients == null || !threadRecipients.isMuted()) {
|
||||
|
@ -21,12 +21,12 @@ public class NotificationItem {
|
||||
private final long threadId;
|
||||
private final CharSequence text;
|
||||
private final long timestamp;
|
||||
private final ListenableFutureTask<SlideDeck> slideDeck;
|
||||
private final @Nullable SlideDeck slideDeck;
|
||||
|
||||
public NotificationItem(Recipient individualRecipient, Recipients recipients,
|
||||
Recipients threadRecipients, long threadId,
|
||||
CharSequence text, long timestamp,
|
||||
@Nullable ListenableFutureTask<SlideDeck> slideDeck)
|
||||
@Nullable SlideDeck slideDeck)
|
||||
{
|
||||
this.individualRecipient = individualRecipient;
|
||||
this.recipients = recipients;
|
||||
@ -57,7 +57,7 @@ public class NotificationItem {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
public @Nullable ListenableFutureTask<SlideDeck> getSlideDeck() {
|
||||
public @Nullable SlideDeck getSlideDeck() {
|
||||
return slideDeck;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.Action;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
@ -26,7 +25,6 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.preferences.NotificationPrivacyPreference;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -38,7 +36,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
||||
|
||||
private final List<CharSequence> messageBodies = new LinkedList<>();
|
||||
|
||||
private ListenableFutureTask<SlideDeck> slideDeck;
|
||||
private SlideDeck slideDeck;
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public SingleRecipientNotificationBuilder(@NonNull Context context,
|
||||
@ -81,7 +79,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
||||
setNumber(messageCount);
|
||||
}
|
||||
|
||||
public void setPrimaryMessageBody(CharSequence message, @Nullable ListenableFutureTask<SlideDeck> slideDeck) {
|
||||
public void setPrimaryMessageBody(CharSequence message, @Nullable SlideDeck slideDeck) {
|
||||
if (privacy.isDisplayMessage()) {
|
||||
setContentText(message);
|
||||
this.slideDeck = slideDeck;
|
||||
@ -166,30 +164,25 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasBigPictureSlide(@Nullable ListenableFutureTask<SlideDeck> slideDeck) {
|
||||
try {
|
||||
private boolean hasBigPictureSlide(@Nullable SlideDeck slideDeck) {
|
||||
if (masterSecret == null || slideDeck == null || Build.VERSION.SDK_INT < 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Slide thumbnailSlide = slideDeck.get().getThumbnailSlide();
|
||||
Slide thumbnailSlide = slideDeck.getThumbnailSlide();
|
||||
|
||||
return thumbnailSlide != null &&
|
||||
thumbnailSlide.hasImage() &&
|
||||
!thumbnailSlide.isInProgress() &&
|
||||
thumbnailSlide.getThumbnailUri() != null;
|
||||
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getBigPicture(@NonNull MasterSecret masterSecret,
|
||||
@NonNull ListenableFutureTask<SlideDeck> slideDeck)
|
||||
@NonNull SlideDeck slideDeck)
|
||||
{
|
||||
try {
|
||||
Uri uri = slideDeck.get().getThumbnailSlide().getThumbnailUri();
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
Uri uri = slideDeck.getThumbnailSlide().getThumbnailUri();
|
||||
|
||||
return Glide.with(context)
|
||||
.load(new DecryptableStreamUriLoader.DecryptableUri(masterSecret, uri))
|
||||
|
Loading…
x
Reference in New Issue
Block a user