Fix sentry support + misc androidacy updates

Sentry init is completely disabled if crash_reporting is false, which means now to change that preference an app restart is necessary.

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/208/head
androidacy-user 2 years ago
parent d9ba93f41c
commit 72a26a0103

@ -13,8 +13,7 @@ android {
minSdk 21
targetSdk 33
versionCode 57
versionName "0.6.6"
versionName "0.6.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

@ -113,10 +113,10 @@
android:name="androidx.work.WorkManagerInitializer"
tools:node="remove" />
</provider>
<meta-data android:name="io.sentry.auto-init" android:value="false" />
<meta-data
android:name="io.sentry.dsn"
android:value="https://ba5cb5ef513b423cbd54a2a8457113b1@sentry.androidacy.com/7" />
android:value="https://cdcdb0efca4a42a28df90e4b7f087347@sentry.androidacy.com/2" />
<!-- Sane value, but feel free to lower it -->
<meta-data
android:name="io.sentry.traces.sample-rate"

@ -1,5 +1,8 @@
package com.fox2code.mmm;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Intent;
@ -28,6 +31,8 @@ import com.fox2code.mmm.utils.Http;
import com.fox2code.rosettax.LanguageSwitcher;
import com.topjohnwu.superuser.Shell;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@ -43,6 +48,10 @@ import io.noties.markwon.syntax.Prism4jThemeDefault;
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
import io.noties.prism4j.Prism4j;
import io.noties.prism4j.annotations.PrismBundle;
import io.sentry.JsonObjectWriter;
import io.sentry.NoOpLogger;
import io.sentry.android.core.SentryAndroid;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
@PrismBundle(
includeAll = true,
@ -180,10 +189,10 @@ public class MainApplication extends FoxApplication
getSharedPreferences().edit().putBoolean("has_root_access", bool).apply();
}
/*public static boolean isCrashReportingEnabled() {
public static boolean isCrashReportingEnabled() {
return getSharedPreferences().getBoolean("pref_crash_reporting",
BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING && !BuildConfig.DEBUG);
}*/
}
public static SharedPreferences getBootSharedPreferences() {
return bootSharedPreferences;
@ -360,73 +369,60 @@ public class MainApplication extends FoxApplication
}, "Emoji compat init.").start();
}
/*SentryAndroid.init(this, options -> {
options.addIntegration(new FragmentLifecycleIntegration(this, true, false));
// Note: Sentry library only take a screenshot of Fox Magisk Module Manager.
// The screen shot doesn't and cannot contain other applications (if in multi windows)
// status bar and notifications (even if notification shade is pulled down)
// In the possibility you find this library sending anything listed above,
// it's a serious bug and a security issue you should report to Google
// Google bug bounties on Android are huge, so you can also get rich by doing that.
options.setAttachScreenshot(true);
// User interaction tracing is not needed to get context of crash
options.setEnableUserInteractionTracing(false);
// Send client reports has nothing to do with error reporting
options.setSendClientReports(false);
// Auto session tracking has nothing to do with error reporting
options.setEnableAutoSessionTracking(false);
// Add a callback that will be used before the event is sent to Sentry.
// With this callback, you can modify the event or, when returning null, also discard the event.
options.setBeforeSend((event, hint) -> {
if (BuildConfig.DEBUG) { // Debug sentry events for debug.
StringBuilder stringBuilder = new StringBuilder("Sentry report debug: ");
try {
event.serialize(new JsonObjectWriter(new Writer() {
@Override
public void write(char[] cbuf) {
stringBuilder.append(cbuf);
}
@Override
public void write(String str) {
stringBuilder.append(str);
}
@Override
public void write(char[] chars, int i, int i1) {
stringBuilder.append(chars, i, i1);
}
@Override
public void write(String str, int off, int len) {
stringBuilder.append(str, off, len);
}
@Override
public void flush() {}
@Override
public void close() {}
}, 4), NoOpLogger.getInstance());
} catch (IOException ignored) {}
Log.i(TAG, stringBuilder.toString());
}
// Check saved preferences to see if the user has opted out of crash reporting.
// If the user has opted out, return null.
if (isCrashReportingEnabled()) {
Log.i(TAG, "Relayed sentry report according to user preference");
SentryAndroid.init(this, options -> {
// If crash reporting is disabled, stop here.
if (!sharedPreferences.getBoolean("pref_crash_reporting", true)) {
options.setDsn("");
} else {
options.addIntegration(new FragmentLifecycleIntegration(this, true, true));
// Sentry sends ABSOLUTELY NO Personally Identifiable Information (PII) by default.
// A screenshot of the app itself is only sent if the app crashes, and it only shows the last activity
// In addition, sentry is configured with a trusted third party other than sentry.io, and only trusted people have access to the sentry instance
// Add a callback that will be used before the event is sent to Sentry.
// With this callback, you can modify the event or, when returning null, also discard the event.
options.setBeforeSend((event, hint) -> {
if (BuildConfig.DEBUG) { // Debug sentry events for debug.
StringBuilder stringBuilder = new StringBuilder("Sentry report debug: ");
try {
event.serialize(new JsonObjectWriter(new Writer() {
@Override
public void write(char[] cbuf) {
stringBuilder.append(cbuf);
}
@Override
public void write(String str) {
stringBuilder.append(str);
}
@Override
public void write(char[] chars, int i, int i1) {
stringBuilder.append(chars, i, i1);
}
@Override
public void write(String str, int off, int len) {
stringBuilder.append(str, off, len);
}
@Override
public void flush() {
}
@Override
public void close() {
}
}, 4), NoOpLogger.getInstance());
} catch (IOException ignored) {
}
Log.i(TAG, stringBuilder.toString());
}
// We already know that the user has opted in to crash reporting, so we don't need to ask again.
return event;
} else {
Log.i(TAG, "Blocked sentry report according to user preference");
// We need to do this to avoid crash delay on crash when the event is dropped
DiskFlushNotification diskFlushNotification = hint.getAs(
TypeCheckHint.SENTRY_TYPE_CHECK_HINT, DiskFlushNotification.class);
if (diskFlushNotification != null) diskFlushNotification.markFlushed();
return null;
}
});
});*/
});
}
});
}
@Override

@ -32,7 +32,6 @@ import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
@ -60,6 +59,7 @@ public final class AndroidacyActivity extends FoxActivity {
AndroidacyWebAPI androidacyWebAPI;
boolean backOnResume;
@SuppressWarnings("deprecation")
@Override
@SuppressLint({"SetJavaScriptEnabled", "JavascriptInterface"})
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -115,7 +115,8 @@ public final class AndroidacyActivity extends FoxActivity {
IntentHelper.openConfig(this, config);
return true;
});
} catch (PackageManager.NameNotFoundException ignored) {}
} catch (PackageManager.NameNotFoundException ignored) {
}
}
}
this.webView = this.findViewById(R.id.webView);
@ -125,12 +126,21 @@ public final class AndroidacyActivity extends FoxActivity {
webSettings.setDomStorageEnabled(true);
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowFileAccess(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Make website follow app theme
webSettings.setForceDark(MainApplication.getINSTANCE().isLightTheme() ?
WebSettings.FORCE_DARK_OFF : WebSettings.FORCE_DARK_ON);
} else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
WebSettingsCompat.setForceDark(webSettings, MainApplication.getINSTANCE().isLightTheme() ?
WebSettingsCompat.FORCE_DARK_OFF : WebSettingsCompat.FORCE_DARK_ON);
// If API level is .= 33, allow setAlgorithmicDarkeningAllowed
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
try {
webSettings.setAlgorithmicDarkeningAllowed(true);
} catch (NoSuchMethodError ignored) {
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Make website follow app theme
webSettings.setForceDark(MainApplication.getINSTANCE().isLightTheme() ?
WebSettings.FORCE_DARK_OFF : WebSettings.FORCE_DARK_ON);
} else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
// If api level is < 32, use force dark
WebSettingsCompat.setForceDark(webSettings, MainApplication.getINSTANCE().isLightTheme() ?
WebSettingsCompat.FORCE_DARK_OFF : WebSettingsCompat.FORCE_DARK_ON);
}
}
this.webView.setWebViewClient(new WebViewClientCompat() {
private String pageUrl;
@ -173,7 +183,7 @@ public final class AndroidacyActivity extends FoxActivity {
}
private void onReceivedError(String url, int errorCode) {
if ((url.startsWith("https://api.androidacy.com/magisk/") ||
if ((url.startsWith("https://production-api.androidacy.com/magisk/") ||
url.startsWith("https://staging-api.androidacy.com/magisk/") ||
url.equals(pageUrl)) && (errorCode == 419 || errorCode == 429 || errorCode == 503)) {
Toast.makeText(AndroidacyActivity.this,
@ -247,33 +257,33 @@ public final class AndroidacyActivity extends FoxActivity {
if (moduleId != null) {
webView.evaluateJavascript("document.querySelector(" +
"\"#download-form input[name=_token]\").value",
result -> new Thread("Androidacy popup workaround thread") {
@Override
public void run() {
if (androidacyWebAPI.consumedAction) return;
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("moduleId", moduleId);
jsonObject.put("token", AndroidacyRepoData
.getInstance().getToken());
jsonObject.put("_token", result);
String realUrl = Http.doHttpPostRedirect(downloadUrl,
jsonObject.toString(), true);
if (downloadUrl.equals(realUrl)) {
Log.e(TAG, "Failed to resolve URL from " +
downloadUrl);
AndroidacyActivity.this.megaIntercept(
webView.getUrl(), downloadUrl);
return;
result -> new Thread("Androidacy popup workaround thread") {
@Override
public void run() {
if (androidacyWebAPI.consumedAction) return;
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("moduleId", moduleId);
jsonObject.put("token", AndroidacyRepoData
.getInstance().getToken());
jsonObject.put("_token", result);
String realUrl = Http.doHttpPostRedirect(downloadUrl,
jsonObject.toString(), true);
if (downloadUrl.equals(realUrl)) {
Log.e(TAG, "Failed to resolve URL from " +
downloadUrl);
AndroidacyActivity.this.megaIntercept(
webView.getUrl(), downloadUrl);
return;
}
Log.i(TAG, "Got url: " + realUrl);
androidacyWebAPI.openNativeModuleDialogRaw(realUrl,
moduleId, "", androidacyWebAPI.canInstall());
} catch (IOException | JSONException e) {
Log.e(TAG, "Failed redirect intercept", e);
}
Log.i(TAG, "Got url: " + realUrl);
androidacyWebAPI.openNativeModuleDialogRaw(realUrl,
moduleId, "", androidacyWebAPI.canInstall());
} catch (IOException | JSONException e) {
Log.e(TAG, "Failed redirect intercept", e);
}
}
}.start());
}.start());
return;
} else if (this.megaIntercept(webView.getUrl(), downloadUrl))
return;
@ -285,7 +295,7 @@ public final class AndroidacyActivity extends FoxActivity {
Log.i(TAG, "Exiting WebView " +
AndroidacyUtil.hideToken(downloadUrl));
for (String prefix : new String[]{
"https://api.androidacy.com/magisk/download/",
"https://production-api.androidacy.com/magisk/download/",
"https://staging-api.androidacy.com/magisk/download/"
}) {
if (downloadUrl.startsWith(prefix)) {
@ -328,14 +338,15 @@ public final class AndroidacyActivity extends FoxActivity {
private String moduleIdOfUrl(String url) {
for (String prefix : new String[]{
"https://api.androidacy.com/magisk/download/",
"https://production-api.androidacy.com/magisk/download/",
"https://staging-api.androidacy.com/magisk/download/",
"https://api.androidacy.com/magisk/readme/",
"https://production-api.androidacy.com/magisk/readme/",
"https://staging-api.androidacy.com/magisk/readme/",
"https://api.androidacy.com/magisk/info/",
"https://prodiuction-api.androidacy.com/magisk/info/",
"https://staging-api.androidacy.com/magisk/info/"
}) { // Make both staging and non staging act the same
int i = url.indexOf('?', prefix.length()); if (i == -1) i = url.length();
int i = url.indexOf('?', prefix.length());
if (i == -1) i = url.length();
if (url.startsWith(prefix)) return url.substring(prefix.length(), i);
}
return null;
@ -343,7 +354,7 @@ public final class AndroidacyActivity extends FoxActivity {
private boolean isFileUrl(String url) {
for (String prefix : new String[]{
"https://api.androidacy.com/magisk/file/",
"https://production-api.androidacy.com/magisk/file/",
"https://staging-api.androidacy.com/magisk/file/"
}) { // Make both staging and non staging act the same
if (url.startsWith(prefix)) return true;

@ -1,5 +1,6 @@
package com.fox2code.mmm.androidacy;
import android.annotation.SuppressLint;
import android.content.res.Resources;
import android.graphics.Color;
import android.net.Uri;
@ -107,10 +108,8 @@ public class AndroidacyWebAPI {
fConfig = config, fChecksum = checksum;
final boolean fMMTReborn = mmtReborn;
builder.setPositiveButton(hasUpdate ?
R.string.update_module : R.string.install_module, (x, y) -> {
IntentHelper.openInstaller(this.activity,
fModuleUrl, fTitle, fConfig, fChecksum, fMMTReborn);
});
R.string.update_module : R.string.install_module, (x, y) -> IntentHelper.openInstaller(this.activity,
fModuleUrl, fTitle, fConfig, fChecksum, fMMTReborn));
}
builder.setOnCancelListener(dialogInterface -> {
if (!this.activity.backOnResume)
@ -500,7 +499,7 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public String getMonetColor(String id) {
int nameResourceID = this.activity.getResources().getIdentifier("@android:color/" + id,
@SuppressLint("DiscouragedApi") int nameResourceID = this.activity.getResources().getIdentifier("@android:color/" + id,
"color", this.activity.getApplicationInfo().packageName);
if (nameResourceID == 0) {
throw new IllegalArgumentException(

@ -50,6 +50,10 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
public class InstallerActivity extends FoxActivity {
private static final String TAG = "InstallerActivity";
public LinearProgressIndicator progressIndicator;
@ -104,7 +108,7 @@ public class InstallerActivity extends FoxActivity {
}
Log.i(TAG, "Install link: " + target);
// Note: Sentry only send this info on crash.
/*if (MainApplication.isCrashReportingEnabled()) {
if (MainApplication.isCrashReportingEnabled()) {
Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("install");
breadcrumb.setData("target", target);
@ -113,7 +117,7 @@ public class InstallerActivity extends FoxActivity {
breadcrumb.setCategory("app.action.preinstall");
breadcrumb.setLevel(SentryLevel.INFO);
Sentry.addBreadcrumb(breadcrumb);
}*/
}
boolean urlMode = target.startsWith("http://") || target.startsWith("https://");
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setTitle(name);
@ -173,9 +177,7 @@ public class InstallerActivity extends FoxActivity {
if (this.canceled) return;
if (checksum != null && !checksum.isEmpty()) {
Log.d(TAG, "Checking for checksum: " + checksum);
this.runOnUiThread(() -> {
this.installerTerminal.addLine("- Checking file integrity");
});
this.runOnUiThread(() -> this.installerTerminal.addLine("- Checking file integrity"));
if (!Hashes.checkSumMatch(rawModule, checksum)) {
this.setInstallStateFinished(false,
"! File integrity check failed", "");
@ -223,9 +225,7 @@ public class InstallerActivity extends FoxActivity {
}
} else {
errMessage = "Failed to patch module zip";
this.runOnUiThread(() -> {
this.installerTerminal.addLine("- Patching " + name);
});
this.runOnUiThread(() -> this.installerTerminal.addLine("- Patching " + name));
Log.i(TAG, "Patching: " + moduleCache.getName());
try (OutputStream outputStream = new FileOutputStream(moduleCache)) {
Files.patchModuleSimple(rawModule, outputStream);
@ -235,9 +235,7 @@ public class InstallerActivity extends FoxActivity {
//noinspection UnusedAssignment (Important to avoid OutOfMemoryError)
rawModule = null; // Because reference is kept when calling doInstall
if (this.canceled) return;
this.runOnUiThread(() -> {
this.installerTerminal.addLine("- Installing " + name);
});
this.runOnUiThread(() -> this.installerTerminal.addLine("- Installing " + name));
errMessage = "Failed to install module zip";
this.doInstall(moduleCache, noExtensions, rootless);
} catch (IOException e) {
@ -451,7 +449,7 @@ public class InstallerActivity extends FoxActivity {
installCommand).to(installerController, installerMonitor);
}
// Note: Sentry only send this info on crash.
/*if (MainApplication.isCrashReportingEnabled()) {
if (MainApplication.isCrashReportingEnabled()) {
Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("install");
breadcrumb.setData("moduleId", moduleId == null ? "<null>" : moduleId);
@ -464,7 +462,7 @@ public class InstallerActivity extends FoxActivity {
breadcrumb.setCategory("app.action.install");
breadcrumb.setLevel(SentryLevel.INFO);
Sentry.addBreadcrumb(breadcrumb);
}*/
}
if (mmtReborn && magiskCmdLine) {
Log.w(TAG, "mmtReborn and magiskCmdLine may not work well together");
}
@ -738,12 +736,8 @@ public class InstallerActivity extends FoxActivity {
.setTitle(R.string.install_terminal_reboot_now)
.setCancelable(false)
.setIcon(R.drawable.ic_reboot_24)
.setPositiveButton(R.string.yes, (x, y) -> {
Shell.cmd(reboot_cmd).submit();
})
.setNegativeButton(R.string.no, (x, y) -> {
x.dismiss();
}).show();
.setPositiveButton(R.string.yes, (x, y) -> Shell.cmd(reboot_cmd).submit())
.setNegativeButton(R.string.no, (x, y) -> x.dismiss()).show();
} else {
Shell.cmd(reboot_cmd).submit();
}

@ -21,7 +21,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
@ -106,6 +105,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
}
public static class SettingsFragment extends PreferenceFragmentCompat implements FoxActivity.OnBackPressedCallback {
@SuppressLint("UnspecifiedImmutableFlag")
@Override
@SuppressWarnings("ConstantConditions")
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@ -133,13 +133,47 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
// Crash reporting
/*TwoStatePreference crashReportingPreference = findPreference("pref_crash_reporting");
TwoStatePreference crashReportingPreference = findPreference("pref_crash_reporting");
crashReportingPreference.setChecked(MainApplication.isCrashReportingEnabled());
crashReportingPreference.setOnPreferenceChangeListener((preference, newValue) -> {
devModeStepFirstBootIgnore = true;
devModeStep = 0;
// Save the new value and restart the app
MainApplication.getSharedPreferences().edit()
.putBoolean("crash_reporting", (boolean) newValue).apply();
// Show a dialog to restart the app
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(requireContext());
materialAlertDialogBuilder.setTitle(R.string.crash_reporting_restart_title);
materialAlertDialogBuilder.setMessage(R.string.crash_reporting_restart_message);
materialAlertDialogBuilder.setPositiveButton(R.string.restart, (dialog, which) -> {
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save crash reporting preference: " + newValue);
}
System.exit(0); // Exit app process
});
// Reverse the change if the user cancels the dialog
materialAlertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> {
crashReportingPreference.setChecked(!crashReportingPreference.isChecked());
MainApplication.getSharedPreferences().edit()
.putBoolean("crash_reporting", crashReportingPreference.isChecked()).apply();
});
materialAlertDialogBuilder.show();
return true;
});*/
});
Preference enableBlur = findPreference("pref_enable_blur");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
enableBlur.setSummary(R.string.require_android_6);
@ -289,7 +323,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
findPreference("pref_pkg_info").setSummary(BuildConfig.APPLICATION_ID +
" v" + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")" +
" v" + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")" +
getRepackageState()); // State may not be "I am just running from myself as myself"
}
@ -298,7 +332,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
Application initialApplication = null;
try {
initialApplication = FoxProcessExt.getInitialApplication();
} catch (Throwable ignored) {}
} catch (Throwable ignored) {
}
String realPackageName;
if (initialApplication != null) {
realPackageName = initialApplication.getPackageName();
@ -350,7 +385,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
onCreatePreferencesAndroidacy();
}
@SuppressLint("RestrictedApi")
@SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"})
public void onCreatePreferencesAndroidacy() {
Preference androidacyTestMode = Objects.requireNonNull(findPreference("pref_androidacy_test_mode"));
if (!MainApplication.isDeveloper()) {
@ -359,12 +394,70 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Show a warning if user tries to enable test mode
androidacyTestMode.setOnPreferenceChangeListener((preference, newValue) -> {
if (Boolean.parseBoolean(String.valueOf(newValue))) {
new AlertDialog.Builder(this.requireContext())
// Use MaterialAlertDialogBuilder
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.warning)
.setMessage(R.string.androidacy_test_mode_warning)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
// Do nothing
}).show();
// User clicked OK button
MainApplication.getSharedPreferences().edit().putBoolean("androidacy_test_mode", true).apply();
// Check the switch
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save staging endpoint preference: " + newValue);
}
System.exit(0); // Exit app process
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
// User cancelled the dialog
// Uncheck the switch
SwitchPreferenceCompat switchPreferenceCompat = (SwitchPreferenceCompat) androidacyTestMode;
switchPreferenceCompat.setChecked(false);
// There's probably a better way to do this than duplicate code but I'm too lazy to figure it out
MainApplication.getSharedPreferences().edit().putBoolean("androidacy_test_mode", false).apply();
})
.show();
} else {
MainApplication.getSharedPreferences().edit().putBoolean("androidacy_test_mode", false).apply();
// Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.warning)
.setMessage(R.string.androidacy_test_mode_disable_warning)
.setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save staging endpoint preference: " + newValue);
}
System.exit(0); // Exit app process
})
.show();
}
return true;
});

@ -136,7 +136,7 @@
<string name="restore_module_list">Restore modules</string>
<string name="require_internet">This operation require an internet connection</string>
<string name="androidacy_test_mode_pref">Androidacy test mode</string>
<string name="androidacy_test_mode_desc">Use staging Androidacy endpoint instead of release endpoint. (Require app process restart)</string>
<string name="androidacy_test_mode_desc">Use staging Androidacy endpoint instead of release endpoint. (Will restart app)</string>
<!-- Background Notification translation -->
<string name="notification_update_title">Found %i module updates</string>
@ -168,8 +168,12 @@
<string name="api_key_removed">Successfully reset API key</string>
<string name="save_api_key">Validate</string>
<string name="warning">Warning!</string>
<string name="androidacy_test_mode_warning">You are setting the app to use a non-production endpoint for Androidacy. This may result in app instability and failure to load the online repo. Do NOT report bugs if you have this switch on. Change will take effect on app restart.</string>
<string name="androidacy_test_mode_warning">You are setting the app to use a non-production endpoint for Androidacy. This may result in app instability and failure to load the online repo. Do NOT report bugs if you have this switch on. App will be restarted to reload repos.</string>
<string name="crash">Crash the app for testing</string>
<string name="repackaged_as">Repackaged as:</string>
<string name="wrapped_from">Wrapped from:</string>
<string name="crash_reporting_restart_title">Restart app to apply changes?</string>
<string name="crash_reporting_restart_message">The app needs to restart to apply this setting</string>
<string name="restart">Restart</string>
<string name="androidacy_test_mode_disable_warning">App will be restarted to disable staging endpoint</string>
</resources>

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">androidacy.com</domain>
<trust-anchors>
<certificates src="@raw/androidacy_root_ca" />
</trust-anchors>
<pin-set expiration="2023-03-16">
<pin digest="SHA-256">6x/7mHVkS/6XLcenTc5gxonnGPTB1MD5mPQFqHTbfa4=</pin>
<pin digest="SHA-256">Y9mvm0exBk1JoQ57f9Vm28jKo5lFm/woKcVxrYxu80o=</pin>

@ -116,13 +116,13 @@
app:summary="@string/prevent_reboot_desc"
app:title="@string/prevent_reboot_pref" />
<!-- Crash reporting -->
<!--<SwitchPreferenceCompat
<SwitchPreferenceCompat
app:defaultValue="true"
app:icon="@drawable/ic_baseline_bug_report_24"
app:key="pref_crash_reporting"
app:singleLineTitle="false"
app:summary="@string/crash_reporting_desc"
app:title="@string/crash_reporting" />-->
app:title="@string/crash_reporting" />
<!-- Purposely crash the app -->
<Preference
app:icon="@drawable/ic_baseline_bug_report_24"

Loading…
Cancel
Save