Attempt to recover from encountering octet stream media.

This commit is contained in:
Cody Henthorne
2021-06-15 11:54:14 -04:00
committed by GitHub
parent be297120a1
commit 4af078007e
7 changed files with 214 additions and 24 deletions

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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;