mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-29 04:55:15 +00:00
51d6144591
1) Remove all our PDU code and switch to the PDU code from the klinker library 2) Switch to using the system Lollipop MMS library by default, and falling back to our own custom library if that fails. 3) Format SMIL differently, using code from klinker instead of what we've pieced together. 4) Pull per-carrier MMS media constraints from the XML config files in the klinker library, instead of hardcoding it at 280kb. Hopefully this is an improvement, but given that MMS is involved, it will probably make things worse instead.
497 lines
15 KiB
Java
497 lines
15 KiB
Java
/**
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.thoughtcrime.securesms.util;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.annotation.TargetApi;
|
|
import android.app.ActivityManager;
|
|
import android.content.ClipData;
|
|
import android.content.ClipboardManager;
|
|
import android.content.Context;
|
|
import android.content.pm.PackageManager;
|
|
import android.graphics.Typeface;
|
|
import android.os.Build;
|
|
import android.os.Build.VERSION;
|
|
import android.os.Build.VERSION_CODES;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.provider.Telephony;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.telephony.TelephonyManager;
|
|
import android.text.Spannable;
|
|
import android.text.SpannableString;
|
|
import android.text.TextUtils;
|
|
import android.text.style.StyleSpan;
|
|
import android.util.Log;
|
|
import android.widget.EditText;
|
|
|
|
import com.google.android.mms.pdu_alt.CharacterSets;
|
|
import com.google.android.mms.pdu_alt.EncodedStringValue;
|
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
|
|
|
import org.thoughtcrime.securesms.BuildConfig;
|
|
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
|
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.SecureRandom;
|
|
import java.text.DecimalFormat;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
public class Util {
|
|
private static final String TAG = Util.class.getSimpleName();
|
|
|
|
public static Handler handler = new Handler(Looper.getMainLooper());
|
|
|
|
public static String join(String[] list, String delimiter) {
|
|
return join(Arrays.asList(list), delimiter);
|
|
}
|
|
|
|
public static String join(Collection<String> list, String delimiter) {
|
|
StringBuilder result = new StringBuilder();
|
|
int i = 0;
|
|
|
|
for (String item : list) {
|
|
result.append(item);
|
|
|
|
if (++i < list.size())
|
|
result.append(delimiter);
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
public static String join(long[] list, String delimeter) {
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int j=0;j<list.length;j++) {
|
|
if (j != 0) sb.append(delimeter);
|
|
sb.append(list[j]);
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
public static ExecutorService newSingleThreadedLifoExecutor() {
|
|
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<Runnable>());
|
|
|
|
executor.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
|
|
}
|
|
});
|
|
|
|
return executor;
|
|
}
|
|
|
|
public static boolean isEmpty(EncodedStringValue[] value) {
|
|
return value == null || value.length == 0;
|
|
}
|
|
|
|
public static boolean isEmpty(EditText value) {
|
|
return value == null || value.getText() == null || TextUtils.isEmpty(value.getText().toString());
|
|
}
|
|
|
|
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 void close(InputStream in) {
|
|
try {
|
|
in.close();
|
|
} catch (IOException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
}
|
|
|
|
public static void close(OutputStream out) {
|
|
try {
|
|
out.close();
|
|
} catch (IOException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
}
|
|
|
|
public static long getStreamLength(InputStream in) throws IOException {
|
|
byte[] buffer = new byte[4096];
|
|
int totalSize = 0;
|
|
|
|
int read;
|
|
|
|
while ((read = in.read(buffer)) != -1) {
|
|
totalSize += read;
|
|
}
|
|
|
|
return totalSize;
|
|
}
|
|
|
|
public static String canonicalizeNumber(Context context, String number)
|
|
throws InvalidNumberException
|
|
{
|
|
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
|
return PhoneNumberFormatter.formatNumber(number, localNumber);
|
|
}
|
|
|
|
public static String canonicalizeNumberOrGroup(@NonNull Context context, @NonNull String number)
|
|
throws InvalidNumberException
|
|
{
|
|
if (GroupUtil.isEncodedGroup(number)) return number;
|
|
else return canonicalizeNumber(context, number);
|
|
}
|
|
|
|
public static boolean isOwnNumber(Context context, String number) {
|
|
try {
|
|
String e164number = canonicalizeNumber(context, number);
|
|
return TextSecurePreferences.getLocalNumber(context).equals(e164number);
|
|
} catch (InvalidNumberException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static byte[] readFully(InputStream in) throws IOException {
|
|
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
|
byte[] buffer = new byte[4096];
|
|
int read;
|
|
|
|
while ((read = in.read(buffer)) != -1) {
|
|
bout.write(buffer, 0, read);
|
|
}
|
|
|
|
in.close();
|
|
|
|
return bout.toByteArray();
|
|
}
|
|
|
|
public static String readFullyAsString(InputStream in) throws IOException {
|
|
return new String(readFully(in));
|
|
}
|
|
|
|
public static long copy(InputStream in, OutputStream out) throws IOException {
|
|
byte[] buffer = new byte[4096];
|
|
int read;
|
|
long total = 0;
|
|
|
|
while ((read = in.read(buffer)) != -1) {
|
|
out.write(buffer, 0, read);
|
|
total += read;
|
|
}
|
|
|
|
in.close();
|
|
out.close();
|
|
|
|
return total;
|
|
}
|
|
|
|
public static @Nullable String getDeviceE164Number(Context context) {
|
|
final String localNumber = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
|
|
final String countryIso = getSimCountryIso(context);
|
|
final Integer countryCode = PhoneNumberUtil.getInstance().getCountryCodeForRegion(countryIso);
|
|
|
|
if (TextUtils.isEmpty(localNumber)) return null;
|
|
|
|
if (localNumber.startsWith("+")) return localNumber;
|
|
else if (!TextUtils.isEmpty(countryIso)) return PhoneNumberFormatter.formatE164(String.valueOf(countryCode), localNumber);
|
|
else if (localNumber.length() == 10) return "+1" + localNumber;
|
|
else return "+" + localNumber;
|
|
}
|
|
|
|
public static @Nullable String getSimCountryIso(Context context) {
|
|
String simCountryIso = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getSimCountryIso();
|
|
return simCountryIso != null ? simCountryIso.toUpperCase() : null;
|
|
}
|
|
|
|
public static <T> List<List<T>> partition(List<T> list, int partitionSize) {
|
|
List<List<T>> results = new LinkedList<>();
|
|
|
|
for (int index=0;index<list.size();index+=partitionSize) {
|
|
int subListSize = Math.min(partitionSize, list.size() - index);
|
|
|
|
results.add(list.subList(index, index + subListSize));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public static List<String> split(String source, String delimiter) {
|
|
List<String> 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;
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
public static boolean isDefaultSmsProvider(Context context){
|
|
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) ||
|
|
(context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context)));
|
|
}
|
|
|
|
public static int getCurrentApkReleaseVersion(Context context) {
|
|
try {
|
|
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
public static String getSecret(int size) {
|
|
byte[] secret = getSecretBytes(size);
|
|
return Base64.encodeBytes(secret);
|
|
}
|
|
|
|
public static byte[] getSecretBytes(int size) {
|
|
byte[] secret = new byte[size];
|
|
getSecureRandom().nextBytes(secret);
|
|
return secret;
|
|
}
|
|
|
|
public static SecureRandom getSecureRandom() {
|
|
try {
|
|
return SecureRandom.getInstance("SHA1PRNG");
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
public static int getDaysTillBuildExpiry() {
|
|
int age = (int)TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - BuildConfig.BUILD_TIMESTAMP);
|
|
return 90 - age;
|
|
}
|
|
|
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
|
public static boolean isMmsCapable(Context context) {
|
|
return (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) || OutgoingLegacyMmsConnection.isConnectionPossible(context);
|
|
}
|
|
|
|
public static boolean isMainThread() {
|
|
return Looper.myLooper() == Looper.getMainLooper();
|
|
}
|
|
|
|
public static void assertMainThread() {
|
|
if (!isMainThread()) {
|
|
throw new AssertionError("Main-thread assertion failed.");
|
|
}
|
|
}
|
|
|
|
public static void runOnMain(final @NonNull Runnable runnable) {
|
|
if (isMainThread()) runnable.run();
|
|
else handler.post(runnable);
|
|
}
|
|
|
|
public static void runOnMainSync(final @NonNull Runnable runnable) {
|
|
if (isMainThread()) {
|
|
runnable.run();
|
|
} else {
|
|
final CountDownLatch sync = new CountDownLatch(1);
|
|
runOnMain(new Runnable() {
|
|
@Override public void run() {
|
|
try {
|
|
runnable.run();
|
|
} finally {
|
|
sync.countDown();
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
sync.await();
|
|
} catch (InterruptedException ie) {
|
|
throw new AssertionError(ie);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static <T> T getRandomElement(T[] elements) {
|
|
try {
|
|
return elements[SecureRandom.getInstance("SHA1PRNG").nextInt(elements.length)];
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
@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.getMemoryClass() <= 64;
|
|
}
|
|
|
|
public static int clamp(int value, int min, int 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);
|
|
}
|
|
|
|
public static @Nullable String readTextFromClipboard(@NonNull Context context) {
|
|
if (VERSION.SDK_INT >= 11) {
|
|
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;
|
|
}
|
|
} else {
|
|
android.text.ClipboardManager clipboardManager = (android.text.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
|
|
if (clipboardManager.hasText()) {
|
|
return clipboardManager.getText().toString();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) {
|
|
if (VERSION.SDK_INT >= 11) {
|
|
ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
clipboardManager.setPrimaryClip(ClipData.newPlainText("Safety numbers", text));
|
|
} else {
|
|
android.text.ClipboardManager clipboardManager = (android.text.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
clipboardManager.setText(text);
|
|
}
|
|
}
|
|
|
|
public static int toIntExact(long value) {
|
|
if ((int)value != value) {
|
|
throw new ArithmeticException("integer overflow");
|
|
}
|
|
return (int)value;
|
|
}
|
|
|
|
public static boolean isStringEquals(String first, String second) {
|
|
if (first == null) return second == null;
|
|
return first.equals(second);
|
|
}
|
|
|
|
public static boolean isEquals(@Nullable Long first, long second) {
|
|
return first != null && first == second;
|
|
}
|
|
|
|
public static String getPrettyFileSize(long sizeBytes) {
|
|
if (sizeBytes <= 0) return "0";
|
|
|
|
String[] units = new String[]{"B", "kB", "MB", "GB", "TB"};
|
|
int digitGroups = (int) (Math.log10(sizeBytes) / Math.log10(1024));
|
|
|
|
return new DecimalFormat("#,##0.#").format(sizeBytes/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
|
|
}
|
|
}
|