mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 18:00:02 +01:00
Attempt to recover from encountering octet stream media.
This commit is contained in:
@@ -179,4 +179,19 @@ public class Media implements Parcelable {
|
||||
public int hashCode() {
|
||||
return uri.hashCode();
|
||||
}
|
||||
|
||||
public static @NonNull Media withMimeType(@NonNull Media media, @NonNull String newMimeType) {
|
||||
return new Media(media.getUri(),
|
||||
newMimeType,
|
||||
media.getDate(),
|
||||
media.getWidth(),
|
||||
media.getHeight(),
|
||||
media.getSize(),
|
||||
media.getDuration(),
|
||||
media.isBorderless(),
|
||||
media.isVideoGif(),
|
||||
media.getBucketId(),
|
||||
media.getCaption(),
|
||||
media.getTransformProperties());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
@@ -35,6 +36,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles the retrieval of media present on the user's device.
|
||||
@@ -242,7 +244,7 @@ public class MediaRepository {
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.SIZE));
|
||||
long duration = !isImage ? cursor.getInt(cursor.getColumnIndexOrThrow(Video.Media.DURATION)) : 0;
|
||||
|
||||
media.add(new Media(uri, mimetype, date, width, height, size, duration, false, false, Optional.of(bucketId), Optional.absent(), Optional.absent()));
|
||||
media.add(fixMimeType(context, new Media(uri, mimetype, date, width, height, size, duration, false, false, Optional.of(bucketId), Optional.absent(), Optional.absent())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,19 +257,22 @@ public class MediaRepository {
|
||||
|
||||
@WorkerThread
|
||||
private List<Media> getPopulatedMedia(@NonNull Context context, @NonNull List<Media> media) {
|
||||
return Stream.of(media).map(m -> {
|
||||
try {
|
||||
if (isPopulated(m)) {
|
||||
return m;
|
||||
} else if (PartAuthority.isLocalUri(m.getUri())) {
|
||||
return getLocallyPopulatedMedia(context, m);
|
||||
} else {
|
||||
return getContentResolverPopulatedMedia(context, m);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return m;
|
||||
}
|
||||
}).toList();
|
||||
return media.stream()
|
||||
.map(m -> {
|
||||
try {
|
||||
if (isPopulated(m)) {
|
||||
return m;
|
||||
} else if (PartAuthority.isLocalUri(m.getUri())) {
|
||||
return getLocallyPopulatedMedia(context, m);
|
||||
} else {
|
||||
return getContentResolverPopulatedMedia(context, m);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return m;
|
||||
}
|
||||
})
|
||||
.map(m -> fixMimeType(context, m))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -361,6 +366,25 @@ public class MediaRepository {
|
||||
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.isVideoGif(), media.getBucketId(), media.getCaption(), Optional.absent());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static @NonNull Media fixMimeType(@NonNull Context context, @NonNull Media media) {
|
||||
if (MediaUtil.isOctetStream(media.getMimeType())) {
|
||||
Log.w(TAG, "Media has mimetype octet stream");
|
||||
String newMimeType = MediaUtil.getMimeType(context, media.getUri());
|
||||
if (newMimeType != null && !newMimeType.equals(media.getMimeType())) {
|
||||
Log.d(TAG, "Changing mime type to '" + newMimeType + "'");
|
||||
return Media.withMimeType(media, newMimeType);
|
||||
} else if (media.getSize() > 0 && media.getWidth() > 0 && media.getHeight() > 0) {
|
||||
boolean likelyVideo = media.getDuration() > 0;
|
||||
Log.d(TAG, "Assuming content is " + (likelyVideo ? "a video" : "an image") + ", setting mimetype");
|
||||
return Media.withMimeType(media, likelyVideo ? MediaUtil.VIDEO_UNSPECIFIED : MediaUtil.IMAGE_JPEG);
|
||||
} else {
|
||||
Log.d(TAG, "Unable to fix mimetype");
|
||||
}
|
||||
}
|
||||
return media;
|
||||
}
|
||||
|
||||
private static class FolderResult {
|
||||
private final String cameraBucketId;
|
||||
private final Uri thumbnail;
|
||||
|
||||
@@ -818,10 +818,16 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
|
||||
case ITEM_TOO_LARGE:
|
||||
Toast.makeText(this, R.string.MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ITEM_TOO_LARGE_OR_INVALID_TYPE:
|
||||
Toast.makeText(this, R.string.MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit_or_had_an_unknown_type, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ONLY_ITEM_TOO_LARGE:
|
||||
Toast.makeText(this, R.string.MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit, Toast.LENGTH_LONG).show();
|
||||
onNoMediaAvailable();
|
||||
break;
|
||||
case ONLY_ITEM_IS_INVALID_TYPE:
|
||||
Toast.makeText(this, R.string.MediaSendActivity_an_item_was_removed_because_it_had_an_unknown_type, Toast.LENGTH_LONG).show();
|
||||
onNoMediaAvailable();
|
||||
case TOO_MANY_ITEMS:
|
||||
int maxSelection = viewModel.getMaxSelection();
|
||||
Toast.makeText(this, getResources().getQuantityString(R.plurals.MediaSendActivity_cant_share_more_than_n_items, maxSelection, maxSelection), Toast.LENGTH_SHORT).show();
|
||||
|
||||
@@ -145,19 +145,23 @@ class MediaSendViewModel extends ViewModel {
|
||||
void onSelectedMediaChanged(@NonNull Context context, @NonNull List<Media> newMedia) {
|
||||
List<Media> originalMedia = getSelectedMediaOrDefault();
|
||||
|
||||
if (!newMedia.isEmpty()) {
|
||||
selectedMedia.setValue(newMedia);
|
||||
}
|
||||
|
||||
repository.getPopulatedMedia(context, newMedia, populatedMedia -> {
|
||||
ThreadUtil.runOnMain(() -> {
|
||||
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
|
||||
|
||||
if (filteredMedia.size() != newMedia.size()) {
|
||||
if (filteredMedia.isEmpty() && newMedia.size() == 1 && page == Page.UNKNOWN) {
|
||||
error.setValue(Error.ONLY_ITEM_TOO_LARGE);
|
||||
if (MediaUtil.isImageOrVideoType(newMedia.get(0).getMimeType())) {
|
||||
error.setValue(Error.ONLY_ITEM_TOO_LARGE);
|
||||
} else {
|
||||
error.setValue(Error.ONLY_ITEM_IS_INVALID_TYPE);
|
||||
}
|
||||
} else {
|
||||
error.setValue(Error.ITEM_TOO_LARGE);
|
||||
if (newMedia.stream().allMatch(m -> MediaUtil.isImageOrVideoType(m.getMimeType()))) {
|
||||
error.setValue(Error.ITEM_TOO_LARGE);
|
||||
} else {
|
||||
error.setValue(Error.ITEM_TOO_LARGE_OR_INVALID_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +203,6 @@ class MediaSendViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
void onSingleMediaSelected(@NonNull Context context, @NonNull Media media) {
|
||||
selectedMedia.setValue(Collections.singletonList(media));
|
||||
|
||||
repository.getPopulatedMedia(context, Collections.singletonList(media), populatedMedia -> {
|
||||
ThreadUtil.runOnMain(() -> {
|
||||
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
|
||||
@@ -702,7 +704,7 @@ class MediaSendViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
enum Error {
|
||||
ITEM_TOO_LARGE, TOO_MANY_ITEMS, NO_ITEMS, ONLY_ITEM_TOO_LARGE
|
||||
ITEM_TOO_LARGE, TOO_MANY_ITEMS, NO_ITEMS, ONLY_ITEM_TOO_LARGE, ONLY_ITEM_IS_INVALID_TYPE, ITEM_TOO_LARGE_OR_INVALID_TYPE
|
||||
}
|
||||
|
||||
enum Event {
|
||||
|
||||
@@ -64,6 +64,7 @@ public class MediaUtil {
|
||||
public static final String LONG_TEXT = "text/x-signal-plain";
|
||||
public static final String VIEW_ONCE = "application/x-signal-view-once";
|
||||
public static final String UNKNOWN = "*/*";
|
||||
public static final String OCTET = "application/octet-stream";
|
||||
|
||||
public static SlideType getSlideTypeFromContentType(@NonNull String contentType) {
|
||||
if (isGif(contentType)) {
|
||||
@@ -111,7 +112,7 @@ public class MediaUtil {
|
||||
}
|
||||
|
||||
String type = context.getContentResolver().getType(uri);
|
||||
if (type == null) {
|
||||
if (type == null || isOctetStream(type)) {
|
||||
final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
|
||||
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
|
||||
}
|
||||
@@ -325,6 +326,10 @@ public class MediaUtil {
|
||||
return (null != contentType) && contentType.equals(VIEW_ONCE);
|
||||
}
|
||||
|
||||
public static boolean isOctetStream(@Nullable String contentType) {
|
||||
return OCTET.equals(contentType);
|
||||
}
|
||||
|
||||
public static boolean hasVideoThumbnail(@NonNull Context context, @Nullable Uri uri) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user