diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f058f19..11ef553 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.kt b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.kt index ff94615..42917ca 100644 --- a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.kt +++ b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.kt @@ -15,13 +15,13 @@ import java.nio.charset.StandardCharsets class AppUpdateManager private constructor() { private val compatDataId = HashMap() 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 { diff --git a/app/src/main/java/com/fox2code/mmm/CrashHandler.kt b/app/src/main/java/com/fox2code/mmm/CrashHandler.kt index 1677ac2..4e3cc7f 100644 --- a/app/src/main/java/com/fox2code/mmm/CrashHandler.kt +++ b/app/src/main/java/com/fox2code/mmm/CrashHandler.kt @@ -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() diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.kt b/app/src/main/java/com/fox2code/mmm/MainActivity.kt index 70e3dee..0bce7a8 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.kt @@ -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(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( diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java deleted file mode 100644 index 751f35b..0000000 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ /dev/null @@ -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 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 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 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 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 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 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); - } - } - } - } -} diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.kt b/app/src/main/java/com/fox2code/mmm/MainApplication.kt new file mode 100644 index 0000000..fd49eca --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.kt @@ -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 = 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() + 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() + @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? = 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 = + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/NotificationType.kt b/app/src/main/java/com/fox2code/mmm/NotificationType.kt index 97445b5..4c1b6b6 100644 --- a/app/src/main/java/com/fox2code/mmm/NotificationType.kt +++ b/app/src/main/java/com/fox2code/mmm/NotificationType.kt @@ -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) } }; diff --git a/app/src/main/java/com/fox2code/mmm/SetupActivity.kt b/app/src/main/java/com/fox2code/mmm/SetupActivity.kt index 069e06f..b185008 100644 --- a/app/src/main/java/com/fox2code/mmm/SetupActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/SetupActivity.kt @@ -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) } diff --git a/app/src/main/java/com/fox2code/mmm/UpdateActivity.kt b/app/src/main/java/com/fox2code/mmm/UpdateActivity.kt index 8909308..22551f5 100644 --- a/app/src/main/java/com/fox2code/mmm/UpdateActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/UpdateActivity.kt @@ -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 diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt index a51bc45..d786993 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt @@ -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) diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt index d461e1b..9907831 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt @@ -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 } /** diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.kt b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.kt index bfb4c9e..1e0db66 100644 --- a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.kt +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.kt @@ -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) diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.kt b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.kt index f31c9e0..52c2a73 100644 --- a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.kt +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.kt @@ -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 diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.kt b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.kt index 20bb8c7..a06422d 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.kt @@ -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) "" else moduleId) diff --git a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt index 7eabf3d..50a94e7 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt +++ b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt @@ -25,12 +25,12 @@ import java.util.Objects class ModuleManager private constructor() : SyncManager() { private val moduleInfos: HashMap = 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>(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) { diff --git a/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.kt b/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.kt index c36f261..8219aa6 100644 --- a/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/markdown/MarkdownActivity.kt @@ -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(R.id.bottom_navigation) if (bottomNavigationView != null) { diff --git a/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.kt b/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.kt index f1a969c..7687cd2 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.kt +++ b/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.kt @@ -172,7 +172,7 @@ class ModuleViewAdapter : RecyclerView.Adapter() { 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 diff --git a/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.kt b/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.kt index 75f9acb..447fbdf 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.kt +++ b/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.kt @@ -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 } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.kt b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.kt index 02c4c83..d57bb8d 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.kt +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.kt @@ -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) { diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index bad091a..4c0e863 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -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 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 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); diff --git a/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt b/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt index 98b9bc6..b92ceb0 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt @@ -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( diff --git a/app/src/main/java/com/fox2code/mmm/utils/TimberUtils.kt b/app/src/main/java/com/fox2code/mmm/utils/TimberUtils.kt index 074310f..7071040 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/TimberUtils.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/TimberUtils.kt @@ -18,7 +18,7 @@ object TimberUtils { if (BuildConfig.DEBUG) { plant(Timber.DebugTree()) } else { - if (MainApplication.isCrashReportingEnabled()) { + if (MainApplication.isCrashReportingEnabled) { plant( SentryTimberTree( Sentry.getCurrentHub(), diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/FileUtils.kt b/app/src/main/java/com/fox2code/mmm/utils/io/FileUtils.kt index 9637ec1..437aebe 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/FileUtils.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/io/FileUtils.kt @@ -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") } diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/Files.kt b/app/src/main/java/com/fox2code/mmm/utils/io/Files.kt index 3867dd2..07de3d8 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/Files.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/io/Files.kt @@ -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) } diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/net/Http.kt b/app/src/main/java/com/fox2code/mmm/utils/io/net/Http.kt index b84a6c6..b02fc95 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/net/Http.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/io/net/Http.kt @@ -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( diff --git a/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.kt b/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.kt index 02bdf59..c0e9e66 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.kt @@ -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) } } diff --git a/build.gradle.kts b/build.gradle.kts index ec7f271..2407e75 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,12 @@ buildscript { } } +subprojects { + plugins.withType { + apply(plugin = "org.gradle.android.cache-fix") + } +} + tasks.register("clean", Delete::class) { delete(rootProject.buildDir) }