Add support for borderless images.

Added support for 'borderless' images. Basically images that we'd like to render 
as if they were stickers, even though they're not stickers. On iOS, this will be 
stuff like memoji and bitmoji. On Android, in my initial pass, I've just added 
support for Giphy stickers. However, we can also detect bitmoji and keyboard 
stickers in the future. This is kind of a 'best effort' thing, so as long as we 
support receiving, we can just add sending support for more things as we go.
This commit is contained in:
Greyson Parrelli
2020-07-06 14:13:08 -07:00
parent 1e250ee95c
commit 545ba80697
55 changed files with 348 additions and 150 deletions

View File

@@ -85,6 +85,7 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
height,
data.length,
0,
false,
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
Optional.absent(),
Optional.absent()));

View File

@@ -49,7 +49,7 @@ public final class ImageEditorModelRenderMediaTransform implements MediaTransfor
.withMimeType(MediaUtil.IMAGE_JPEG)
.createForSingleSessionOnDisk(context);
return new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), outputStream.size(), 0, media.getBucketId(), media.getCaption(), Optional.absent());
return new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), outputStream.size(), 0, false, media.getBucketId(), media.getCaption(), Optional.absent());
} catch (IOException e) {
Log.w(TAG, "Failed to render image. Using base image.");
return media;

View File

@@ -19,13 +19,14 @@ public class Media implements Parcelable {
public static final String ALL_MEDIA_BUCKET_ID = "org.thoughtcrime.securesms.ALL_MEDIA";
private final Uri uri;
private final String mimeType;
private final long date;
private final int width;
private final int height;
private final long size;
private final long duration;
private final Uri uri;
private final String mimeType;
private final long date;
private final int width;
private final int height;
private final long size;
private final long duration;
private final boolean borderless;
private Optional<String> bucketId;
private Optional<String> caption;
@@ -38,6 +39,7 @@ public class Media implements Parcelable {
int height,
long size,
long duration,
boolean borderless,
Optional<String> bucketId,
Optional<String> caption,
Optional<AttachmentDatabase.TransformProperties> transformProperties)
@@ -49,21 +51,23 @@ public class Media implements Parcelable {
this.height = height;
this.size = size;
this.duration = duration;
this.borderless = borderless;
this.bucketId = bucketId;
this.caption = caption;
this.transformProperties = transformProperties;
}
protected Media(Parcel in) {
uri = in.readParcelable(Uri.class.getClassLoader());
mimeType = in.readString();
date = in.readLong();
width = in.readInt();
height = in.readInt();
size = in.readLong();
duration = in.readLong();
bucketId = Optional.fromNullable(in.readString());
caption = Optional.fromNullable(in.readString());
uri = in.readParcelable(Uri.class.getClassLoader());
mimeType = in.readString();
date = in.readLong();
width = in.readInt();
height = in.readInt();
size = in.readLong();
duration = in.readLong();
borderless = in.readInt() == 1;
bucketId = Optional.fromNullable(in.readString());
caption = Optional.fromNullable(in.readString());
try {
String json = in.readString();
transformProperties = json == null ? Optional.absent() : Optional.fromNullable(JsonUtil.fromJson(json, AttachmentDatabase.TransformProperties.class));
@@ -100,6 +104,10 @@ public class Media implements Parcelable {
return duration;
}
public boolean isBorderless() {
return borderless;
}
public Optional<String> getBucketId() {
return bucketId;
}
@@ -130,6 +138,7 @@ public class Media implements Parcelable {
dest.writeInt(height);
dest.writeLong(size);
dest.writeLong(duration);
dest.writeInt(borderless ? 1 : 0);
dest.writeString(bucketId.orNull());
dest.writeString(caption.orNull());
dest.writeString(transformProperties.transform(JsonUtil::toJson).orNull());

View File

@@ -217,7 +217,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, Optional.of(bucketId), Optional.absent(), Optional.absent()));
media.add(new Media(uri, mimetype, date, width, height, size, duration, false, Optional.of(bucketId), Optional.absent(), Optional.absent()));
}
}
@@ -311,7 +311,7 @@ public class MediaRepository {
height = dimens.second;
}
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.getBucketId(), media.getCaption(), Optional.absent());
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.getBucketId(), media.getCaption(), Optional.absent());
}
private Media getContentResolverPopulatedMedia(@NonNull Context context, @NonNull Media media) throws IOException {
@@ -337,7 +337,7 @@ public class MediaRepository {
height = dimens.second;
}
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.getBucketId(), media.getCaption(), Optional.absent());
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.getBucketId(), media.getCaption(), Optional.absent());
}
private static class FolderResult {

View File

@@ -411,21 +411,20 @@ public class MediaSendActivity extends PassphraseRequiredActivity implements Med
long length = getLength.apply(data);
Uri uri = createBlobBuilder.apply(BlobProvider.getInstance(), data, length)
.withMimeType(mimeType)
.createForSingleSessionOnDisk(this);
.withMimeType(mimeType)
.createForSingleSessionOnDisk(this);
return new Media(
uri,
mimeType,
System.currentTimeMillis(),
width,
height,
length,
0,
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
Optional.absent(),
Optional.absent()
);
return new Media(uri,
mimeType,
System.currentTimeMillis(),
width,
height,
length,
0,
false,
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
Optional.absent(),
Optional.absent());
} catch (IOException e) {
return null;
}

View File

@@ -303,7 +303,7 @@ class MediaSendViewModel extends ViewModel {
captionVisible = false;
List<Media> uncaptioned = Stream.of(getSelectedMediaOrDefault())
.map(m -> new Media(m.getUri(), m.getMimeType(), m.getDate(), m.getWidth(), m.getHeight(), m.getSize(), m.getDuration(), m.getBucketId(), Optional.absent(), Optional.absent()))
.map(m -> new Media(m.getUri(), m.getMimeType(), m.getDate(), m.getWidth(), m.getHeight(), m.getSize(), m.getDuration(), m.isBorderless(), m.getBucketId(), Optional.absent(), Optional.absent()))
.toList();
selectedMedia.setValue(uncaptioned);
@@ -406,7 +406,7 @@ class MediaSendViewModel extends ViewModel {
}
void onVideoBeginEdit(@NonNull Uri uri) {
cancelUpload(new Media(uri, "", 0, 0, 0, 0, 0, Optional.absent(), Optional.absent(), Optional.absent()));
cancelUpload(new Media(uri, "", 0, 0, 0, 0, 0, false, Optional.absent(), Optional.absent(), Optional.absent()));
}
void onMediaCaptured(@NonNull Media media) {
@@ -485,7 +485,7 @@ class MediaSendViewModel extends ViewModel {
if (splitMessage.getTextSlide().isPresent()) {
Slide slide = splitMessage.getTextSlide().get();
uploadRepository.startUpload(new Media(Objects.requireNonNull(slide.getUri()), slide.getContentType(), System.currentTimeMillis(), 0, 0, slide.getFileSize(), 0, Optional.absent(), Optional.absent(), Optional.absent()), recipient);
uploadRepository.startUpload(new Media(Objects.requireNonNull(slide.getUri()), slide.getContentType(), System.currentTimeMillis(), 0, 0, slide.getFileSize(), 0, slide.isBorderless(), Optional.absent(), Optional.absent(), Optional.absent()), recipient);
}
uploadRepository.applyMediaUpdates(oldToNew, recipient);

View File

@@ -193,9 +193,9 @@ class MediaUploadRepository {
if (MediaUtil.isVideoType(media.getMimeType())) {
return new VideoSlide(context, media.getUri(), 0, media.getCaption().orNull(), media.getTransformProperties().orNull()).asAttachment();
} else if (MediaUtil.isGif(media.getMimeType())) {
return new GifSlide(context, media.getUri(), 0, media.getWidth(), media.getHeight(), media.getCaption().orNull()).asAttachment();
return new GifSlide(context, media.getUri(), 0, media.getWidth(), media.getHeight(), media.isBorderless(), media.getCaption().orNull()).asAttachment();
} else if (MediaUtil.isImageType(media.getMimeType())) {
return new ImageSlide(context, media.getUri(), 0, media.getWidth(), media.getHeight(), media.getCaption().orNull(), null).asAttachment();
return new ImageSlide(context, media.getUri(), 0, media.getWidth(), media.getHeight(), media.isBorderless(), media.getCaption().orNull(), null).asAttachment();
} else if (MediaUtil.isTextType(media.getMimeType())) {
return new TextSlide(context, media.getUri(), null, media.getSize()).asAttachment();
} else {

View File

@@ -26,6 +26,7 @@ public final class VideoTrimTransform implements MediaTransform {
media.getHeight(),
media.getSize(),
media.getDuration(),
media.isBorderless(),
media.getBucketId(),
media.getCaption(),
Optional.of(new AttachmentDatabase.TransformProperties(false, data.durationEdited, data.startTimeUs, data.endTimeUs)));