mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 08:39:22 +01:00
Move the rest of the permissions classes.
This commit is contained in:
@@ -13,15 +13,17 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.backup.v2.local.ArchiveFileSystem;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.signal.core.util.NoExternalStorageException;
|
||||
import org.signal.core.ui.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.asynclayoutinflater.appcompat.AsyncAppCompatFactory;
|
||||
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.concurrent.JvmRxExtensions;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
@@ -42,7 +43,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinUpdateRequiredBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
|
||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameLinkConversionResult;
|
||||
import org.thoughtcrime.securesms.proxy.ProxyBottomSheetFragment;
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobs.ConversationShortcutUpdateJob;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.signal.core.ui.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
|
||||
import org.thoughtcrime.securesms.notifications.DeviceSpecificNotificationConfig;
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.widget.Toast;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class LongClickCopySpan extends URLSpan {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.Util
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.MessageTypes
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.webkit.MimeTypeMap
|
||||
import androidx.core.content.contentValuesOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.ui.util.StorageUtil
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.logI
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.Util;
|
||||
|
||||
import com.annimon.stream.ComparatorCompat;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.util
|
||||
import org.signal.core.util.BidiUtil
|
||||
import org.signal.core.util.E164Util
|
||||
import org.signal.core.util.LRUCache
|
||||
import org.signal.core.util.Util
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.os.storage.StorageVolume;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.signal.core.util.permissions.PermissionCompat;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class StorageUtil {
|
||||
|
||||
private static final String PRODUCTION_PACKAGE_ID = "org.thoughtcrime.securesms";
|
||||
|
||||
public static File getOrCreateBackupDirectory() throws NoExternalStorageException {
|
||||
File storage = Environment.getExternalStorageDirectory();
|
||||
|
||||
if (!storage.canWrite()) {
|
||||
throw new NoExternalStorageException();
|
||||
}
|
||||
|
||||
File backups = getBackupDirectory();
|
||||
|
||||
if (!backups.exists()) {
|
||||
if (!backups.mkdirs()) {
|
||||
throw new NoExternalStorageException("Unable to create backup directory...");
|
||||
}
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
public static File getOrCreateBackupV2Directory() throws NoExternalStorageException {
|
||||
File storage = Environment.getExternalStorageDirectory();
|
||||
|
||||
if (!storage.canWrite()) {
|
||||
throw new NoExternalStorageException();
|
||||
}
|
||||
|
||||
File backups = getBackupV2Directory();
|
||||
|
||||
if (!backups.exists()) {
|
||||
if (!backups.mkdirs()) {
|
||||
throw new NoExternalStorageException("Unable to create backup directory...");
|
||||
}
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
public static File getBackupDirectory() throws NoExternalStorageException {
|
||||
File storage = Environment.getExternalStorageDirectory();
|
||||
File signal = new File(storage, "Signal");
|
||||
File backups = new File(signal, "Backups");
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (BuildConfig.APPLICATION_ID.startsWith(PRODUCTION_PACKAGE_ID + ".")) {
|
||||
backups = new File(backups, BuildConfig.APPLICATION_ID.substring(PRODUCTION_PACKAGE_ID.length() + 1));
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
public static File getBackupV2Directory() throws NoExternalStorageException {
|
||||
File storage = Environment.getExternalStorageDirectory();
|
||||
File backups = new File(storage, "Signal");
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (BuildConfig.APPLICATION_ID.startsWith(PRODUCTION_PACKAGE_ID + ".")) {
|
||||
backups = new File(storage, BuildConfig.APPLICATION_ID.substring(PRODUCTION_PACKAGE_ID.length() + 1));
|
||||
}
|
||||
|
||||
return backups;
|
||||
}
|
||||
|
||||
@RequiresApi(24)
|
||||
public static @NonNull String getDisplayPath(@NonNull Context context, @NonNull Uri uri) {
|
||||
String lastPathSegment = Objects.requireNonNull(uri.getLastPathSegment());
|
||||
String backupVolume = lastPathSegment.replaceFirst(":.*", "");
|
||||
String backupName = lastPathSegment.replaceFirst(".*:", "");
|
||||
|
||||
StorageManager storageManager = ServiceUtil.getStorageManager(context);
|
||||
List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
|
||||
StorageVolume storageVolume = null;
|
||||
|
||||
for (StorageVolume volume : storageVolumes) {
|
||||
if (Objects.equals(volume.getUuid(), backupVolume)) {
|
||||
storageVolume = volume;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (storageVolume == null) {
|
||||
return backupName;
|
||||
} else {
|
||||
return context.getString(R.string.StorageUtil__s_s, storageVolume.getDescription(context), backupName);
|
||||
}
|
||||
}
|
||||
|
||||
private static File getSignalStorageDir() throws NoExternalStorageException {
|
||||
final File storage = Environment.getExternalStorageDirectory();
|
||||
|
||||
if (!storage.canWrite()) {
|
||||
throw new NoExternalStorageException();
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
public static boolean canWriteInSignalStorageDir() {
|
||||
File storage;
|
||||
|
||||
try {
|
||||
storage = getSignalStorageDir();
|
||||
} catch (NoExternalStorageException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return storage.canWrite();
|
||||
}
|
||||
|
||||
public static boolean canWriteToMediaStore() {
|
||||
return Build.VERSION.SDK_INT > 28 ||
|
||||
Permissions.hasAll(AppDependencies.getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
public static boolean canReadAnyFromMediaStore() {
|
||||
return Permissions.hasAny(AppDependencies.getApplication(), PermissionCompat.forImagesAndVideos());
|
||||
}
|
||||
|
||||
public static boolean canOnlyReadSelectedMediaStore() {
|
||||
return Build.VERSION.SDK_INT >= 34 &&
|
||||
Permissions.hasAll(AppDependencies.getApplication(), Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) &&
|
||||
!Permissions.hasAny(AppDependencies.getApplication(), Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO);
|
||||
}
|
||||
|
||||
public static boolean canReadAllFromMediaStore() {
|
||||
return Permissions.hasAll(AppDependencies.getApplication(), PermissionCompat.forImagesAndVideos());
|
||||
}
|
||||
|
||||
public static @NonNull Uri getVideoUri() {
|
||||
return MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
public static @NonNull Uri getAudioUri() {
|
||||
return MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
public static @NonNull Uri getImageUri() {
|
||||
return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
public static @NonNull Uri getDownloadUri() {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
return getLegacyUri(Environment.DIRECTORY_DOWNLOADS);
|
||||
} else {
|
||||
return MediaStore.Downloads.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull Uri getLegacyUri(@NonNull String directory) {
|
||||
return Uri.fromFile(Environment.getExternalStoragePublicDirectory(directory));
|
||||
}
|
||||
|
||||
public static @Nullable String getCleanFileName(@Nullable String fileName) {
|
||||
if (fileName == null) return null;
|
||||
|
||||
fileName = fileName.replace('\u202D', '\uFFFD');
|
||||
fileName = fileName.replace('\u202E', '\uFFFD');
|
||||
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.signal.core.util.ResourceUtil;
|
||||
import org.signal.core.util.Util;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
||||
@@ -1,462 +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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.telephony.TelephonyManager;
|
||||
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 androidx.annotation.RequiresPermission;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.PendingIntentFlags;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.payments.backup.phrase.ClearClipboardAlarmReceiver;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Util {
|
||||
private static final String TAG = Log.tag(Util.class);
|
||||
|
||||
private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90);
|
||||
|
||||
public static final String COPY_LABEL = "text\u00AD";
|
||||
|
||||
public static <T> List<T> asList(T... elements) {
|
||||
List<T> 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 <T> String join(Collection<T> 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<Long> 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 <E> List<E> join(@NonNull List<E>... lists) {
|
||||
int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size());
|
||||
List<E> joined = new ArrayList<>(totalSize);
|
||||
|
||||
for (List<E> list : lists) {
|
||||
joined.addAll(list);
|
||||
}
|
||||
|
||||
return joined;
|
||||
}
|
||||
|
||||
public static String join(List<Long> 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(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 <K, V> boolean hasItems(@Nullable Map<K, V> map) {
|
||||
return map != null && !map.isEmpty();
|
||||
}
|
||||
|
||||
public static <K, V> V getOrDefault(@NonNull Map<K, V> 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 byte[] toIsoBytes(String isoString) {
|
||||
return isoString.getBytes(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
public static void wait(Object lock, long timeout) {
|
||||
try {
|
||||
lock.wait(timeout);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new AssertionError(ie);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = {
|
||||
android.Manifest.permission.READ_PHONE_STATE,
|
||||
android.Manifest.permission.READ_PHONE_NUMBERS
|
||||
})
|
||||
@SuppressLint("MissingPermission")
|
||||
public static Optional<Phonenumber.PhoneNumber> getDeviceNumber(Context context) {
|
||||
try {
|
||||
final String localNumber = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
|
||||
final Optional<String> countryIso = getSimCountryIso(context);
|
||||
|
||||
if (TextUtils.isEmpty(localNumber)) return Optional.empty();
|
||||
if (!countryIso.isPresent()) return Optional.empty();
|
||||
|
||||
return Optional.ofNullable(PhoneNumberUtil.getInstance().parse(localNumber, countryIso.get()));
|
||||
} catch (NumberParseException e) {
|
||||
Log.w(TAG, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> getSimCountryIso(Context context) {
|
||||
String simCountryIso = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getSimCountryIso();
|
||||
return Optional.ofNullable(simCountryIso != null ? simCountryIso.toUpperCase() : null);
|
||||
}
|
||||
|
||||
public static @Nullable String getNetworkCountryIso(Context context) {
|
||||
String networkCountryIso = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkCountryIso();
|
||||
return networkCountryIso == null ? null : networkCountryIso.toUpperCase();
|
||||
}
|
||||
|
||||
public static @NonNull <T> T firstNonNull(@Nullable T optional, @NonNull T fallback) {
|
||||
return optional != null ? optional : fallback;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static @NonNull <T> T firstNonNull(T ... ts) {
|
||||
for (T t : ts) {
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("All choices were 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The app version.
|
||||
* <p>
|
||||
* This code should be used in all places that compare app versions rather than
|
||||
* {@link #getManifestApkVersion(Context)} or {@link BuildConfig#VERSION_CODE}.
|
||||
*/
|
||||
public static int getCanonicalVersionCode() {
|
||||
return BuildConfig.CANONICAL_VERSION_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BuildConfig#VERSION_CODE} may not be the actual version due to ABI split code adding a
|
||||
* postfix after BuildConfig is generated.
|
||||
* <p>
|
||||
* However, in most cases you want to use {@link BuildConfig#CANONICAL_VERSION_CODE} via
|
||||
* {@link #getCanonicalVersionCode()}
|
||||
*/
|
||||
public static int getManifestApkVersion(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.encodeWithPadding(secret);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The amount of time (in ms) until this build of Signal will be considered 'expired'.
|
||||
* Takes into account both the build age as well as any remote deprecation values.
|
||||
*/
|
||||
public static long getTimeUntilBuildExpiry(long currentTime) {
|
||||
if (SignalStore.misc().isClientDeprecated()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long buildAge = currentTime - BuildConfig.BUILD_TIMESTAMP;
|
||||
long timeUntilBuildDeprecation = BUILD_LIFESPAN - buildAge;
|
||||
long timeUntilRemoteDeprecation = RemoteDeprecation.getTimeUntilDeprecation(currentTime);
|
||||
|
||||
if (timeUntilRemoteDeprecation != -1) {
|
||||
long timeUntilDeprecation = Math.min(timeUntilBuildDeprecation, timeUntilRemoteDeprecation);
|
||||
return Math.max(timeUntilDeprecation, 0);
|
||||
} else {
|
||||
return Math.max(timeUntilBuildDeprecation, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getRandomElement(List<T> 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);
|
||||
}
|
||||
|
||||
public static boolean isLowMemory(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
|
||||
return 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) {
|
||||
android.content.ClipboardManager clipboard = (android.content.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 void copyToClipboard(@NonNull Context context, @NonNull CharSequence text) {
|
||||
ServiceUtil.getClipboardManager(context).setPrimaryClip(ClipData.newPlainText(COPY_LABEL, text));
|
||||
}
|
||||
|
||||
public static void copyToClipboard(@NonNull Context context, @NonNull CharSequence text, int expiresInSeconds) {
|
||||
ClipboardManager clipboardManager = ServiceUtil.getClipboardManager(context);
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(context.getString(R.string.app_name), text));
|
||||
|
||||
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
|
||||
Intent alarmIntent = new Intent(context, ClearClipboardAlarmReceiver.class);
|
||||
PendingIntent pendingAlarmIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntentFlags.mutable());
|
||||
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiresInSeconds), pendingAlarmIntent);
|
||||
}
|
||||
|
||||
|
||||
public static int parseInt(String integer, int defaultValue) {
|
||||
try {
|
||||
return Integer.parseInt(integer);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user