mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-11-30 15:52:55 +01:00
feat: Add Check environment
patch (#683)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
a324b16096
commit
e856455283
@ -54,18 +54,15 @@ public class GmsCoreSupport {
|
|||||||
String dialogMessageRef,
|
String dialogMessageRef,
|
||||||
String positiveButtonStringRef,
|
String positiveButtonStringRef,
|
||||||
DialogInterface.OnClickListener onPositiveClickListener) {
|
DialogInterface.OnClickListener onPositiveClickListener) {
|
||||||
// Use a delay to allow the activity to finish initializing.
|
// Do not set cancelable to false, to allow using back button to skip the action,
|
||||||
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
// just in case the check can never be satisfied.
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
var dialog = new AlertDialog.Builder(context)
|
||||||
new AlertDialog.Builder(context)
|
.setIconAttribute(android.R.attr.alertDialogIcon)
|
||||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
.setTitle(str("gms_core_dialog_title"))
|
||||||
.setTitle(str("gms_core_dialog_title"))
|
.setMessage(str(dialogMessageRef))
|
||||||
.setMessage(str(dialogMessageRef))
|
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
|
||||||
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
|
.create();
|
||||||
// Allow using back button to skip the action, just in case the check can never be satisfied.
|
Utils.showDialog(context, dialog);
|
||||||
.setCancelable(true)
|
|
||||||
.show();
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
package app.revanced.integrations.shared;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG;
|
|
||||||
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_STACKTRACE;
|
|
||||||
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
import static app.revanced.integrations.shared.settings.BaseSettings.*;
|
||||||
|
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log messages using lambdas.
|
* Log messages using lambdas.
|
||||||
*/
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
public interface LogMessage {
|
public interface LogMessage {
|
||||||
@NonNull
|
@NonNull
|
||||||
String buildMessageString();
|
String buildMessageString();
|
||||||
@ -59,19 +56,33 @@ public class Logger {
|
|||||||
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||||
*/
|
*/
|
||||||
public static void printDebug(@NonNull LogMessage message) {
|
public static void printDebug(@NonNull LogMessage message) {
|
||||||
|
printDebug(message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs debug messages under the outer class name of the code calling this method.
|
||||||
|
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
|
||||||
|
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||||
|
*/
|
||||||
|
public static void printDebug(@NonNull LogMessage message, @Nullable Exception ex) {
|
||||||
if (DEBUG.get()) {
|
if (DEBUG.get()) {
|
||||||
var messageString = message.buildMessageString();
|
String logMessage = message.buildMessageString();
|
||||||
|
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
|
||||||
|
|
||||||
if (DEBUG_STACKTRACE.get()) {
|
if (DEBUG_STACKTRACE.get()) {
|
||||||
var builder = new StringBuilder(messageString);
|
var builder = new StringBuilder(logMessage);
|
||||||
var sw = new StringWriter();
|
var sw = new StringWriter();
|
||||||
new Throwable().printStackTrace(new PrintWriter(sw));
|
new Throwable().printStackTrace(new PrintWriter(sw));
|
||||||
|
|
||||||
builder.append('\n').append(sw);
|
builder.append('\n').append(sw);
|
||||||
messageString = builder.toString();
|
logMessage = builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(REVANCED_LOG_PREFIX + message.findOuterClassSimpleName(), messageString);
|
if (ex == null) {
|
||||||
|
Log.d(logTag, logMessage);
|
||||||
|
} else {
|
||||||
|
Log.d(logTag, logMessage, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package app.revanced.integrations.shared;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.DialogFragment;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
@ -8,6 +12,7 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
@ -380,6 +385,82 @@ public class Utils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore this class. It must be public to satisfy Android requirement.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static class DialogFragmentWrapper extends DialogFragment {
|
||||||
|
|
||||||
|
private Dialog dialog;
|
||||||
|
@Nullable
|
||||||
|
private DialogFragmentOnStartAction onStartAction;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
// Do not call super method to prevent state saving.
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
try {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
if (onStartAction != null) {
|
||||||
|
onStartAction.onStart((AlertDialog) getDialog());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "onStart failure: " + dialog.getClass().getSimpleName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for {@link #showDialog(Activity, AlertDialog, boolean, DialogFragmentOnStartAction)}.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DialogFragmentOnStartAction {
|
||||||
|
void onStart(AlertDialog dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showDialog(Activity activity, AlertDialog dialog) {
|
||||||
|
showDialog(activity, dialog, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to allow showing an AlertDialog on top of other alert dialogs.
|
||||||
|
* Calling this will always display the dialog on top of all other dialogs
|
||||||
|
* previously called using this method.
|
||||||
|
* <br>
|
||||||
|
* Be aware the on start action can be called multiple times for some situations,
|
||||||
|
* such as the user switching apps without dismissing the dialog then switching back to this app.
|
||||||
|
*<br>
|
||||||
|
* This method is only useful during app startup and multiple patches may show their own dialog,
|
||||||
|
* and the most important dialog can be called last (using a delay) so it's always on top.
|
||||||
|
*<br>
|
||||||
|
* For all other situations it's better to not use this method and
|
||||||
|
* call {@link AlertDialog#show()} on the dialog.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static void showDialog(Activity activity,
|
||||||
|
AlertDialog dialog,
|
||||||
|
boolean isCancelable,
|
||||||
|
@Nullable DialogFragmentOnStartAction onStartAction) {
|
||||||
|
verifyOnMainThread();
|
||||||
|
|
||||||
|
DialogFragmentWrapper fragment = new DialogFragmentWrapper();
|
||||||
|
fragment.dialog = dialog;
|
||||||
|
fragment.onStartAction = onStartAction;
|
||||||
|
fragment.setCancelable(isCancelable);
|
||||||
|
|
||||||
|
fragment.show(activity.getFragmentManager(), null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safe to call from any thread
|
* Safe to call from any thread
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,164 @@
|
|||||||
|
package app.revanced.integrations.shared.checks;
|
||||||
|
|
||||||
|
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.shared.Utils.DialogFragmentOnStartAction;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
|
abstract class Check {
|
||||||
|
private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2;
|
||||||
|
|
||||||
|
private static final int SECONDS_BEFORE_SHOWING_IGNORE_BUTTON = 15;
|
||||||
|
private static final int SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON = 10;
|
||||||
|
|
||||||
|
private static final Uri GOOD_SOURCE = Uri.parse("https://revanced.app");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the check conclusively passed or failed. A null value indicates it neither passed nor failed.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected abstract Boolean check();
|
||||||
|
|
||||||
|
protected abstract String failureReason();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a sorting order for displaying the checks that failed.
|
||||||
|
* A lower value indicates to show first before other checks.
|
||||||
|
*/
|
||||||
|
public abstract int uiSortingValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For debugging and development only.
|
||||||
|
* Forces all checks to be performed and the check failed dialog to be shown.
|
||||||
|
* Can be enabled by importing settings text with {@link Settings#CHECK_ENVIRONMENT_WARNINGS_ISSUED}
|
||||||
|
* set to -1.
|
||||||
|
*/
|
||||||
|
static boolean debugAlwaysShowWarning() {
|
||||||
|
final boolean alwaysShowWarning = Settings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get() < 0;
|
||||||
|
if (alwaysShowWarning) {
|
||||||
|
Logger.printInfo(() -> "Debug forcing environment check warning to show");
|
||||||
|
}
|
||||||
|
|
||||||
|
return alwaysShowWarning;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean shouldRun() {
|
||||||
|
return Settings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()
|
||||||
|
< NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disableForever() {
|
||||||
|
Logger.printInfo(() -> "Environment checks disabled forever");
|
||||||
|
|
||||||
|
Settings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
static void issueWarning(Activity activity, Collection<Check> failedChecks) {
|
||||||
|
final var reasons = new StringBuilder();
|
||||||
|
|
||||||
|
reasons.append("<ul>");
|
||||||
|
for (var check : failedChecks) {
|
||||||
|
// Add a non breaking space to fix bullet points spacing issue.
|
||||||
|
reasons.append("<li> ").append(check.failureReason());
|
||||||
|
}
|
||||||
|
reasons.append("</ul>");
|
||||||
|
|
||||||
|
var message = Html.fromHtml(
|
||||||
|
str("revanced_check_environment_failed_message", reasons.toString()),
|
||||||
|
FROM_HTML_MODE_COMPACT
|
||||||
|
);
|
||||||
|
|
||||||
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
|
AlertDialog alert = new AlertDialog.Builder(activity)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setIconAttribute(android.R.attr.alertDialogIcon)
|
||||||
|
.setTitle(str("revanced_check_environment_failed_title"))
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(
|
||||||
|
" ",
|
||||||
|
(dialog, which) -> {
|
||||||
|
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
|
||||||
|
// Shutdown to prevent the user from navigating back to this app,
|
||||||
|
// which is no longer showing a warning dialog.
|
||||||
|
activity.finishAffinity();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
).setNegativeButton(
|
||||||
|
" ",
|
||||||
|
(dialog, which) -> {
|
||||||
|
// Cleanup data if the user incorrectly imported a huge negative number.
|
||||||
|
final int current = Math.max(0, Settings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
|
||||||
|
Settings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
).create();
|
||||||
|
|
||||||
|
Utils.showDialog(activity, alert, false, new DialogFragmentOnStartAction() {
|
||||||
|
boolean hasRun;
|
||||||
|
@Override
|
||||||
|
public void onStart(AlertDialog dialog) {
|
||||||
|
// Only run this once, otherwise if the user changes to a different app
|
||||||
|
// then changes back, this handler will run again and disable the buttons.
|
||||||
|
if (hasRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hasRun = true;
|
||||||
|
|
||||||
|
var openWebsiteButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||||
|
openWebsiteButton.setEnabled(false);
|
||||||
|
|
||||||
|
var dismissButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
|
||||||
|
dismissButton.setEnabled(false);
|
||||||
|
|
||||||
|
getCountdownRunnable(dismissButton, openWebsiteButton).run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Runnable getCountdownRunnable(Button dismissButton, Button openWebsiteButton) {
|
||||||
|
return new Runnable() {
|
||||||
|
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Utils.verifyOnMainThread();
|
||||||
|
|
||||||
|
if (secondsRemaining > 0) {
|
||||||
|
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON == 0) {
|
||||||
|
openWebsiteButton.setText(str("revanced_check_environment_dialog_open_official_source_button"));
|
||||||
|
openWebsiteButton.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
secondsRemaining--;
|
||||||
|
|
||||||
|
Utils.runOnMainThreadDelayed(this, 1000);
|
||||||
|
} else {
|
||||||
|
dismissButton.setText(str("revanced_check_environment_dialog_ignore_button"));
|
||||||
|
dismissButton.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,369 @@
|
|||||||
|
package app.revanced.integrations.shared.checks;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Base64;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.shared.checks.Check.debugAlwaysShowWarning;
|
||||||
|
import static app.revanced.integrations.shared.checks.PatchInfo.Build.*;
|
||||||
|
import static app.revanced.integrations.shared.checks.PatchInfo.PATCH_TIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to check if the app was patched by the user
|
||||||
|
* and not downloaded pre-patched, because pre-patched apps are difficult to trust.
|
||||||
|
* <br>
|
||||||
|
* Various indicators help to detect if the app was patched by the user.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class CheckEnvironmentPatch {
|
||||||
|
private static final boolean DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG = debugAlwaysShowWarning();
|
||||||
|
|
||||||
|
private enum InstallationType {
|
||||||
|
/**
|
||||||
|
* CLI patching, manual installation of a previously patched using adb,
|
||||||
|
* or root installation if stock app is first installed using adb.
|
||||||
|
*/
|
||||||
|
ADB((String) null),
|
||||||
|
ROOT_MOUNT_ON_APP_STORE("com.android.vending"),
|
||||||
|
MANAGER("app.revanced.manager.flutter",
|
||||||
|
"app.revanced.manager",
|
||||||
|
"app.revanced.manager.debug");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static InstallationType installTypeFromPackageName(@Nullable String packageName) {
|
||||||
|
for (InstallationType type : values()) {
|
||||||
|
for (String installPackageName : type.packageNames) {
|
||||||
|
if (Objects.equals(installPackageName, packageName)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array elements can be null.
|
||||||
|
*/
|
||||||
|
final String[] packageNames;
|
||||||
|
|
||||||
|
InstallationType(String... packageNames) {
|
||||||
|
this.packageNames = packageNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the app is installed by the manager, the app store, or through adb/CLI.
|
||||||
|
* <br>
|
||||||
|
* Does not conclusively
|
||||||
|
* If the app is installed by the manager or the app store, it is likely, the app was patched using the manager,
|
||||||
|
* or installed manually via ADB (in the case of ReVanced CLI for example).
|
||||||
|
* <br>
|
||||||
|
* If the app is not installed by the manager or the app store, then the app was likely downloaded pre-patched
|
||||||
|
* and installed by the browser or another unknown app.
|
||||||
|
*/
|
||||||
|
private static class CheckExpectedInstaller extends Check {
|
||||||
|
@Nullable
|
||||||
|
InstallationType installerFound;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Boolean check() {
|
||||||
|
final var context = Utils.getContext();
|
||||||
|
|
||||||
|
final var installerPackageName =
|
||||||
|
context.getPackageManager().getInstallerPackageName(context.getPackageName());
|
||||||
|
|
||||||
|
Logger.printInfo(() -> "Installed by: " + installerPackageName);
|
||||||
|
|
||||||
|
installerFound = InstallationType.installTypeFromPackageName(installerPackageName);
|
||||||
|
final boolean passed = (installerFound != null);
|
||||||
|
|
||||||
|
Logger.printInfo(() -> passed
|
||||||
|
? "Apk was not installed from an unknown source"
|
||||||
|
: "Apk was installed from an unknown source");
|
||||||
|
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String failureReason() {
|
||||||
|
return str("revanced_check_environment_manager_not_expected_installer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int uiSortingValue() {
|
||||||
|
return -100; // Show first.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the build properties are the same as during the patch.
|
||||||
|
* <br>
|
||||||
|
* If the build properties are the same as during the patch, it is likely, the app was patched on the same device.
|
||||||
|
* <br>
|
||||||
|
* If the build properties are different, the app was likely downloaded pre-patched or patched on another device.
|
||||||
|
*/
|
||||||
|
private static class CheckWasPatchedOnSameDevice extends Check {
|
||||||
|
@SuppressLint({"NewApi", "HardwareIds"})
|
||||||
|
@Override
|
||||||
|
protected Boolean check() {
|
||||||
|
if (PATCH_BOARD.isEmpty()) {
|
||||||
|
// Did not patch with Manager, and cannot conclusively say where this was from.
|
||||||
|
Logger.printInfo(() -> "APK does not contain a hardware signature and cannot compare to current device");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection deprecation
|
||||||
|
final var passed = buildFieldEqualsHash("BOARD", Build.BOARD, PATCH_BOARD) &
|
||||||
|
buildFieldEqualsHash("BOOTLOADER", Build.BOOTLOADER, PATCH_BOOTLOADER) &
|
||||||
|
buildFieldEqualsHash("BRAND", Build.BRAND, PATCH_BRAND) &
|
||||||
|
buildFieldEqualsHash("CPU_ABI", Build.CPU_ABI, PATCH_CPU_ABI) &
|
||||||
|
buildFieldEqualsHash("CPU_ABI2", Build.CPU_ABI2, PATCH_CPU_ABI2) &
|
||||||
|
buildFieldEqualsHash("DEVICE", Build.DEVICE, PATCH_DEVICE) &
|
||||||
|
buildFieldEqualsHash("DISPLAY", Build.DISPLAY, PATCH_DISPLAY) &
|
||||||
|
buildFieldEqualsHash("FINGERPRINT", Build.FINGERPRINT, PATCH_FINGERPRINT) &
|
||||||
|
buildFieldEqualsHash("HARDWARE", Build.HARDWARE, PATCH_HARDWARE) &
|
||||||
|
buildFieldEqualsHash("HOST", Build.HOST, PATCH_HOST) &
|
||||||
|
buildFieldEqualsHash("ID", Build.ID, PATCH_ID) &
|
||||||
|
buildFieldEqualsHash("MANUFACTURER", Build.MANUFACTURER, PATCH_MANUFACTURER) &
|
||||||
|
buildFieldEqualsHash("MODEL", Build.MODEL, PATCH_MODEL) &
|
||||||
|
buildFieldEqualsHash("ODM_SKU", Build.ODM_SKU, PATCH_ODM_SKU) &
|
||||||
|
buildFieldEqualsHash("PRODUCT", Build.PRODUCT, PATCH_PRODUCT) &
|
||||||
|
buildFieldEqualsHash("RADIO", Build.RADIO, PATCH_RADIO) &
|
||||||
|
buildFieldEqualsHash("SKU", Build.SKU, PATCH_SKU) &
|
||||||
|
buildFieldEqualsHash("SOC_MANUFACTURER", Build.SOC_MANUFACTURER, PATCH_SOC_MANUFACTURER) &
|
||||||
|
buildFieldEqualsHash("SOC_MODEL", Build.SOC_MODEL, PATCH_SOC_MODEL) &
|
||||||
|
buildFieldEqualsHash("TAGS", Build.TAGS, PATCH_TAGS) &
|
||||||
|
buildFieldEqualsHash("TYPE", Build.TYPE, PATCH_TYPE) &
|
||||||
|
buildFieldEqualsHash("USER", Build.USER, PATCH_USER);
|
||||||
|
|
||||||
|
Logger.printInfo(() -> passed
|
||||||
|
? "Device hardware signature matches current device"
|
||||||
|
: "Device hardware signature does not match current device");
|
||||||
|
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String failureReason() {
|
||||||
|
return str("revanced_check_environment_not_same_patching_device");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int uiSortingValue() {
|
||||||
|
return 0; // Show in the middle.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the app was installed within the last 30 minutes after being patched.
|
||||||
|
* <br>
|
||||||
|
* If the app was installed within the last 30 minutes, it is likely, the app was patched by the user.
|
||||||
|
* <br>
|
||||||
|
* If the app was installed much later than the patch time, it is likely the app was
|
||||||
|
* downloaded pre-patched or the user waited too long to install the app.
|
||||||
|
*/
|
||||||
|
private static class CheckIsNearPatchTime extends Check {
|
||||||
|
/**
|
||||||
|
* How soon after patching the app must be first launched.
|
||||||
|
*/
|
||||||
|
static final int THRESHOLD_FOR_PATCHING_RECENTLY = 30 * 60 * 1000; // 30 minutes.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How soon after installation or updating the app to check the patch time.
|
||||||
|
* If the install/update is older than this, this entire check is ignored
|
||||||
|
* to prevent showing any errors if the user clears the app data after installation.
|
||||||
|
*/
|
||||||
|
static final int THRESHOLD_FOR_RECENT_INSTALLATION = 12 * 60 * 60 * 1000; // 12 hours.
|
||||||
|
|
||||||
|
static final long DURATION_SINCE_PATCHING = System.currentTimeMillis() - PATCH_TIME;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean check() {
|
||||||
|
Logger.printInfo(() -> "Installed: " + (DURATION_SINCE_PATCHING / 1000) + " seconds after patching");
|
||||||
|
|
||||||
|
// Also verify patched time is not in the future.
|
||||||
|
if (DURATION_SINCE_PATCHING < 0) {
|
||||||
|
// Patch time is in the future and clearly wrong.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DURATION_SINCE_PATCHING < THRESHOLD_FOR_PATCHING_RECENTLY) {
|
||||||
|
// App is recently patched and this installation is new or recently updated.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the app install/update is recent,
|
||||||
|
// to prevent showing errors if the user later clears the app data.
|
||||||
|
try {
|
||||||
|
Context context = Utils.getContext();
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
|
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||||
|
|
||||||
|
// Duration since initial install or last update, which ever is sooner.
|
||||||
|
final long durationSinceInstallUpdate = System.currentTimeMillis() - packageInfo.lastUpdateTime;
|
||||||
|
Logger.printInfo(() -> "App was installed/updated: "
|
||||||
|
+ (durationSinceInstallUpdate / (60 * 60 * 1000)) + " hours ago");
|
||||||
|
|
||||||
|
if (durationSinceInstallUpdate > THRESHOLD_FOR_RECENT_INSTALLATION) {
|
||||||
|
Logger.printInfo(() -> "Ignoring install time check since install/update was over "
|
||||||
|
+ THRESHOLD_FOR_RECENT_INSTALLATION + " hours ago");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException ex) {
|
||||||
|
Logger.printException(() -> "Package name not found exception", ex); // Will never happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was patched between 30 minutes and 12 hours ago.
|
||||||
|
// This can only happen if someone installs the app then waits 30+ minutes to launch,
|
||||||
|
// or they clear the app data within 12 hours after installation.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String failureReason() {
|
||||||
|
if (DURATION_SINCE_PATCHING < 0) {
|
||||||
|
// Could happen if the user has their device clock incorrectly set in the past,
|
||||||
|
// but assume that isn't the case and the apk was patched on a device with the wrong system time.
|
||||||
|
return str("revanced_check_environment_not_near_patch_time_invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If patched over 1 day ago, show how old this pre-patched apk is.
|
||||||
|
// Showing the age can help convey it's better to patch yourself and know it's the latest.
|
||||||
|
final long oneDay = 24 * 60 * 60 * 1000;
|
||||||
|
final long daysSincePatching = DURATION_SINCE_PATCHING / oneDay;
|
||||||
|
if (daysSincePatching > 1) { // Use over 1 day to avoid singular vs plural strings.
|
||||||
|
return str("revanced_check_environment_not_near_patch_time_days", daysSincePatching);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str("revanced_check_environment_not_near_patch_time");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int uiSortingValue() {
|
||||||
|
return 100; // Show last.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void check(Activity context) {
|
||||||
|
// If the warning was already issued twice, or if the check was successful in the past,
|
||||||
|
// do not run the checks again.
|
||||||
|
if (!Check.shouldRun() && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||||
|
Logger.printDebug(() -> "Environment checks are disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.runOnBackgroundThread(() -> {
|
||||||
|
try {
|
||||||
|
Logger.printInfo(() -> "Running environment checks");
|
||||||
|
List<Check> failedChecks = new ArrayList<>();
|
||||||
|
|
||||||
|
CheckWasPatchedOnSameDevice sameHardware = new CheckWasPatchedOnSameDevice();
|
||||||
|
Boolean hardwareCheckPassed = sameHardware.check();
|
||||||
|
if (hardwareCheckPassed != null) {
|
||||||
|
if (hardwareCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||||
|
// Patched on the same device using Manager,
|
||||||
|
// and no further checks are needed.
|
||||||
|
Check.disableForever();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
failedChecks.add(sameHardware);
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
||||||
|
Boolean timeCheckPassed = nearPatchTime.check();
|
||||||
|
if (timeCheckPassed != null) {
|
||||||
|
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||||
|
if (failedChecks.isEmpty()) {
|
||||||
|
// Recently patched and installed. No further checks are needed.
|
||||||
|
// Stopping here also prevents showing warnings if patching and installing with Termux.
|
||||||
|
Check.disableForever();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failedChecks.add(nearPatchTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckExpectedInstaller installerCheck = new CheckExpectedInstaller();
|
||||||
|
// If the installer package is Manager but this code is reached,
|
||||||
|
// that means it must not be the right Manager otherwise the hardware hash
|
||||||
|
// signatures would be present and this check would not have run.
|
||||||
|
final boolean isManagerInstall = installerCheck.installerFound == InstallationType.MANAGER;
|
||||||
|
if (!installerCheck.check() || isManagerInstall) {
|
||||||
|
failedChecks.add(installerCheck);
|
||||||
|
|
||||||
|
if (isManagerInstall) {
|
||||||
|
// If using Manager and reached here, then this must
|
||||||
|
// have been patched on a different device.
|
||||||
|
failedChecks.add(sameHardware);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||||
|
// Show all failures for debugging layout.
|
||||||
|
failedChecks = Arrays.asList(
|
||||||
|
sameHardware,
|
||||||
|
nearPatchTime,
|
||||||
|
installerCheck
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedChecks.isEmpty()) {
|
||||||
|
Check.disableForever();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection ComparatorCombinators
|
||||||
|
Collections.sort(failedChecks, (o1, o2) -> o1.uiSortingValue() - o2.uiSortingValue());
|
||||||
|
|
||||||
|
Check.issueWarning(
|
||||||
|
context,
|
||||||
|
failedChecks
|
||||||
|
);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "check failure", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean buildFieldEqualsHash(String buildFieldName, String buildFieldValue, @Nullable String hash) {
|
||||||
|
try {
|
||||||
|
final var sha1 = MessageDigest.getInstance("SHA-1")
|
||||||
|
.digest(buildFieldValue.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// Must be careful to use same base64 encoding Kotlin uses.
|
||||||
|
String runtimeHash = new String(Base64.encode(sha1, Base64.NO_WRAP), StandardCharsets.ISO_8859_1);
|
||||||
|
final boolean equals = runtimeHash.equals(hash);
|
||||||
|
if (!equals) {
|
||||||
|
Logger.printInfo(() -> "Hashes do not match. " + buildFieldName + ": '" + buildFieldValue
|
||||||
|
+ "' runtimeHash: '" + runtimeHash + "' patchTimeHash: '" + hash + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return equals;
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
Logger.printException(() -> "buildFieldEqualsHash failure", ex); // Will never happen.
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.integrations.shared.checks;
|
||||||
|
|
||||||
|
// Fields are set by the patch. Do not modify.
|
||||||
|
// Fields are not final, because the compiler is inlining them.
|
||||||
|
final class PatchInfo {
|
||||||
|
static long PATCH_TIME = 0L;
|
||||||
|
|
||||||
|
final static class Build {
|
||||||
|
static String PATCH_BOARD = "";
|
||||||
|
static String PATCH_BOOTLOADER = "";
|
||||||
|
static String PATCH_BRAND = "";
|
||||||
|
static String PATCH_CPU_ABI = "";
|
||||||
|
static String PATCH_CPU_ABI2 = "";
|
||||||
|
static String PATCH_DEVICE = "";
|
||||||
|
static String PATCH_DISPLAY = "";
|
||||||
|
static String PATCH_FINGERPRINT = "";
|
||||||
|
static String PATCH_HARDWARE = "";
|
||||||
|
static String PATCH_HOST = "";
|
||||||
|
static String PATCH_ID = "";
|
||||||
|
static String PATCH_MANUFACTURER = "";
|
||||||
|
static String PATCH_MODEL = "";
|
||||||
|
static String PATCH_ODM_SKU = "";
|
||||||
|
static String PATCH_PRODUCT = "";
|
||||||
|
static String PATCH_RADIO = "";
|
||||||
|
static String PATCH_SERIAL = "";
|
||||||
|
static String PATCH_SKU = "";
|
||||||
|
static String PATCH_SOC_MANUFACTURER = "";
|
||||||
|
static String PATCH_SOC_MODEL = "";
|
||||||
|
static String PATCH_TAGS = "";
|
||||||
|
static String PATCH_TYPE = "";
|
||||||
|
static String PATCH_USER = "";
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThread(() -> {
|
||||||
var alertDialog = new android.app.AlertDialog.Builder(context)
|
var alert = new android.app.AlertDialog.Builder(context)
|
||||||
.setTitle(str("revanced_check_watch_history_domain_name_dialog_title"))
|
.setTitle(str("revanced_check_watch_history_domain_name_dialog_title"))
|
||||||
.setMessage(Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")))
|
.setMessage(Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")))
|
||||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
.setIconAttribute(android.R.attr.alertDialogIcon)
|
||||||
@ -64,9 +64,9 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
|
|||||||
}).setNegativeButton(str("revanced_check_watch_history_domain_name_dialog_ignore"), (dialog, which) -> {
|
}).setNegativeButton(str("revanced_check_watch_history_domain_name_dialog_ignore"), (dialog, which) -> {
|
||||||
Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false);
|
Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
})
|
}).create();
|
||||||
.setCancelable(false)
|
|
||||||
.show();
|
Utils.showDialog(context, alert, false, null);
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "checkDnsResolver failure", ex);
|
Logger.printException(() -> "checkDnsResolver failure", ex);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.revanced.integrations.youtube.patches.announcements;
|
package app.revanced.integrations.youtube.patches.announcements;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
@ -103,8 +104,6 @@ public final class AnnouncementsPatch {
|
|||||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||||
if (Settings.ANNOUNCEMENT_LAST_ID.get() == id) return;
|
if (Settings.ANNOUNCEMENT_LAST_ID.get() == id) return;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int finalId = id;
|
int finalId = id;
|
||||||
final var finalTitle = title;
|
final var finalTitle = title;
|
||||||
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
||||||
@ -112,7 +111,7 @@ public final class AnnouncementsPatch {
|
|||||||
|
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThread(() -> {
|
||||||
// Show the announcement.
|
// Show the announcement.
|
||||||
var alertDialog = new android.app.AlertDialog.Builder(context)
|
var alert = new AlertDialog.Builder(context)
|
||||||
.setTitle(finalTitle)
|
.setTitle(finalTitle)
|
||||||
.setMessage(finalMessage)
|
.setMessage(finalMessage)
|
||||||
.setIcon(finalLevel.icon)
|
.setIcon(finalLevel.icon)
|
||||||
@ -123,11 +122,13 @@ public final class AnnouncementsPatch {
|
|||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
})
|
})
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.show();
|
.create();
|
||||||
|
|
||||||
// Make links clickable.
|
Utils.showDialog(context, alert, false, (AlertDialog dialog) -> {
|
||||||
((TextView)alertDialog.findViewById(android.R.id.message))
|
// Make links clickable.
|
||||||
.setMovementMethod(LinkMovementMethod.getInstance());
|
((TextView) dialog.findViewById(android.R.id.message))
|
||||||
|
.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
final var message = "Failed to get announcement";
|
final var message = "Failed to get announcement";
|
||||||
|
@ -1,18 +1,5 @@
|
|||||||
package app.revanced.integrations.youtube.settings;
|
package app.revanced.integrations.youtube.settings;
|
||||||
|
|
||||||
import static java.lang.Boolean.FALSE;
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
import static app.revanced.integrations.shared.settings.Setting.*;
|
|
||||||
import static app.revanced.integrations.youtube.patches.MiniplayerPatch.MiniplayerType;
|
|
||||||
import static app.revanced.integrations.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_1;
|
|
||||||
import static app.revanced.integrations.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_3;
|
|
||||||
import static app.revanced.integrations.youtube.patches.spoof.SpoofClientPatch.ClientType;
|
|
||||||
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.*;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.settings.*;
|
import app.revanced.integrations.shared.settings.*;
|
||||||
import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
|
import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
|
||||||
@ -24,6 +11,19 @@ import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch;
|
|||||||
import app.revanced.integrations.youtube.patches.spoof.SpoofClientPatch;
|
import app.revanced.integrations.youtube.patches.spoof.SpoofClientPatch;
|
||||||
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
|
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.settings.Setting.*;
|
||||||
|
import static app.revanced.integrations.youtube.patches.MiniplayerPatch.MiniplayerType;
|
||||||
|
import static app.revanced.integrations.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_1;
|
||||||
|
import static app.revanced.integrations.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_3;
|
||||||
|
import static app.revanced.integrations.youtube.patches.spoof.SpoofClientPatch.ClientType;
|
||||||
|
import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.*;
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends BaseSettings {
|
||||||
// Video
|
// Video
|
||||||
@ -264,6 +264,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1);
|
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1);
|
||||||
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
||||||
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
||||||
|
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user