2022-06-24 00:16:32 +02:00
|
|
|
package app.revanced.integrations.utils;
|
|
|
|
|
2022-11-25 00:10:58 +01:00
|
|
|
import android.annotation.SuppressLint;
|
2022-06-24 00:16:32 +02:00
|
|
|
import android.content.Context;
|
2022-06-27 22:08:50 +02:00
|
|
|
import android.content.res.Resources;
|
2023-04-02 16:10:01 +02:00
|
|
|
import android.net.ConnectivityManager;
|
2022-06-24 00:16:32 +02:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Looper;
|
2023-05-13 01:24:44 +02:00
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
2023-04-02 16:10:01 +02:00
|
|
|
import android.view.animation.Animation;
|
|
|
|
import android.view.animation.AnimationUtils;
|
2023-10-02 01:12:59 +02:00
|
|
|
import android.widget.*;
|
2023-01-28 08:38:31 +01:00
|
|
|
import androidx.annotation.NonNull;
|
2023-04-02 16:10:01 +02:00
|
|
|
import androidx.annotation.Nullable;
|
2023-10-02 01:12:59 +02:00
|
|
|
import app.revanced.integrations.settings.SettingsEnum;
|
2023-01-28 08:38:31 +01:00
|
|
|
|
2022-12-31 07:38:47 +01:00
|
|
|
import java.text.Bidi;
|
|
|
|
import java.util.Locale;
|
2023-04-02 16:10:01 +02:00
|
|
|
import java.util.Objects;
|
2023-10-02 01:12:59 +02:00
|
|
|
import java.util.concurrent.*;
|
2022-11-30 19:58:11 +01:00
|
|
|
|
2022-06-24 00:16:32 +02:00
|
|
|
public class ReVancedUtils {
|
|
|
|
|
2022-11-25 00:10:58 +01:00
|
|
|
@SuppressLint("StaticFieldLeak")
|
2022-06-27 22:08:50 +02:00
|
|
|
public static Context context;
|
2022-10-29 01:54:03 +02:00
|
|
|
|
2022-11-25 00:10:58 +01:00
|
|
|
private ReVancedUtils() {
|
|
|
|
} // utility class
|
|
|
|
|
2023-05-10 01:04:38 +02:00
|
|
|
/**
|
|
|
|
* Hide a view by setting its layout height and width to 1dp.
|
|
|
|
*
|
|
|
|
* @param condition The setting to check for hiding the view.
|
|
|
|
* @param view The view to hide.
|
|
|
|
*/
|
|
|
|
public static void hideViewBy1dpUnderCondition(SettingsEnum condition, View view) {
|
|
|
|
if (!condition.getBoolean()) return;
|
|
|
|
|
|
|
|
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
|
|
|
|
|
|
|
hideViewByLayoutParams(view);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hide a view by setting its visibility to GONE.
|
|
|
|
*
|
|
|
|
* @param condition The setting to check for hiding the view.
|
|
|
|
* @param view The view to hide.
|
|
|
|
*/
|
|
|
|
public static void hideViewUnderCondition(SettingsEnum condition, View view) {
|
|
|
|
if (!condition.getBoolean()) return;
|
|
|
|
|
|
|
|
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
|
|
|
|
|
|
|
view.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-30 19:58:11 +01:00
|
|
|
/**
|
|
|
|
* General purpose pool for network calls and other background tasks.
|
2022-12-21 22:19:34 +01:00
|
|
|
* All tasks run at max thread priority.
|
2022-11-30 19:58:11 +01:00
|
|
|
*/
|
|
|
|
private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor(
|
2023-04-02 16:10:01 +02:00
|
|
|
2, // 2 threads always ready to go
|
|
|
|
Integer.MAX_VALUE,
|
2022-11-30 19:58:11 +01:00
|
|
|
10, // For any threads over the minimum, keep them alive 10 seconds after they go idle
|
|
|
|
TimeUnit.SECONDS,
|
2023-04-02 16:10:01 +02:00
|
|
|
new SynchronousQueue<>(),
|
|
|
|
r -> { // ThreadFactory
|
|
|
|
Thread t = new Thread(r);
|
|
|
|
t.setPriority(Thread.MAX_PRIORITY); // run at max priority
|
|
|
|
return t;
|
2022-12-21 22:19:34 +01:00
|
|
|
});
|
2022-11-30 19:58:11 +01:00
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static void runOnBackgroundThread(@NonNull Runnable task) {
|
2022-11-30 19:58:11 +01:00
|
|
|
backgroundThreadPool.execute(task);
|
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
@NonNull
|
|
|
|
public static <T> Future<T> submitOnBackgroundThread(@NonNull Callable<T> call) {
|
|
|
|
return backgroundThreadPool.submit(call);
|
2022-11-30 19:58:11 +01:00
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static boolean containsAny(@NonNull String value, @NonNull String... targets) {
|
2022-10-29 01:54:03 +02:00
|
|
|
for (String string : targets)
|
2022-11-19 23:22:34 +01:00
|
|
|
if (!string.isEmpty() && value.contains(string)) return true;
|
2022-10-29 01:54:03 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-06-27 22:08:50 +02:00
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
/**
|
|
|
|
* @return zero, if the resource is not found
|
|
|
|
*/
|
|
|
|
@SuppressLint("DiscouragedApi")
|
|
|
|
public static int getResourceIdentifier(@NonNull Context context, @NonNull String resourceIdentifierName, @NonNull String type) {
|
|
|
|
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName());
|
2022-07-05 22:31:13 +02:00
|
|
|
}
|
2022-10-29 01:54:03 +02:00
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
/**
|
|
|
|
* @return zero, if the resource is not found
|
|
|
|
*/
|
|
|
|
public static int getResourceIdentifier(@NonNull String resourceIdentifierName, @NonNull String type) {
|
|
|
|
return getResourceIdentifier(getContext(), resourceIdentifierName, type);
|
2022-07-05 22:31:13 +02:00
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static int getResourceInteger(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
|
|
|
return getContext().getResources().getInteger(getResourceIdentifier(resourceIdentifierName, "integer"));
|
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
|
|
|
public static Animation getResourceAnimation(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
|
|
|
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifier(resourceIdentifierName, "anim"));
|
2022-07-11 14:29:39 +02:00
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static int getResourceColor(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
|
|
|
return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color"));
|
2022-06-27 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static int getResourceDimensionPixelSize(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
|
|
|
return getContext().getResources().getDimensionPixelSize(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
2022-06-27 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static float getResourceDimension(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
|
|
|
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
2022-06-24 00:16:32 +02:00
|
|
|
}
|
|
|
|
|
2023-05-19 22:42:02 +02:00
|
|
|
/**
|
|
|
|
* @return The first child view that matches the filter.
|
|
|
|
*/
|
|
|
|
@Nullable
|
|
|
|
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) {
|
|
|
|
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
|
|
|
|
View childAt = viewGroup.getChildAt(i);
|
|
|
|
if (filter.matches(childAt)) {
|
|
|
|
return (T) childAt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public interface MatchFilter<T> {
|
|
|
|
boolean matches(T object);
|
|
|
|
}
|
|
|
|
|
2022-06-27 22:08:50 +02:00
|
|
|
public static Context getContext() {
|
|
|
|
if (context != null) {
|
|
|
|
return context;
|
|
|
|
}
|
2023-04-02 16:10:01 +02:00
|
|
|
LogHelper.printException(() -> "Context is null, returning null!");
|
|
|
|
return null;
|
2022-06-27 22:08:50 +02:00
|
|
|
}
|
2022-07-14 18:42:43 +02:00
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static void setClipboard(@NonNull String text) {
|
2022-12-31 18:47:57 +01:00
|
|
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
|
|
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
|
|
|
|
clipboard.setPrimaryClip(clip);
|
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public static boolean isTablet() {
|
2022-07-14 18:42:43 +02:00
|
|
|
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
|
|
|
}
|
2022-11-25 00:10:58 +01:00
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
@Nullable
|
2023-01-10 19:01:21 +01:00
|
|
|
private static Boolean isRightToLeftTextLayout;
|
2023-05-10 01:04:38 +02:00
|
|
|
|
2022-12-31 07:38:47 +01:00
|
|
|
/**
|
|
|
|
* If the device language uses right to left text layout (hebrew, arabic, etc)
|
|
|
|
*/
|
|
|
|
public static boolean isRightToLeftTextLayout() {
|
2023-01-10 19:01:21 +01:00
|
|
|
if (isRightToLeftTextLayout == null) {
|
|
|
|
String displayLanguage = Locale.getDefault().getDisplayLanguage();
|
|
|
|
isRightToLeftTextLayout = new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft();
|
|
|
|
}
|
2022-12-31 07:38:47 +01:00
|
|
|
return isRightToLeftTextLayout;
|
|
|
|
}
|
|
|
|
|
2022-12-21 22:19:34 +01:00
|
|
|
/**
|
2023-04-02 16:10:01 +02:00
|
|
|
* Safe to call from any thread
|
|
|
|
*/
|
|
|
|
public static void showToastShort(@NonNull String messageToToast) {
|
|
|
|
showToast(messageToToast, Toast.LENGTH_SHORT);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Safe to call from any thread
|
|
|
|
*/
|
|
|
|
public static void showToastLong(@NonNull String messageToToast) {
|
|
|
|
showToast(messageToToast, Toast.LENGTH_LONG);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void showToast(@NonNull String messageToToast, int toastDuration) {
|
|
|
|
Objects.requireNonNull(messageToToast);
|
|
|
|
runOnMainThreadNowOrLater(() -> {
|
|
|
|
// cannot use getContext(), otherwise if context is null it will cause infinite recursion of error logging
|
|
|
|
if (context == null) {
|
|
|
|
LogHelper.printDebug(() -> "Cannot show toast (context is null)");
|
|
|
|
} else {
|
|
|
|
LogHelper.printDebug(() -> "Showing toast: " + messageToToast);
|
|
|
|
Toast.makeText(context, messageToToast, toastDuration).show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Automatically logs any exceptions the runnable throws.
|
|
|
|
*
|
|
|
|
* @see #runOnMainThreadNowOrLater(Runnable)
|
2022-12-21 22:19:34 +01:00
|
|
|
*/
|
2023-04-02 16:10:01 +02:00
|
|
|
public static void runOnMainThread(@NonNull Runnable runnable) {
|
2023-01-28 08:38:31 +01:00
|
|
|
runOnMainThreadDelayed(runnable, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Automatically logs any exceptions the runnable throws
|
|
|
|
*/
|
2023-04-02 16:10:01 +02:00
|
|
|
public static void runOnMainThreadDelayed(@NonNull Runnable runnable, long delayMillis) {
|
2023-01-28 08:38:31 +01:00
|
|
|
Runnable loggingRunnable = () -> {
|
2022-12-21 22:19:34 +01:00
|
|
|
try {
|
|
|
|
runnable.run();
|
|
|
|
} catch (Exception ex) {
|
2023-01-28 08:38:31 +01:00
|
|
|
LogHelper.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
2022-12-21 22:19:34 +01:00
|
|
|
}
|
|
|
|
};
|
2023-01-28 08:38:31 +01:00
|
|
|
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
2022-11-25 00:10:58 +01:00
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
/**
|
|
|
|
* If called from the main thread, the code is run immediately.<p>
|
|
|
|
* If called off the main thread, this is the same as {@link #runOnMainThread(Runnable)}.
|
|
|
|
*/
|
|
|
|
public static void runOnMainThreadNowOrLater(@NonNull Runnable runnable) {
|
|
|
|
if (isCurrentlyOnMainThread()) {
|
|
|
|
runnable.run();
|
|
|
|
} else {
|
|
|
|
runOnMainThread(runnable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-25 00:10:58 +01:00
|
|
|
/**
|
|
|
|
* @return if the calling thread is on the main thread
|
|
|
|
*/
|
2023-04-02 16:10:01 +02:00
|
|
|
public static boolean isCurrentlyOnMainThread() {
|
2022-11-25 00:10:58 +01:00
|
|
|
return Looper.getMainLooper().isCurrentThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-01-28 08:38:31 +01:00
|
|
|
* @throws IllegalStateException if the calling thread is _off_ the main thread
|
2022-11-25 00:10:58 +01:00
|
|
|
*/
|
|
|
|
public static void verifyOnMainThread() throws IllegalStateException {
|
2023-04-02 16:10:01 +02:00
|
|
|
if (!isCurrentlyOnMainThread()) {
|
2022-11-25 00:10:58 +01:00
|
|
|
throw new IllegalStateException("Must call _on_ the main thread");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-01-28 08:38:31 +01:00
|
|
|
* @throws IllegalStateException if the calling thread is _on_ the main thread
|
2022-11-25 00:10:58 +01:00
|
|
|
*/
|
|
|
|
public static void verifyOffMainThread() throws IllegalStateException {
|
2023-04-02 16:10:01 +02:00
|
|
|
if (isCurrentlyOnMainThread()) {
|
2022-11-25 00:10:58 +01:00
|
|
|
throw new IllegalStateException("Must call _off_ the main thread");
|
|
|
|
}
|
|
|
|
}
|
2023-04-02 16:10:01 +02:00
|
|
|
|
|
|
|
public static boolean isNetworkConnected() {
|
|
|
|
NetworkType networkType = getNetworkType();
|
|
|
|
return networkType == NetworkType.MOBILE
|
|
|
|
|| networkType == NetworkType.OTHER;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("MissingPermission") // permission already included in YouTube
|
|
|
|
public static NetworkType getNetworkType() {
|
|
|
|
Context networkContext = getContext();
|
|
|
|
if (networkContext == null) {
|
|
|
|
return NetworkType.NONE;
|
|
|
|
}
|
|
|
|
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
|
|
var networkInfo = cm.getActiveNetworkInfo();
|
|
|
|
|
|
|
|
if (networkInfo == null || !networkInfo.isConnected()) {
|
|
|
|
return NetworkType.NONE;
|
|
|
|
}
|
|
|
|
var type = networkInfo.getType();
|
|
|
|
return (type == ConnectivityManager.TYPE_MOBILE)
|
|
|
|
|| (type == ConnectivityManager.TYPE_BLUETOOTH) ? NetworkType.MOBILE : NetworkType.OTHER;
|
|
|
|
}
|
|
|
|
|
2023-05-13 01:24:44 +02:00
|
|
|
/**
|
|
|
|
* Hide a view by setting its layout params to 1x1
|
|
|
|
* @param view The view to hide.
|
|
|
|
*/
|
2023-05-10 01:04:38 +02:00
|
|
|
public static void hideViewByLayoutParams(View view) {
|
2023-05-13 01:24:44 +02:00
|
|
|
if (view instanceof LinearLayout) {
|
|
|
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(1, 1);
|
|
|
|
view.setLayoutParams(layoutParams);
|
|
|
|
} else if (view instanceof FrameLayout) {
|
|
|
|
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(1, 1);
|
|
|
|
view.setLayoutParams(layoutParams2);
|
|
|
|
} else if (view instanceof RelativeLayout) {
|
|
|
|
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(1, 1);
|
|
|
|
view.setLayoutParams(layoutParams3);
|
|
|
|
} else if (view instanceof Toolbar) {
|
|
|
|
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(1, 1);
|
|
|
|
view.setLayoutParams(layoutParams4);
|
|
|
|
} else if (view instanceof ViewGroup) {
|
|
|
|
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
|
|
|
view.setLayoutParams(layoutParams5);
|
|
|
|
} else {
|
|
|
|
LogHelper.printDebug(() -> "Hidden view with id " + view.getId());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-02 16:10:01 +02:00
|
|
|
public enum NetworkType {
|
|
|
|
NONE,
|
|
|
|
MOBILE,
|
|
|
|
OTHER,
|
|
|
|
}
|
2022-06-24 00:16:32 +02:00
|
|
|
}
|