kopia lustrzana https://github.com/ryukoposting/Signal-Android
Permanent attachment failure.
Co-authored-by: Cody Henthorne <cody@signal.org>main
rodzic
9ef58516e2
commit
6f46e9000b
|
@ -0,0 +1,170 @@
|
|||
package org.thoughtcrime.securesms.conversation
|
||||
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
|
||||
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Helper test for rendering conversation items for preview.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@Ignore("For testing/previewing manually, no assertions")
|
||||
class ConversationItemPreviewer {
|
||||
|
||||
@get:Rule
|
||||
val harness = SignalActivityRule(othersCount = 10)
|
||||
|
||||
@Test
|
||||
fun testShowLongName() {
|
||||
val other: Recipient = Recipient.resolved(harness.others.first())
|
||||
|
||||
SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("Seef", "$$$"))
|
||||
|
||||
insertFailedMediaMessage(other = other, attachmentCount = 1)
|
||||
insertFailedMediaMessage(other = other, attachmentCount = 2)
|
||||
insertFailedMediaMessage(other = other, body = "Test", attachmentCount = 1)
|
||||
// insertFailedOutgoingMediaMessage(other = other, body = "Test", attachmentCount = 1)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
// insertMediaMessage(other = other)
|
||||
|
||||
val scenario: ActivityScenario<ConversationActivity> = harness.launchActivity { putExtra("recipient_id", other.id.serialize()) }
|
||||
scenario.onActivity {
|
||||
}
|
||||
|
||||
// Uncomment to make dialog stay on screen, otherwise will show/dismiss immediately
|
||||
// ThreadUtil.sleep(45000)
|
||||
}
|
||||
|
||||
private fun insertMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) {
|
||||
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
|
||||
attachment()
|
||||
}
|
||||
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
body = body,
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
serverTimeMillis = System.currentTimeMillis(),
|
||||
receivedTimeMillis = System.currentTimeMillis(),
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
)
|
||||
|
||||
SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
|
||||
private fun insertFailedMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) {
|
||||
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
|
||||
attachment()
|
||||
}
|
||||
|
||||
val message = IncomingMediaMessage(
|
||||
from = other.id,
|
||||
body = body,
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
serverTimeMillis = System.currentTimeMillis(),
|
||||
receivedTimeMillis = System.currentTimeMillis(),
|
||||
attachments = PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
)
|
||||
|
||||
val insert = SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
|
||||
|
||||
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
|
||||
// if (index != 1) {
|
||||
SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert.messageId)
|
||||
// } else {
|
||||
// SignalDatabase.attachments.setTransferState(insert.messageId, attachment, TRANSFER_PROGRESS_STARTED)
|
||||
// }
|
||||
}
|
||||
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
|
||||
private fun insertFailedOutgoingMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) {
|
||||
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
|
||||
attachment()
|
||||
}
|
||||
|
||||
val message = OutgoingMediaMessage(
|
||||
other,
|
||||
body,
|
||||
PointerAttachment.forPointers(Optional.of(attachments)),
|
||||
System.currentTimeMillis(),
|
||||
-1,
|
||||
0,
|
||||
false,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
StoryType.NONE,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
null
|
||||
)
|
||||
|
||||
val insert = SignalDatabase.mms.insertMessageOutbox(
|
||||
OutgoingSecureMediaMessage(message),
|
||||
SignalDatabase.threads.getOrCreateThreadIdFor(other),
|
||||
false,
|
||||
null
|
||||
)
|
||||
|
||||
SignalDatabase.attachments.getAttachmentsForMessage(insert).forEachIndexed { index, attachment ->
|
||||
SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert)
|
||||
}
|
||||
|
||||
ThreadUtil.sleep(1)
|
||||
}
|
||||
|
||||
private fun attachment(): SignalServiceAttachmentPointer {
|
||||
return SignalServiceAttachmentPointer(
|
||||
ReleaseChannel.CDN_NUMBER,
|
||||
SignalServiceAttachmentRemoteId.from(""),
|
||||
"image/webp",
|
||||
null,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
1024,
|
||||
1024,
|
||||
Optional.empty(),
|
||||
Optional.of("/not-there.jpg"),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -110,6 +110,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
|
|||
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true))
|
||||
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
||||
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
||||
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey)
|
||||
others += recipientId
|
||||
}
|
||||
|
|
|
@ -119,7 +119,12 @@ public abstract class Attachment {
|
|||
|
||||
public boolean isInProgress() {
|
||||
return transferState != AttachmentDatabase.TRANSFER_PROGRESS_DONE &&
|
||||
transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED;
|
||||
transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED &&
|
||||
transferState != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
public boolean isPermanentlyFailed() {
|
||||
return transferState == AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
|
|
|
@ -18,6 +18,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
@ -34,9 +35,11 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
@ -62,10 +65,12 @@ public class ThumbnailView extends FrameLayout {
|
|||
private static final int MIN_HEIGHT = 2;
|
||||
private static final int MAX_HEIGHT = 3;
|
||||
|
||||
private ImageView image;
|
||||
private ImageView blurhash;
|
||||
private View playOverlay;
|
||||
private View captionIcon;
|
||||
private final ImageView image;
|
||||
private final ImageView blurhash;
|
||||
private final View playOverlay;
|
||||
private final View captionIcon;
|
||||
private final AppCompatImageView errorImage;
|
||||
|
||||
private OnClickListener parentClickListener;
|
||||
|
||||
private final int[] dimens = new int[2];
|
||||
|
@ -97,6 +102,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
this.blurhash = findViewById(R.id.thumbnail_blurhash);
|
||||
this.playOverlay = findViewById(R.id.play_overlay);
|
||||
this.captionIcon = findViewById(R.id.thumbnail_caption_icon);
|
||||
this.errorImage = findViewById(R.id.thumbnail_error);
|
||||
|
||||
super.setOnClickListener(new ThumbnailClickDispatcher());
|
||||
|
||||
|
@ -302,6 +308,34 @@ public class ThumbnailView extends FrameLayout {
|
|||
boolean showControls, boolean isPreview,
|
||||
int naturalWidth, int naturalHeight)
|
||||
{
|
||||
if (slide.asAttachment().isPermanentlyFailed()) {
|
||||
this.slide = slide;
|
||||
|
||||
transferControls.ifPresent(c -> c.setVisibility(View.GONE));
|
||||
playOverlay.setVisibility(View.GONE);
|
||||
|
||||
glideRequests.clear(blurhash);
|
||||
blurhash.setImageDrawable(null);
|
||||
|
||||
glideRequests.clear(image);
|
||||
image.setImageDrawable(null);
|
||||
|
||||
int errorImageResource;
|
||||
if (slide instanceof ImageSlide) {
|
||||
errorImageResource = R.drawable.ic_photo_slash_outline_24;
|
||||
} else if (slide instanceof VideoSlide) {
|
||||
errorImageResource = R.drawable.ic_video_slash_outline_24;
|
||||
} else {
|
||||
errorImageResource = R.drawable.ic_error_outline_24;
|
||||
}
|
||||
errorImage.setImageResource(errorImageResource);
|
||||
errorImage.setVisibility(View.VISIBLE);
|
||||
|
||||
return new SettableFuture<>(true);
|
||||
} else {
|
||||
errorImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (showControls) {
|
||||
getTransferControls().setSlide(slide);
|
||||
getTransferControls().setDownloadClickListener(new DownloadClickDispatcher());
|
||||
|
@ -532,11 +566,13 @@ public class ThumbnailView extends FrameLayout {
|
|||
private class ThumbnailClickDispatcher implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (thumbnailClickListener != null &&
|
||||
slide != null &&
|
||||
slide.asAttachment().getUri() != null &&
|
||||
slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE)
|
||||
{
|
||||
boolean validThumbnail = slide != null &&
|
||||
slide.asAttachment().getUri() != null &&
|
||||
slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||
|
||||
boolean permanentFailure = slide != null && slide.asAttachment().isPermanentlyFailed();
|
||||
|
||||
if (thumbnailClickListener != null && (validThumbnail || permanentFailure)) {
|
||||
thumbnailClickListener.onClick(view, slide);
|
||||
} else if (parentClickListener != null) {
|
||||
parentClickListener.onClick(view);
|
||||
|
|
|
@ -183,15 +183,20 @@ public final class TransferControlView extends FrameLayout {
|
|||
}
|
||||
|
||||
private int getTransferState(@NonNull List<Slide> slides) {
|
||||
int transferState = AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||
int transferState = AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||
boolean allFailed = true;
|
||||
|
||||
for (Slide slide : slides) {
|
||||
if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING && transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) {
|
||||
transferState = slide.getTransferState();
|
||||
} else {
|
||||
transferState = Math.max(transferState, slide.getTransferState());
|
||||
if (slide.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE) {
|
||||
allFailed = false;
|
||||
if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING && transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) {
|
||||
transferState = slide.getTransferState();
|
||||
} else {
|
||||
transferState = Math.max(transferState, slide.getTransferState());
|
||||
}
|
||||
}
|
||||
}
|
||||
return transferState;
|
||||
return allFailed ? AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE : transferState;
|
||||
}
|
||||
|
||||
private String getDownloadText(@NonNull List<Slide> slides) {
|
||||
|
|
|
@ -64,6 +64,7 @@ import androidx.lifecycle.LifecycleOwner;
|
|||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
|
@ -2348,6 +2349,25 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
Log.w(TAG, "No activity existed to view the media.");
|
||||
Toast.makeText(context, R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} else if (slide.asAttachment().isPermanentlyFailed()) {
|
||||
String failedMessage;
|
||||
|
||||
if (slide instanceof ImageSlide) {
|
||||
failedMessage = messageRecord.isOutgoing() ? context.getString(R.string.ConversationItem_cant_download_image_you_will_need_to_send_it_again)
|
||||
: context.getString(R.string.ConversationItem_cant_download_image_s_will_need_to_send_it_again, messageRecord.getIndividualRecipient().getShortDisplayName(context));
|
||||
} else if (slide instanceof VideoSlide) {
|
||||
failedMessage = messageRecord.isOutgoing() ? context.getString(R.string.ConversationItem_cant_download_video_you_will_need_to_send_it_again)
|
||||
: context.getString(R.string.ConversationItem_cant_download_video_s_will_need_to_send_it_again, messageRecord.getIndividualRecipient().getShortDisplayName(context));
|
||||
} else {
|
||||
failedMessage = messageRecord.isOutgoing() ? context.getString(R.string.ConversationItem_cant_download_message_you_will_need_to_send_it_again)
|
||||
: context.getString(R.string.ConversationItem_cant_download_message_s_will_need_to_send_it_again, messageRecord.getIndividualRecipient().getShortDisplayName(context));
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setMessage(failedMessage)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,10 +127,11 @@ public class AttachmentDatabase extends Database {
|
|||
|
||||
private static final String DIRECTORY = "parts";
|
||||
|
||||
public static final int TRANSFER_PROGRESS_DONE = 0;
|
||||
public static final int TRANSFER_PROGRESS_STARTED = 1;
|
||||
public static final int TRANSFER_PROGRESS_PENDING = 2;
|
||||
public static final int TRANSFER_PROGRESS_FAILED = 3;
|
||||
public static final int TRANSFER_PROGRESS_DONE = 0;
|
||||
public static final int TRANSFER_PROGRESS_STARTED = 1;
|
||||
public static final int TRANSFER_PROGRESS_PENDING = 2;
|
||||
public static final int TRANSFER_PROGRESS_FAILED = 3;
|
||||
public static final int TRANSFER_PROGRESS_PERMANENT_FAILURE = 4;
|
||||
|
||||
public static final long PREUPLOAD_MESSAGE_ID = -8675309;
|
||||
|
||||
|
@ -233,6 +234,17 @@ public class AttachmentDatabase extends Database {
|
|||
ContentValues values = new ContentValues();
|
||||
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_FAILED);
|
||||
|
||||
database.update(TABLE_NAME, values, PART_ID_WHERE + " AND " + TRANSFER_STATE + " < " + TRANSFER_PROGRESS_PERMANENT_FAILURE, attachmentId.toStrings());
|
||||
notifyConversationListeners(SignalDatabase.mms().getThreadIdForMessage(mmsId));
|
||||
}
|
||||
|
||||
public void setTransferProgressPermanentFailure(AttachmentId attachmentId, long mmsId)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_PERMANENT_FAILURE);
|
||||
|
||||
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
|
||||
notifyConversationListeners(SignalDatabase.mms().getThreadIdForMessage(mmsId));
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.annotation.VisibleForTesting;
|
|||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidMacException;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
|
@ -107,7 +108,8 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
final AttachmentDatabase database = SignalDatabase.attachments();
|
||||
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
|
||||
final DatabaseAttachment attachment = database.getAttachment(attachmentId);
|
||||
final boolean pending = attachment != null && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE;
|
||||
final boolean pending = attachment != null && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE
|
||||
&& attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE;
|
||||
|
||||
if (pending && (manual || AttachmentUtil.isAutoDownloadPermitted(context, attachment))) {
|
||||
Log.i(TAG, "onAdded() Marking attachment progress as 'started'");
|
||||
|
@ -136,6 +138,11 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
if (attachment.isPermanentlyFailed()) {
|
||||
Log.w(TAG, "Attachment was marked as a permanent failure. Refusing to download.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!attachment.isInProgress()) {
|
||||
Log.w(TAG, "Attachment was already downloaded.");
|
||||
return;
|
||||
|
@ -194,9 +201,17 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
} else {
|
||||
throw new IOException("Failed to delete temp download file following range exception");
|
||||
}
|
||||
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException | MissingConfigurationException e) {
|
||||
} catch (InvalidPartException | NonSuccessfulResponseCodeException | MmsException | MissingConfigurationException e) {
|
||||
Log.w(TAG, "Experienced exception while trying to download an attachment.", e);
|
||||
markFailed(messageId, attachmentId);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, "Experienced an InvalidMessageException while trying to download an attachment.", e);
|
||||
if (e.getCause() instanceof InvalidMacException) {
|
||||
Log.w(TAG, "Detected an invalid mac. Treating as a permanent failure.");
|
||||
markPermanentlyFailed(messageId, attachmentId);
|
||||
} else {
|
||||
markFailed(messageId, attachmentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,6 +277,15 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private void markPermanentlyFailed(long messageId, AttachmentId attachmentId) {
|
||||
try {
|
||||
AttachmentDatabase database = SignalDatabase.attachments();
|
||||
database.setTransferProgressPermanentFailure(attachmentId, messageId);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting static class InvalidPartException extends Exception {
|
||||
InvalidPartException(String s) {super(s);}
|
||||
InvalidPartException(Exception e) {super(e);}
|
||||
|
|
|
@ -8,12 +8,12 @@ import android.view.View
|
|||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
/**
|
||||
|
@ -24,10 +24,6 @@ class StorySlateView @JvmOverloads constructor(
|
|||
attrs: AttributeSet? = null
|
||||
) : FrameLayout(context, attrs) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(StorySlateView::class.java)
|
||||
}
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
var state: State = State.HIDDEN
|
||||
|
@ -43,10 +39,10 @@ class StorySlateView @JvmOverloads constructor(
|
|||
private val loadingSpinner: View = findViewById(R.id.loading_spinner)
|
||||
private val errorCircle: View = findViewById(R.id.error_circle)
|
||||
private val errorBackground: View = findViewById(R.id.stories_error_background)
|
||||
private val unavailableText: View = findViewById(R.id.unavailable)
|
||||
private val unavailableText: TextView = findViewById(R.id.unavailable)
|
||||
private val errorText: TextView = findViewById(R.id.error_text)
|
||||
|
||||
fun moveToState(state: State, postId: Long) {
|
||||
fun moveToState(state: State, postId: Long, sender: Recipient? = null) {
|
||||
if (this.state == state && this.postId == postId) {
|
||||
return
|
||||
}
|
||||
|
@ -61,7 +57,7 @@ class StorySlateView @JvmOverloads constructor(
|
|||
State.LOADING -> moveToProgressState(State.LOADING)
|
||||
State.ERROR -> moveToErrorState()
|
||||
State.RETRY -> moveToProgressState(State.RETRY)
|
||||
State.NOT_FOUND -> moveToNotFoundState()
|
||||
State.NOT_FOUND, State.FAILED -> moveToNotFoundState(state, sender)
|
||||
State.HIDDEN -> moveToHiddenState()
|
||||
}
|
||||
|
||||
|
@ -106,8 +102,8 @@ class StorySlateView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun moveToNotFoundState() {
|
||||
state = State.NOT_FOUND
|
||||
private fun moveToNotFoundState(state: State, sender: Recipient?) {
|
||||
this.state = state
|
||||
visible = true
|
||||
background.visible = true
|
||||
loadingSpinner.visible = false
|
||||
|
@ -115,6 +111,12 @@ class StorySlateView @JvmOverloads constructor(
|
|||
errorBackground.visible = false
|
||||
unavailableText.visible = true
|
||||
errorText.visible = false
|
||||
|
||||
if (state == State.FAILED && sender != null) {
|
||||
unavailableText.text = context.getString(R.string.StorySlateView__cant_download_story_s_will_need_to_share_it_again, sender.getShortDisplayName(context))
|
||||
} else {
|
||||
unavailableText.setText(R.string.StorySlateView__this_story_is_no_longer_available)
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveToHiddenState() {
|
||||
|
@ -155,7 +157,8 @@ class StorySlateView @JvmOverloads constructor(
|
|||
ERROR(1, true),
|
||||
RETRY(2, true),
|
||||
NOT_FOUND(3, false),
|
||||
HIDDEN(4, false);
|
||||
HIDDEN(4, false),
|
||||
FAILED(5, false);
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: Int): State {
|
||||
|
|
|
@ -736,6 +736,11 @@ class StoryViewerPageFragment :
|
|||
sharedViewModel.setContentIsReady()
|
||||
viewModel.setIsDisplayingSlate(true)
|
||||
}
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE -> {
|
||||
storySlate.moveToState(StorySlateView.State.FAILED, post.id, post.sender)
|
||||
sharedViewModel.setContentIsReady()
|
||||
viewModel.setIsDisplayingSlate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M2.844,6.849C2.829,6.967 2.817,7.088 2.807,7.212C2.75,7.909 2.75,8.775 2.75,9.867V14.133C2.75,15.225 2.75,16.091 2.807,16.788C2.865,17.502 2.987,18.105 3.268,18.656C3.723,19.55 4.45,20.277 5.344,20.732C5.895,21.013 6.498,21.135 7.212,21.193C7.909,21.25 8.775,21.25 9.867,21.25H14.133C15.225,21.25 16.091,21.25 16.788,21.193C16.912,21.183 17.033,21.171 17.151,21.156L15.735,19.74C15.277,19.75 14.741,19.75 14.1,19.75H9.9C8.768,19.75 7.963,19.749 7.334,19.698C6.713,19.647 6.329,19.551 6.025,19.396C5.413,19.084 4.916,18.587 4.604,17.976C4.449,17.671 4.353,17.287 4.302,16.666C4.291,16.528 4.282,16.381 4.275,16.224L7.297,12.984C7.659,12.596 8.207,12.492 8.668,12.673L7.253,11.258C6.865,11.402 6.503,11.636 6.2,11.961L4.25,14.052V9.9C4.25,9.259 4.25,8.723 4.26,8.265L2.844,6.849ZM13.202,11.197L14.251,10.071C15.338,8.903 17.188,8.903 18.276,10.071L19.75,11.653V9.9C19.75,8.768 19.749,7.963 19.698,7.334C19.647,6.713 19.551,6.329 19.396,6.025C19.084,5.413 18.587,4.916 17.976,4.604C17.671,4.449 17.287,4.353 16.666,4.302C16.037,4.251 15.233,4.25 14.1,4.25H9.9C8.768,4.25 7.963,4.251 7.334,4.302C6.967,4.332 6.682,4.378 6.448,4.443L5.297,3.292C5.312,3.284 5.328,3.276 5.344,3.268C5.895,2.987 6.498,2.865 7.212,2.807C7.909,2.75 8.775,2.75 9.867,2.75H14.133C15.225,2.75 16.091,2.75 16.788,2.807C17.502,2.865 18.105,2.987 18.656,3.268C19.55,3.723 20.277,4.45 20.732,5.344C21.013,5.895 21.135,6.498 21.193,7.212C21.25,7.909 21.25,8.775 21.25,9.867V14.133C21.25,15.225 21.25,16.091 21.193,16.788C21.135,17.502 21.013,18.105 20.732,18.656C20.724,18.672 20.716,18.688 20.708,18.703L19.557,17.552C19.622,17.318 19.668,17.033 19.698,16.666C19.749,16.037 19.75,15.233 19.75,14.1V13.854L17.178,11.093C16.684,10.562 15.843,10.562 15.349,11.093L14.263,12.258L13.202,11.197Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M1.47,2.47C1.763,2.177 2.237,2.177 2.53,2.47L21.53,21.47C21.823,21.763 21.823,22.237 21.53,22.53C21.237,22.823 20.763,22.823 20.47,22.53L1.47,3.53C1.177,3.237 1.177,2.763 1.47,2.47Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M2.813,7.141C2.811,7.165 2.809,7.188 2.807,7.212C2.75,7.909 2.75,8.775 2.75,9.867V14.133C2.75,15.225 2.75,16.091 2.807,16.788C2.865,17.502 2.987,18.105 3.268,18.656C3.723,19.55 4.45,20.277 5.344,20.732C5.895,21.013 6.498,21.135 7.212,21.193C7.909,21.25 8.775,21.25 9.867,21.25H14.133C15.225,21.25 16.091,21.25 16.788,21.193C16.812,21.191 16.835,21.189 16.859,21.187L15.417,19.745C15.033,19.75 14.598,19.75 14.1,19.75H9.9C8.768,19.75 7.963,19.749 7.334,19.698C6.713,19.647 6.329,19.551 6.025,19.396C5.413,19.084 4.916,18.587 4.604,17.976C4.449,17.671 4.353,17.287 4.302,16.666C4.251,16.037 4.25,15.233 4.25,14.1V9.9C4.25,9.402 4.25,8.967 4.255,8.583L2.813,7.141ZM8.625,12.953V15.031C8.625,15.705 9.354,16.126 9.938,15.789L10.903,15.231L8.625,12.953ZM14.489,13.161L9.424,8.096C9.594,8.081 9.772,8.115 9.938,8.211L15.188,11.242C15.771,11.579 15.771,12.421 15.188,12.758L14.489,13.161ZM19.364,18.036C19.375,18.016 19.385,17.996 19.396,17.976C19.551,17.671 19.647,17.287 19.698,16.666C19.749,16.037 19.75,15.233 19.75,14.1V9.9C19.75,8.768 19.749,7.963 19.698,7.334C19.647,6.713 19.551,6.329 19.396,6.025C19.084,5.413 18.587,4.916 17.976,4.604C17.671,4.449 17.287,4.353 16.666,4.302C16.037,4.251 15.233,4.25 14.1,4.25H9.9C8.768,4.25 7.963,4.251 7.334,4.302C6.713,4.353 6.329,4.449 6.025,4.604C6.004,4.615 5.984,4.625 5.964,4.636L4.872,3.543C5.023,3.443 5.181,3.351 5.344,3.268C5.895,2.987 6.498,2.865 7.212,2.807C7.909,2.75 8.775,2.75 9.867,2.75H14.133C15.225,2.75 16.091,2.75 16.788,2.807C17.502,2.865 18.105,2.987 18.656,3.268C19.55,3.723 20.277,4.45 20.732,5.344C21.013,5.895 21.135,6.498 21.193,7.212C21.25,7.909 21.25,8.775 21.25,9.867V14.133C21.25,15.225 21.25,16.091 21.193,16.788C21.135,17.502 21.013,18.105 20.732,18.656C20.649,18.819 20.557,18.977 20.457,19.128L19.364,18.036Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M1.47,2.97C1.763,2.677 2.237,2.677 2.53,2.97L21.03,21.47C21.323,21.763 21.323,22.237 21.03,22.53C20.737,22.823 20.263,22.823 19.97,22.53L1.47,4.03C1.177,3.737 1.177,3.263 1.47,2.97Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:viewBindingIgnore="true"
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
tools:parentTag="android.widget.FrameLayout"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail_blurhash"
|
||||
|
@ -11,9 +11,9 @@
|
|||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="false"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:longClickable="false"
|
||||
android:scaleType="fitCenter"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description" />
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail_image"
|
||||
|
@ -21,26 +21,37 @@
|
|||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="false"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:longClickable="false"
|
||||
android:scaleType="fitCenter"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description" />
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail_caption_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/ThumbnailView_Has_a_caption_description"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_caption_28"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="@string/ThumbnailView_Has_a_caption_description"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/thumbnail_error"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:longClickable="false"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_error_outline_24"
|
||||
app:tint="@color/signal_colorOnSurface"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/play_overlay"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/circle_white"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_white"
|
||||
android:longClickable="false"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
|
|
@ -297,6 +297,18 @@
|
|||
<string name="ConversationItem_pending">  Pending</string>
|
||||
<string name="ConversationItem_this_message_was_deleted">This message was deleted.</string>
|
||||
<string name="ConversationItem_you_deleted_this_message">You deleted this message.</string>
|
||||
<!-- Dialog error message shown when user can't download a message from someone else due to a permanent failure (e.g., unable to decrypt), placeholder is other's name -->
|
||||
<string name="ConversationItem_cant_download_message_s_will_need_to_send_it_again">Can\'t download message. %1$s will need to send it again.</string>
|
||||
<!-- Dialog error message shown when user can't download an image message from someone else due to a permanent failure (e.g., unable to decrypt), placeholder is other's name -->
|
||||
<string name="ConversationItem_cant_download_image_s_will_need_to_send_it_again">Can\'t download image. %1$s will need to send it again.</string>
|
||||
<!-- Dialog error message shown when user can't download a video message from someone else due to a permanent failure (e.g., unable to decrypt), placeholder is other's name -->
|
||||
<string name="ConversationItem_cant_download_video_s_will_need_to_send_it_again">Can\'t download video. %1$s will need to send it again.</string>
|
||||
<!-- Dialog error message shown when user can't download a their own message via a linked device due to a permanent failure (e.g., unable to decrypt) -->
|
||||
<string name="ConversationItem_cant_download_message_you_will_need_to_send_it_again">Can\'t download message. You will need to send it again.</string>
|
||||
<!-- Dialog error message shown when user can't download a their own image message via a linked device due to a permanent failure (e.g., unable to decrypt) -->
|
||||
<string name="ConversationItem_cant_download_image_you_will_need_to_send_it_again">Can\'t download image. You will need to send it again.</string>
|
||||
<!-- Dialog error message shown when user can't download a their own video message via a linked device due to a permanent failure (e.g., unable to decrypt) -->
|
||||
<string name="ConversationItem_cant_download_video_you_will_need_to_send_it_again">Can\'t download video. You will need to send it again.</string>
|
||||
|
||||
<!-- ConversationActivity -->
|
||||
<string name="ConversationActivity_add_attachment">Add attachment</string>
|
||||
|
@ -5093,6 +5105,8 @@
|
|||
<string name="StoryDirectReplyDialogFragment__sending_reply">Sending reply…</string>
|
||||
<!-- Displayed in the viewer when a story is no longer available -->
|
||||
<string name="StorySlateView__this_story_is_no_longer_available">This story is no longer available.</string>
|
||||
<!-- Displayed in the viewer when a story has permanently failed to download. -->
|
||||
<string name="StorySlateView__cant_download_story_s_will_need_to_share_it_again">Can\'t download story. %1$s will need to share it again.</string>
|
||||
<!-- Displayed in the viewer when the network is not available -->
|
||||
<string name="StorySlateView__no_internet_connection">No Internet Connection</string>
|
||||
<!-- Displayed in the viewer when network is available but content could not be downloaded -->
|
||||
|
|
Ładowanie…
Reference in New Issue