update styles

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/31/head
androidacy-user 3 years ago
parent 7914bcb70c
commit cf095f39be

@ -8,7 +8,6 @@ import java.util.Properties
plugins {
// Gradle doesn't allow conditionally enabling/disabling plugins
id("com.android.application")
id("org.gradle.android.cache-fix")
id("com.mikepenz.aboutlibraries.plugin")
kotlin("android")
kotlin("kapt")

@ -15,13 +15,13 @@ import java.nio.charset.StandardCharsets
class AppUpdateManager private constructor() {
private val compatDataId = HashMap<String, Int>()
private val updateLock = Any()
private val compatFile: File = File(MainApplication.getINSTANCE().filesDir, "compat.txt")
private val compatFile: File = File(MainApplication.INSTANCE!!.filesDir, "compat.txt")
private var latestRelease: String?
private var lastChecked: Long
init {
latestRelease = MainApplication.getBootSharedPreferences()
.getString("updater_latest_release", BuildConfig.VERSION_NAME)
latestRelease = MainApplication.bootSharedPreferences
?.getString("updater_latest_release", BuildConfig.VERSION_NAME)
lastChecked = 0
if (compatFile.isFile) {
try {

@ -169,7 +169,7 @@ class CrashHandler : FoxActivity() {
builder.setMessage(R.string.reset_app_confirmation)
builder.setPositiveButton(R.string.reset) { _: DialogInterface?, _: Int ->
// reset the app
MainApplication.getINSTANCE().resetApp()
MainApplication.INSTANCE!!.resetApp()
}
builder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int -> }
builder.show()

@ -5,8 +5,6 @@ import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
@ -99,11 +97,11 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
}
onMainActivityCreate(this)
super.onCreate(savedInstanceState)
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker)
// track enabled repos
val realmConfig = RealmConfiguration.Builder().name("ReposList.realm")
.encryptionKey(MainApplication.getINSTANCE().key)
.directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1)
.encryptionKey(MainApplication.INSTANCE!!.key)
.directory(MainApplication.INSTANCE!!.getDataDirWithPath("realms")).schemaVersion(1)
.allowQueriesOnUiThread(true).allowWritesOnUiThread(true).build()
val realm = Realm.getInstance(realmConfig)
val enabledRepos = StringBuilder()
@ -118,7 +116,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
enabledRepos.setLength(enabledRepos.length - 1)
}
TrackHelper.track().event("enabled_repos", enabledRepos.toString())
.with(MainApplication.getINSTANCE().tracker)
.with(MainApplication.INSTANCE!!.tracker)
realm.close()
// hide this behind a buildconfig flag for now, but crash the app if it's not an official build and not debug
if (BuildConfig.ENABLE_PROTECTION && !MainApplication.o && !BuildConfig.DEBUG) {
@ -140,7 +138,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
// ignore status bar space
this.window.setDecorFitsSystemWindows(false)
} else {
@Suppress("DEPRECATION")
this.window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
@ -243,14 +240,14 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
when (item.itemId) {
R.id.settings_menu_item -> {
TrackHelper.track().event("view_list", "settings")
.with(MainApplication.getINSTANCE().tracker)
.with(MainApplication.INSTANCE!!.tracker)
startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
finish()
}
R.id.online_menu_item -> {
TrackHelper.track().event("view_list", "online_modules")
.with(MainApplication.getINSTANCE().tracker)
.with(MainApplication.INSTANCE!!.tracker)
// set module_list_online as visible and module_list as gone. fade in/out
moduleListOnline.alpha = 0f
moduleListOnline.visibility = View.VISIBLE
@ -267,7 +264,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
}
R.id.installed_menu_item -> {
TrackHelper.track().event("view_list", "installed_modules")
.with(MainApplication.getINSTANCE().tracker)
.with(MainApplication.INSTANCE!!.tracker)
// set module_list_online as gone and module_list as visible. fade in/out
moduleList.alpha = 0f
moduleList.visibility = View.VISIBLE
@ -304,14 +301,14 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
rootContainer.y = 0f
}
// reset update module and update module count in main application
MainApplication.getINSTANCE().resetUpdateModule()
MainApplication.INSTANCE!!.resetUpdateModule()
tryGetMagiskPathAsync(object : InstallerInitializer.Callback {
override fun onPathReceived(path: String?) {
Timber.i("Got magisk path: %s", path)
if (peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND) moduleViewListBuilder.addNotification(
NotificationType.MAGISK_OUTDATED
)
if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(
if (!MainApplication.isShowcaseMode) moduleViewListBuilder.addNotification(
NotificationType.INSTALL_FROM_STORAGE
)
instance!!.scan()
@ -350,7 +347,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
return
}
swipeRefreshBlocker = System.currentTimeMillis() + 5000L
if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(
if (MainApplication.isShowcaseMode) moduleViewListBuilder.addNotification(
NotificationType.SHOWCASE_MODE
)
if (!hasWebView()) {
@ -448,13 +445,13 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
moduleViewListBuilder.applyTo(moduleListOnline, moduleViewAdapterOnline!!)
moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline!!)
// if moduleViewListBuilderOnline has the upgradeable notification, show a badge on the online repo nav item
if (MainApplication.getINSTANCE().modulesHaveUpdates) {
if (MainApplication.INSTANCE!!.modulesHaveUpdates) {
Timber.i("Applying badge")
Handler(Looper.getMainLooper()).post {
val badge = bottomNavigationView.getOrCreateBadge(R.id.online_menu_item)
badge.isVisible = true
badge.number = MainApplication.getINSTANCE().updateModuleCount
badge.applyTheme(MainApplication.getInitialApplication().theme)
badge.number = MainApplication.INSTANCE!!.updateModuleCount
badge.applyTheme(MainApplication.INSTANCE!!.theme)
Timber.i("Badge applied")
}
}
@ -480,37 +477,12 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
}
ExternalHelper.INSTANCE.refreshHelper(this)
initMode = false
// add preference listener to set isMatomoAllowed
val listener =
OnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences, key: String ->
if (key == "pref_analytics_enabled") {
MainApplication.getINSTANCE().isMatomoAllowed =
sharedPreferences.getBoolean(key, false)
MainApplication.getINSTANCE().tracker.isOptOut =
MainApplication.getINSTANCE().isMatomoAllowed
Timber.d(
"Matomo is allowed change: %s",
MainApplication.getINSTANCE().isMatomoAllowed
)
}
if (MainApplication.getINSTANCE().isMatomoAllowed) {
val value = sharedPreferences.getString(key, null)
// then log
if (value != null) {
TrackHelper.track().event("pref_changed", "$key=$value")
.with(MainApplication.getINSTANCE().tracker)
}
}
Timber.d("Preference changed: %s", key)
}
MainApplication.getSharedPreferences("mmm")
.registerOnSharedPreferenceChangeListener(listener)
}
private fun cardIconifyUpdate() {
val iconified = searchView!!.isIconified
val backgroundAttr =
if (iconified) if (MainApplication.isMonetEnabled()) com.google.android.material.R.attr.colorSecondaryContainer else // Monet is special...
if (iconified) if (MainApplication.isMonetEnabled) com.google.android.material.R.attr.colorSecondaryContainer else // Monet is special...
com.google.android.material.R.attr.colorSecondary else com.google.android.material.R.attr.colorPrimarySurface
val theme = searchCard!!.context.theme
val value = TypedValue()
@ -546,7 +518,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
}
private fun updateBlurState() {
if (MainApplication.isBlurEnabled()) {
if (MainApplication.isBlurEnabled) {
// set bottom navigation bar color to transparent blur
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation)
if (bottomNavigationView != null) {
@ -590,7 +562,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
if (peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND) moduleViewListBuilder.addNotification(
NotificationType.MAGISK_OUTDATED
)
if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(
if (!MainApplication.isShowcaseMode) moduleViewListBuilder.addNotification(
NotificationType.INSTALL_FROM_STORAGE
)
instance!!.scan()
@ -607,7 +579,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
fun commonNext() {
Timber.i("Common Before")
if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(
if (MainApplication.isShowcaseMode) moduleViewListBuilder.addNotification(
NotificationType.SHOWCASE_MODE
)
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilderOnline)
@ -731,7 +703,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
override fun onQueryTextSubmit(query: String): Boolean {
searchView!!.clearFocus()
if (initMode) return false
TrackHelper.track().search(query).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker)
if (moduleViewListBuilder.setQueryChange(query)) {
Timber.i("Query submit: %s on offline list", query)
Thread(
@ -754,7 +726,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
override fun onQueryTextChange(query: String): Boolean {
if (initMode) return false
TrackHelper.track().search(query).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().search(query).with(MainApplication.INSTANCE!!.tracker)
if (moduleViewListBuilder.setQueryChange(query)) {
Timber.i("Query submit: %s on offline list", query)
Thread(

@ -1,750 +0,0 @@
package com.fox2code.mmm;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.SystemClock;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.core.app.NotificationManagerCompat;
import androidx.emoji2.text.DefaultEmojiCompatConfig;
import androidx.emoji2.text.EmojiCompat;
import androidx.emoji2.text.FontRequestEmojiCompatConfig;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.foxcompat.app.FoxApplication;
import com.fox2code.foxcompat.app.internal.FoxProcessExt;
import com.fox2code.foxcompat.view.FoxThemeWrapper;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.TimberUtils;
import com.fox2code.mmm.utils.io.FileUtils;
import com.fox2code.mmm.utils.io.GMSProviderInstaller;
import com.fox2code.mmm.utils.io.net.Http;
import com.fox2code.mmm.utils.sentry.SentryMain;
import com.fox2code.rosettax.LanguageSwitcher;
import com.google.common.hash.Hashing;
import com.topjohnwu.superuser.Shell;
import org.matomo.sdk.Matomo;
import org.matomo.sdk.Tracker;
import org.matomo.sdk.TrackerBuilder;
import org.matomo.sdk.extra.TrackHelper;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import io.noties.markwon.Markwon;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import io.realm.Realm;
import timber.log.Timber;
public class MainApplication extends FoxApplication implements androidx.work.Configuration.Provider {
// Warning! Locales that don't exist will crash the app
// Anything that is commented out is supported but the translation is not complete to at least 60%
public static final HashSet<String> supportedLocales = new HashSet<>();
private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
private static final Shell.Builder shellBuilder;
@SuppressLint("RestrictedApi")
// Use FoxProcess wrapper helper.
private static final boolean wrapped = !FoxProcessExt.isRootLoader();
private static final ArrayList<String> callers = new ArrayList<>();
public static boolean o = false;
private static String SHOWCASE_MODE_TRUE = null;
private static long secret;
private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().getLocales().get(0);
private static SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale);
private static String relPackageName = BuildConfig.APPLICATION_ID;
@SuppressLint("StaticFieldLeak")
private static MainApplication INSTANCE;
private static boolean firstBoot;
private static HashMap<Object, Object> mSharedPrefs;
public static String updateCheckBg;
static {
Shell.setDefaultBuilder(shellBuilder = Shell.Builder.create().setFlags(Shell.FLAG_REDIRECT_STDERR | Shell.FLAG_MOUNT_MASTER).setTimeout(15).setInitializers(InstallerInitializer.class));
Random random = new Random();
do {
secret = random.nextLong();
} while (secret == 0);
}
public boolean modulesHaveUpdates = false;
public int updateModuleCount = 0;
public List<String> updateModules = new ArrayList<>();
public boolean isMatomoAllowed;
@StyleRes
private int managerThemeResId = R.style.Theme_MagiskModuleManager;
private FoxThemeWrapper markwonThemeContext;
private Markwon markwon;
private byte[] existingKey;
private Tracker tracker;
private boolean makingNewKey = false;
private boolean isCrashHandler;
public MainApplication() {
if (INSTANCE != null && INSTANCE != this)
throw new IllegalStateException("Duplicate application instance!");
INSTANCE = this;
}
public static Shell build(String... command) {
return shellBuilder.build(command);
}
public static void addSecret(Intent intent) {
ComponentName componentName = intent.getComponent();
String packageName = componentName != null ? componentName.getPackageName() : intent.getPackage();
if (!BuildConfig.APPLICATION_ID.equalsIgnoreCase(packageName) && !relPackageName.equals(packageName)) {
// Code safeguard, we should never reach here.
throw new IllegalArgumentException("Can't add secret to outbound Intent");
}
intent.putExtra("secret", secret);
}
public static SharedPreferences getSharedPreferences(String name) {
// encryptedSharedPreferences is used
Context mContext = getINSTANCE();
name = name + "x";
if (mSharedPrefs == null) {
Timber.d("Creating shared prefs map");
mSharedPrefs = new HashMap<>();
}
/*
this part is only here because with added encryption, parts of code that were previously calling this over and over again or on each invocation of a method are causing performance issues.
*/
if (BuildConfig.DEBUG) {
// get file, function, and line number
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// get the caller of this method
StackTraceElement caller = stackTrace[3];
Timber.d("Shared prefs file: %s, caller: %s:%d in %s", name, caller.getFileName(), caller.getLineNumber(), caller.getMethodName());
// add the caller to an array. if the last 3 callers are the same, then we are in a loop, log at error level
callers.add(name + ":" + caller.getLineNumber() + ":" + caller.getMethodName());
// get the last 3 callers
List<String> last3 = callers.subList(Math.max(callers.size() - 3, 0), callers.size());
// if the last 3 callers are the same, then we are in a loop, log at error level
if (last3.size() == 3 && last3.get(0).equals(last3.get(1)) && last3.get(1).equals(last3.get(2))) {
Timber.e("Shared prefs loop detected. File: %s, caller: %s:%d", name, caller.getMethodName(), caller.getLineNumber());
}
}
if (mSharedPrefs.containsKey(name)) {
Timber.d("Returning cached shared prefs");
return (SharedPreferences) mSharedPrefs.get(name);
}
try {
Timber.d("Creating encrypted shared prefs");
MasterKey masterKey = new MasterKey.Builder(mContext).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(mContext, name, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
mSharedPrefs.put(name, sharedPreferences);
return sharedPreferences;
} catch (Exception e) {
Timber.e(e, "Failed to create encrypted shared preferences");
return mContext.getSharedPreferences(name, Context.MODE_PRIVATE);
}
}
// Is application wrapped, and therefore must reduce it's feature set.
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isWrapped() {
return wrapped;
}
public static boolean checkSecret(Intent intent) {
return intent != null && intent.getLongExtra("secret", ~secret) == secret;
}
public static boolean isShowcaseMode() {
if (SHOWCASE_MODE_TRUE != null) {
// convert from String to boolean
return Boolean.parseBoolean(SHOWCASE_MODE_TRUE);
}
boolean showcaseMode = getSharedPreferences("mmm").getBoolean("pref_showcase_mode", false);
SHOWCASE_MODE_TRUE = String.valueOf(showcaseMode);
return showcaseMode;
}
public static boolean shouldPreventReboot() {
return getSharedPreferences("mmm").getBoolean("pref_prevent_reboot", true);
}
public static boolean isShowIncompatibleModules() {
return getSharedPreferences("mmm").getBoolean("pref_show_incompatible", false);
}
public static boolean isForceDarkTerminal() {
return getSharedPreferences("mmm").getBoolean("pref_force_dark_terminal", false);
}
public static boolean isTextWrapEnabled() {
return getSharedPreferences("mmm").getBoolean("pref_wrap_text", false);
}
public static boolean isDohEnabled() {
return getSharedPreferences("mmm").getBoolean("pref_dns_over_https", true);
}
public static boolean isMonetEnabled() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && getSharedPreferences("mmm").getBoolean("pref_enable_monet", true);
}
public static boolean isBlurEnabled() {
return getSharedPreferences("mmm").getBoolean("pref_enable_blur", false);
}
public static boolean isDeveloper() {
if (BuildConfig.DEBUG) return true;
return getSharedPreferences("mmm").getBoolean("developer", false);
}
public static boolean isDisableLowQualityModuleFilter() {
return getSharedPreferences("mmm").getBoolean("pref_disable_low_quality_module_filter", false) && isDeveloper();
}
public static boolean isUsingMagiskCommand() {
return InstallerInitializer.peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND && getSharedPreferences("mmm").getBoolean("pref_use_magisk_install_command", false) && isDeveloper();
}
public static boolean isBackgroundUpdateCheckEnabled() {
if (updateCheckBg != null) {
return Boolean.parseBoolean(updateCheckBg);
}
boolean wrapped = isWrapped();
boolean updateCheckBgTemp = !wrapped && getSharedPreferences("mmm").getBoolean("pref_background_update_check", true);
updateCheckBg = String.valueOf(updateCheckBgTemp);
return Boolean.parseBoolean(updateCheckBg);
}
public static boolean isAndroidacyTestMode() {
return isDeveloper() && getSharedPreferences("mmm").getBoolean("pref_androidacy_test_mode", false);
}
public static boolean isFirstBoot() {
return firstBoot;
}
public static void setHasGottenRootAccess(boolean bool) {
getSharedPreferences("mmm").edit().putBoolean("has_root_access", bool).apply();
}
public static boolean isCrashReportingEnabled() {
return SentryMain.IS_SENTRY_INSTALLED && getSharedPreferences("mmm").getBoolean("pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING);
}
public static SharedPreferences getBootSharedPreferences() {
return getSharedPreferences("mmm_boot");
}
public static MainApplication getINSTANCE() {
return INSTANCE;
}
public static String formatTime(long timeStamp) {
// new Date(x) also get the local timestamp for format
return timeFormat.format(new Date(timeStamp));
}
public static boolean isNotificationPermissionGranted() {
return NotificationManagerCompat.from(INSTANCE).areNotificationsEnabled();
}
public Markwon getMarkwon() {
if (isCrashHandler) return null;
if (this.markwon != null) return this.markwon;
FoxThemeWrapper contextThemeWrapper = this.markwonThemeContext;
if (contextThemeWrapper == null) {
contextThemeWrapper = this.markwonThemeContext = new FoxThemeWrapper(this, this.managerThemeResId);
}
Markwon markwon = Markwon.builder(contextThemeWrapper).usePlugin(HtmlPlugin.create()).usePlugin(ImagesPlugin.create().addSchemeHandler(OkHttpNetworkSchemeHandler.create(Http.getHttpClientWithCache()))).build();
return this.markwon = markwon;
}
@NonNull
@Override
public androidx.work.Configuration getWorkManagerConfiguration() {
return new androidx.work.Configuration.Builder().build();
}
public void updateTheme() {
@StyleRes int themeResId;
String theme;
boolean monet = isMonetEnabled();
switch (theme = getSharedPreferences("mmm").getString("pref_theme", "system")) {
default:
Timber.w("Unknown theme id: %s", theme);
case "system":
themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet : R.style.Theme_MagiskModuleManager;
break;
case "dark":
themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet_Dark : R.style.Theme_MagiskModuleManager_Dark;
break;
case "black":
themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet_Black : R.style.Theme_MagiskModuleManager_Black;
break;
case "light":
themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet_Light : R.style.Theme_MagiskModuleManager_Light;
break;
case "transparent_light":
if (monet) {
Timber.tag("MainApplication").w("Monet is not supported for transparent theme");
}
themeResId = R.style.Theme_MagiskModuleManager_Transparent_Light;
break;
}
this.setManagerThemeResId(themeResId);
}
@StyleRes
public int getManagerThemeResId() {
return managerThemeResId;
}
@SuppressLint("NonConstantResourceId")
public void setManagerThemeResId(@StyleRes int resId) {
this.managerThemeResId = resId;
if (this.markwonThemeContext != null) {
this.markwonThemeContext.setTheme(resId);
}
this.markwon = null;
}
@SuppressLint("NonConstantResourceId")
public boolean isLightTheme() {
return switch (getSharedPreferences("mmm").getString("pref_theme", "system")) {
case "system" -> this.isSystemLightTheme();
case "dark", "black" -> false;
default -> true;
};
}
private boolean isSystemLightTheme() {
return (this.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) != Configuration.UI_MODE_NIGHT_YES;
}
@SuppressWarnings("unused")
public boolean isDarkTheme() {
return !this.isLightTheme();
}
@SuppressWarnings("UnusedReturnValue")
public synchronized Tracker getTracker() {
if (tracker == null) {
tracker = TrackerBuilder.createDefault(BuildConfig.ANALYTICS_ENDPOINT, 1).build(Matomo.getInstance(this));
tracker.startNewSession();
tracker.setDispatchInterval(1000);
}
return tracker;
}
@Override
public void onCreate() {
supportedLocales.addAll(Arrays.asList("ar", "bs", "cs", "de", "es-rMX", "fr", "hu", "id", "ja", "hu", "nl", "pl", "pt", "pt-rBR", "ro", "ru", "tr", "uk", "zh", "zh-rTW", "en"));
if (INSTANCE == null) INSTANCE = this;
relPackageName = this.getPackageName();
super.onCreate();
SentryMain.initialize(this);
// Initialize Timber
TimberUtils.configTimber();
Timber.i("Starting AMM version %s (%d) - commit %s", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, BuildConfig.COMMIT_HASH);
// Update SSL Ciphers if update is possible
GMSProviderInstaller.installIfNeeded(this);
// get intent. if isCrashing is not present or false, call FileUtils.ensureCacheDirs and FileUtils.ensureURLHandler
isCrashHandler = getIntent().getBooleanExtra("isCrashing", false);
if (!isCrashHandler) {
FileUtils fileUtils = new FileUtils();
fileUtils.ensureCacheDirs();
fileUtils.ensureURLHandler(this);
}
Timber.d("Initializing AMM");
Timber.d("Started from background: %s", !isInForeground());
Timber.d("AMM is running in debug mode");
Timber.d("Initializing Realm");
Realm.init(this);
Timber.d("Initialized Realm");
// analytics
Timber.d("Initializing matomo");
isMatomoAllowed = isMatomoAllowed();
getTracker();
if (!isMatomoAllowed) {
Timber.d("Matomo is not allowed");
tracker.setOptOut(true);
} else {
tracker.setOptOut(false);
}
if (getSharedPreferences("matomo").getBoolean("install_tracked", false)) {
TrackHelper.track().download().with(MainApplication.getINSTANCE().getTracker());
Timber.d("Sent install event to matomo");
getSharedPreferences("matomo").edit().putBoolean("install_tracked", true).apply();
} else {
Timber.d("Matomo already has install");
}
try {
@SuppressLint("PackageManagerGetSignatures") Signature[] s = this.getPackageManager().getPackageInfo(this.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
@SuppressWarnings("SpellCheckingInspection") String[] osh = new String[]{"7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "e8ce7deca880304d7ff09f8fc37656cfa927cee7f6a0bb7b3feda6a5942931f5", "339af2fb5b671fa4af6436b585351f2f1fc746d1d922f9a0b01df2d576381015"};
//noinspection SpellCheckingInspection
String oosh = Hashing.sha256().hashBytes(s[0].toByteArray()).toString();
o = Arrays.asList(osh).contains(oosh);
} catch (PackageManager.NameNotFoundException ignored) {
}
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("mmm");
// We are only one process so it's ok to do this
SharedPreferences bootPrefs = MainApplication.getSharedPreferences("mmm_boot");
long lastBoot = System.currentTimeMillis() - SystemClock.elapsedRealtime();
long lastBootPrefs = bootPrefs.getLong("last_boot", 0);
if (lastBootPrefs == 0 || Math.abs(lastBoot - lastBootPrefs) > 100) {
boolean firstBoot = sharedPreferences.getBoolean("first_boot", true);
bootPrefs.edit().clear().putLong("last_boot", lastBoot).putBoolean("first_boot", firstBoot).apply();
if (firstBoot) {
sharedPreferences.edit().putBoolean("first_boot", false).apply();
}
MainApplication.firstBoot = firstBoot;
} else {
MainApplication.firstBoot = bootPrefs.getBoolean("first_boot", false);
}
// Force initialize language early.
new LanguageSwitcher(this);
this.updateTheme();
// Update emoji config
FontRequestEmojiCompatConfig fontRequestEmojiCompatConfig = DefaultEmojiCompatConfig.create(this);
if (fontRequestEmojiCompatConfig != null) {
fontRequestEmojiCompatConfig.setReplaceAll(true);
fontRequestEmojiCompatConfig.setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
EmojiCompat emojiCompat = EmojiCompat.init(fontRequestEmojiCompatConfig);
new Thread(() -> {
Timber.i("Loading emoji compat...");
emojiCompat.load();
Timber.i("Emoji compat loaded!");
}, "Emoji compat init.").start();
}
if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) {
Timber.w("Androidacy client id is empty! Please set it in androidacy.properties. Will not enable Androidacy.");
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("pref_androidacy_repo_enabled", false);
Timber.w("ANDROIDACY_CLIENT_ID is empty, disabling AndroidacyRepoData 1");
editor.apply();
}
}
private boolean isMatomoAllowed() {
return getSharedPreferences("mmm").getBoolean("pref_analytics_enabled", BuildConfig.DEFAULT_ENABLE_ANALYTICS);
}
@SuppressWarnings("unused")
private Intent getIntent() {
return this.getPackageManager().getLaunchIntentForPackage(this.getPackageName());
}
@Override
public void onCreateFoxActivity(FoxActivity compatActivity) {
super.onCreateFoxActivity(compatActivity);
compatActivity.setTheme(this.managerThemeResId);
}
@Override
public void onRefreshUI(FoxActivity compatActivity) {
super.onRefreshUI(compatActivity);
compatActivity.setThemeRecreate(this.managerThemeResId);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
Locale newTimeFormatLocale = newConfig.getLocales().get(0);
if (timeFormatLocale != newTimeFormatLocale) {
timeFormatLocale = newTimeFormatLocale;
timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale);
}
super.onConfigurationChanged(newConfig);
}
// getDataDir wrapper with optional path parameter
public File getDataDirWithPath(String path) {
File dataDir = this.getDataDir();
// for path with / somewhere in the middle, its a subdirectory
if (path != null) {
if (path.startsWith("/")) path = path.substring(1);
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
if (path.contains("/")) {
String[] dirs = path.split("/");
for (String dir : dirs) {
dataDir = new File(dataDir, dir);
// make sure the directory exists
if (!dataDir.exists()) {
if (!dataDir.mkdirs()) {
if (BuildConfig.DEBUG)
Timber.w("Failed to create directory %s", dataDir);
}
}
}
} else {
dataDir = new File(dataDir, path);
// create the directory if it doesn't exist
if (!dataDir.exists()) {
if (!dataDir.mkdirs()) {
if (BuildConfig.DEBUG) Timber.w("Failed to create directory %s", dataDir);
}
}
}
return dataDir;
} else {
throw new IllegalArgumentException("Path cannot be null");
}
}
@SuppressLint("RestrictedApi")
// view is nullable because it's called from xml
public void resetApp() {
// cant show a dialog because android is throwing a fit so here's hoping anybody who calls this method is otherwise confirming that the user wants to reset the app
Timber.w("Resetting app...");
// recursively delete the app's data
((ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE)).clearApplicationUserData();
}
public boolean isInForeground() {
// determine if the app is in the foreground
ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null) {
Timber.d("appProcesses is null");
return false;
}
final String packageName = this.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
Timber.d("Process: %s, Importance: %d", appProcess.processName, appProcess.importance);
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
// returns if background execution is restricted
@SuppressWarnings("unused")
public boolean isBackgroundRestricted() {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return am.isBackgroundRestricted();
} else {
return false;
}
}
// Create a key to encrypt a realm and save it securely in the keystore
public byte[] getKey() {
if (makingNewKey) {
// sleep until the key is made
while (makingNewKey) try {
//noinspection BusyWait
Thread.sleep(100);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
// attempt to read the existingKey property
if (existingKey != null) {
return existingKey;
}
// check if we have a key already
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("realm_key");
if (sharedPreferences.contains("iv_and_encrypted_key")) {
return getExistingKey();
} else {
makingNewKey = true;
}
// open a connection to the android keystore
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException |
IOException e) {
Timber.v("Failed to open the keystore.");
throw new RuntimeException(e);
}
// create a securely generated random asymmetric RSA key
byte[] realmKey = new byte[Realm.ENCRYPTION_KEY_LENGTH];
do {
new SecureRandom().nextBytes(realmKey);
} while (realmKey[0] == 0);
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Timber.e("Failed to create a cipher.");
throw new RuntimeException(e);
}
Timber.v("Cipher created.");
// generate secret key
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
Timber.e("Failed to access the key generator.");
throw new RuntimeException(e);
}
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder("realm_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(KeyProperties.BLOCK_MODE_CBC).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7).build();
try {
keyGenerator.init(keySpec);
} catch (InvalidAlgorithmParameterException e) {
Timber.e("Failed to generate a secret key.");
throw new RuntimeException(e);
}
Timber.v("Secret key generated.");
keyGenerator.generateKey();
Timber.v("Secret key stored in the keystore.");
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
byte[] initializationVector;
byte[] encryptedKeyForRealm;
try {
SecretKey secretKey = (SecretKey) keyStore.getKey("realm_key", null);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
encryptedKeyForRealm = cipher.doFinal(realmKey);
initializationVector = cipher.getIV();
} catch (InvalidKeyException | UnrecoverableKeyException | NoSuchAlgorithmException |
KeyStoreException | BadPaddingException | IllegalBlockSizeException e) {
Timber.e("Failed encrypting the key with the secret key.");
throw new RuntimeException(e);
}
// keep the encrypted key in shared preferences
// to persist it across application runs
byte[] initializationVectorAndEncryptedKey = new byte[Integer.BYTES + initializationVector.length + encryptedKeyForRealm.length];
ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putInt(initializationVector.length);
buffer.put(initializationVector);
buffer.put(encryptedKeyForRealm);
Timber.d("Created all keys successfully.");
MainApplication.getSharedPreferences("realm_key").edit().putString("iv_and_encrypted_key", Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)).apply();
Timber.d("Saved the encrypted key in shared preferences.");
makingNewKey = false;
return realmKey; // pass to a realm configuration via encryptionKey()
}
// Access the encrypted key in the keystore, decrypt it with the secret,
// and use it to open and read from the realm again
public byte[] getExistingKey() {
// attempt to read the existingKey property
if (existingKey != null) {
return existingKey;
}
// open a connection to the android keystore
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException |
IOException e) {
Timber.e("Failed to open the keystore.");
throw new RuntimeException(e);
}
Timber.v("Keystore opened.");
// access the encrypted key that's stored in shared preferences
byte[] initializationVectorAndEncryptedKey = Base64.decode(MainApplication.getSharedPreferences("realm_key").getString("iv_and_encrypted_key", null), Base64.DEFAULT);
Timber.d("Retrieved the encrypted key from shared preferences. Key length: %d", initializationVectorAndEncryptedKey.length);
ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey);
buffer.order(ByteOrder.BIG_ENDIAN);
// extract the length of the initialization vector from the buffer
int initializationVectorLength = buffer.getInt();
// extract the initialization vector based on that length
byte[] initializationVector = new byte[initializationVectorLength];
buffer.get(initializationVector);
// extract the encrypted key
byte[] encryptedKey = new byte[initializationVectorAndEncryptedKey.length - Integer.BYTES - initializationVectorLength];
buffer.get(encryptedKey);
Timber.d("Got key from shared preferences.");
// create a cipher that uses AES encryption to decrypt our key
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Timber.e("Failed to create cipher.");
throw new RuntimeException(e);
}
// decrypt the encrypted key with the secret key stored in the keystore
byte[] decryptedKey;
try {
final SecretKey secretKey = (SecretKey) keyStore.getKey("realm_key", null);
final IvParameterSpec initializationVectorSpec = new IvParameterSpec(initializationVector);
cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec);
decryptedKey = cipher.doFinal(encryptedKey);
} catch (InvalidKeyException e) {
Timber.e("Failed to decrypt. Invalid key.");
throw new RuntimeException(e);
} catch (UnrecoverableKeyException | NoSuchAlgorithmException | BadPaddingException |
KeyStoreException | IllegalBlockSizeException |
InvalidAlgorithmParameterException e) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.");
throw new RuntimeException(e);
}
// set property on MainApplication to indicate that the key has been accessed
existingKey = decryptedKey;
return decryptedKey; // pass to a realm configuration via encryptionKey()
}
public void resetUpdateModule() {
modulesHaveUpdates = false;
updateModuleCount = 0;
updateModules = new ArrayList<>();
}
public static class ReleaseTree extends Timber.Tree {
@Override
protected void log(int priority, String tag, @NonNull String message, Throwable t) {
// basically silently drop all logs below error, and write the rest to logcat
if (priority >= Log.ERROR) {
if (t != null) {
Log.println(priority, tag, message);
t.printStackTrace();
} else {
Log.println(priority, tag, message);
}
}
}
}
}

@ -0,0 +1,910 @@
package com.fox2code.mmm
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.Build
import android.os.SystemClock
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import androidx.annotation.StyleRes
import androidx.core.app.NotificationManagerCompat
import androidx.emoji2.text.DefaultEmojiCompatConfig
import androidx.emoji2.text.EmojiCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import androidx.work.Configuration
import com.fox2code.foxcompat.app.FoxActivity
import com.fox2code.foxcompat.app.FoxApplication
import com.fox2code.foxcompat.app.internal.FoxProcessExt
import com.fox2code.foxcompat.view.FoxThemeWrapper
import com.fox2code.mmm.installer.InstallerInitializer
import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskVersion
import com.fox2code.mmm.utils.TimberUtils.configTimber
import com.fox2code.mmm.utils.io.FileUtils
import com.fox2code.mmm.utils.io.GMSProviderInstaller.Companion.installIfNeeded
import com.fox2code.mmm.utils.io.net.Http.Companion.getHttpClientWithCache
import com.fox2code.mmm.utils.sentry.SentryMain
import com.fox2code.mmm.utils.sentry.SentryMain.initialize
import com.fox2code.rosettax.LanguageSwitcher
import com.google.common.hash.Hashing
import com.topjohnwu.superuser.Shell
import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import io.realm.Realm
import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder
import org.matomo.sdk.extra.TrackHelper
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.NoSuchProviderException
import java.security.SecureRandom
import java.security.UnrecoverableKeyException
import java.security.cert.CertificateException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.KeyGenerator
import javax.crypto.NoSuchPaddingException
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import kotlin.math.abs
import kotlin.math.max
@Suppress("unused", "MemberVisibilityCanBePrivate")
class MainApplication : FoxApplication(), Configuration.Provider {
@JvmField
var modulesHaveUpdates = false
@JvmField
var updateModuleCount = 0
@JvmField
var updateModules: List<String> = ArrayList()
@StyleRes
private var managerThemeResId = R.style.Theme_MagiskModuleManager
private var markwonThemeContext: FoxThemeWrapper? = null
@JvmField
var markwon: Markwon? = null
private var existingKey: ByteArray? = byteArrayOf(0)
@JvmField
var tracker: Tracker? = null
private var makingNewKey = false
private var isCrashHandler = false
init {
check(!(INSTANCE != null && INSTANCE !== this)) { "Duplicate application instance!" }
INSTANCE = this
}
fun getMarkwon(): Markwon? {
if (isCrashHandler) return null
if (markwon != null) return markwon
var contextThemeWrapper = markwonThemeContext
if (contextThemeWrapper == null) {
markwonThemeContext = FoxThemeWrapper(this, managerThemeResId)
contextThemeWrapper = markwonThemeContext
}
val markwon =
Markwon.builder(contextThemeWrapper!!).usePlugin(HtmlPlugin.create()).usePlugin(
ImagesPlugin.create().addSchemeHandler(
OkHttpNetworkSchemeHandler.create(
getHttpClientWithCache()!!
)
)
).build()
return markwon.also { this.markwon = it }
}
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder().build()
}
fun updateTheme() {
@StyleRes val themeResId: Int
var theme: String?
val monet = isMonetEnabled
when (getSharedPreferences("mmm")!!.getString("pref_theme", "system").also { theme = it }) {
"system" -> themeResId =
if (monet) R.style.Theme_MagiskModuleManager_Monet else R.style.Theme_MagiskModuleManager
"dark" -> themeResId =
if (monet) R.style.Theme_MagiskModuleManager_Monet_Dark else R.style.Theme_MagiskModuleManager_Dark
"black" -> themeResId =
if (monet) R.style.Theme_MagiskModuleManager_Monet_Black else R.style.Theme_MagiskModuleManager_Black
"light" -> themeResId =
if (monet) R.style.Theme_MagiskModuleManager_Monet_Light else R.style.Theme_MagiskModuleManager_Light
"transparent_light" -> {
if (monet) {
Timber.tag("MainApplication").w("Monet is not supported for transparent theme")
}
themeResId = R.style.Theme_MagiskModuleManager_Transparent_Light
}
else -> {
Timber.w("Unknown theme id: %s", theme)
themeResId =
if (monet) R.style.Theme_MagiskModuleManager_Monet else R.style.Theme_MagiskModuleManager
}
}
setManagerThemeResId(themeResId)
}
@StyleRes
fun getManagerThemeResId(): Int {
return managerThemeResId
}
@SuppressLint("NonConstantResourceId")
fun setManagerThemeResId(@StyleRes resId: Int) {
managerThemeResId = resId
if (markwonThemeContext != null) {
markwonThemeContext!!.setTheme(resId)
}
markwon = null
}
@SuppressLint("NonConstantResourceId")
override fun isLightTheme(): Boolean {
return when (getSharedPreferences("mmm")!!
.getString("pref_theme", "system")) {
"system" -> isSystemLightTheme
"dark", "black" -> false
else -> true
}
}
private val isSystemLightTheme: Boolean
get() = (this.resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) != android.content.res.Configuration.UI_MODE_NIGHT_YES
val isDarkTheme: Boolean
get() = !this.isLightTheme
@Synchronized
fun getTracker(): Tracker? {
if (tracker == null) {
tracker = TrackerBuilder.createDefault(BuildConfig.ANALYTICS_ENDPOINT, 1)
.build(Matomo.getInstance(this))
val tracker = tracker!!
tracker.startNewSession()
tracker.dispatchInterval = 1000
}
return tracker
}
override fun onCreate() {
supportedLocales.addAll(
listOf(
"ar",
"bs",
"cs",
"de",
"es-rMX",
"fr",
"hu",
"id",
"ja",
"hu",
"nl",
"pl",
"pt",
"pt-rBR",
"ro",
"ru",
"tr",
"uk",
"zh",
"zh-rTW",
"en"
)
)
if (INSTANCE == null) INSTANCE = this
relPackageName = this.packageName
super.onCreate()
initialize(this)
// Initialize Timber
configTimber()
Timber.i(
"Starting AMM version %s (%d) - commit %s",
BuildConfig.VERSION_NAME,
BuildConfig.VERSION_CODE,
BuildConfig.COMMIT_HASH
)
// Update SSL Ciphers if update is possible
installIfNeeded(this)
// get intent. if isCrashing is not present or false, call FileUtils.ensureCacheDirs and FileUtils.ensureURLHandler
isCrashHandler = intent!!.getBooleanExtra("isCrashing", false)
if (!isCrashHandler) {
val fileUtils = FileUtils()
fileUtils.ensureCacheDirs()
fileUtils.ensureURLHandler(this)
}
Timber.d("Initializing AMM")
Timber.d("Started from background: %s", !isInForeground)
Timber.d("AMM is running in debug mode")
Timber.d("Initializing Realm")
Realm.init(this)
Timber.d("Initialized Realm")
// analytics
Timber.d("Initializing matomo")
getTracker()
if (!isMatomoAllowed()) {
Timber.d("Matomo is not allowed")
tracker!!.isOptOut = true
} else {
tracker!!.isOptOut = false
}
if (getSharedPreferences("matomo")!!
.getBoolean("install_tracked", false)
) {
TrackHelper.track().download().with(INSTANCE!!.getTracker())
Timber.d("Sent install event to matomo")
getSharedPreferences("matomo")!!
.edit().putBoolean("install_tracked", true).apply()
} else {
Timber.d("Matomo already has install")
}
try {
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures") val s = this.packageManager.getPackageInfo(
this.packageName,
PackageManager.GET_SIGNATURES
).signatures
@Suppress("SpellCheckingInspection") val osh = arrayOf(
"7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"e8ce7deca880304d7ff09f8fc37656cfa927cee7f6a0bb7b3feda6a5942931f5",
"339af2fb5b671fa4af6436b585351f2f1fc746d1d922f9a0b01df2d576381015"
)
val oosh = Hashing.sha256().hashBytes(s[0].toByteArray()).toString()
o = listOf(*osh).contains(oosh)
} catch (ignored: PackageManager.NameNotFoundException) {
}
val sharedPreferences = getSharedPreferences("mmm")
// We are only one process so it's ok to do this
val bootPrefs = getSharedPreferences("mmm_boot")
val lastBoot = System.currentTimeMillis() - SystemClock.elapsedRealtime()
val lastBootPrefs = bootPrefs!!.getLong("last_boot", 0)
isFirstBoot = if (lastBootPrefs == 0L || abs(lastBoot - lastBootPrefs) > 100) {
val firstBoot = sharedPreferences!!.getBoolean("first_boot", true)
bootPrefs.edit().clear().putLong("last_boot", lastBoot)
.putBoolean("first_boot", firstBoot).apply()
if (firstBoot) {
sharedPreferences.edit().putBoolean("first_boot", false).apply()
}
firstBoot
} else {
bootPrefs.getBoolean("first_boot", false)
}
// Force initialize language early.
LanguageSwitcher(this)
updateTheme()
// Update emoji config
val fontRequestEmojiCompatConfig = DefaultEmojiCompatConfig.create(this)
if (fontRequestEmojiCompatConfig != null) {
fontRequestEmojiCompatConfig.setReplaceAll(true)
fontRequestEmojiCompatConfig.setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL)
val emojiCompat = EmojiCompat.init(fontRequestEmojiCompatConfig)
Thread({
Timber.i("Loading emoji compat...")
emojiCompat.load()
Timber.i("Emoji compat loaded!")
}, "Emoji compat init.").start()
}
@Suppress("KotlinConstantConditions")
if ((BuildConfig.ANDROIDACY_CLIENT_ID == "")) {
Timber.w("Androidacy client id is empty! Please set it in androidacy.properties. Will not enable Androidacy.")
val editor = sharedPreferences!!.edit()
editor.putBoolean("pref_androidacy_repo_enabled", false)
Timber.w("ANDROIDACY_CLIENT_ID is empty, disabling AndroidacyRepoData 1")
editor.apply()
}
}
private val intent: Intent?
get() = this.packageManager.getLaunchIntentForPackage(this.packageName)
override fun onCreateFoxActivity(compatActivity: FoxActivity) {
super.onCreateFoxActivity(compatActivity)
compatActivity.setTheme(managerThemeResId)
}
override fun onRefreshUI(compatActivity: FoxActivity) {
super.onRefreshUI(compatActivity)
compatActivity.setThemeRecreate(managerThemeResId)
}
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
val newTimeFormatLocale = newConfig.locales[0]
if (timeFormatLocale !== newTimeFormatLocale) {
timeFormatLocale = newTimeFormatLocale
timeFormat = SimpleDateFormat(timeFormatString, timeFormatLocale)
}
super.onConfigurationChanged(newConfig)
}
// getDataDir wrapper with optional path parameter
@Suppress("NAME_SHADOWING")
fun getDataDirWithPath(path: String?): File {
var path = path
var dataDir = this.dataDir
// for path with / somewhere in the middle, its a subdirectory
return if (path != null) {
if (path.startsWith("/")) path = path.substring(1)
if (path.endsWith("/")) path = path.substring(0, path.length - 1)
if (path.contains("/")) {
val dirs = path.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (dir: String? in dirs) {
dataDir = File(dataDir, dir!!)
// make sure the directory exists
if (!dataDir.exists()) {
if (!dataDir.mkdirs()) {
if (BuildConfig.DEBUG) Timber.w(
"Failed to create directory %s",
dataDir
)
}
}
}
} else {
dataDir = File(dataDir, path)
// create the directory if it doesn't exist
if (!dataDir.exists()) {
if (!dataDir.mkdirs()) {
if (BuildConfig.DEBUG) Timber.w("Failed to create directory %s", dataDir)
}
}
}
dataDir
} else {
throw IllegalArgumentException("Path cannot be null")
}
}
@SuppressLint("RestrictedApi") // view is nullable because it's called from xml
fun resetApp() {
// cant show a dialog because android is throwing a fit so here's hoping anybody who calls this method is otherwise confirming that the user wants to reset the app
Timber.w("Resetting app...")
// recursively delete the app's data
(this.getSystemService(ACTIVITY_SERVICE) as ActivityManager).clearApplicationUserData()
}
// determine if the app is in the foreground
val isInForeground: Boolean
get() {
// determine if the app is in the foreground
val activityManager = this.getSystemService(ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses
if (appProcesses == null) {
Timber.d("appProcesses is null")
return false
}
val packageName = this.packageName
for (appProcess in appProcesses) {
Timber.d(
"Process: %s, Importance: %d",
appProcess.processName,
appProcess.importance
)
if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) {
return true
}
}
return false
}
// returns if background execution is restricted
val isBackgroundRestricted: Boolean
get() {
val am = getSystemService(ACTIVITY_SERVICE) as ActivityManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
am.isBackgroundRestricted
} else {
false
}
}// sleep until the key is made
// attempt to read the existingKey property
// check if we have a key already
// open a connection to the android keystore
// create a securely generated random asymmetric RSA key
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
// generate secret key
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
// keep the encrypted key in shared preferences
// to persist it across application runs
// pass to a realm configuration via encryptionKey()
// Create a key to encrypt a realm and save it securely in the keystore
val key: ByteArray
get() {
if (makingNewKey) {
// sleep until the key is made
while (makingNewKey) try {
Thread.sleep(100)
} catch (ignored: InterruptedException) {
Thread.currentThread().interrupt()
}
}
// attempt to read the existingKey property
if (existingKey != null) {
return existingKey as ByteArray
}
// check if we have a key already
val sharedPreferences = getSharedPreferences("realm_key")
makingNewKey = if (sharedPreferences!!.contains("iv_and_encrypted_key")) {
return getExistingKey()
} else {
true
}
// open a connection to the android keystore
val keyStore: KeyStore
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
} catch (e: KeyStoreException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: CertificateException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: IOException) {
Timber.v("Failed to open the keystore.")
throw RuntimeException(e)
}
// create a securely generated random asymmetric RSA key
val realmKey = ByteArray(Realm.ENCRYPTION_KEY_LENGTH)
do {
SecureRandom().nextBytes(realmKey)
} while (realmKey[0].toInt() == 0)
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
val cipher: Cipher = try {
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to create a cipher.")
throw RuntimeException(e)
} catch (e: NoSuchPaddingException) {
Timber.e("Failed to create a cipher.")
throw RuntimeException(e)
}
Timber.v("Cipher created.")
// generate secret key
val keyGenerator: KeyGenerator = try {
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to access the key generator.")
throw RuntimeException(e)
} catch (e: NoSuchProviderException) {
Timber.e("Failed to access the key generator.")
throw RuntimeException(e)
}
val keySpec = KeyGenParameterSpec.Builder(
"realm_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7).build()
try {
keyGenerator.init(keySpec)
} catch (e: InvalidAlgorithmParameterException) {
Timber.e("Failed to generate a secret key.")
throw RuntimeException(e)
}
Timber.v("Secret key generated.")
keyGenerator.generateKey()
Timber.v("Secret key stored in the keystore.")
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
val initializationVector: ByteArray
val encryptedKeyForRealm: ByteArray
try {
val secretKey = keyStore.getKey("realm_key", null) as SecretKey
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
encryptedKeyForRealm = cipher.doFinal(realmKey)
initializationVector = cipher.iv
} catch (e: InvalidKeyException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: UnrecoverableKeyException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: KeyStoreException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: BadPaddingException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
} catch (e: IllegalBlockSizeException) {
Timber.e("Failed encrypting the key with the secret key.")
throw RuntimeException(e)
}
// keep the encrypted key in shared preferences
// to persist it across application runs
val initializationVectorAndEncryptedKey =
ByteArray(Integer.BYTES + initializationVector.size + encryptedKeyForRealm.size)
val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey)
buffer.order(ByteOrder.BIG_ENDIAN)
buffer.putInt(initializationVector.size)
buffer.put(initializationVector)
buffer.put(encryptedKeyForRealm)
Timber.d("Created all keys successfully.")
getSharedPreferences("realm_key")!!
.edit().putString(
"iv_and_encrypted_key",
Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)
).apply()
Timber.d("Saved the encrypted key in shared preferences.")
makingNewKey = false
return realmKey // pass to a realm configuration via encryptionKey()
}
// Access the encrypted key in the keystore, decrypt it with the secret,
// and use it to open and read from the realm again
fun getExistingKey(): ByteArray {
// attempt to read the existingKey property
if (existingKey != null) {
return existingKey as ByteArray
}
// open a connection to the android keystore
val keyStore: KeyStore
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
} catch (e: KeyStoreException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: CertificateException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
} catch (e: IOException) {
Timber.e("Failed to open the keystore.")
throw RuntimeException(e)
}
Timber.v("Keystore opened.")
// access the encrypted key that's stored in shared preferences
val initializationVectorAndEncryptedKey = Base64.decode(
getSharedPreferences("realm_key")!!
.getString("iv_and_encrypted_key", null), Base64.DEFAULT
)
Timber.d(
"Retrieved the encrypted key from shared preferences. Key length: %d",
initializationVectorAndEncryptedKey.size
)
val buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey)
buffer.order(ByteOrder.BIG_ENDIAN)
// extract the length of the initialization vector from the buffer
val initializationVectorLength = buffer.int
// extract the initialization vector based on that length
val initializationVector = ByteArray(initializationVectorLength)
buffer[initializationVector]
// extract the encrypted key
val encryptedKey =
ByteArray(initializationVectorAndEncryptedKey.size - Integer.BYTES - initializationVectorLength)
buffer[encryptedKey]
Timber.d("Got key from shared preferences.")
// create a cipher that uses AES encryption to decrypt our key
val cipher: Cipher = try {
Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to create cipher.")
throw RuntimeException(e)
} catch (e: NoSuchPaddingException) {
Timber.e("Failed to create cipher.")
throw RuntimeException(e)
}
// decrypt the encrypted key with the secret key stored in the keystore
val decryptedKey: ByteArray = try {
val secretKey = keyStore.getKey("realm_key", null) as SecretKey
val initializationVectorSpec = IvParameterSpec(initializationVector)
cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec)
cipher.doFinal(encryptedKey)
} catch (e: InvalidKeyException) {
Timber.e("Failed to decrypt. Invalid key.")
throw RuntimeException(e)
} catch (e: UnrecoverableKeyException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: NoSuchAlgorithmException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: BadPaddingException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: KeyStoreException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: IllegalBlockSizeException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
} catch (e: InvalidAlgorithmParameterException) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.")
throw RuntimeException(e)
}
// set property on MainApplication to indicate that the key has been accessed
existingKey = decryptedKey
return decryptedKey // pass to a realm configuration via encryptionKey()
}
fun resetUpdateModule() {
modulesHaveUpdates = false
updateModuleCount = 0
updateModules = ArrayList()
}
class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// basically silently drop all logs below error, and write the rest to logcat
if (priority >= Log.ERROR) {
if (t != null) {
Log.println(priority, tag, message)
t.printStackTrace()
} else {
Log.println(priority, tag, message)
}
}
}
}
companion object {
// Warning! Locales that don't exist will crash the app
// Anything that is commented out is supported but the translation is not complete to at least 60%
@JvmField
val supportedLocales = HashSet<String>()
private const val timeFormatString = "dd MMM yyyy" // Example: 13 july 2001
private var shellBuilder: Shell.Builder? = null
// Is application wrapped, and therefore must reduce it's feature set.
@SuppressLint("RestrictedApi") // Use FoxProcess wrapper helper.
@JvmField
val isWrapped = !FoxProcessExt.isRootLoader()
private val callers = ArrayList<String>()
@JvmField
var o = false
private var SHOWCASE_MODE_TRUE: String? = null
private var secret: Long = 0
private var timeFormatLocale = Resources.getSystem().configuration.locales[0]
private var timeFormat = SimpleDateFormat(timeFormatString, timeFormatLocale)
private var relPackageName = BuildConfig.APPLICATION_ID
@SuppressLint("StaticFieldLeak")
@JvmStatic
var INSTANCE: MainApplication? = null
private set
get() {
if (field == null) {
throw IllegalStateException("Application not created yet!")
}
return field
}
@JvmStatic
var isFirstBoot = false
private var mSharedPrefs: HashMap<Any, Any>? = null
var updateCheckBg: String? = null
init {
Shell.setDefaultBuilder(
Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR or Shell.FLAG_MOUNT_MASTER).setTimeout(15)
.setInitializers(
InstallerInitializer::class.java
).also { shellBuilder = it })
val random = Random()
do {
secret = random.nextLong()
} while (secret == 0L)
}
fun build(vararg command: String?): Shell {
return shellBuilder!!.build(*command)
}
fun addSecret(intent: Intent) {
val componentName = intent.component
val packageName = componentName?.packageName ?: (intent.getPackage())!!
require(
!(!BuildConfig.APPLICATION_ID.equals(
packageName,
ignoreCase = true
) && relPackageName != packageName)
) {
// Code safeguard, we should never reach here.
"Can't add secret to outbound Intent"
}
intent.putExtra("secret", secret)
}
@Suppress("NAME_SHADOWING")
@JvmStatic
fun getSharedPreferences(name: String): SharedPreferences? {
// encryptedSharedPreferences is used
var name = name
val mContext: Context? = INSTANCE
name += "x"
if (mSharedPrefs == null) {
Timber.d("Creating shared prefs map")
mSharedPrefs = HashMap()
}
/*
this part is only here because with added encryption, parts of code that were previously calling this over and over again or on each invocation of a method are causing performance issues.
*/if (BuildConfig.DEBUG) {
// get file, function, and line number
val stackTrace = Thread.currentThread().stackTrace
// get the caller of this method
val caller = stackTrace[3]
Timber.d(
"Shared prefs file: %s, caller: %s:%d in %s",
name,
caller.fileName,
caller.lineNumber,
caller.methodName
)
// add the caller to an array. if the last 3 callers are the same, then we are in a loop, log at error level
callers.add(name + ":" + caller.lineNumber + ":" + caller.methodName)
// get the last 3 callers
val last3: List<String> =
callers.subList(max(callers.size - 3, 0), callers.size)
// if the last 3 callers are the same, then we are in a loop, log at error level
if (((last3.size == 3) && last3[0] == last3[1]) && last3[1] == last3[2]) {
Timber.e(
"Shared prefs loop detected. File: %s, caller: %s:%d",
name,
caller.methodName,
caller.lineNumber
)
}
}
if (mSharedPrefs!!.containsKey(name)) {
Timber.d("Returning cached shared prefs")
return mSharedPrefs!![name] as SharedPreferences?
}
return try {
Timber.d("Creating encrypted shared prefs")
val masterKey =
MasterKey.Builder(mContext!!).setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
mContext,
name,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
mSharedPrefs!![name] = sharedPreferences
sharedPreferences
} catch (e: Exception) {
Timber.e(e, "Failed to create encrypted shared preferences")
mContext!!.getSharedPreferences(name, MODE_PRIVATE)
}
}
fun checkSecret(intent: Intent?): Boolean {
return intent != null && intent.getLongExtra("secret", secret.inv()) == secret
}
// convert from String to boolean
val isShowcaseMode: Boolean
get() {
if (SHOWCASE_MODE_TRUE != null) {
// convert from String to boolean
return java.lang.Boolean.parseBoolean(SHOWCASE_MODE_TRUE)
}
val showcaseMode = getSharedPreferences("mmm")!!
.getBoolean("pref_showcase_mode", false)
SHOWCASE_MODE_TRUE = showcaseMode.toString()
return showcaseMode
}
fun shouldPreventReboot(): Boolean {
return getSharedPreferences("mmm")!!
.getBoolean("pref_prevent_reboot", true)
}
val isShowIncompatibleModules: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_show_incompatible", false)
val isForceDarkTerminal: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_force_dark_terminal", false)
val isTextWrapEnabled: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_wrap_text", false)
val isDohEnabled: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_dns_over_https", true)
val isMonetEnabled: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && getSharedPreferences("mmm")!!
.getBoolean("pref_enable_monet", true)
val isBlurEnabled: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_enable_blur", false)
@JvmStatic
val isDeveloper: Boolean
get() {
return if (BuildConfig.DEBUG) true else getSharedPreferences("mmm")!!
.getBoolean("developer", false)
}
val isDisableLowQualityModuleFilter: Boolean
get() = getSharedPreferences("mmm")!!
.getBoolean("pref_disable_low_quality_module_filter", false) && isDeveloper
val isUsingMagiskCommand: Boolean
get() = (peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND) && getSharedPreferences(
"mmm"
)!!
.getBoolean("pref_use_magisk_install_command", false) && isDeveloper
@JvmStatic
val isBackgroundUpdateCheckEnabled: Boolean
get() {
if (updateCheckBg != null) {
return java.lang.Boolean.parseBoolean(updateCheckBg)
}
val wrapped = isWrapped
val updateCheckBgTemp = !wrapped && getSharedPreferences("mmm")!!
.getBoolean("pref_background_update_check", true)
updateCheckBg = updateCheckBgTemp.toString()
return java.lang.Boolean.parseBoolean(updateCheckBg)
}
val isAndroidacyTestMode: Boolean
get() = isDeveloper && getSharedPreferences("mmm")!!
.getBoolean("pref_androidacy_test_mode", false)
fun setHasGottenRootAccess(bool: Boolean) {
getSharedPreferences("mmm")!!
.edit().putBoolean("has_root_access", bool).apply()
}
val isCrashReportingEnabled: Boolean
get() = SentryMain.IS_SENTRY_INSTALLED && getSharedPreferences("mmm")!!
.getBoolean("pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING)
val bootSharedPreferences: SharedPreferences?
get() = getSharedPreferences("mmm_boot")
@JvmStatic
fun formatTime(timeStamp: Long): String {
// new Date(x) also get the local timestamp for format
return timeFormat.format(Date(timeStamp))
}
@JvmStatic
val isNotificationPermissionGranted: Boolean
get() = NotificationManagerCompat.from((INSTANCE)!!).areNotificationsEnabled()
@JvmStatic
fun isMatomoAllowed(): Boolean {
return getSharedPreferences("mmm")!!
.getBoolean("pref_analytics_enabled", BuildConfig.DEFAULT_ENABLE_ANALYTICS)
}
}
}

@ -53,7 +53,7 @@ enum class NotificationType constructor(
androidx.appcompat.R.attr.colorPrimary, com.google.android.material.R.attr.colorOnPrimary
) {
override fun shouldRemove(): Boolean {
return !MainApplication.isShowcaseMode()
return !MainApplication.isShowcaseMode
}
},
@JvmStatic
@ -208,7 +208,7 @@ enum class NotificationType constructor(
) {
override fun shouldRemove(): Boolean {
return !BuildConfig.DEBUG &&
(MainApplication.isShowcaseMode() ||
(MainApplication.isShowcaseMode ||
InstallerInitializer.peekMagiskPath() == null)
}
};

@ -45,7 +45,7 @@ class SetupActivity : FoxActivity(), LanguageActivity {
createFiles()
disableUpdateActivityForFdroidFlavor()
// Set theme
val prefs = MainApplication.getSharedPreferences("mmm")
val prefs = MainApplication.getSharedPreferences("mmm")!!
when (prefs.getString("theme", "system")) {
"light" -> setTheme(R.style.Theme_MagiskModuleManager_Monet_Light)
"dark" -> setTheme(R.style.Theme_MagiskModuleManager_Monet_Dark)
@ -219,8 +219,8 @@ class SetupActivity : FoxActivity(), LanguageActivity {
Timber.d("Saving preferences")
// Set the repos in the ReposList realm db
val realmConfig = RealmConfiguration.Builder().name("ReposList.realm")
.encryptionKey(MainApplication.getINSTANCE().key)
.directory(MainApplication.getINSTANCE().getDataDirWithPath("realms"))
.encryptionKey(MainApplication.INSTANCE!!.key)
.directory(MainApplication.INSTANCE!!.getDataDirWithPath("realms"))
.schemaVersion(1).build()
val androidacyRepo = andRepoView.isChecked
val magiskAltRepo = magiskAltRepoView.isChecked
@ -294,7 +294,7 @@ class SetupActivity : FoxActivity(), LanguageActivity {
return theme
}
// Set the theme
val prefs = MainApplication.getSharedPreferences("mmm")
val prefs = MainApplication.getSharedPreferences("mmm")!!
when (prefs.getString("pref_theme", "system")) {
"light" -> {
theme.applyStyle(R.style.Theme_MagiskModuleManager_Monet_Light, true)
@ -346,11 +346,11 @@ class SetupActivity : FoxActivity(), LanguageActivity {
val startTime = System.currentTimeMillis()
// create encryption key
Timber.d("Creating encryption key")
val key = MainApplication.getINSTANCE().key
val key = MainApplication.INSTANCE!!.key
// create the realm database for ReposList
// create the realm configuration
val config = RealmConfiguration.Builder().name("ReposList.realm")
.directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1)
.directory(MainApplication.INSTANCE!!.getDataDirWithPath("realms")).schemaVersion(1)
.encryptionKey(key).build()
// get the instance
Realm.getInstanceAsync(config, object : Realm.Callback() {
@ -440,10 +440,10 @@ class SetupActivity : FoxActivity(), LanguageActivity {
}
// we literally only use these to create the http cache folders
try {
FileUtils.forceMkdir(File(MainApplication.getINSTANCE().dataDir.toString() + "/cache/cronet"))
FileUtils.forceMkdir(File(MainApplication.getINSTANCE().dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/wasm"))
FileUtils.forceMkdir(File(MainApplication.getINSTANCE().dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/js"))
FileUtils.forceMkdir(File(MainApplication.getINSTANCE().dataDir.toString() + "/repos/magiskAltRepo"))
FileUtils.forceMkdir(File(MainApplication.INSTANCE!!.dataDir.toString() + "/cache/cronet"))
FileUtils.forceMkdir(File(MainApplication.INSTANCE!!.dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/wasm"))
FileUtils.forceMkdir(File(MainApplication.INSTANCE!!.dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/js"))
FileUtils.forceMkdir(File(MainApplication.INSTANCE!!.dataDir.toString() + "/repos/magiskAltRepo"))
} catch (e: IOException) {
Timber.e(e)
}

@ -25,8 +25,8 @@ import java.util.Objects
class UpdateActivity : FoxActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (MainApplication.getINSTANCE().isMatomoAllowed) {
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().tracker)
if (MainApplication.isMatomoAllowed()) {
TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker)
}
setContentView(R.layout.activity_update)
// Get the progress bar and make it indeterminate for now

@ -73,7 +73,7 @@ class AndroidacyActivity : FoxActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
moduleFile = File(this.cacheDir, "module.zip")
super.onCreate(savedInstanceState)
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker)
val intent = this.intent
var uri: Uri? = intent.data
@Suppress("KotlinConstantConditions")
@ -168,7 +168,7 @@ class AndroidacyActivity : FoxActivity() {
WebView.setWebContentsDebuggingEnabled(true)
}
// if app is in dark mode, force dark mode on webview
if (MainApplication.getINSTANCE().isDarkTheme) {
if (MainApplication.INSTANCE!!.isDarkTheme) {
// for api 33, use setAlgorithmicDarkeningAllowed, for api 29-32 use setForceDark, for api 28 and below use setForceDarkStrategy
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
WebSettingsCompat.setAlgorithmicDarkeningAllowed(webSettings!!, true)

@ -229,7 +229,7 @@ class AndroidacyWebAPI(
*/
@get:JavascriptInterface
val isLightTheme: Boolean
get() = MainApplication.getINSTANCE().isLightTheme
get() = MainApplication.INSTANCE!!.isLightTheme
/**
* Check if the manager has received root access
@ -246,7 +246,7 @@ class AndroidacyWebAPI(
@JavascriptInterface
fun canInstall(): Boolean {
// With lockdown mode enabled or lack of root, install should not have any effect
return allowInstall && hasRoot() && !MainApplication.isShowcaseMode()
return allowInstall && hasRoot() && !MainApplication.isShowcaseMode
}
/**

@ -9,10 +9,10 @@ import com.fox2code.mmm.utils.io.net.Http
class BackgroundBootListener : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (BOOT_COMPLETED != intent.action) return
if (!MainApplication.isBackgroundUpdateCheckEnabled()) return
if (!MainApplication.isBackgroundUpdateCheckEnabled) return
if (!Http.hasConnectivity(context)) return
// clear boot shared prefs
MainApplication.getBootSharedPreferences().edit().clear().apply()
MainApplication.bootSharedPreferences!!.edit().clear().apply()
synchronized(BackgroundUpdateChecker.lock) {
Thread {
BackgroundUpdateChecker.onMainActivityCreate(context)

@ -37,7 +37,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
Worker(context, workerParams) {
override fun doWork(): Result {
if (!NotificationManagerCompat.from(this.applicationContext)
.areNotificationsEnabled() || !MainApplication.isBackgroundUpdateCheckEnabled()
.areNotificationsEnabled() || !MainApplication.isBackgroundUpdateCheckEnabled
) return Result.success()
synchronized(lock) { doCheck(this.applicationContext) }
return Result.success()
@ -98,7 +98,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
builder.setContentTitle(context.getString(R.string.notification_channel_background_update_app))
builder.setContentText(context.getString(R.string.notification_channel_background_update_app_description))
if (ContextCompat.checkSelfPermission(
MainApplication.getINSTANCE(),
MainApplication.INSTANCE!!.applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
@ -109,17 +109,17 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
@Suppress("NAME_SHADOWING")
fun doCheck(context: Context) {
// first, check if the user has enabled background update checking
if (!MainApplication.getSharedPreferences("mmm")
if (!MainApplication.getSharedPreferences("mmm")!!
.getBoolean("pref_background_update_check", false)
) {
return
}
if (MainApplication.getINSTANCE().isInForeground) {
if (MainApplication.INSTANCE!!.isInForeground) {
// don't check if app is in foreground, this is a background check
return
}
// next, check if user requires wifi
if (MainApplication.getSharedPreferences("mmm")
if (MainApplication.getSharedPreferences("mmm")!!
.getBoolean("pref_background_update_check_wifi", true)
) {
// check if wifi is connected
@ -138,7 +138,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
// post checking notification if notifications are enabled
if (ContextCompat.checkSelfPermission(
MainApplication.getINSTANCE(),
MainApplication.INSTANCE!!.applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
@ -187,7 +187,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
// exclude all modules with id's stored in the pref pref_background_update_check_excludes
try {
if (Objects.requireNonNull(
MainApplication.getSharedPreferences("mmm").getStringSet(
MainApplication.getSharedPreferences("mmm")!!.getStringSet(
"pref_background_update_check_excludes",
HashSet()
)
@ -198,7 +198,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
// now, we just had to make it more fucking complicated, didn't we?
// we now have pref_background_update_check_excludes_version, which is a id:version stringset of versions the user may want to "skip"
// oh, and because i hate myself, i made ^ at the beginning match that version and newer, and $ at the end match that version and older
val stringSet = MainApplication.getSharedPreferences("mmm").getStringSet(
val stringSet = MainApplication.getSharedPreferences("mmm")!!.getStringSet(
"pref_background_update_check_excludes_version",
HashSet()
)
@ -272,7 +272,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
}
}
// check for app updates
if (MainApplication.getSharedPreferences("mmm")
if (MainApplication.getSharedPreferences("mmm")!!
.getBoolean("pref_background_update_check_app", false)
) {
try {
@ -287,7 +287,7 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
}
// remove checking notification
if (ContextCompat.checkSelfPermission(
MainApplication.getINSTANCE(),
MainApplication.INSTANCE!!.applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
@ -297,9 +297,9 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
}
}
// increment or create counter in shared preferences
MainApplication.getSharedPreferences("mmm").edit().putInt(
MainApplication.getSharedPreferences("mmm")!!.edit().putInt(
"pref_background_update_counter",
MainApplication.getSharedPreferences("mmm")
MainApplication.getSharedPreferences("mmm")!!
.getInt("pref_background_update_counter", 0) + 1
).apply()
}
@ -354,21 +354,21 @@ class BackgroundUpdateChecker(context: Context, workerParams: WorkerParameters)
// set long text to summary so it doesn't get cut off
builder.setStyle(NotificationCompat.BigTextStyle().bigText(summary))
if (ContextCompat.checkSelfPermission(
MainApplication.getINSTANCE(),
MainApplication.INSTANCE!!.applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return
}
// check if app is in foreground. if so, don't show notification
if (MainApplication.getINSTANCE().isInForeground && !test) return
if (MainApplication.INSTANCE!!.isInForeground && !test) return
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build())
}
@JvmStatic
fun onMainActivityCreate(context: Context) {
// Refuse to run if first_launch pref is not false
if (MainApplication.getSharedPreferences("mmm")
if (MainApplication.getSharedPreferences("mmm")!!
.getString("last_shown_setup", null) != "v2"
) return
// create notification channel group

@ -82,7 +82,7 @@ class InstallerActivity : FoxActivity() {
moduleCache = File(this.cacheDir, "installer")
if (!moduleCache!!.exists() && !moduleCache!!.mkdirs()) Timber.e("Failed to mkdir module cache dir!")
super.onCreate(savedInstanceState)
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker)
setDisplayHomeAsUpEnabled(true)
setActionBarBackground(null)
setOnBackPressedCallback { _: FoxActivity? ->
@ -107,7 +107,7 @@ class InstallerActivity : FoxActivity() {
target = intent.getStringExtra(Constants.EXTRA_INSTALL_PATH)!!.replace(
Regex("(\\.\\.|%2E%2E|%252E%252E|%20)"), ""
)
if (target.isEmpty() || !target.startsWith(MainApplication.getINSTANCE().dataDir.absolutePath) && !target.startsWith(
if (target.isEmpty() || !target.startsWith(MainApplication.INSTANCE!!.dataDir.absolutePath) && !target.startsWith(
"https://"
)
) {
@ -131,7 +131,7 @@ class InstallerActivity : FoxActivity() {
return
}
// Note: Sentry only send this info on crash.
if (MainApplication.isCrashReportingEnabled()) {
if (MainApplication.isCrashReportingEnabled) {
val breadcrumb = SentryBreadcrumb()
breadcrumb.setType("install")
breadcrumb.setData("target", target)
@ -143,11 +143,11 @@ class InstallerActivity : FoxActivity() {
val urlMode = target.startsWith("http://") || target.startsWith("https://")
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
title = name
textWrap = MainApplication.isTextWrapEnabled()
textWrap = MainApplication.isTextWrapEnabled
setContentView(if (textWrap) R.layout.installer_wrap else R.layout.installer)
val background: Int
val foreground: Int
if (MainApplication.getINSTANCE().isLightTheme && !MainApplication.isForceDarkTerminal()) {
if (MainApplication.INSTANCE!!.isLightTheme && !MainApplication.isForceDarkTerminal) {
background = Color.WHITE
foreground = Color.BLACK
} else {
@ -183,11 +183,11 @@ class InstallerActivity : FoxActivity() {
prgInd?.visibility = View.VISIBLE
if (urlMode) installerTerminal!!.addLine("- Downloading $name")
TrackHelper.track().event("installer_start", name)
.with(MainApplication.getINSTANCE().tracker)
.with(MainApplication.INSTANCE!!.tracker)
Thread(Runnable {
// ensure module cache is is in our cache dir
if (urlMode && !moduleCache!!.absolutePath.startsWith(MainApplication.getINSTANCE().cacheDir.absolutePath)) throw SecurityException(
if (urlMode && !moduleCache!!.absolutePath.startsWith(MainApplication.INSTANCE!!.cacheDir.absolutePath)) throw SecurityException(
"Module cache is not in cache dir!"
)
toDelete = if (urlMode) File(moduleCache, "module.zip") else File(
@ -326,6 +326,7 @@ class InstallerActivity : FoxActivity() {
}, "Module install Thread").start()
}
@Suppress("KotlinConstantConditions")
@Keep
private fun doInstall(file: File?, noExtensions: Boolean, rootless: Boolean) {
@Suppress("NAME_SHADOWING") var noExtensions = noExtensions
@ -492,7 +493,7 @@ class InstallerActivity : FoxActivity() {
}
installCommand =
ashExec + " \"" + installExecutable.absolutePath + "\"" + " 3 1 \"" + file.absolutePath + "\""
} else if (InstallerInitializer.peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND && (compatFlags and AppUpdateManager.FLAG_COMPAT_MAGISK_CMD != 0 || noExtensions || MainApplication.isUsingMagiskCommand())) {
} else if (InstallerInitializer.peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND && (compatFlags and AppUpdateManager.FLAG_COMPAT_MAGISK_CMD != 0 || noExtensions || MainApplication.isUsingMagiskCommand)) {
installCommand = "magisk --install-module \"" + file.absolutePath + "\""
installExecutable =
File(if (mgskPath == "/sbin") "/sbin/magisk" else "/system/bin/magisk")
@ -544,7 +545,7 @@ class InstallerActivity : FoxActivity() {
).to(installerController, installerMonitor)
}
// Note: Sentry only send this info on crash.
if (MainApplication.isCrashReportingEnabled()) {
if (MainApplication.isCrashReportingEnabled) {
val breadcrumb = SentryBreadcrumb()
breadcrumb.setType("install")
breadcrumb.setData("moduleId", if (moduleId == null) "<null>" else moduleId)

@ -25,12 +25,12 @@ import java.util.Objects
class ModuleManager private constructor() : SyncManager() {
private val moduleInfos: HashMap<String, LocalModuleInfo> = HashMap()
private val bootPrefs: SharedPreferences = MainApplication.getBootSharedPreferences()
private val bootPrefs: SharedPreferences = MainApplication.bootSharedPreferences!!
private var updatableModuleCount = 0
override fun scanInternal(updateListener: UpdateListener) {
// if last_shown_setup is not "v2", then refuse to continue
if (MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", "") != "v2") {
if (MainApplication.getSharedPreferences("mmm")!!.getString("last_shown_setup", "") != "v2") {
return
}
val firstScan = bootPrefs.getBoolean("mm_first_scan", true)
@ -63,7 +63,7 @@ class ModuleManager private constructor() : SyncManager() {
var realmConfiguration: RealmConfiguration?
// get all dirs under the realms/repos/ dir under app's data dir
val cacheRoot =
File(MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/").toURI())
File(MainApplication.INSTANCE!!.getDataDirWithPath("realms/repos/").toURI())
var moduleListCache: ModuleListCache?
for (dir in Objects.requireNonNull<Array<File>>(cacheRoot.listFiles())) {
if (dir.isDirectory) {
@ -72,7 +72,7 @@ class ModuleManager private constructor() : SyncManager() {
Timber.d("Looking for cache in %s", tempCacheRoot)
realmConfiguration =
RealmConfiguration.Builder().name("ModuleListCache.realm")
.encryptionKey(MainApplication.getINSTANCE().key).schemaVersion(1)
.encryptionKey(MainApplication.INSTANCE!!.key).schemaVersion(1)
.deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true)
.allowQueriesOnUiThread(true).directory(tempCacheRoot).build()
val realm = Realm.getInstance(realmConfiguration!!)
@ -164,7 +164,7 @@ class ModuleManager private constructor() : SyncManager() {
}
// send list to matomo
TrackHelper.track().event("installed_modules", modulesList.toString())
.with(MainApplication.getINSTANCE().tracker)
.with(MainApplication.INSTANCE!!.tracker)
if (BuildConfig.DEBUG) Timber.d("Scan update")
val modulesUpdate = SuFile("/data/adb/modules_update").list()
if (modulesUpdate != null) {

@ -34,7 +34,7 @@ class MarkdownActivity : FoxActivity() {
private var footer: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().screen(this).with(MainApplication.INSTANCE!!.tracker)
setDisplayHomeAsUpEnabled(true)
val intent = this.intent
if (!MainApplication.checkSecret(intent)) {
@ -104,7 +104,7 @@ class MarkdownActivity : FoxActivity() {
Timber.i("Done!")
runOnUiThread {
footer?.minimumHeight = this.navigationBarHeight
MainApplication.getINSTANCE().markwon.setMarkdown(
MainApplication.INSTANCE!!.markwon!!.setMarkdown(
textView,
MarkdownUrlLinker.urlLinkify(markdown)
)
@ -122,7 +122,7 @@ class MarkdownActivity : FoxActivity() {
}
private fun updateBlurState() {
if (MainApplication.isBlurEnabled()) {
if (MainApplication.isBlurEnabled) {
// set bottom navigation bar color to transparent blur
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation)
if (bottomNavigationView != null) {

@ -172,7 +172,7 @@ class ModuleViewAdapter : RecyclerView.Adapter<ModuleViewAdapter.ViewHolder>() {
val type = moduleHolder.type
val vType = moduleHolder.getCompareType(type)
cardView.visibility = View.VISIBLE
val showCaseMode = MainApplication.isShowcaseMode()
val showCaseMode = MainApplication.isShowcaseMode
if (moduleHolder.isModuleHolder) {
buttonAction.visibility = View.GONE
buttonAction.background = null

@ -68,7 +68,7 @@ class ModuleViewListBuilder(private val activity: Activity) {
Timber.i("appendRemoteModules() called")
}
synchronized(updateLock) {
val showIncompatible = MainApplication.isShowIncompatibleModules()
val showIncompatible = MainApplication.isShowIncompatibleModules
for (moduleHolder in mappedModuleHolders.values) {
moduleHolder.repoModule = null
}

@ -35,9 +35,9 @@ class RepoUpdater(repoData2: RepoData) {
if (!repoData.shouldUpdate() && repoData.id == "androidacy_repo") { // for now, only enable cache reading for androidacy repo, until we handle storing module prop file values in cache
Timber.d("Fetching index from cache for %s", repoData.id)
val cacheRoot =
MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + repoData.id)
MainApplication.INSTANCE!!.getDataDirWithPath("realms/repos/" + repoData.id)
val realmConfiguration = RealmConfiguration.Builder().name("ModuleListCache.realm")
.encryptionKey(MainApplication.getINSTANCE().key).schemaVersion(1)
.encryptionKey(MainApplication.INSTANCE!!.key).schemaVersion(1)
.deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true)
.allowQueriesOnUiThread(true).directory(cacheRoot).build()
val realm = Realm.getInstance(realmConfiguration)
@ -46,9 +46,9 @@ class RepoUpdater(repoData2: RepoData) {
).equalTo("repoId", repoData.id).findAll()
// repos-list realm
val realmConfiguration2 = RealmConfiguration.Builder().name("ReposList.realm")
.encryptionKey(MainApplication.getINSTANCE().key).allowQueriesOnUiThread(true)
.encryptionKey(MainApplication.INSTANCE!!.key).allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.directory(MainApplication.getINSTANCE().getDataDirWithPath("realms"))
.directory(MainApplication.INSTANCE!!.getDataDirWithPath("realms"))
.schemaVersion(1).build()
val realm2 = Realm.getInstance(realmConfiguration2)
toUpdate = emptyList()
@ -139,9 +139,9 @@ class RepoUpdater(repoData2: RepoData) {
// use realm to insert to
// props avail:
val cacheRoot =
MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + repoData.id)
MainApplication.INSTANCE!!.getDataDirWithPath("realms/repos/" + repoData.id)
val realmConfiguration = RealmConfiguration.Builder().name("ModuleListCache.realm")
.encryptionKey(MainApplication.getINSTANCE().key).schemaVersion(1)
.encryptionKey(MainApplication.INSTANCE!!.key).schemaVersion(1)
.deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true)
.allowQueriesOnUiThread(true).directory(cacheRoot).build()
// array with module info default values
@ -346,9 +346,9 @@ class RepoUpdater(repoData2: RepoData) {
}
indexRaw = null
val realmConfiguration2 = RealmConfiguration.Builder().name("ReposList.realm")
.encryptionKey(MainApplication.getINSTANCE().key).allowQueriesOnUiThread(true)
.encryptionKey(MainApplication.INSTANCE!!.key).allowQueriesOnUiThread(true)
.allowWritesOnUiThread(true)
.directory(MainApplication.getINSTANCE().getDataDirWithPath("realms"))
.directory(MainApplication.INSTANCE!!.getDataDirWithPath("realms"))
.schemaVersion(1).build()
val realm2 = Realm.getInstance(realmConfiguration2)
if (realm2.isInTransaction) {

@ -113,13 +113,13 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
public final static int PERFORMANCE_CLASS_AVERAGE = 1;
public final static int PERFORMANCE_CLASS_HIGH = 2;
private static final int LANGUAGE_SUPPORT_LEVEL = 1;
private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper();
private static boolean devModeStepFirstBootIgnore = MainApplication.Companion.isDeveloper();
private static int devModeStep = 0;
@SuppressLint("RestrictedApi")
private final NavigationBarView.OnItemSelectedListener onItemSelectedListener = item -> {
int itemId = item.getItemId();
if (itemId == R.id.back) {
TrackHelper.track().event("view_list", "main_modules").with(MainApplication.getINSTANCE().getTracker());
TrackHelper.track().event("view_list", "main_modules").with(Objects.requireNonNull(MainApplication.getINSTANCE()).getTracker());
startActivity(new Intent(this, MainActivity.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
@ -136,7 +136,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int devicePerformanceClass;
int androidVersion = Build.VERSION.SDK_INT;
int cpuCount = Runtime.getRuntime().availableProcessors();
int memoryClass = ((ActivityManager) MainApplication.getINSTANCE().getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int memoryClass = ((ActivityManager) Objects.requireNonNull(MainApplication.getINSTANCE()).getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int totalCpuFreq = 0;
int freqResolved = 0;
for (int i = 0; i < cpuCount; i++) {
@ -169,7 +169,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
protected void onCreate(Bundle savedInstanceState) {
devModeStep = 0;
super.onCreate(savedInstanceState);
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().getTracker());
TrackHelper.track().screen(this).with(Objects.requireNonNull(MainApplication.getINSTANCE()).getTracker());
setContentView(R.layout.settings_activity);
setTitle(R.string.app_name);
//hideActionBar();
@ -303,8 +303,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Crash reporting
TwoStatePreference crashReportingPreference = findPreference("pref_crash_reporting");
if (!SentryMain.IS_SENTRY_INSTALLED) crashReportingPreference.setVisible(false);
crashReportingPreference.setChecked(MainApplication.isCrashReportingEnabled());
final Object initialValue = MainApplication.isCrashReportingEnabled();
crashReportingPreference.setChecked(MainApplication.Companion.isCrashReportingEnabled());
final Object initialValue = MainApplication.Companion.isCrashReportingEnabled();
crashReportingPreference.setOnPreferenceChangeListener((preference, newValue) -> {
devModeStepFirstBootIgnore = true;
devModeStep = 0;
@ -507,8 +507,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
Preference debugNotification = findPreference("pref_background_update_check_debug");
Preference updateCheckExcludes = findPreference("pref_background_update_check_excludes");
Preference updateCheckVersionExcludes = findPreference("pref_background_update_check_excludes_version");
debugNotification.setEnabled(MainApplication.isBackgroundUpdateCheckEnabled());
debugNotification.setVisible(MainApplication.isDeveloper() && !MainApplication.isWrapped() && MainApplication.isBackgroundUpdateCheckEnabled());
debugNotification.setEnabled(MainApplication.Companion.isBackgroundUpdateCheckEnabled());
debugNotification.setVisible(MainApplication.isDeveloper() && !MainApplication.isWrapped && MainApplication.Companion.isBackgroundUpdateCheckEnabled());
debugNotification.setOnPreferenceClickListener(preference -> {
// fake updatable modules hashmap
HashMap<String, String> updateableModules = new HashMap<>();
@ -530,7 +530,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
Preference backgroundUpdateCheck = findPreference("pref_background_update_check");
backgroundUpdateCheck.setVisible(!MainApplication.isWrapped());
backgroundUpdateCheck.setVisible(!MainApplication.isWrapped);
// Make uncheckable if POST_NOTIFICATIONS permission is not granted
if (!MainApplication.isNotificationPermissionGranted()) {
// Instead of disabling the preference, we make it uncheckable and when the user
@ -553,13 +553,13 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
backgroundUpdateCheck.setSummary(R.string.background_update_check_permission_required);
}
updateCheckExcludes.setVisible(MainApplication.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped());
updateCheckExcludes.setVisible(MainApplication.Companion.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped);
backgroundUpdateCheck.setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = Boolean.parseBoolean(String.valueOf(newValue));
debugNotification.setEnabled(enabled);
debugNotification.setVisible(MainApplication.isDeveloper() && !MainApplication.isWrapped() && enabled);
debugNotification.setVisible(MainApplication.isDeveloper() && !MainApplication.isWrapped && enabled);
updateCheckExcludes.setEnabled(enabled);
updateCheckExcludes.setVisible(enabled && !MainApplication.isWrapped());
updateCheckExcludes.setVisible(enabled && !MainApplication.isWrapped);
if (!enabled) {
BackgroundUpdateChecker.onMainActivityResume(this.requireContext());
}
@ -610,7 +610,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
// now handle pref_background_update_check_excludes_version
updateCheckVersionExcludes.setVisible(MainApplication.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped());
updateCheckVersionExcludes.setVisible(MainApplication.Companion.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped);
updateCheckVersionExcludes.setOnPreferenceClickListener(preference -> {
// get the stringset pref_background_update_check_excludes_version
Set<String> stringSet = sharedPreferences.getStringSet("pref_background_update_check_excludes_version", new HashSet<>());
@ -703,7 +703,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
// for pref_background_update_check_debug_download, do the same as pref_update except with DOWNLOAD action
Preference debugDownload = findPreference("pref_background_update_check_debug_download");
debugDownload.setVisible(MainApplication.isDeveloper() && MainApplication.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped());
debugDownload.setVisible(MainApplication.isDeveloper() && MainApplication.Companion.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped);
debugDownload.setOnPreferenceClickListener(p -> {
devModeStep = 0;
Intent intent = new Intent(requireContext(), UpdateActivity.class);

@ -125,7 +125,7 @@ class RuntimeUtils {
}
builder.setNegativeButton(R.string.cancel) { dialog, _ ->
// Set pref_background_update_check to false and dismiss dialog
val prefs = MainApplication.getSharedPreferences("mmm")
val prefs = MainApplication.getSharedPreferences("mmm")!!
prefs.edit().putBoolean("pref_background_update_check", false).apply()
dialog.dismiss()
MainActivity.doSetupNowRunning = false
@ -149,7 +149,7 @@ class RuntimeUtils {
fun checkShowInitialSetup(context: Context, activity: MainActivity) {
if (BuildConfig.DEBUG) Timber.i("Checking if we need to run setup")
// Check if context is the first launch using prefs and if doSetupRestarting was passed in the intent
val prefs = MainApplication.getSharedPreferences("mmm")
val prefs = MainApplication.getSharedPreferences("mmm")!!
var firstLaunch = prefs.getString("last_shown_setup", null) != "v2"
// First launch
// context is intentionally separate from the above if statement, because it needs to be checked even if the first launch check is true due to some weird edge cases
@ -207,7 +207,7 @@ class RuntimeUtils {
) {
MainActivity.isShowingWeblateSb = true
// if we haven't shown context snackbar for context version yet
val prefs = MainApplication.getSharedPreferences("mmm")
val prefs = MainApplication.getSharedPreferences("mmm")!!
if (prefs.getInt("weblate_snackbar_shown", 0) == BuildConfig.VERSION_CODE) return
val snackbar: Snackbar = Snackbar.make(
activity.findViewById(R.id.root_container),
@ -244,7 +244,7 @@ class RuntimeUtils {
}, 4500)
return
}
val prefs = MainApplication.getSharedPreferences("mmm")
val prefs = MainApplication.getSharedPreferences("mmm")!!
// if last shown < 7 days ago
if (prefs.getLong("ugsns4", 0) > System.currentTimeMillis() - 604800000) return
val snackbar: Snackbar = Snackbar.make(

@ -18,7 +18,7 @@ object TimberUtils {
if (BuildConfig.DEBUG) {
plant(Timber.DebugTree())
} else {
if (MainApplication.isCrashReportingEnabled()) {
if (MainApplication.isCrashReportingEnabled) {
plant(
SentryTimberTree(
Sentry.getCurrentHub(),

@ -14,9 +14,9 @@ class FileUtils {
fun ensureCacheDirs() {
try {
FileUtils.forceMkdir(File((MainApplication.getINSTANCE().dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/wasm").replace("//".toRegex(), "/")))
FileUtils.forceMkdir(File((MainApplication.getINSTANCE().dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/js").replace("//".toRegex(), "/")))
FileUtils.forceMkdir(File((MainApplication.getINSTANCE().dataDir.toString() + "/cache/cronet").replace("//".toRegex(), "/")))
FileUtils.forceMkdir(File((MainApplication.INSTANCE!!.dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/wasm").replace("//".toRegex(), "/")))
FileUtils.forceMkdir(File((MainApplication.INSTANCE!!.dataDir.toString() + "/cache/WebView/Default/HTTP Cache/Code Cache/js").replace("//".toRegex(), "/")))
FileUtils.forceMkdir(File((MainApplication.INSTANCE!!.dataDir.toString() + "/cache/cronet").replace("//".toRegex(), "/")))
} catch (e: IOException) {
Timber.e("Could not create cache dirs")
}

@ -217,7 +217,7 @@ enum class Files {
fun fixSourceArchiveShit(rawModule: ByteArray?) {
// unzip the module, check if it has just one folder within. if so, switch to the folder and zip up contents, and replace the original file with that
try {
val tempDir = File(MainApplication.getINSTANCE().cacheDir, "temp")
val tempDir = File(MainApplication.INSTANCE!!.cacheDir, "temp")
if (tempDir.exists()) {
FileUtils.deleteDirectory(tempDir)
}

@ -191,7 +191,7 @@ enum class Http {;
private var doh = false
init {
val mainApplication = MainApplication.getINSTANCE()
val mainApplication = MainApplication.INSTANCE
if (mainApplication == null) {
val error = Error("Initialized Http too soon!")
error.fillInStackTrace()
@ -417,7 +417,7 @@ enum class Http {;
httpclientBuilder.dns(fallbackDNS!!)
httpClientWithCacheDoH = followRedirects(httpclientBuilder, true).build()
Timber.i("Initialized Http successfully!")
doh = MainApplication.isDohEnabled()
doh = MainApplication.isDohEnabled
}
private fun followRedirects(

@ -37,9 +37,9 @@ object SentryMain {
fun initialize(mainApplication: MainApplication) {
Thread.setDefaultUncaughtExceptionHandler { _: Thread?, throwable: Throwable ->
isCrashing = true
TrackHelper.track().exception(throwable).with(MainApplication.getINSTANCE().tracker)
TrackHelper.track().exception(throwable).with(MainApplication.INSTANCE!!.tracker)
val editor =
MainApplication.getINSTANCE().getSharedPreferences("sentry", Context.MODE_PRIVATE)
MainApplication.INSTANCE!!.getSharedPreferences("sentry", Context.MODE_PRIVATE)
.edit()
editor.putString("lastExitReason", "crash")
editor.putLong("lastExitTime", System.currentTimeMillis())
@ -72,7 +72,7 @@ object SentryMain {
Process.killProcess(Process.myPid())
}
// If first_launch pref is not false, refuse to initialize Sentry
val sharedPreferences = MainApplication.getSharedPreferences("mmm")
val sharedPreferences = MainApplication.getSharedPreferences("mmm")!!
if (sharedPreferences.getString("last_shown_setup", null) != "v2") {
return
}
@ -85,7 +85,7 @@ object SentryMain {
}
SentryAndroid.init(mainApplication) { options: SentryAndroidOptions ->
// If crash reporting is disabled, stop here.
if (!MainApplication.isCrashReportingEnabled()) {
if (!MainApplication.isCrashReportingEnabled) {
isSentryEnabled = false // Set sentry state to disabled
options.dsn = ""
} else {
@ -144,7 +144,7 @@ object SentryMain {
}
fun addSentryBreadcrumb(sentryBreadcrumb: SentryBreadcrumb) {
if (MainApplication.isCrashReportingEnabled()) {
if (MainApplication.isCrashReportingEnabled) {
Sentry.addBreadcrumb(sentryBreadcrumb.breadcrumb)
}
}

@ -26,6 +26,12 @@ buildscript {
}
}
subprojects {
plugins.withType<com.android.build.gradle.api.AndroidBasePlugin> {
apply(plugin = "org.gradle.android.cache-fix")
}
}
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}

Loading…
Cancel
Save