Skip to content

Commit

Permalink
feat: Add Check environment patch (#683)
Browse files Browse the repository at this point in the history
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
  • Loading branch information
oSumAtrIX and LisoUseInAIKyrios authored Sep 6, 2024
1 parent a324b16 commit e856455
Show file tree
Hide file tree
Showing 9 changed files with 703 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,15 @@ private static void showBatteryOptimizationDialog(Activity context,
String dialogMessageRef,
String positiveButtonStringRef,
DialogInterface.OnClickListener onPositiveClickListener) {
// Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> {
new AlertDialog.Builder(context)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setTitle(str("gms_core_dialog_title"))
.setMessage(str(dialogMessageRef))
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
// Allow using back button to skip the action, just in case the check can never be satisfied.
.setCancelable(true)
.show();
}, 100);
// Do not set cancelable to false, to allow using back button to skip the action,
// just in case the check can never be satisfied.
var dialog = new AlertDialog.Builder(context)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setTitle(str("gms_core_dialog_title"))
.setMessage(str(dialogMessageRef))
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
.create();
Utils.showDialog(context, dialog);
}

/**
Expand Down
31 changes: 21 additions & 10 deletions app/src/main/java/app/revanced/integrations/shared/Logger.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.integrations.shared.settings.BaseSettings;

import java.io.PrintWriter;
import java.io.StringWriter;

import app.revanced.integrations.shared.settings.BaseSettings;
import static app.revanced.integrations.shared.settings.BaseSettings.*;

public class Logger {

/**
* Log messages using lambdas.
*/
@FunctionalInterface
public interface LogMessage {
@NonNull
String buildMessageString();
Expand Down Expand Up @@ -59,19 +56,33 @@ private String findOuterClassSimpleName() {
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
*/
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()) {
var messageString = message.buildMessageString();
String logMessage = message.buildMessageString();
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();

if (DEBUG_STACKTRACE.get()) {
var builder = new StringBuilder(messageString);
var builder = new StringBuilder(logMessage);
var sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(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);
}
}
}

Expand Down
81 changes: 81 additions & 0 deletions app/src/main/java/app/revanced/integrations/shared/Utils.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package app.revanced.integrations.shared;

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.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.Preference;
Expand Down Expand Up @@ -380,6 +385,82 @@ public static boolean containsNumber(@NonNull CharSequence text) {
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
*/
Expand Down
164 changes: 164 additions & 0 deletions app/src/main/java/app/revanced/integrations/shared/checks/Check.java
Original file line number Diff line number Diff line change
@@ -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>&nbsp;").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);
}
}
};
}
}
Loading

0 comments on commit e856455

Please sign in to comment.