From 97728617263deb0d0a0b6a79c865986a3c1cb263 Mon Sep 17 00:00:00 2001 From: Al Lansley Date: Thu, 18 Jul 2024 10:09:10 +1000 Subject: [PATCH 1/2] Fixes implemented --- .../emoji/CompositeEmojiPageModel.java | 2 +- .../securesms/conversation/v2/Util.java | 381 ----------------- .../securesms/conversation/v2/Util.kt | 388 ++++++++++++++++++ .../org/thoughtcrime/securesms/mms/Slide.java | 209 ---------- .../org/thoughtcrime/securesms/mms/Slide.kt | 185 +++++++++ 5 files changed, 574 insertions(+), 591 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/CompositeEmojiPageModel.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/CompositeEmojiPageModel.java index cc36ab31c63..59c3cfba917 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/CompositeEmojiPageModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/CompositeEmojiPageModel.java @@ -22,7 +22,7 @@ public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull List. - */ -package org.thoughtcrime.securesms.conversation.v2; - -import android.annotation.TargetApi; -import android.app.ActivityManager; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.StyleSpan; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; -import com.google.android.mms.pdu_alt.CharacterSets; -import com.google.android.mms.pdu_alt.EncodedStringValue; - -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.components.ComposeText; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import network.loki.messenger.R; - -public class Util { - private static final String TAG = Log.tag(Util.class); - - private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90); - - public static List asList(T... elements) { - List result = new LinkedList<>(); - Collections.addAll(result, elements); - return result; - } - - public static String join(String[] list, String delimiter) { - return join(Arrays.asList(list), delimiter); - } - - public static String join(Collection list, String delimiter) { - StringBuilder result = new StringBuilder(); - int i = 0; - - for (T item : list) { - result.append(item); - - if (++i < list.size()) - result.append(delimiter); - } - - return result.toString(); - } - - public static String join(long[] list, String delimeter) { - List boxed = new ArrayList<>(list.length); - - for (int i = 0; i < list.length; i++) { - boxed.add(list[i]); - } - - return join(boxed, delimeter); - } - - @SafeVarargs - public static @NonNull List join(@NonNull List... lists) { - int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size()); - List joined = new ArrayList<>(totalSize); - - for (List list : lists) { - joined.addAll(list); - } - - return joined; - } - - public static String join(List list, String delimeter) { - StringBuilder sb = new StringBuilder(); - - for (int j = 0; j < list.size(); j++) { - if (j != 0) sb.append(delimeter); - sb.append(list.get(j)); - } - - return sb.toString(); - } - - public static String rightPad(String value, int length) { - if (value.length() >= length) { - return value; - } - - StringBuilder out = new StringBuilder(value); - while (out.length() < length) { - out.append(" "); - } - - return out.toString(); - } - - public static boolean isEmpty(EncodedStringValue[] value) { - return value == null || value.length == 0; - } - - public static boolean isEmpty(ComposeText value) { - return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed()); - } - - public static boolean isEmpty(Collection collection) { - return collection == null || collection.isEmpty(); - } - - public static boolean isEmpty(@Nullable CharSequence charSequence) { - return charSequence == null || charSequence.length() == 0; - } - - public static boolean hasItems(@Nullable Collection collection) { - return collection != null && !collection.isEmpty(); - } - - public static V getOrDefault(@NonNull Map map, K key, V defaultValue) { - return map.containsKey(key) ? map.get(key) : defaultValue; - } - - public static String getFirstNonEmpty(String... values) { - for (String value : values) { - if (!Util.isEmpty(value)) { - return value; - } - } - return ""; - } - - public static @NonNull String emptyIfNull(@Nullable String value) { - return value != null ? value : ""; - } - - public static @NonNull CharSequence emptyIfNull(@Nullable CharSequence value) { - return value != null ? value : ""; - } - - public static CharSequence getBoldedString(String value) { - SpannableString spanned = new SpannableString(value); - spanned.setSpan(new StyleSpan(Typeface.BOLD), 0, - spanned.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - - return spanned; - } - - public static @NonNull String toIsoString(byte[] bytes) { - try { - return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); - } catch (UnsupportedEncodingException e) { - throw new AssertionError("ISO_8859_1 must be supported!"); - } - } - - public static byte[] toIsoBytes(String isoString) { - try { - return isoString.getBytes(CharacterSets.MIMENAME_ISO_8859_1); - } catch (UnsupportedEncodingException e) { - throw new AssertionError("ISO_8859_1 must be supported!"); - } - } - - public static byte[] toUtf8Bytes(String utf8String) { - try { - return utf8String.getBytes(CharacterSets.MIMENAME_UTF_8); - } catch (UnsupportedEncodingException e) { - throw new AssertionError("UTF_8 must be supported!"); - } - } - - public static void wait(Object lock, long timeout) { - try { - lock.wait(timeout); - } catch (InterruptedException ie) { - throw new AssertionError(ie); - } - } - - public static List split(String source, String delimiter) { - List results = new LinkedList<>(); - - if (TextUtils.isEmpty(source)) { - return results; - } - - String[] elements = source.split(delimiter); - Collections.addAll(results, elements); - - return results; - } - - public static byte[][] split(byte[] input, int firstLength, int secondLength) { - byte[][] parts = new byte[2][]; - - parts[0] = new byte[firstLength]; - System.arraycopy(input, 0, parts[0], 0, firstLength); - - parts[1] = new byte[secondLength]; - System.arraycopy(input, firstLength, parts[1], 0, secondLength); - - return parts; - } - - public static byte[] combine(byte[]... elements) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - for (byte[] element : elements) { - baos.write(element); - } - - return baos.toByteArray(); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - public static byte[] trim(byte[] input, int length) { - byte[] result = new byte[length]; - System.arraycopy(input, 0, result, 0, result.length); - - return result; - } - - public static byte[] getSecretBytes(int size) { - return getSecretBytes(new SecureRandom(), size); - } - - public static byte[] getSecretBytes(@NonNull SecureRandom secureRandom, int size) { - byte[] secret = new byte[size]; - secureRandom.nextBytes(secret); - return secret; - } - - public static T getRandomElement(T[] elements) { - return elements[new SecureRandom().nextInt(elements.length)]; - } - - public static T getRandomElement(List elements) { - return elements.get(new SecureRandom().nextInt(elements.size())); - } - - public static boolean equals(@Nullable Object a, @Nullable Object b) { - return a == b || (a != null && a.equals(b)); - } - - public static int hashCode(@Nullable Object... objects) { - return Arrays.hashCode(objects); - } - - public static @Nullable Uri uri(@Nullable String uri) { - if (uri == null) return null; - else return Uri.parse(uri); - } - - @TargetApi(VERSION_CODES.KITKAT) - public static boolean isLowMemory(Context context) { - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) || - activityManager.getLargeMemoryClass() <= 64; - } - - public static int clamp(int value, int min, int max) { - return Math.min(Math.max(value, min), max); - } - - public static long clamp(long value, long min, long max) { - return Math.min(Math.max(value, min), max); - } - - public static float clamp(float value, float min, float max) { - return Math.min(Math.max(value, min), max); - } - - /** - * Returns half of the difference between the given length, and the length when scaled by the - * given scale. - */ - public static float halfOffsetFromScale(int length, float scale) { - float scaledLength = length * scale; - return (length - scaledLength) / 2; - } - - public static @Nullable String readTextFromClipboard(@NonNull Context context) { - { - ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); - - if (clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClip().getItemCount() > 0) { - return clipboardManager.getPrimaryClip().getItemAt(0).getText().toString(); - } else { - return null; - } - } - } - - public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) { - writeTextToClipboard(context, context.getString(R.string.app_name), text); - } - - public static void writeTextToClipboard(@NonNull Context context, @NonNull String label, @NonNull String text) { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(label, text); - clipboard.setPrimaryClip(clip); - } - - public static int toIntExact(long value) { - if ((int)value != value) { - throw new ArithmeticException("integer overflow"); - } - return (int)value; - } - - public static boolean isEquals(@Nullable Long first, long second) { - return first != null && first == second; - } - - @SafeVarargs - public static List concatenatedList(Collection ... items) { - final List concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size())); - - for (Collection list : items) { - concat.addAll(list); - } - - return concat; - } - - public static boolean isLong(String value) { - try { - Long.parseLong(value); - return true; - } catch (NumberFormatException e) { - return false; - } - } - - public static int parseInt(String integer, int defaultValue) { - try { - return Integer.parseInt(integer); - } catch (NumberFormatException e) { - return defaultValue; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt new file mode 100644 index 00000000000..13db1430e9b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.conversation.v2 + +import android.annotation.TargetApi +import android.app.ActivityManager +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.graphics.Typeface +import android.net.Uri +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.text.Spannable +import android.text.SpannableString +import android.text.TextUtils +import android.text.style.StyleSpan +import android.view.View +import androidx.core.text.layoutDirection +import com.annimon.stream.Stream +import com.google.android.mms.pdu_alt.CharacterSets +import com.google.android.mms.pdu_alt.EncodedStringValue +import network.loki.messenger.R +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.components.ComposeText +import org.webrtc.ContextUtils.getApplicationContext +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.UnsupportedEncodingException +import java.security.SecureRandom +import java.util.Arrays +import java.util.Collections +import java.util.LinkedList +import java.util.concurrent.TimeUnit +import kotlin.math.max +import kotlin.math.min + +object Util { + private val TAG: String = Log.tag(Util::class.java) + + private val BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90) + + fun asList(vararg elements: T): List { + val result = mutableListOf() // LinkedList() + Collections.addAll(result, *elements) + return result + } + + fun join(list: Array, delimiter: String?): String { + return join(Arrays.asList(*list), delimiter) + } + + fun join(list: Collection, delimiter: String?): String { + val result = StringBuilder() + var i = 0 + + for (item in list) { + result.append(item) + + if (++i < list.size) result.append(delimiter) + } + + return result.toString() + } + + fun join(list: LongArray, delimeter: String?): String { + val boxed: MutableList = ArrayList(list.size) + + for (i in list.indices) { + boxed.add(list[i]) + } + + return join(boxed, delimeter) + } + + @SafeVarargs + fun join(vararg lists: List): List { + val totalSize = Stream.of(*lists).reduce(0) { sum: Int, list: List -> sum + list.size } + val joined: MutableList = ArrayList(totalSize) + + for (list in lists) { + joined.addAll(list) + } + + return joined + } + + fun join(list: List, delimeter: String?): String { + val sb = StringBuilder() + + for (j in list.indices) { + if (j != 0) sb.append(delimeter) + sb.append(list[j]) + } + + return sb.toString() + } + + fun rightPad(value: String, length: Int): String { + if (value.length >= length) { + return value + } + + val out = StringBuilder(value) + while (out.length < length) { + out.append(" ") + } + + return out.toString() + } + + fun isEmpty(value: Array?): Boolean { + return value == null || value.size == 0 + } + + fun isEmpty(value: ComposeText?): Boolean { + return value == null || value.text == null || TextUtils.isEmpty(value.textTrimmed) + } + + fun isEmpty(collection: Collection<*>?): Boolean { + return collection == null || collection.isEmpty() + } + + fun isEmpty(charSequence: CharSequence?): Boolean { + return charSequence == null || charSequence.length == 0 + } + +// fun hasItems(collection: Collection<*>?): Boolean { +// return collection != null && !collection.isEmpty() +// } + + fun getOrDefault(map: Map, key: K, defaultValue: V): V? { + return if (map.containsKey(key)) map[key] else defaultValue + } + + fun getFirstNonEmpty(vararg values: String?): String { + for (value in values) { + if (!value.isNullOrEmpty()) { return value } + } + return "" + } + + fun emptyIfNull(value: String?): String { + return value ?: "" + } + + fun emptyIfNull(value: CharSequence?): CharSequence { + return value ?: "" + } + + fun getBoldedString(value: String?): CharSequence { + val spanned = SpannableString(value) + spanned.setSpan( + StyleSpan(Typeface.BOLD), 0, + spanned.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + return spanned + } + + fun toIsoString(bytes: ByteArray?): String { + try { + return String(bytes!!, charset(CharacterSets.MIMENAME_ISO_8859_1)) + } catch (e: UnsupportedEncodingException) { + throw AssertionError("ISO_8859_1 must be supported!") + } + } + + fun toIsoBytes(isoString: String): ByteArray { + try { + return isoString.toByteArray(charset(CharacterSets.MIMENAME_ISO_8859_1)) + } catch (e: UnsupportedEncodingException) { + throw AssertionError("ISO_8859_1 must be supported!") + } + } + + fun toUtf8Bytes(utf8String: String): ByteArray { + try { + return utf8String.toByteArray(charset(CharacterSets.MIMENAME_UTF_8)) + } catch (e: UnsupportedEncodingException) { + throw AssertionError("UTF_8 must be supported!") + } + } + + fun wait(lock: Any, timeout: Long) { + try { + (lock as Object).wait(timeout) + } catch (ie: InterruptedException) { + throw AssertionError(ie) + } + } + + fun split(source: String, delimiter: String): List { + val results = mutableListOf() + + if (TextUtils.isEmpty(source)) { + return results + } + + val elements = + source.split(delimiter.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + Collections.addAll(results, *elements) + + return results + } + + fun split(input: ByteArray?, firstLength: Int, secondLength: Int): Array { + val parts = arrayOfNulls(2) + + parts[0] = ByteArray(firstLength) + System.arraycopy(input, 0, parts[0], 0, firstLength) + + parts[1] = ByteArray(secondLength) + System.arraycopy(input, firstLength, parts[1], 0, secondLength) + + return parts + } + + fun combine(vararg elements: ByteArray?): ByteArray { + try { + val baos = ByteArrayOutputStream() + + for (element in elements) { + baos.write(element) + } + + return baos.toByteArray() + } catch (e: IOException) { + throw AssertionError(e) + } + } + + fun trim(input: ByteArray?, length: Int): ByteArray { + val result = ByteArray(length) + System.arraycopy(input, 0, result, 0, result.size) + + return result + } + + fun getSecretBytes(size: Int): ByteArray { + return getSecretBytes(SecureRandom(), size) + } + + fun getSecretBytes(secureRandom: SecureRandom, size: Int): ByteArray { + val secret = ByteArray(size) + secureRandom.nextBytes(secret) + return secret + } + + fun getRandomElement(elements: Array): T { + return elements[SecureRandom().nextInt(elements.size)] + } + + fun getRandomElement(elements: List): T { + return elements[SecureRandom().nextInt(elements.size)] + } + + fun equals(a: Any?, b: Any?): Boolean { + return a === b || (a != null && a == b) + } + + fun hashCode(vararg objects: Any?): Int { + return objects.contentHashCode() + } + + fun uri(uri: String?): Uri? { + return if (uri == null) null + else Uri.parse(uri) + } + + @TargetApi(VERSION_CODES.KITKAT) + fun isLowMemory(context: Context): Boolean { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + + return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice) || + activityManager.largeMemoryClass <= 64 + } + + fun clamp(value: Int, min: Int, max: Int): Int { + return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toInt() + } + + fun clamp(value: Long, min: Long, max: Long): Long { + return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toLong() + } + + fun clamp(value: Float, min: Float, max: Float): Float { + return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toFloat() + } + + /** + * Returns half of the difference between the given length, and the length when scaled by the + * given scale. + */ + fun halfOffsetFromScale(length: Int, scale: Float): Float { + val scaledLength = length * scale + return (length - scaledLength) / 2 + } + + fun readTextFromClipboard(context: Context): String? { + run { + val clipboardManager = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + return if (clipboardManager.hasPrimaryClip() && clipboardManager.primaryClip!!.itemCount > 0) { + clipboardManager.primaryClip!!.getItemAt(0).text.toString() + } else { + null + } + } + } + + fun writeTextToClipboard(context: Context, text: String) { + writeTextToClipboard(context, context.getString(R.string.app_name), text) + } + + fun writeTextToClipboard(context: Context, label: String, text: String) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(label, text) + clipboard.setPrimaryClip(clip) + } + + fun toIntExact(value: Long): Int { + if (value.toInt().toLong() != value) { + throw ArithmeticException("integer overflow") + } + return value.toInt() + } + + fun isEquals(first: Long?, second: Long): Boolean { + return first != null && first == second + } + + @SafeVarargs + fun concatenatedList(vararg items: Collection): List { + val concat: MutableList = ArrayList( + Stream.of(*items).reduce(0) { sum: Int, list: Collection -> sum + list.size }) + + for (list in items) { + concat.addAll(list) + } + + return concat + } + + fun isLong(value: String): Boolean { + try { + value.toLong() + return true + } catch (e: NumberFormatException) { + return false + } + } + + fun parseInt(integer: String, defaultValue: Int): Int { + return try { + integer.toInt() + } catch (e: NumberFormatException) { + defaultValue + } + } + + // Method to determine if we're currently in a left-to-right or right-to-left language like Arabic + fun usingRightToLeftLanguage(context: Context): Boolean { + val config = context.resources.configuration + return config.layoutDirection == View.LAYOUT_DIRECTION_RTL + } + + // Method to determine if we're currently in a left-to-right or right-to-left language like Arabic + fun usingLeftToRightLanguage(context: Context): Boolean { + val config = context.resources.configuration + return config.layoutDirection == View.LAYOUT_DIRECTION_LTR + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java deleted file mode 100644 index 2c92121cf23..00000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java +++ /dev/null @@ -1,209 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.mms; - -import static org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY; - -import android.content.Context; -import android.content.res.Resources.Theme; -import android.net.Uri; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.squareup.phrase.Phrase; -import java.security.SecureRandom; -import network.loki.messenger.R; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; -import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment; -import org.session.libsession.utilities.Util; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.util.MediaUtil; - -public abstract class Slide { - - protected final Attachment attachment; - protected final Context context; - - public Slide(@NonNull Context context, @NonNull Attachment attachment) { - this.context = context; - this.attachment = attachment; - } - - public String getContentType() { - return attachment.getContentType(); - } - - @Nullable - public Uri getUri() { - return attachment.getDataUri(); - } - - @Nullable - public Uri getThumbnailUri() { - return attachment.getThumbnailUri(); - } - - @NonNull - public Optional getBody() { - String attachmentString = context.getString(R.string.attachment); - - if (MediaUtil.isAudio(attachment)) { - // A missing file name is the legacy way to determine if an audio attachment is - // a voice note vs. other arbitrary audio attachments. - if (attachment.isVoiceNote() || attachment.getFileName() == null || - attachment.getFileName().isEmpty()) { - attachmentString = context.getString(R.string.attachment_type_voice_message); - return Optional.fromNullable("🎤 " + attachmentString); - } - } - String txt = Phrase.from(context, R.string.attachmentsNotification) - .put(EMOJI_KEY, emojiForMimeType()) - .format().toString(); - return Optional.fromNullable(txt); - } - - private String emojiForMimeType() { - if (MediaUtil.isImage(attachment)) { - return "📷"; - } else if (MediaUtil.isVideo(attachment)) { - return "🎥"; - } else if (MediaUtil.isAudio(attachment)) { - return "🎧"; - } else if (MediaUtil.isFile(attachment)) { - return "📎"; - } else { - return "🎡"; // `isGif` - } - } - - @NonNull - public Optional getCaption() { - return Optional.fromNullable(attachment.getCaption()); - } - - @NonNull - public Optional getFileName() { - return Optional.fromNullable(attachment.getFileName()); - } - - @Nullable - public String getFastPreflightId() { - return attachment.getFastPreflightId(); - } - - public long getFileSize() { - return attachment.getSize(); - } - - public boolean hasImage() { - return false; - } - - public boolean hasVideo() { - return false; - } - - public boolean hasAudio() { - return false; - } - - public boolean hasDocument() { - return false; - } - - public @NonNull String getContentDescription() { return ""; } - - public @NonNull Attachment asAttachment() { - return attachment; - } - - public boolean isInProgress() { - return attachment.isInProgress(); - } - - public boolean isPendingDownload() { - return getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED || - getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING; - } - - public int getTransferState() { - return attachment.getTransferState(); - } - - public @DrawableRes int getPlaceholderRes(Theme theme) { - throw new AssertionError("getPlaceholderRes() called for non-drawable slide"); - } - - public boolean hasPlaceholder() { - return false; - } - - public boolean hasPlayOverlay() { - return false; - } - - protected static Attachment constructAttachmentFromUri(@NonNull Context context, - @NonNull Uri uri, - @NonNull String defaultMime, - long size, - int width, - int height, - boolean hasThumbnail, - @Nullable String fileName, - @Nullable String caption, - boolean voiceNote, - boolean quote) - { - String resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime); - String fastPreflightId = String.valueOf(new SecureRandom().nextLong()); - return new UriAttachment(uri, - hasThumbnail ? uri : null, - resolvedType, - AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED, - size, - width, - height, - fileName, - fastPreflightId, - voiceNote, - quote, - caption); - } - - @Override - public boolean equals(Object other) { - if (other == null) return false; - if (!(other instanceof Slide)) return false; - - Slide that = (Slide)other; - - return Util.equals(this.getContentType(), that.getContentType()) && - this.hasAudio() == that.hasAudio() && - this.hasImage() == that.hasImage() && - this.hasVideo() == that.hasVideo() && - this.getTransferState() == that.getTransferState() && - Util.equals(this.getUri(), that.getUri()) && - Util.equals(this.getThumbnailUri(), that.getThumbnailUri()); - } - - @Override - public int hashCode() { - return Util.hashCode(getContentType(), hasAudio(), hasImage(), - hasVideo(), getUri(), getThumbnailUri(), getTransferState()); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt new file mode 100644 index 00000000000..21ca54ccee5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt @@ -0,0 +1,185 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see //www.gnu.org/licenses/>. + */ +package org.thoughtcrime.securesms.mms + +import android.content.Context +import android.content.res.Resources +import android.net.Uri +import androidx.annotation.DrawableRes +import com.squareup.phrase.Phrase +import java.security.SecureRandom +import network.loki.messenger.R +import org.session.libsession.messaging.sending_receiving.attachments.Attachment +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress +import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment +import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY +import org.session.libsession.utilities.Util.equals +import org.session.libsession.utilities.Util.hashCode +import org.session.libsignal.utilities.guava.Optional +import org.thoughtcrime.securesms.conversation.v2.Util +import org.thoughtcrime.securesms.util.MediaUtil + +abstract class Slide(@JvmField protected val context: Context, protected val attachment: Attachment) { + val contentType: String + get() = attachment.contentType + + val uri: Uri? + get() = attachment.dataUri + + open val thumbnailUri: Uri? + get() = attachment.thumbnailUri + + val body: Optional + get() { + //var attachmentString = context.getString(R.string.attachment) + val c = context + + if (MediaUtil.isAudio(attachment)) { + // A missing file name is the legacy way to determine if an audio attachment is + // a voice note vs. other arbitrary audio attachments. + if (attachment.isVoiceNote || attachment.fileName.isNullOrEmpty()) { + val baseString = context.getString(R.string.attachment_type_voice_message) + + // Note: 🎙🎙 + val languageIsLTR = Util.usingLeftToRightLanguage(context) + val attachmentString = if (languageIsLTR) { + "🎙 $baseString" + } else { + "$baseString 🎙" + } + return Optional.fromNullable(attachmentString) + } + } + val txt = Phrase.from(context, R.string.attachmentsNotification) + .put(EMOJI_KEY, emojiForMimeType()) + .format().toString() + return Optional.fromNullable(txt) + } + + private fun emojiForMimeType(): String { + return if (MediaUtil.isGif(attachment)) { + "🎡" + } else if (MediaUtil.isImage(attachment)) { + "📷" + } else if (MediaUtil.isVideo(attachment)) { + "🎥" + } else if (MediaUtil.isAudio(attachment)) { + "🎧" + } else if (MediaUtil.isFile(attachment)) { + "📎" + } else { + // We don't provide emojis for other mime-types such as VCARD + "" + } + } + + val caption: Optional + get() = Optional.fromNullable(attachment.caption) + + val fileName: Optional + get() = Optional.fromNullable(attachment.fileName) + + val fastPreflightId: String? + get() = attachment.fastPreflightId + + val fileSize: Long + get() = attachment.size + + open fun hasImage(): Boolean { return false } + + open fun hasVideo(): Boolean { return false } + + open fun hasAudio(): Boolean { return false } + + open fun hasDocument(): Boolean { return false } + + open val contentDescription: String + get() = "" + + fun asAttachment(): Attachment { return attachment } + + val isInProgress: Boolean + get() = attachment.isInProgress + + val isPendingDownload: Boolean + get() = transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED || + transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING + + val transferState: Int + get() = attachment.transferState + + @DrawableRes + open fun getPlaceholderRes(theme: Resources.Theme?): Int { + throw AssertionError("getPlaceholderRes() called for non-drawable slide") + } + + open fun hasPlaceholder(): Boolean { return false } + + open fun hasPlayOverlay(): Boolean { return false } + + override fun equals(other: Any?): Boolean { + if (other == null) return false + if (other !is Slide) return false + + return (equals(this.contentType, other.contentType) && + hasAudio() == other.hasAudio() && + hasImage() == other.hasImage() && + hasVideo() == other.hasVideo()) && + this.transferState == other.transferState && + equals(this.uri, other.uri) && + equals(this.thumbnailUri, other.thumbnailUri) + } + + override fun hashCode(): Int { + return hashCode(contentType, hasAudio(), hasImage(), hasVideo(), uri, thumbnailUri, transferState) + } + + companion object { + @JvmStatic + protected fun constructAttachmentFromUri( + context: Context, + uri: Uri, + defaultMime: String, + size: Long, + width: Int, + height: Int, + hasThumbnail: Boolean, + fileName: String?, + caption: String?, + voiceNote: Boolean, + quote: Boolean + ): Attachment { + val resolvedType = + Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime) + val fastPreflightId = SecureRandom().nextLong().toString() + return UriAttachment( + uri, + if (hasThumbnail) uri else null, + resolvedType!!, + AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED, + size, + width, + height, + fileName, + fastPreflightId, + voiceNote, + quote, + caption + ) + } + } +} From 0a4c4b256dad4ee960ac87591dcc2e7963c17cad Mon Sep 17 00:00:00 2001 From: Al Lansley Date: Thu, 18 Jul 2024 11:15:50 +1000 Subject: [PATCH 2/2] Undid Java to Kotlin class conversion --- .../securesms/conversation/v2/Util.java | 396 ++++++++++++++++++ .../securesms/conversation/v2/Util.kt | 388 ----------------- .../org/thoughtcrime/securesms/mms/Slide.java | 213 ++++++++++ .../org/thoughtcrime/securesms/mms/Slide.kt | 185 -------- 4 files changed, 609 insertions(+), 573 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.java new file mode 100644 index 00000000000..ff4f72a2e19 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.conversation.v2; + +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.StyleSpan; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.annimon.stream.Stream; +import com.google.android.mms.pdu_alt.CharacterSets; +import com.google.android.mms.pdu_alt.EncodedStringValue; + +import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.components.ComposeText; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import network.loki.messenger.R; + +public class Util { + private static final String TAG = Log.tag(Util.class); + + private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90); + + public static List asList(T... elements) { + List result = new LinkedList<>(); + Collections.addAll(result, elements); + return result; + } + + public static String join(String[] list, String delimiter) { + return join(Arrays.asList(list), delimiter); + } + + public static String join(Collection list, String delimiter) { + StringBuilder result = new StringBuilder(); + int i = 0; + + for (T item : list) { + result.append(item); + + if (++i < list.size()) + result.append(delimiter); + } + + return result.toString(); + } + + public static String join(long[] list, String delimeter) { + List boxed = new ArrayList<>(list.length); + + for (int i = 0; i < list.length; i++) { + boxed.add(list[i]); + } + + return join(boxed, delimeter); + } + + @SafeVarargs + public static @NonNull List join(@NonNull List... lists) { + int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size()); + List joined = new ArrayList<>(totalSize); + + for (List list : lists) { + joined.addAll(list); + } + + return joined; + } + + public static String join(List list, String delimeter) { + StringBuilder sb = new StringBuilder(); + + for (int j = 0; j < list.size(); j++) { + if (j != 0) sb.append(delimeter); + sb.append(list.get(j)); + } + + return sb.toString(); + } + + public static String rightPad(String value, int length) { + if (value.length() >= length) { + return value; + } + + StringBuilder out = new StringBuilder(value); + while (out.length() < length) { + out.append(" "); + } + + return out.toString(); + } + + public static boolean isEmpty(EncodedStringValue[] value) { + return value == null || value.length == 0; + } + + public static boolean isEmpty(ComposeText value) { + return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed()); + } + + public static boolean isEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } + + public static boolean isEmpty(@Nullable CharSequence charSequence) { + return charSequence == null || charSequence.length() == 0; + } + + public static boolean hasItems(@Nullable Collection collection) { + return collection != null && !collection.isEmpty(); + } + + public static V getOrDefault(@NonNull Map map, K key, V defaultValue) { + return map.containsKey(key) ? map.get(key) : defaultValue; + } + + public static String getFirstNonEmpty(String... values) { + for (String value : values) { + if (!Util.isEmpty(value)) { + return value; + } + } + return ""; + } + + public static @NonNull String emptyIfNull(@Nullable String value) { + return value != null ? value : ""; + } + + public static @NonNull CharSequence emptyIfNull(@Nullable CharSequence value) { + return value != null ? value : ""; + } + + public static CharSequence getBoldedString(String value) { + SpannableString spanned = new SpannableString(value); + spanned.setSpan(new StyleSpan(Typeface.BOLD), 0, + spanned.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + return spanned; + } + + public static @NonNull String toIsoString(byte[] bytes) { + try { + return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("ISO_8859_1 must be supported!"); + } + } + + public static byte[] toIsoBytes(String isoString) { + try { + return isoString.getBytes(CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("ISO_8859_1 must be supported!"); + } + } + + public static byte[] toUtf8Bytes(String utf8String) { + try { + return utf8String.getBytes(CharacterSets.MIMENAME_UTF_8); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("UTF_8 must be supported!"); + } + } + + public static void wait(Object lock, long timeout) { + try { + lock.wait(timeout); + } catch (InterruptedException ie) { + throw new AssertionError(ie); + } + } + + public static List split(String source, String delimiter) { + List results = new LinkedList<>(); + + if (TextUtils.isEmpty(source)) { + return results; + } + + String[] elements = source.split(delimiter); + Collections.addAll(results, elements); + + return results; + } + + public static byte[][] split(byte[] input, int firstLength, int secondLength) { + byte[][] parts = new byte[2][]; + + parts[0] = new byte[firstLength]; + System.arraycopy(input, 0, parts[0], 0, firstLength); + + parts[1] = new byte[secondLength]; + System.arraycopy(input, firstLength, parts[1], 0, secondLength); + + return parts; + } + + public static byte[] combine(byte[]... elements) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + for (byte[] element : elements) { + baos.write(element); + } + + return baos.toByteArray(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + public static byte[] trim(byte[] input, int length) { + byte[] result = new byte[length]; + System.arraycopy(input, 0, result, 0, result.length); + + return result; + } + + public static byte[] getSecretBytes(int size) { + return getSecretBytes(new SecureRandom(), size); + } + + public static byte[] getSecretBytes(@NonNull SecureRandom secureRandom, int size) { + byte[] secret = new byte[size]; + secureRandom.nextBytes(secret); + return secret; + } + + public static T getRandomElement(T[] elements) { + return elements[new SecureRandom().nextInt(elements.length)]; + } + + public static T getRandomElement(List elements) { + return elements.get(new SecureRandom().nextInt(elements.size())); + } + + public static boolean equals(@Nullable Object a, @Nullable Object b) { + return a == b || (a != null && a.equals(b)); + } + + public static int hashCode(@Nullable Object... objects) { + return Arrays.hashCode(objects); + } + + public static @Nullable Uri uri(@Nullable String uri) { + if (uri == null) return null; + else return Uri.parse(uri); + } + + @TargetApi(VERSION_CODES.KITKAT) + public static boolean isLowMemory(Context context) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) || + activityManager.getLargeMemoryClass() <= 64; + } + + public static int clamp(int value, int min, int max) { + return Math.min(Math.max(value, min), max); + } + + public static long clamp(long value, long min, long max) { + return Math.min(Math.max(value, min), max); + } + + public static float clamp(float value, float min, float max) { + return Math.min(Math.max(value, min), max); + } + + /** + * Returns half of the difference between the given length, and the length when scaled by the + * given scale. + */ + public static float halfOffsetFromScale(int length, float scale) { + float scaledLength = length * scale; + return (length - scaledLength) / 2; + } + + public static @Nullable String readTextFromClipboard(@NonNull Context context) { + { + ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); + + if (clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClip().getItemCount() > 0) { + return clipboardManager.getPrimaryClip().getItemAt(0).getText().toString(); + } else { + return null; + } + } + } + + public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) { + writeTextToClipboard(context, context.getString(R.string.app_name), text); + } + + public static void writeTextToClipboard(@NonNull Context context, @NonNull String label, @NonNull String text) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(label, text); + clipboard.setPrimaryClip(clip); + } + + public static int toIntExact(long value) { + if ((int)value != value) { + throw new ArithmeticException("integer overflow"); + } + return (int)value; + } + + public static boolean isEquals(@Nullable Long first, long second) { + return first != null && first == second; + } + + @SafeVarargs + public static List concatenatedList(Collection ... items) { + final List concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size())); + + for (Collection list : items) { + concat.addAll(list); + } + + return concat; + } + + public static boolean isLong(String value) { + try { + Long.parseLong(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static int parseInt(String integer, int defaultValue) { + try { + return Integer.parseInt(integer); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + // Method to determine if we're currently in a left-to-right or right-to-left language like Arabic + public static boolean usingRightToLeftLanguage(Context context) { + Configuration c = context.getResources().getConfiguration(); + return c.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } + + // Method to determine if we're currently in a left-to-right or right-to-left language like Arabic + public static boolean usingLeftToRightLanguage(Context context) { + Configuration c = context.getResources().getConfiguration(); + return c.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; + } +} + diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt deleted file mode 100644 index 13db1430e9b..00000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/Util.kt +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.conversation.v2 - -import android.annotation.TargetApi -import android.app.ActivityManager -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.graphics.Typeface -import android.net.Uri -import android.os.Build.VERSION -import android.os.Build.VERSION_CODES -import android.text.Spannable -import android.text.SpannableString -import android.text.TextUtils -import android.text.style.StyleSpan -import android.view.View -import androidx.core.text.layoutDirection -import com.annimon.stream.Stream -import com.google.android.mms.pdu_alt.CharacterSets -import com.google.android.mms.pdu_alt.EncodedStringValue -import network.loki.messenger.R -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.components.ComposeText -import org.webrtc.ContextUtils.getApplicationContext -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.UnsupportedEncodingException -import java.security.SecureRandom -import java.util.Arrays -import java.util.Collections -import java.util.LinkedList -import java.util.concurrent.TimeUnit -import kotlin.math.max -import kotlin.math.min - -object Util { - private val TAG: String = Log.tag(Util::class.java) - - private val BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90) - - fun asList(vararg elements: T): List { - val result = mutableListOf() // LinkedList() - Collections.addAll(result, *elements) - return result - } - - fun join(list: Array, delimiter: String?): String { - return join(Arrays.asList(*list), delimiter) - } - - fun join(list: Collection, delimiter: String?): String { - val result = StringBuilder() - var i = 0 - - for (item in list) { - result.append(item) - - if (++i < list.size) result.append(delimiter) - } - - return result.toString() - } - - fun join(list: LongArray, delimeter: String?): String { - val boxed: MutableList = ArrayList(list.size) - - for (i in list.indices) { - boxed.add(list[i]) - } - - return join(boxed, delimeter) - } - - @SafeVarargs - fun join(vararg lists: List): List { - val totalSize = Stream.of(*lists).reduce(0) { sum: Int, list: List -> sum + list.size } - val joined: MutableList = ArrayList(totalSize) - - for (list in lists) { - joined.addAll(list) - } - - return joined - } - - fun join(list: List, delimeter: String?): String { - val sb = StringBuilder() - - for (j in list.indices) { - if (j != 0) sb.append(delimeter) - sb.append(list[j]) - } - - return sb.toString() - } - - fun rightPad(value: String, length: Int): String { - if (value.length >= length) { - return value - } - - val out = StringBuilder(value) - while (out.length < length) { - out.append(" ") - } - - return out.toString() - } - - fun isEmpty(value: Array?): Boolean { - return value == null || value.size == 0 - } - - fun isEmpty(value: ComposeText?): Boolean { - return value == null || value.text == null || TextUtils.isEmpty(value.textTrimmed) - } - - fun isEmpty(collection: Collection<*>?): Boolean { - return collection == null || collection.isEmpty() - } - - fun isEmpty(charSequence: CharSequence?): Boolean { - return charSequence == null || charSequence.length == 0 - } - -// fun hasItems(collection: Collection<*>?): Boolean { -// return collection != null && !collection.isEmpty() -// } - - fun getOrDefault(map: Map, key: K, defaultValue: V): V? { - return if (map.containsKey(key)) map[key] else defaultValue - } - - fun getFirstNonEmpty(vararg values: String?): String { - for (value in values) { - if (!value.isNullOrEmpty()) { return value } - } - return "" - } - - fun emptyIfNull(value: String?): String { - return value ?: "" - } - - fun emptyIfNull(value: CharSequence?): CharSequence { - return value ?: "" - } - - fun getBoldedString(value: String?): CharSequence { - val spanned = SpannableString(value) - spanned.setSpan( - StyleSpan(Typeface.BOLD), 0, - spanned.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - - return spanned - } - - fun toIsoString(bytes: ByteArray?): String { - try { - return String(bytes!!, charset(CharacterSets.MIMENAME_ISO_8859_1)) - } catch (e: UnsupportedEncodingException) { - throw AssertionError("ISO_8859_1 must be supported!") - } - } - - fun toIsoBytes(isoString: String): ByteArray { - try { - return isoString.toByteArray(charset(CharacterSets.MIMENAME_ISO_8859_1)) - } catch (e: UnsupportedEncodingException) { - throw AssertionError("ISO_8859_1 must be supported!") - } - } - - fun toUtf8Bytes(utf8String: String): ByteArray { - try { - return utf8String.toByteArray(charset(CharacterSets.MIMENAME_UTF_8)) - } catch (e: UnsupportedEncodingException) { - throw AssertionError("UTF_8 must be supported!") - } - } - - fun wait(lock: Any, timeout: Long) { - try { - (lock as Object).wait(timeout) - } catch (ie: InterruptedException) { - throw AssertionError(ie) - } - } - - fun split(source: String, delimiter: String): List { - val results = mutableListOf() - - if (TextUtils.isEmpty(source)) { - return results - } - - val elements = - source.split(delimiter.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - Collections.addAll(results, *elements) - - return results - } - - fun split(input: ByteArray?, firstLength: Int, secondLength: Int): Array { - val parts = arrayOfNulls(2) - - parts[0] = ByteArray(firstLength) - System.arraycopy(input, 0, parts[0], 0, firstLength) - - parts[1] = ByteArray(secondLength) - System.arraycopy(input, firstLength, parts[1], 0, secondLength) - - return parts - } - - fun combine(vararg elements: ByteArray?): ByteArray { - try { - val baos = ByteArrayOutputStream() - - for (element in elements) { - baos.write(element) - } - - return baos.toByteArray() - } catch (e: IOException) { - throw AssertionError(e) - } - } - - fun trim(input: ByteArray?, length: Int): ByteArray { - val result = ByteArray(length) - System.arraycopy(input, 0, result, 0, result.size) - - return result - } - - fun getSecretBytes(size: Int): ByteArray { - return getSecretBytes(SecureRandom(), size) - } - - fun getSecretBytes(secureRandom: SecureRandom, size: Int): ByteArray { - val secret = ByteArray(size) - secureRandom.nextBytes(secret) - return secret - } - - fun getRandomElement(elements: Array): T { - return elements[SecureRandom().nextInt(elements.size)] - } - - fun getRandomElement(elements: List): T { - return elements[SecureRandom().nextInt(elements.size)] - } - - fun equals(a: Any?, b: Any?): Boolean { - return a === b || (a != null && a == b) - } - - fun hashCode(vararg objects: Any?): Int { - return objects.contentHashCode() - } - - fun uri(uri: String?): Uri? { - return if (uri == null) null - else Uri.parse(uri) - } - - @TargetApi(VERSION_CODES.KITKAT) - fun isLowMemory(context: Context): Boolean { - val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - - return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice) || - activityManager.largeMemoryClass <= 64 - } - - fun clamp(value: Int, min: Int, max: Int): Int { - return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toInt() - } - - fun clamp(value: Long, min: Long, max: Long): Long { - return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toLong() - } - - fun clamp(value: Float, min: Float, max: Float): Float { - return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toFloat() - } - - /** - * Returns half of the difference between the given length, and the length when scaled by the - * given scale. - */ - fun halfOffsetFromScale(length: Int, scale: Float): Float { - val scaledLength = length * scale - return (length - scaledLength) / 2 - } - - fun readTextFromClipboard(context: Context): String? { - run { - val clipboardManager = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - return if (clipboardManager.hasPrimaryClip() && clipboardManager.primaryClip!!.itemCount > 0) { - clipboardManager.primaryClip!!.getItemAt(0).text.toString() - } else { - null - } - } - } - - fun writeTextToClipboard(context: Context, text: String) { - writeTextToClipboard(context, context.getString(R.string.app_name), text) - } - - fun writeTextToClipboard(context: Context, label: String, text: String) { - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText(label, text) - clipboard.setPrimaryClip(clip) - } - - fun toIntExact(value: Long): Int { - if (value.toInt().toLong() != value) { - throw ArithmeticException("integer overflow") - } - return value.toInt() - } - - fun isEquals(first: Long?, second: Long): Boolean { - return first != null && first == second - } - - @SafeVarargs - fun concatenatedList(vararg items: Collection): List { - val concat: MutableList = ArrayList( - Stream.of(*items).reduce(0) { sum: Int, list: Collection -> sum + list.size }) - - for (list in items) { - concat.addAll(list) - } - - return concat - } - - fun isLong(value: String): Boolean { - try { - value.toLong() - return true - } catch (e: NumberFormatException) { - return false - } - } - - fun parseInt(integer: String, defaultValue: Int): Int { - return try { - integer.toInt() - } catch (e: NumberFormatException) { - defaultValue - } - } - - // Method to determine if we're currently in a left-to-right or right-to-left language like Arabic - fun usingRightToLeftLanguage(context: Context): Boolean { - val config = context.resources.configuration - return config.layoutDirection == View.LAYOUT_DIRECTION_RTL - } - - // Method to determine if we're currently in a left-to-right or right-to-left language like Arabic - fun usingLeftToRightLanguage(context: Context): Boolean { - val config = context.resources.configuration - return config.layoutDirection == View.LAYOUT_DIRECTION_LTR - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java new file mode 100644 index 00000000000..8dd1dbd1620 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java @@ -0,0 +1,213 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.mms; + +import static org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY; +import static org.thoughtcrime.securesms.conversation.v2.Util.usingLeftToRightLanguage; + +import android.content.Context; +import android.content.res.Resources.Theme; +import android.net.Uri; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.squareup.phrase.Phrase; +import java.security.SecureRandom; +import network.loki.messenger.R; +import org.session.libsession.messaging.sending_receiving.attachments.Attachment; +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; +import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment; +import org.session.libsession.utilities.Util; +import org.session.libsignal.utilities.guava.Optional; +import org.thoughtcrime.securesms.util.MediaUtil; + +public abstract class Slide { + + protected final Attachment attachment; + protected final Context context; + + public Slide(@NonNull Context context, @NonNull Attachment attachment) { + this.context = context; + this.attachment = attachment; + } + + public String getContentType() { + return attachment.getContentType(); + } + + @Nullable + public Uri getUri() { + return attachment.getDataUri(); + } + + @Nullable + public Uri getThumbnailUri() { + return attachment.getThumbnailUri(); + } + + @NonNull + public Optional getBody() { + + if (MediaUtil.isAudio(attachment)) { + // A missing file name is the legacy way to determine if an audio attachment is + // a voice note vs. other arbitrary audio attachments. + if (attachment.isVoiceNote() || attachment.getFileName() == null || attachment.getFileName().isEmpty()) { + String baseString = context.getString(R.string.attachment_type_voice_message); + boolean languageIsLTR = usingLeftToRightLanguage(context); + String attachmentString = languageIsLTR ? "🎙 " + baseString : baseString + " 🎙"; + return Optional.fromNullable(attachmentString); + } + } + String txt = Phrase.from(context, R.string.attachmentsNotification) + .put(EMOJI_KEY, emojiForMimeType()) + .format().toString(); + return Optional.fromNullable(txt); + } + + private String emojiForMimeType() { + if (MediaUtil.isGif(attachment)) { + return "🎡"; + } else if (MediaUtil.isImage(attachment)) { + return "📷"; + } else if (MediaUtil.isVideo(attachment)) { + return "🎥"; + } else if (MediaUtil.isAudio(attachment)) { + return "🎧"; + } else if (MediaUtil.isFile(attachment)) { + return "📎"; + } else { + return ""; // We don't provide emojis for other attachment types such as VCARD + } + } + + @NonNull + public Optional getCaption() { + return Optional.fromNullable(attachment.getCaption()); + } + + @NonNull + public Optional getFileName() { + return Optional.fromNullable(attachment.getFileName()); + } + + @Nullable + public String getFastPreflightId() { + return attachment.getFastPreflightId(); + } + + public long getFileSize() { + return attachment.getSize(); + } + + public boolean hasImage() { + return false; + } + + public boolean hasVideo() { + return false; + } + + public boolean hasAudio() { + return false; + } + + public boolean hasDocument() { + return false; + } + + public @NonNull String getContentDescription() { return ""; } + + public @NonNull Attachment asAttachment() { + return attachment; + } + + public boolean isInProgress() { + return attachment.isInProgress(); + } + + public boolean isPendingDownload() { + return getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED || + getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING; + } + + public int getTransferState() { + return attachment.getTransferState(); + } + + public @DrawableRes int getPlaceholderRes(Theme theme) { + throw new AssertionError("getPlaceholderRes() called for non-drawable slide"); + } + + public boolean hasPlaceholder() { + return false; + } + + public boolean hasPlayOverlay() { + return false; + } + + protected static Attachment constructAttachmentFromUri(@NonNull Context context, + @NonNull Uri uri, + @NonNull String defaultMime, + long size, + int width, + int height, + boolean hasThumbnail, + @Nullable String fileName, + @Nullable String caption, + boolean voiceNote, + boolean quote) + { + String resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime); + String fastPreflightId = String.valueOf(new SecureRandom().nextLong()); + return new UriAttachment(uri, + hasThumbnail ? uri : null, + resolvedType, + AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED, + size, + width, + height, + fileName, + fastPreflightId, + voiceNote, + quote, + caption); + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof Slide)) return false; + + Slide that = (Slide)other; + + return Util.equals(this.getContentType(), that.getContentType()) && + this.hasAudio() == that.hasAudio() && + this.hasImage() == that.hasImage() && + this.hasVideo() == that.hasVideo() && + this.getTransferState() == that.getTransferState() && + Util.equals(this.getUri(), that.getUri()) && + Util.equals(this.getThumbnailUri(), that.getThumbnailUri()); + } + + @Override + public int hashCode() { + return Util.hashCode(getContentType(), hasAudio(), hasImage(), + hasVideo(), getUri(), getThumbnailUri(), getTransferState()); + } +} + diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt deleted file mode 100644 index 21ca54ccee5..00000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.kt +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see //www.gnu.org/licenses/>. - */ -package org.thoughtcrime.securesms.mms - -import android.content.Context -import android.content.res.Resources -import android.net.Uri -import androidx.annotation.DrawableRes -import com.squareup.phrase.Phrase -import java.security.SecureRandom -import network.loki.messenger.R -import org.session.libsession.messaging.sending_receiving.attachments.Attachment -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress -import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment -import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY -import org.session.libsession.utilities.Util.equals -import org.session.libsession.utilities.Util.hashCode -import org.session.libsignal.utilities.guava.Optional -import org.thoughtcrime.securesms.conversation.v2.Util -import org.thoughtcrime.securesms.util.MediaUtil - -abstract class Slide(@JvmField protected val context: Context, protected val attachment: Attachment) { - val contentType: String - get() = attachment.contentType - - val uri: Uri? - get() = attachment.dataUri - - open val thumbnailUri: Uri? - get() = attachment.thumbnailUri - - val body: Optional - get() { - //var attachmentString = context.getString(R.string.attachment) - val c = context - - if (MediaUtil.isAudio(attachment)) { - // A missing file name is the legacy way to determine if an audio attachment is - // a voice note vs. other arbitrary audio attachments. - if (attachment.isVoiceNote || attachment.fileName.isNullOrEmpty()) { - val baseString = context.getString(R.string.attachment_type_voice_message) - - // Note: 🎙🎙 - val languageIsLTR = Util.usingLeftToRightLanguage(context) - val attachmentString = if (languageIsLTR) { - "🎙 $baseString" - } else { - "$baseString 🎙" - } - return Optional.fromNullable(attachmentString) - } - } - val txt = Phrase.from(context, R.string.attachmentsNotification) - .put(EMOJI_KEY, emojiForMimeType()) - .format().toString() - return Optional.fromNullable(txt) - } - - private fun emojiForMimeType(): String { - return if (MediaUtil.isGif(attachment)) { - "🎡" - } else if (MediaUtil.isImage(attachment)) { - "📷" - } else if (MediaUtil.isVideo(attachment)) { - "🎥" - } else if (MediaUtil.isAudio(attachment)) { - "🎧" - } else if (MediaUtil.isFile(attachment)) { - "📎" - } else { - // We don't provide emojis for other mime-types such as VCARD - "" - } - } - - val caption: Optional - get() = Optional.fromNullable(attachment.caption) - - val fileName: Optional - get() = Optional.fromNullable(attachment.fileName) - - val fastPreflightId: String? - get() = attachment.fastPreflightId - - val fileSize: Long - get() = attachment.size - - open fun hasImage(): Boolean { return false } - - open fun hasVideo(): Boolean { return false } - - open fun hasAudio(): Boolean { return false } - - open fun hasDocument(): Boolean { return false } - - open val contentDescription: String - get() = "" - - fun asAttachment(): Attachment { return attachment } - - val isInProgress: Boolean - get() = attachment.isInProgress - - val isPendingDownload: Boolean - get() = transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED || - transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING - - val transferState: Int - get() = attachment.transferState - - @DrawableRes - open fun getPlaceholderRes(theme: Resources.Theme?): Int { - throw AssertionError("getPlaceholderRes() called for non-drawable slide") - } - - open fun hasPlaceholder(): Boolean { return false } - - open fun hasPlayOverlay(): Boolean { return false } - - override fun equals(other: Any?): Boolean { - if (other == null) return false - if (other !is Slide) return false - - return (equals(this.contentType, other.contentType) && - hasAudio() == other.hasAudio() && - hasImage() == other.hasImage() && - hasVideo() == other.hasVideo()) && - this.transferState == other.transferState && - equals(this.uri, other.uri) && - equals(this.thumbnailUri, other.thumbnailUri) - } - - override fun hashCode(): Int { - return hashCode(contentType, hasAudio(), hasImage(), hasVideo(), uri, thumbnailUri, transferState) - } - - companion object { - @JvmStatic - protected fun constructAttachmentFromUri( - context: Context, - uri: Uri, - defaultMime: String, - size: Long, - width: Int, - height: Int, - hasThumbnail: Boolean, - fileName: String?, - caption: String?, - voiceNote: Boolean, - quote: Boolean - ): Attachment { - val resolvedType = - Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime) - val fastPreflightId = SecureRandom().nextLong().toString() - return UriAttachment( - uri, - if (hasThumbnail) uri else null, - resolvedType!!, - AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED, - size, - width, - height, - fileName, - fastPreflightId, - voiceNote, - quote, - caption - ) - } - } -}