That fuck shit the fascists are using
1/*
2 * Copyright (C) 2011 Whisper Systems
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17package org.tm.archive.util;
18
19import android.annotation.SuppressLint;
20import android.app.ActivityManager;
21import android.content.ClipData;
22import android.content.ClipboardManager;
23import android.content.Context;
24import android.content.pm.PackageManager;
25import android.graphics.Typeface;
26import android.net.Uri;
27import android.telephony.TelephonyManager;
28import android.text.Spannable;
29import android.text.SpannableString;
30import android.text.TextUtils;
31import android.text.style.StyleSpan;
32
33import androidx.annotation.NonNull;
34import androidx.annotation.Nullable;
35import androidx.annotation.RequiresPermission;
36
37import com.annimon.stream.Stream;
38import com.google.i18n.phonenumbers.NumberParseException;
39import com.google.i18n.phonenumbers.PhoneNumberUtil;
40import com.google.i18n.phonenumbers.Phonenumber;
41
42import org.signal.core.util.Base64;
43import org.signal.core.util.logging.Log;
44import org.tm.archive.BuildConfig;
45import org.tm.archive.R;
46import org.tm.archive.components.ComposeText;
47import org.tm.archive.keyvalue.SignalStore;
48
49import java.io.ByteArrayOutputStream;
50import java.io.IOException;
51import java.nio.charset.StandardCharsets;
52import java.security.SecureRandom;
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.Collection;
56import java.util.Collections;
57import java.util.LinkedList;
58import java.util.List;
59import java.util.Map;
60import java.util.Optional;
61import java.util.concurrent.TimeUnit;
62
63public class Util {
64 private static final String TAG = Log.tag(Util.class);
65
66 private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90);
67
68 public static final String COPY_LABEL = "text\u00AD";
69
70 public static <T> List<T> asList(T... elements) {
71 List<T> result = new LinkedList<>();
72 Collections.addAll(result, elements);
73 return result;
74 }
75
76 public static String join(String[] list, String delimiter) {
77 return join(Arrays.asList(list), delimiter);
78 }
79
80 public static <T> String join(Collection<T> list, String delimiter) {
81 StringBuilder result = new StringBuilder();
82 int i = 0;
83
84 for (T item : list) {
85 result.append(item);
86
87 if (++i < list.size())
88 result.append(delimiter);
89 }
90
91 return result.toString();
92 }
93
94 public static String join(long[] list, String delimeter) {
95 List<Long> boxed = new ArrayList<>(list.length);
96
97 for (int i = 0; i < list.length; i++) {
98 boxed.add(list[i]);
99 }
100
101 return join(boxed, delimeter);
102 }
103
104 @SafeVarargs
105 public static @NonNull <E> List<E> join(@NonNull List<E>... lists) {
106 int totalSize = Stream.of(lists).reduce(0, (sum, list) -> sum + list.size());
107 List<E> joined = new ArrayList<>(totalSize);
108
109 for (List<E> list : lists) {
110 joined.addAll(list);
111 }
112
113 return joined;
114 }
115
116 public static String join(List<Long> list, String delimeter) {
117 StringBuilder sb = new StringBuilder();
118
119 for (int j = 0; j < list.size(); j++) {
120 if (j != 0) sb.append(delimeter);
121 sb.append(list.get(j));
122 }
123
124 return sb.toString();
125 }
126
127 public static String rightPad(String value, int length) {
128 if (value.length() >= length) {
129 return value;
130 }
131
132 StringBuilder out = new StringBuilder(value);
133 while (out.length() < length) {
134 out.append(" ");
135 }
136
137 return out.toString();
138 }
139
140 public static boolean isEmpty(ComposeText value) {
141 return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed());
142 }
143
144 public static boolean isEmpty(Collection<?> collection) {
145 return collection == null || collection.isEmpty();
146 }
147
148 public static boolean isEmpty(@Nullable CharSequence charSequence) {
149 return charSequence == null || charSequence.length() == 0;
150 }
151
152 public static boolean hasItems(@Nullable Collection<?> collection) {
153 return collection != null && !collection.isEmpty();
154 }
155
156 public static <K, V> boolean hasItems(@Nullable Map<K, V> map) {
157 return map != null && !map.isEmpty();
158 }
159
160 public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {
161 return map.containsKey(key) ? map.get(key) : defaultValue;
162 }
163
164 public static String getFirstNonEmpty(String... values) {
165 for (String value : values) {
166 if (!Util.isEmpty(value)) {
167 return value;
168 }
169 }
170 return "";
171 }
172
173 public static @NonNull String emptyIfNull(@Nullable String value) {
174 return value != null ? value : "";
175 }
176
177 public static @NonNull CharSequence emptyIfNull(@Nullable CharSequence value) {
178 return value != null ? value : "";
179 }
180
181 public static CharSequence getBoldedString(String value) {
182 SpannableString spanned = new SpannableString(value);
183 spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
184 spanned.length(),
185 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
186
187 return spanned;
188 }
189
190 public static @NonNull String toIsoString(byte[] bytes) {
191 return new String(bytes, StandardCharsets.ISO_8859_1);
192 }
193
194 public static byte[] toIsoBytes(String isoString) {
195 return isoString.getBytes(StandardCharsets.ISO_8859_1);
196 }
197
198 public static byte[] toUtf8Bytes(String utf8String) {
199 return utf8String.getBytes(StandardCharsets.UTF_8);
200 }
201
202 public static void wait(Object lock, long timeout) {
203 try {
204 lock.wait(timeout);
205 } catch (InterruptedException ie) {
206 throw new AssertionError(ie);
207 }
208 }
209
210 @RequiresPermission(anyOf = {
211 android.Manifest.permission.READ_PHONE_STATE,
212 android.Manifest.permission.READ_PHONE_NUMBERS
213 })
214 @SuppressLint("MissingPermission")
215 public static Optional<Phonenumber.PhoneNumber> getDeviceNumber(Context context) {
216 try {
217 final String localNumber = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
218 final Optional<String> countryIso = getSimCountryIso(context);
219
220 if (TextUtils.isEmpty(localNumber)) return Optional.empty();
221 if (!countryIso.isPresent()) return Optional.empty();
222
223 return Optional.ofNullable(PhoneNumberUtil.getInstance().parse(localNumber, countryIso.get()));
224 } catch (NumberParseException e) {
225 Log.w(TAG, e);
226 return Optional.empty();
227 }
228 }
229
230 public static Optional<String> getSimCountryIso(Context context) {
231 String simCountryIso = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getSimCountryIso();
232 return Optional.ofNullable(simCountryIso != null ? simCountryIso.toUpperCase() : null);
233 }
234
235 public static @NonNull <T> T firstNonNull(@Nullable T optional, @NonNull T fallback) {
236 return optional != null ? optional : fallback;
237 }
238
239 @SafeVarargs
240 public static @NonNull <T> T firstNonNull(T ... ts) {
241 for (T t : ts) {
242 if (t != null) {
243 return t;
244 }
245 }
246
247 throw new IllegalStateException("All choices were null.");
248 }
249
250 public static <T> List<List<T>> partition(List<T> list, int partitionSize) {
251 List<List<T>> results = new LinkedList<>();
252
253 for (int index=0;index<list.size();index+=partitionSize) {
254 int subListSize = Math.min(partitionSize, list.size() - index);
255
256 results.add(list.subList(index, index + subListSize));
257 }
258
259 return results;
260 }
261
262 public static List<String> split(String source, String delimiter) {
263 List<String> results = new LinkedList<>();
264
265 if (TextUtils.isEmpty(source)) {
266 return results;
267 }
268
269 String[] elements = source.split(delimiter);
270 Collections.addAll(results, elements);
271
272 return results;
273 }
274
275 public static byte[][] split(byte[] input, int firstLength, int secondLength) {
276 byte[][] parts = new byte[2][];
277
278 parts[0] = new byte[firstLength];
279 System.arraycopy(input, 0, parts[0], 0, firstLength);
280
281 parts[1] = new byte[secondLength];
282 System.arraycopy(input, firstLength, parts[1], 0, secondLength);
283
284 return parts;
285 }
286
287 public static byte[] combine(byte[]... elements) {
288 try {
289 ByteArrayOutputStream baos = new ByteArrayOutputStream();
290
291 for (byte[] element : elements) {
292 baos.write(element);
293 }
294
295 return baos.toByteArray();
296 } catch (IOException e) {
297 throw new AssertionError(e);
298 }
299 }
300
301 public static byte[] trim(byte[] input, int length) {
302 byte[] result = new byte[length];
303 System.arraycopy(input, 0, result, 0, result.length);
304
305 return result;
306 }
307
308 /**
309 * The app version.
310 * <p>
311 * This code should be used in all places that compare app versions rather than
312 * {@link #getManifestApkVersion(Context)} or {@link BuildConfig#VERSION_CODE}.
313 */
314 public static int getCanonicalVersionCode() {
315 return BuildConfig.CANONICAL_VERSION_CODE;
316 }
317
318 /**
319 * {@link BuildConfig#VERSION_CODE} may not be the actual version due to ABI split code adding a
320 * postfix after BuildConfig is generated.
321 * <p>
322 * However, in most cases you want to use {@link BuildConfig#CANONICAL_VERSION_CODE} via
323 * {@link #getCanonicalVersionCode()}
324 */
325 public static int getManifestApkVersion(Context context) {
326 try {
327 return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
328 } catch (PackageManager.NameNotFoundException e) {
329 throw new AssertionError(e);
330 }
331 }
332
333 public static String getSecret(int size) {
334 byte[] secret = getSecretBytes(size);
335 return Base64.encodeWithPadding(secret);
336 }
337
338 public static byte[] getSecretBytes(int size) {
339 return getSecretBytes(new SecureRandom(), size);
340 }
341
342 public static byte[] getSecretBytes(@NonNull SecureRandom secureRandom, int size) {
343 byte[] secret = new byte[size];
344 secureRandom.nextBytes(secret);
345 return secret;
346 }
347
348 /**
349 * @return The amount of time (in ms) until this build of Signal will be considered 'expired'.
350 * Takes into account both the build age as well as any remote deprecation values.
351 */
352 public static long getTimeUntilBuildExpiry() {
353 if (SignalStore.misc().isClientDeprecated()) {
354 return 0;
355 }
356
357 long buildAge = System.currentTimeMillis() - BuildConfig.BUILD_TIMESTAMP;
358 long timeUntilBuildDeprecation = BUILD_LIFESPAN - buildAge;
359 long timeUntilRemoteDeprecation = RemoteDeprecation.getTimeUntilDeprecation();
360
361 if (timeUntilRemoteDeprecation != -1) {
362 long timeUntilDeprecation = Math.min(timeUntilBuildDeprecation, timeUntilRemoteDeprecation);
363 return Math.max(timeUntilDeprecation, 0);
364 } else {
365 return Math.max(timeUntilBuildDeprecation, 0);
366 }
367 }
368
369 public static <T> T getRandomElement(T[] elements) {
370 return elements[new SecureRandom().nextInt(elements.length)];
371 }
372
373 public static <T> T getRandomElement(List<T> elements) {
374 return elements.get(new SecureRandom().nextInt(elements.size()));
375 }
376
377 public static boolean equals(@Nullable Object a, @Nullable Object b) {
378 return a == b || (a != null && a.equals(b));
379 }
380
381 public static int hashCode(@Nullable Object... objects) {
382 return Arrays.hashCode(objects);
383 }
384
385 public static @Nullable Uri uri(@Nullable String uri) {
386 if (uri == null) return null;
387 else return Uri.parse(uri);
388 }
389
390 public static boolean isLowMemory(Context context) {
391 ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
392
393 return activityManager.isLowRamDevice() || activityManager.getLargeMemoryClass() <= 64;
394 }
395
396 public static int clamp(int value, int min, int max) {
397 return Math.min(Math.max(value, min), max);
398 }
399
400 public static long clamp(long value, long min, long max) {
401 return Math.min(Math.max(value, min), max);
402 }
403
404 public static float clamp(float value, float min, float max) {
405 return Math.min(Math.max(value, min), max);
406 }
407
408 /**
409 * Returns half of the difference between the given length, and the length when scaled by the
410 * given scale.
411 */
412 public static float halfOffsetFromScale(int length, float scale) {
413 float scaledLength = length * scale;
414 return (length - scaledLength) / 2;
415 }
416
417 public static @Nullable String readTextFromClipboard(@NonNull Context context) {
418 {
419 ClipboardManager clipboardManager = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
420
421 if (clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClip().getItemCount() > 0) {
422 return clipboardManager.getPrimaryClip().getItemAt(0).getText().toString();
423 } else {
424 return null;
425 }
426 }
427 }
428
429 public static void writeTextToClipboard(@NonNull Context context, @NonNull String text) {
430 writeTextToClipboard(context, context.getString(R.string.app_name), text);
431 }
432
433 public static void writeTextToClipboard(@NonNull Context context, @NonNull String label, @NonNull String text) {
434 android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
435 ClipData clip = ClipData.newPlainText(label, text);
436 clipboard.setPrimaryClip(clip);
437 }
438
439 public static int toIntExact(long value) {
440 if ((int)value != value) {
441 throw new ArithmeticException("integer overflow");
442 }
443 return (int)value;
444 }
445
446 public static boolean isEquals(@Nullable Long first, long second) {
447 return first != null && first == second;
448 }
449
450 public static String getPrettyFileSize(long sizeBytes) {
451 return MemoryUnitFormat.formatBytes(sizeBytes);
452 }
453
454 public static void copyToClipboard(@NonNull Context context, @NonNull CharSequence text) {
455 ServiceUtil.getClipboardManager(context).setPrimaryClip(ClipData.newPlainText(COPY_LABEL, text));
456 }
457
458 @SafeVarargs
459 public static <T> List<T> concatenatedList(Collection <T>... items) {
460 final List<T> concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size()));
461
462 for (Collection<T> list : items) {
463 concat.addAll(list);
464 }
465
466 return concat;
467 }
468
469 public static boolean isLong(String value) {
470 try {
471 Long.parseLong(value);
472 return true;
473 } catch (NumberFormatException e) {
474 return false;
475 }
476 }
477
478 public static int parseInt(String integer, int defaultValue) {
479 try {
480 return Integer.parseInt(integer);
481 } catch (NumberFormatException e) {
482 return defaultValue;
483 }
484 }
485}