diff --git a/README.md b/README.md index 1b5ea4e..933d8f4 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,19 @@ Important news -I have health problems that made me work slow on everything. -I don't like sharing my health problmes but it has been to much recently for me to keep it for myself. +I have health problems that made me work slow on everything. I don't like sharing my health problmes +but it has been to much recently for me to keep it for myself. -This has been too much for me recently, so my moderators (same that on telegram) +This has been too much for me recently, so my moderators (same that on telegram) will be handling the project for me for a while. -I had theses problems even before I started FoxMMM, the only reason no one noticed is because -I can work or go to any school because of how much pain and exhaustion I feel everyday. +I had theses problems even before I started FoxMMM, the only reason no one noticed is because I can +work or go to any school because of how much pain and exhaustion I feel everyday. The only two thing that helped me reduce the pain is making code and playing with firends. -Even tho I'm very slow at doing anything, the only thing that made me look like I was working -on this project at a normal speed like someone that work is because 75% of my time was on this project. +Even tho I'm very slow at doing anything, the only thing that made me look like I was working on +this project at a normal speed like someone that work is because 75% of my time was on this project. There was also some times I couldn't work on this projects for multiple days because of my health, sometimes I was forcing myself to change one line of code from this project because doing nothing @@ -25,33 +25,33 @@ was more painful that trying something while in pain. Spending time with my friend and working on this project was a sort of pain killer for me. -Even tho I received money from my parent and the governement for my health problems, -I didn't know what to do with it cause anything I could have bought had no use for me -because my extreme pain made me unable to use anything. (Even video games) - -My health issues also prevented me to do any project of greater complexcity that this, -and without community support I would have been physically unable to continue this project. +Even tho I received money from my parent and the governement for my health problems, I didn't know +what to do with it cause anything I could have bought had no use for me because my extreme pain made +me unable to use anything. (Even video games) +My health issues also prevented me to do any project of greater complexcity that this, and without +community support I would have been physically unable to continue this project. There was clues of my health problems, right on this project, and theses are the following: + - My commit time of day being random proving I have no jobs. - Me not commiting for entire week, or having only commited one line in a week. - Me taking too much time to publish release after I did the relase commit. - Me missing obvious bugs and being able to do simple task properly (Well maybe this last one is harder to proove via commit history) -But sine many peoples are faking health issues for clout, if any data-scientist want -to do an analysis to proove what it would make my day, and I would be happy to give money -if someone does that because I don't know what do to with my money at this point. +But sine many peoples are faking health issues for clout, if any data-scientist want to do an +analysis to proove what it would make my day, and I would be happy to give money if someone does +that because I don't know what do to with my money at this point. -This is really sickening peoples need to give proof for their mental/health -issues because some peoples fake having thoses issues for clout. +This is really sickening peoples need to give proof for their mental/health issues because some +peoples fake having thoses issues for clout. -If you want to use my health problems for coult, I don't care as long as you are respectful, -at least you won't be hurting peoples with mental/health issues by faking having thoses issues. +If you want to use my health problems for coult, I don't care as long as you are respectful, at +least you won't be hurting peoples with mental/health issues by faking having thoses issues. -I'll probably delete this section once my health would be gotten better, or at -least good enough for me to not be stuck on my bed at least once a day because of pain. +I'll probably delete this section once my health would be gotten better, or at least good enough for +me to not be stuck on my bed at least once a day because of pain. @@ -68,9 +68,10 @@ Main activity: [](screenshot-dark.jpg) [](screenshot-light.jpg) -## What is this? +## What is this? -The official Magisk has dropped support to download online modules, so I made Fox's Magisk Module Manager to help you download and install Magisk modules. +The official Magisk has dropped support to download online modules, so I made Fox's Magisk Module +Manager to help you download and install Magisk modules. **This app is not officially supported by Magisk or its developers** @@ -80,11 +81,13 @@ The official Magisk has dropped support to download online modules, so I made Fo ## Requirements Minimum: + - Android 5.0+ - Magisk 19.0+ - An internet connection Recommended: + - Android 6.0+ - Magisk 21.2+ - An internet connection @@ -93,17 +96,18 @@ Note: This app may require the use of a VPN in countries with a state wide firew ## For users -To install the app go to [releases](https://github.com/Fox2Code/FoxMagiskModuleManager/releases), +To install the app go to [releases](https://github.com/Fox2Code/FoxMagiskModuleManager/releases), and download and install the latest `.apk` on your device. ## Repositories Available - -The app currently use these two repos as module sources, each with their own benefits and drawback: +The app currently use these two repos as module sources, each with their own benefits and +drawbacks: (Note: Each module repo can be disabled in the settings of the app) (Note²: I do not own or actively monitor any of the repos or modules, **download at your own risk**) -#### [https://github.com/Magisk-Modules-Alt-Repo](https://github.com/Magisk-Modules-Alt-Repo) +#### [https://github.com/Magisk-Modules-Alt-Repo](https://github.com/Magisk-Modules-Alt-Repo) + - Accepting new modules [here](https://github.com/Magisk-Modules-Alt-Repo/submission) - Less restrictive than the original repo - Officially supported by Fox's mmm @@ -113,22 +117,25 @@ Support: [![GitHub issues](https://img.shields.io/github/issues/Magisk-Modules-Alt-Repo/submission)](https://github.com/Magisk-Modules-Alt-Repo/submission/issues) #### [https://www.androidacy.com/modules-repo/](https://www.androidacy.com/modules-repo/) + - Accepting new modules [here](https://www.androidacy.com/module-repository-applications/) - Modules downloadable easily outside the app - Officially supported by Fox's mmm -- Contains ads to help cover server costs +- May show ads to help cover infrastrcture costs. + - [Read more](https://www.androidacycom/doing-it-alone-the-what-the-how-and-the-why/) + | [Privacy policy](https://www.androidacy.com/privacy/) - Added features like module reviews, automatic VirusTotal scans, and more Support: [![Telegram Group](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fandroidacy_discussions)](https://telegram.dog/androidacy_discussions) -If a module is in multiple repos, the manager will just pick the most up to date version -of the module, if a module is in multiple repos it will just use first registered repo. +If a module is in multiple repos, the manager will just pick the most up to date version of the +module, if a module is in multiple repos it will just use first registered repo. -Note: If you or a friend uploaded a module and it doesn't appear in your module -list you can disable the low quality filter in the app settings. -Go to the [developer documentation](DEVELOPERS.md) for more info. +Note: If you or a friend uploaded a module and it doesn't appear in your module list you can disable +the low quality filter in the app settings. +Go to the [developer documentation](docs/DEVELOPERS.md) for more info. ## For developers @@ -142,14 +149,15 @@ And if you want to be event fancier you can setup `config` to your own config ap It also add new ways to control the installer ui via a new `#!` command system It allow module developers to have a more customizable install experience -For more information please check the [developer documentation](DEVELOPERS.md) +For more information please check the [developer documentation](docs/DEVELOPERS.md) ## For translators We use Weblate for translations: https://translate.nift4.org/engage/foxmmm/ (Make sure to check your spam folder when registering) -If you do not want to register on the self-hosted Weblate instance, you can do a pull request on GitHub: +If you do not want to register on the self-hosted Weblate instance, you can do a pull request on +GitHub: See [`app/src/main/res/values/strings.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/strings.xml) and [`app/src/main/res/values/arrays.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/arrays.xml) @@ -159,46 +167,39 @@ If your language is right to left don't forget to set `lang_support_rtl` to `tru Translators are not expected to have any previous coding experience. ## License -See [LICENSE](LICENCE). Library licenses can be found in the licenses section of the app. - -Cronet is licensed under the Apache License, Version 2.0. Static libraries are licensed under -the BSD license. See [LICENSE](https://chromium.googlesource.com/chromium/src/+/master/LICENSE) -for more information. Libraries were built using the microg build script which can be found [here](https://github.com/microg/cronet-build). - -## I want to add my own repo - -To add you own repo to Fox's mmm it need to follow theses conditions: -- The module repo or at least one of it's owners must be known. -- Modules in the repo must be monitored, and malicious modules must be removed. -- Module repo must have a valid, working, automatically or frequently updated `modules.json` - ([Example](https://github.com/Magisk-Modules-Alt-Repo/json/blob/main/modules.json)) - -In addition of these initial condition the repo must follow these rules: -- Repos must process and take-down off their repo module where it's removal was requested - by their original author, even if their licences legally allow their distributions. -- Repos may collect and store "mixed anonymous data" without user permission - (Anonymous means no personal data, usernames, email, or IP addresses) - (Mixed means users data must be split and not that separate data is not linkable together) -- Temporary storage of IPs address without user consent is allowed for rate limiting, GeoIP, - security reason, and must not be used for any other purpose without user explicit consent. - (GeoIP is the process of getting the country of an IP address) -- Repos may not collect and/or distribute any personal data without informing users that they do so and offering a way to opt out -- Modules owners must be aware that their modules are being hosted on the repository - (This rule doesn't apply for modules from `Magisk-Modules-Repo` last updated before 2022) -- Modules owners must be aware of any change made of the distributed version of their modules. - -Please note Androidacy has their Module Repository Policies outlined [on their website](https://www.androidacy.com/module-requirements/?utm_source=foxmmm-readme&utm_medium=web). Please refer to that document for the latest changes regarding their Repository. - -If all of these conditions are met you can open an issue for review. -(And don't forget to include a link to the `modules.json`) - -If an existing repo is not respecting theses rules please open an issue. -If a repo is repeatedly violating these rule will be removed from the app. -Last update of theses rules are: 4 May 2022 - -Please note that these rules does not apply retroactively. -If your post an issue about rules violation they must violate both the version of -the rules at the moment of the incident and the latest version of the rules. -(This paragraph doesn't apply for license violation, legal requests, or illegal behaviour.) - -In addition, we advise you to contact the repo host beforehand to attempt to resolve any issues. This helps avoid unnecessary conflict, and most of the time will get your issue solved quickly! + +Fox's Magisk Manager, the icon, and names are copyright +2021-present [Fox2Code](https://github.com/Fox2Code). The Androidacy name, logo, integration, and +later portions of the code are copyright +2022-present [Androidacy](https://www.androidacy.com/?utm_source=fox-repo&utm_medium=web). See +[LICENSE](LICENCE) for details. Library licenses can be found in the licenses section of the app. + +Modules are not covered by this license, please check the license of each module before using it. + +Some third party backend services may be proprietary, please check their terms of service before +using them. + +## Disclaimer + +In no event shall the developer be liable for any special, direct, indirect, consequential, or +incidental damages or any damages whatsoever, whether in an action of contract, negligence or other +tort, arising out of or in connection with the use of the app or the contents of the app. The +developer reserves the right to make additions, deletions, or modification to the contents on the +app at any time without prior notice. + +This app is not affiliated with Magisk or its developers, nor with any of the module repos or +developers of the modules. + +## Add your own repos + +See [the documentation](docs/add-repo.md) + +## Issues with a repo + +If you have a problem with a repo, please contact the repo owner **first**. If you are unable to +reach them or they are not willing to help, you can contact us as a last resort. + +Default repo owners: + +- Androidacy: [Telegram](https://telegram.dog/androidacy_discussions) +- Magisk-Modules-Alt-Repo: [Telegram](https://github.com/Magisk-Modules-Alt-Repo/submission/issues) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 268b1f3..dfb342d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ plugins { android { namespace "com.fox2code.mmm" compileSdk 33 - buildToolsVersion '30.0.3' + buildToolsVersion '33.0.0' signingConfigs { release { // Everything comes from local.properties @@ -25,20 +25,42 @@ android { defaultConfig { applicationId "com.fox2code.mmm" - minSdk 21 + minSdk 23 targetSdk 33 - versionCode 60 - versionName "0.6.8" + versionCode 61 + versionName "1.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" signingConfig signingConfigs.release } + splits { + + // Configures multiple APKs based on ABI. + abi { + + // Enables building multiple APKs per ABI. + enable true + + // By default all ABIs are included, so use reset() and include to specify that we only + // want APKs for x86 and x86_64. + + // Resets the list of ABIs that Gradle should create APKs for to none. + reset() + + // Specifies a list of ABIs that Gradle should create APKs for. + include "x86", "x86_64", "armeabi-v7a", "arm64-v8a" + + // Specifies that we do not want to also generate a universal APK that includes all ABIs. + universalApk true + } + } + buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), -'proguard-rules.pro' + 'proguard-rules.pro' } debug { applicationIdSuffix '.debug' @@ -57,6 +79,17 @@ android { dimension "type" buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true" buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true" + if (hasSentryConfig) { + Properties properties = new Properties() + try (FileInputStream fis = new FileInputStream(sentryConfigFile)) { + properties.load(fis) + } + buildConfigField "String", "SENTRY_TOKEN", '"' + properties.getProperty("auth." + "token") + '"' + } else { + + buildConfigField "String", "SENTRY_TOKEN", '""' + + } // Get the androidacy client ID from the androidacy.properties Properties properties = new Properties() // If androidacy.properties doesn't exist, use the default client ID which is heavily @@ -91,6 +124,16 @@ android { // Disable crash reporting for F-Droid flavor by default buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false" + if (hasSentryConfig) { + Properties properties = new Properties() + try (FileInputStream fis = new FileInputStream(sentryConfigFile)) { + properties.load(fis) + } + buildConfigField "String", "SENTRY_TOKEN", '"' + properties.getProperty("auth." + "token") + '"' + } else { + buildConfigField "String", "SENTRY_TOKEN", '""' + } + // Repo with ads or tracking feature are disabled by default for the // F-Droid flavor. buildConfigField("java.util.List", @@ -99,7 +142,7 @@ android { // Get the androidacy client ID from the androidacy.properties Properties properties = new Properties() - // If androidacy.properties doesn't exist, use the default client ID which is limited + // If androidacy.properties doesn't exist, use the fdroid client ID which is limited // to 50 requests per minute if (project.rootProject.file('androidacy.properties').exists()) { properties.load(project.rootProject.file('androidacy.properties').newDataInputStream()) @@ -111,8 +154,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } lint { disable 'MissingTranslation' @@ -121,14 +164,14 @@ android { } aboutLibraries { - additionalLicenses = ["LGPL_3_0_only"] + additionalLicenses = ["LGPL_3_0_only", "Apache_2_0"] } // "true" is not allowed inside this block, use "hasSentryConfig" instead. // This is because gradle doesn't allow to enable/disable plugins conditionally sentry { // Disable sentry on F-Droid flavor - ignoredFlavors = hasSentryConfig ? [] : ["default", "fdroid"] + ignoredFlavors = [] // Disables or enables the handling of Proguard mapping for Sentry. // If enabled the plugin will generate a UUID and will take care of @@ -164,14 +207,14 @@ sentry { // Does auto instrumentation for specified features through bytecode manipulation. // Default is enabled. tracingInstrumentation { - enabled = hasSentryConfig + enabled = true } // Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations). // Default is enabled. // Only available v3.1.0 and above. autoInstallation { - enabled = hasSentryConfig + enabled = true // Specifies a version of the sentry-android SDK and fragment, timber and okhttp integrations. // @@ -188,13 +231,6 @@ sentry { configurations { implementation.exclude group: 'org.jetbrains', module: 'annotations' - if (!hasSentryConfig) { - implementation.exclude group: 'io.sentry', module: 'sentry-android' - implementation.exclude group: 'io.sentry', module: 'sentry-android-fragment' - implementation.exclude group: 'io.sentry', module: 'sentry-android-okhttp' - implementation.exclude group: 'io.sentry', module: 'sentry-android-core' - implementation.exclude group: 'io.sentry', module: 'sentry-android-ndk' - } } dependencies { @@ -212,7 +248,7 @@ dependencies { implementation "dev.rikka.rikkax.insets:insets:1.3.0" implementation 'com.github.Dimezis:BlurView:version-2.0.2' implementation 'com.github.KieronQuinn:MonetCompat:0.4.1' - implementation 'com.github.Fox2Code:FoxCompat:0.1.5' + implementation 'com.github.Fox2Code:FoxCompat:0.1.6' // Update the version code in the root build.gradle implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" @@ -220,21 +256,14 @@ dependencies { implementation 'androidx.work:work-runtime:2.7.1' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10' implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.10' - // Chromium cronet from microG - implementation fileTree(dir: 'libs', include: '*.jar') + // Chromium cronet from androidacy + implementation 'com.androidacy:cronet-common:108.0.5359.95' + implementation 'com.androidacy:cronet-native:108.0.5359.95' // Force prefer our own version of Cronet implementation 'com.github.topjohnwu.libsu:io:5.0.1' implementation 'com.github.Fox2Code:RosettaX:1.0.9' implementation 'com.github.Fox2Code:AndroidANSI:1.0.1' - - if (hasSentryConfig) { - // Error reporting - defaultImplementation 'io.sentry:sentry-android:6.9.2' - defaultImplementation 'io.sentry:sentry-android-fragment:6.9.2' - defaultImplementation 'io.sentry:sentry-android-okhttp:6.9.2' - defaultImplementation 'io.sentry:sentry-android-core:6.9.2' - defaultImplementation 'io.sentry:sentry-android-ndk:6.9.2' - } + implementation 'io.sentry:sentry-android:6.9.2' // Markdown implementation "io.noties.markwon:core:4.6.2" @@ -242,13 +271,19 @@ dependencies { implementation "io.noties.markwon:image:4.6.2" implementation "io.noties.markwon:syntax-highlight:4.6.2" implementation 'com.google.net.cronet:cronet-okhttp:0.1.0' - // Ignore all org.chromium.net dependencies annotationProcessor "io.noties:prism4j-bundler:2.0.0" implementation "com.caverock:androidsvg:1.4" + // Icons + // implementation "com.mikepenz:iconics-core:3.2.5" + //implementation "androidx.appcompat:appcompat:${versions.appCompat}" + // implementation 'com.mikepenz:community-material-typeface:7.0.96.0-kotlin@aar' // Test testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.4' + + // Add okhttp logging interceptor if debug build + debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.10' } if (hasSentryConfig) { @@ -265,8 +300,7 @@ if (hasSentryConfig) { } final String sentrySrc = hasSentryConfig ? 'src/sentry/java' : 'src/sentryless/java' -final String sentryManifestSrc = hasSentryConfig ? - 'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml' +final String sentryManifestSrc = hasSentryConfig ? 'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml' android { sourceSets { diff --git a/app/libs/arm64-v8a.jar b/app/libs/arm64-v8a.jar deleted file mode 100644 index 315c2b8..0000000 Binary files a/app/libs/arm64-v8a.jar and /dev/null differ diff --git a/app/libs/armeabi-v7a.jar b/app/libs/armeabi-v7a.jar deleted file mode 100644 index c3c8f84..0000000 Binary files a/app/libs/armeabi-v7a.jar and /dev/null differ diff --git a/app/libs/cronet_impl_common_java.jar b/app/libs/cronet_impl_common_java.jar deleted file mode 100644 index cc492a6..0000000 Binary files a/app/libs/cronet_impl_common_java.jar and /dev/null differ diff --git a/app/libs/cronet_impl_native_java.jar b/app/libs/cronet_impl_native_java.jar deleted file mode 100644 index ecb9d8a..0000000 Binary files a/app/libs/cronet_impl_native_java.jar and /dev/null differ diff --git a/app/libs/x86.jar b/app/libs/x86.jar deleted file mode 100644 index 8a1556c..0000000 Binary files a/app/libs/x86.jar and /dev/null differ diff --git a/app/libs/x86_64.jar b/app/libs/x86_64.jar deleted file mode 100644 index 6d45fad..0000000 Binary files a/app/libs/x86_64.jar and /dev/null differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 888744f..5c61a76 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ compatDataId = new HashMap<>(); private final Object updateLock = new Object(); private final File compatFile; @@ -49,13 +42,10 @@ public class AppUpdateManager { private long lastChecked; private boolean preReleaseNewer; private boolean lastCheckSuccess; - private AppUpdateManager() { this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt"); - this.latestRelease = MainApplication.getBootSharedPreferences() - .getString("updater_latest_release", BuildConfig.VERSION_NAME); - this.latestPreRelease = MainApplication.getBootSharedPreferences() - .getString("updater_latest_pre_release", BuildConfig.VERSION_NAME); + this.latestRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_release", BuildConfig.VERSION_NAME); + this.latestPreRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_pre_release", BuildConfig.VERSION_NAME); this.lastChecked = 0; this.preReleaseNewer = true; if (this.compatFile.isFile()) { @@ -67,24 +57,34 @@ public class AppUpdateManager { } } + public static AppUpdateManager getAppUpdateManager() { + return INSTANCE; + } + + public static int getFlagsForModule(String moduleId) { + return INSTANCE.getCompatibilityFlags(moduleId); + } + + public static boolean shouldForceHide(String repoId) { + if (BuildConfig.DEBUG || repoId.startsWith("repo_") || repoId.equals("magisk_alt_repo")) + return false; + return !repoId.startsWith("repo_") && (INSTANCE.getCompatibilityFlags(repoId) & FLAG_COMPAT_FORCE_HIDE) != 0; + } + // Return true if should show a notification public boolean checkUpdate(boolean force) { - if (!BuildConfig.ENABLE_AUTO_UPDATER) - return false; - if (!force && this.peekShouldUpdate()) - return true; + if (!BuildConfig.ENABLE_AUTO_UPDATER) return false; + if (!force && this.peekShouldUpdate()) return true; long lastChecked = this.lastChecked; if (lastChecked != 0 && // Avoid spam calls by putting a 60 seconds timer lastChecked < System.currentTimeMillis() - 60000L) return force && this.peekShouldUpdate(); synchronized (this.updateLock) { - if (lastChecked != this.lastChecked) - return this.peekShouldUpdate(); + if (lastChecked != this.lastChecked) return this.peekShouldUpdate(); boolean preReleaseNewer = true; try { - JSONArray releases = new JSONArray(new String(Http.doHttpGet( - RELEASES_API_URL, false), StandardCharsets.UTF_8)); + JSONArray releases = new JSONArray(new String(Http.doHttpGet(RELEASES_API_URL, false), StandardCharsets.UTF_8)); String latestRelease = null, latestPreRelease = null; for (int i = 0; i < releases.length(); i++) { JSONObject release = releases.getJSONObject(i); @@ -92,22 +92,18 @@ public class AppUpdateManager { if (release.getBoolean("draft")) continue; boolean preRelease = release.getBoolean("prerelease"); String version = release.getString("tag_name"); - if (version.startsWith("v")) - version = version.substring(1); + if (version.startsWith("v")) version = version.substring(1); if (preRelease) { - if (latestPreRelease == null) - latestPreRelease = version; + if (latestPreRelease == null) latestPreRelease = version; } else if (latestRelease == null) { latestRelease = version; - if (latestPreRelease == null) - preReleaseNewer = false; + if (latestPreRelease == null) preReleaseNewer = false; } if (latestRelease != null && latestPreRelease != null) { break; // We read everything we needed to read. } } - if (latestRelease != null) - this.latestRelease = latestRelease; + if (latestRelease != null) this.latestRelease = latestRelease; if (latestPreRelease != null) { this.latestPreRelease = latestPreRelease; this.preReleaseNewer = preReleaseNewer; @@ -115,9 +111,9 @@ public class AppUpdateManager { this.latestPreRelease = ""; this.preReleaseNewer = false; } - Log.d(TAG, "Latest release: " + latestRelease); - Log.d(TAG, "Latest pre-release: " + latestPreRelease); - Log.d(TAG, "Latest pre-release newer: " + preReleaseNewer); + if (BuildConfig.DEBUG) Log.d(TAG, "Latest release: " + latestRelease); + if (BuildConfig.DEBUG) Log.d(TAG, "Latest pre-release: " + latestPreRelease); + if (BuildConfig.DEBUG) Log.d(TAG, "Latest pre-release newer: " + preReleaseNewer); this.lastChecked = System.currentTimeMillis(); this.lastCheckSuccess = true; } catch (Exception ioe) { @@ -131,45 +127,42 @@ public class AppUpdateManager { public void checkUpdateCompat() { if (this.compatFile.exists()) { long lastUpdate = this.compatFile.lastModified(); - if (lastUpdate <= System.currentTimeMillis() && - lastUpdate + 600_000L > System.currentTimeMillis()) { + if (lastUpdate <= System.currentTimeMillis() && lastUpdate + 600_000L > System.currentTimeMillis()) { return; // Skip update } } try { - JSONObject object = new JSONObject(new String(Http.doHttpGet( - COMPAT_API_URL, false), StandardCharsets.UTF_8)); + JSONObject object = new JSONObject(new String(Http.doHttpGet(COMPAT_API_URL, false), StandardCharsets.UTF_8)); if (object.isNull("body")) { compatDataId.clear(); Files.write(compatFile, new byte[0]); return; } - byte[] rawData = object.getString("body") - .getBytes(StandardCharsets.UTF_8); + byte[] rawData = object.getString("body").getBytes(StandardCharsets.UTF_8); this.parseCompatibilityFlags(new ByteArrayInputStream(rawData)); Files.write(compatFile, rawData); - if (!BuildConfig.ENABLE_AUTO_UPDATER) - this.lastCheckSuccess = true; + if (!BuildConfig.ENABLE_AUTO_UPDATER) this.lastCheckSuccess = true; } catch (Exception e) { - if (!BuildConfig.ENABLE_AUTO_UPDATER) - this.lastCheckSuccess = false; + if (!BuildConfig.ENABLE_AUTO_UPDATER) this.lastCheckSuccess = false; Log.e("AppUpdateManager", "Failed to update compat list", e); } } public boolean peekShouldUpdate() { - if (!BuildConfig.ENABLE_AUTO_UPDATER) - return false; - return !(BuildConfig.VERSION_NAME.equals(this.latestRelease) || - (this.preReleaseNewer && - BuildConfig.VERSION_NAME.equals(this.latestPreRelease))); + if (!BuildConfig.ENABLE_AUTO_UPDATER) return false; + // Convert both BuildConfig.VERSION_NAME and latestRelease to int + int currentVersion = 0, latestVersion = 0; + try { + currentVersion = Integer.parseInt(BuildConfig.VERSION_NAME.replace(".", "")); + latestVersion = Integer.parseInt(this.latestRelease.replace(".", "")); + } catch (NumberFormatException ignored) { + } + return currentVersion < latestVersion || (this.preReleaseNewer && currentVersion < Integer.parseInt(this.latestPreRelease.replace(".", ""))); } public boolean peekHasUpdate() { - if (!BuildConfig.ENABLE_AUTO_UPDATER) - return false; - return !BuildConfig.VERSION_NAME.equals(this.preReleaseNewer ? - this.latestPreRelease : this.latestRelease); + if (!BuildConfig.ENABLE_AUTO_UPDATER) return false; + return !BuildConfig.VERSION_NAME.equals(this.preReleaseNewer ? this.latestPreRelease : this.latestRelease); } public boolean isLastCheckSuccess() { @@ -178,8 +171,7 @@ public class AppUpdateManager { private void parseCompatibilityFlags(InputStream inputStream) throws IOException { compatDataId.clear(); - BufferedReader bufferedReader = new BufferedReader( - new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line; while ((line = bufferedReader.readLine()) != null) { line = line.trim(); @@ -231,16 +223,4 @@ public class AppUpdateManager { Integer compatFlags = compatDataId.get(moduleId); return compatFlags == null ? 0 : compatFlags; } - - public static int getFlagsForModule(String moduleId) { - return INSTANCE.getCompatibilityFlags(moduleId); - } - - public static boolean shouldForceHide(String repoId) { - if (BuildConfig.DEBUG || repoId.startsWith("repo_") || - repoId.equals("magisk_alt_repo")) return false; - return !repoId.startsWith("repo_") && - (INSTANCE.getCompatibilityFlags(repoId) & - FLAG_COMPAT_FORCE_HIDE) != 0; - } } diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 34d11e3..99bfeea 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -13,17 +13,23 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.text.InputType; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; @@ -46,16 +52,25 @@ import com.fox2code.mmm.utils.BlurUtils; import com.fox2code.mmm.utils.ExternalHelper; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; -import com.fox2code.mmm.utils.NoodleDebug; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.materialswitch.MaterialSwitch; import com.google.android.material.progressindicator.LinearProgressIndicator; +import org.chromium.net.ExperimentalCronetEngine; +import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Objects; + import eightbitlab.com.blurview.BlurView; public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper { private static final String TAG = "MainActivity"; private static final int PRECISION = 10000; - public static boolean noodleDebugState = BuildConfig.DEBUG; public final ModuleViewListBuilder moduleViewListBuilder; public LinearProgressIndicator progressIndicator; private ModuleViewAdapter moduleViewAdapter; @@ -71,8 +86,9 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe private RecyclerView moduleList; private CardView searchCard; private SearchView searchView; - private NoodleDebug noodleDebug; private boolean initMode; + private boolean doSetupNowRunning; + private boolean urlFactoryInstalled = false; public MainActivity() { this.moduleViewListBuilder = new ModuleViewListBuilder(this); @@ -88,7 +104,18 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe @Override protected void onCreate(Bundle savedInstanceState) { this.initMode = true; - noodleDebugState = MainApplication.isDeveloper(); + // Ensure HTTP Cache directories are created + Http.ensureCacheDirs(this); + if (!urlFactoryInstalled) { + try { + ExperimentalCronetEngine cronetEngine = new ExperimentalCronetEngine.Builder(this).build(); + CronetURLStreamHandlerFactory cronetURLStreamHandlerFactory = new CronetURLStreamHandlerFactory(cronetEngine); + URL.setURLStreamHandlerFactory(cronetURLStreamHandlerFactory); + urlFactoryInstalled = true; + } catch (Throwable t) { + Log.e(TAG, "Failed to install CronetURLStreamHandlerFactory", t); + } + } BackgroundUpdateChecker.onMainActivityCreate(this); super.onCreate(savedInstanceState); this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> { @@ -116,7 +143,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.moduleList = findViewById(R.id.module_list); this.searchCard = findViewById(R.id.search_card); this.searchView = findViewById(R.id.search_bar); - this.noodleDebug = new NoodleDebug(this, R.id.noodle_debug); this.moduleViewAdapter = new ModuleViewAdapter(); this.moduleList.setAdapter(this.moduleViewAdapter); this.moduleList.setLayoutManager(new LinearLayoutManager(this)); @@ -158,11 +184,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); - noodleDebug.setEnabled(noodleDebugState); - noodleDebug.bind(); - noodleDebug.push("Ensure Permissions"); - ensurePermissions(); - noodleDebug.pop(); ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules); this.commonNext(); @@ -171,21 +192,32 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe @Override public void onFailure(int error) { Log.i(TAG, "Failed to get magisk path!"); - noodleDebug.setEnabled(noodleDebugState); - noodleDebug.bind(); moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification()); this.commonNext(); } public void commonNext() { - NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); + doSetupNowRunning = true; + doSetupNow(); + + // Wait for doSetupNow to finish + while (doSetupNowRunning) { + try { + //noinspection BusyWait + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + } + /*if (BuildConfig.DEBUG) { + SharedPreferences prefs = MainApplication.getSharedPreferences(); + if (BuildConfig.DEBUG) Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false))); + }*/ swipeRefreshBlocker = System.currentTimeMillis() + 5_000L; updateScreenInsets(); // Fix an edge case if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); if (!Http.hasWebView()) // Check Http for WebView availability moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW); - noodleDebug.push("Apply"); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); runOnUiThread(() -> { progressIndicator.setIndeterminate(false); @@ -193,17 +225,33 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe // Fix insets not being accounted for correctly updateScreenInsets(getResources().getConfiguration()); }); + + // On every preferences change, log the change if debug is enabled + if (BuildConfig.DEBUG) { + Log.i("PrefsListener", "onCreate: Preferences: " + MainApplication.getSharedPreferences().getAll()); + // Log all preferences changes + MainApplication.getSharedPreferences().registerOnSharedPreferenceChangeListener((prefs, key) -> Log.i("PrefsListener", "onSharedPreferenceChanged: " + key + " = " + prefs.getAll().get(key))); + } Log.i(TAG, "Scanning for modules!"); - noodleDebug.replace("Initialize Update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Initialize Update"); final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount(); if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) { Log.w(TAG, "Need update on create?"); } - noodleDebug.replace("Check Update Compat"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update Compat"); AppUpdateManager.getAppUpdateManager().checkUpdateCompat(); - noodleDebug.replace("Check Update"); + /*if (BuildConfig.DEBUG) { + SharedPreferences prefs = MainApplication.getSharedPreferences(); + Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false))); + }*/ + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update"); RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true))); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); + + /*if (BuildConfig.DEBUG) { + SharedPreferences prefs = MainApplication.getSharedPreferences(); + Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false))); + }*/ if (!NotificationType.NO_INTERNET.shouldRemove()) { moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) { @@ -211,16 +259,21 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe } else { // Compatibility data still needs to be updated AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); - noodleDebug.replace("Check App Update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check App Update"); if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); - noodleDebug.replace("Check Json Update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Json Update"); if (max != 0) { + + /*if (BuildConfig.DEBUG) { + SharedPreferences prefs = MainApplication.getSharedPreferences(); + Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false))); + }*/ int current = 0; - noodleDebug.push(""); + // noodleDebug.push(""); for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { if (localModuleInfo.updateJson != null) { - noodleDebug.replace(localModuleInfo.id); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", localModuleInfo.id); try { localModuleInfo.checkModuleUpdate(); } catch (Exception e) { @@ -231,7 +284,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true)); } } - noodleDebug.pop(); } } runOnUiThread(() -> { @@ -241,16 +293,101 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe setActionBarBackground(null); updateScreenInsets(getResources().getConfiguration()); }); - noodleDebug.replace("Apply"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply"); RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules); + + /*if (BuildConfig.DEBUG) { + SharedPreferences prefs = MainApplication.getSharedPreferences(); + Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false))); + }*/ moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); - noodleDebug.pop(); Log.i(TAG, "Finished app opening state!"); - noodleDebug.unbind(); + // noodleDebug.unbind(); } }, true); ExternalHelper.INSTANCE.refreshHelper(this); this.initMode = false; + // Show an material alert dialog if lastEventId is not "" or null in the private sentry shared preferences + //noinspection ConstantConditions + if (MainApplication.isCrashReportingEnabled() && !BuildConfig.SENTRY_TOKEN.isEmpty()) { + SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE); + String lastExitReason = preferences.getString("lastExitReason", ""); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Last Exit Reason: " + lastExitReason); + if (lastExitReason.equals("crash")) { + String lastEventId = preferences.getString("lastEventId", ""); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Last Event ID: " + lastEventId); + if (!lastEventId.equals("")) { + // Three edit texts for the user to enter their email, name and a description of the issue + EditText email = new EditText(this); + email.setHint(R.string.email); + email.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + EditText name = new EditText(this); + name.setHint(R.string.name); + name.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME); + EditText description = new EditText(this); + description.setHint(R.string.additional_info); + // Set description to be multiline and auto resize + description.setSingleLine(false); + description.setMaxHeight(1000); + // Make description required- + new MaterialAlertDialogBuilder(this).setCancelable(false).setTitle(R.string.sentry_dialogue_title).setMessage(R.string.sentry_dialogue_message).setView(new LinearLayout(this) {{ + setOrientation(LinearLayout.VERTICAL); + setPadding(40, 20, 40, 10); + addView(email); + addView(name); + addView(description); + }}).setPositiveButton(R.string.submit, (dialog, which) -> { + // Make sure the user has entered a description + if (description.getText().toString().equals("")) { + Toast.makeText(this, R.string.sentry_dialogue_no_description, Toast.LENGTH_LONG).show(); + dialog.cancel(); + } + preferences.edit().remove("lastEventId").apply(); + preferences.edit().putString("lastExitReason", "").apply(); + // Prevent strict mode violation + new Thread(() -> { + try { + HttpURLConnection connection = (HttpURLConnection) new URL("https" + "://sentry.io/api/0/projects/androidacy-i6/foxmmm/user-feedback/").openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Authorization", "Bearer " + BuildConfig.SENTRY_TOKEN); + // Setups the JSON body + String nameString = name.getText().toString(); + String emailString = email.getText().toString(); + if (nameString.equals("")) nameString = "Anonymous"; + if (emailString.equals("")) emailString = "Anonymous"; + JSONObject body = new JSONObject(); + body.put("event_id", lastEventId); + body.put("name", nameString); + body.put("email", emailString); + body.put("comments", description.getText().toString()); + // Send the request + connection.setDoOutput(true); + connection.getOutputStream().write(body.toString().getBytes()); + connection.connect(); + // For debug builds, log the response code and response body + if (BuildConfig.DEBUG) { + Log.d("NoodleDebug", "Response Code: " + connection.getResponseCode()); + } + // Check if the request was successful + if (connection.getResponseCode() == 200) { + runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG).show()); + } else { + runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show()); + } + } catch (IOException | JSONException ignored) { + // Show a toast if the user feedback could not be submitted + runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show()); + } + }).start(); + }).setNegativeButton(R.string.cancel, (dialog, which) -> { + preferences.edit().remove("lastEventId").apply(); + preferences.edit().putString("lastExitReason", "").apply(); + Log.w(TAG, "User cancelled sentry dialog"); + }).show(); + } + } + } } private void cardIconifyUpdate() { @@ -319,7 +456,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.updateBlurState(); this.moduleViewListBuilder.setQuery(null); Log.i(TAG, "Item After"); - noodleDebugState = MainApplication.isDeveloper(); this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter); InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() { @Override @@ -328,8 +464,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); - noodleDebug.setEnabled(noodleDebugState); - noodleDebug.bind(); ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules); this.commonNext(); @@ -338,14 +472,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe @Override public void onFailure(int error) { moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification()); - noodleDebug.setEnabled(noodleDebugState); - noodleDebug.bind(); this.commonNext(); } public void commonNext() { Log.i(TAG, "Common Before"); - NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); @@ -354,26 +485,23 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); RepoManager.getINSTANCE().updateEnabledStates(); - noodleDebug.push(""); if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) { runOnUiThread(() -> { progressIndicator.setIndeterminate(false); progressIndicator.setMax(PRECISION); }); - noodleDebug.replace("Check Update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update"); RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> progressIndicator.setProgressCompat((int) (value * PRECISION), true))); runOnUiThread(() -> { progressIndicator.setProgressCompat(PRECISION, true); progressIndicator.setVisibility(View.GONE); }); } - noodleDebug.replace("Apply"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply"); RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules); Log.i(TAG, "Common Before applyTo"); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); - noodleDebug.pop(); Log.i(TAG, "Common After"); - noodleDebug.unbind(); } }); this.initMode = false; @@ -390,33 +518,33 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.swipeRefreshLayout.setRefreshing(false); return; // Do not double scan } + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Refresh"); this.progressIndicator.setVisibility(View.VISIBLE); this.progressIndicator.setProgressCompat(0, false); this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L; // this.swipeRefreshLayout.setRefreshing(true); ?? new Thread(() -> { - noodleDebug.setEnabled(noodleDebugState); - NoodleDebug noodleDebug = this.noodleDebug.bind(); Http.cleanDnsCache(); // Allow DNS reload from network - noodleDebug.push("Check Update"); + // noodleDebug.push("Check Update"); final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount(); RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true))); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); if (!NotificationType.NO_INTERNET.shouldRemove()) { moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); + } else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) { + moduleViewListBuilder.addNotification(NotificationType.REPO_UPDATE_FAILED); } else { // Compatibility data still needs to be updated AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); - // noodleDebug.replace("Check App Update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check App Update"); if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); - // noodleDebug.replace("Check Json Update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Json Update"); if (max != 0) { int current = 0; - noodleDebug.push(""); for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { if (localModuleInfo.updateJson != null) { - noodleDebug.replace(localModuleInfo.id); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", localModuleInfo.id); try { localModuleInfo.checkModuleUpdate(); } catch (Exception e) { @@ -427,23 +555,20 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true)); } } - noodleDebug.pop(); } } - // noodleDebug.replace("Apply"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply"); runOnUiThread(() -> { this.progressIndicator.setVisibility(View.GONE); this.swipeRefreshLayout.setRefreshing(false); }); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); - if (!NotificationType.NO_INTERNET.shouldRemove()) { - this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); - } RepoManager.getINSTANCE().updateEnabledStates(); RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules); this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); - // noodleDebug.pop(); - // noodleDebug.unbind(); + /* + noodleDebug.unbind(); + */ }, "Repo update thread").start(); } @@ -487,33 +612,50 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe @SuppressLint("RestrictedApi") private void ensurePermissions() { + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Ensure Permissions"); // First, check if user has said don't ask again by checking if pref_dont_ask_again_notification_permission is true if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("pref_dont_ask_again_notification_permission", false)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - // Show a dialog explaining why we need this permission, which is to show - // notifications for updates - runOnUiThread(() -> { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.permission_notification_title); - builder.setMessage(R.string.permission_notification_message); - // Don't ask again checkbox - View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null); - CheckBox checkBox = view.findViewById(R.id.checkbox); - checkBox.setText(R.string.dont_ask_again); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply()); - builder.setView(view); - builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> { - // Request the permission - this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0); - }); - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - // Set pref_background_update_check to false and dismiss dialog - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - prefs.edit().putBoolean("pref_background_update_check", false).apply(); - dialog.dismiss(); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Request Notification Permission"); + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) { + // Show a dialog explaining why we need this permission, which is to show + // notifications for updates + runOnUiThread(() -> { + if (BuildConfig.DEBUG) + Log.d("NoodleDebug", "Show Notification Permission Dialog"); + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(R.string.permission_notification_title); + builder.setMessage(R.string.permission_notification_message); + // Don't ask again checkbox + View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null); + CheckBox checkBox = view.findViewById(R.id.checkbox); + checkBox.setText(R.string.dont_ask_again); + checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply()); + builder.setView(view); + builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> { + // Request the permission + this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0); + }); + builder.setNegativeButton(R.string.cancel, (dialog, which) -> { + // Set pref_background_update_check to false and dismiss dialog + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + prefs.edit().putBoolean("pref_background_update_check", false).apply(); + dialog.dismiss(); + }); + builder.show(); + if (BuildConfig.DEBUG) + Log.d("NoodleDebug", "Show Notification Permission Dialog Done"); }); - builder.show(); - }); + } else { + // Request the permission + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Request Notification Permission"); + this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0); + if (BuildConfig.DEBUG) { + // Log if granted via onRequestPermissionsResult + Log.d("NoodleDebug", "Request Notification Permission Done. Result: " + (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED)); + } + doSetupNowRunning = false; + } // Next branch is for < android 13 and user has blocked notifications } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && !NotificationManagerCompat.from(this).areNotificationsEnabled()) { runOnUiThread(() -> { @@ -533,16 +675,77 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); + doSetupNowRunning = false; }); builder.setNegativeButton(R.string.cancel, (dialog, which) -> { // Set pref_background_update_check to false and dismiss dialog SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs.edit().putBoolean("pref_background_update_check", false).apply(); dialog.dismiss(); + doSetupNowRunning = false; }); builder.show(); }); + } else { + doSetupNowRunning = false; } + } else { + if (BuildConfig.DEBUG) + Log.d("NoodleDebug", "Notification Permission Already Granted or Don't Ask Again"); + doSetupNowRunning = false; + } + } + + // Method to show a setup box on first launch + @SuppressLint({"InflateParams", "RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref"}) + private void doSetupNow() { + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Do setup now"); + // Check if this is the first launch + SharedPreferences prefs = MainApplication.getSharedPreferences(); + boolean firstLaunch = MainApplication.getBootSharedPreferences().getBoolean("first_launch", true); + if (BuildConfig.DEBUG) Log.d("Noodle", "First launch: " + firstLaunch); + if (firstLaunch) { + // Show setup box + runOnUiThread(() -> { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setCancelable(false); + builder.setTitle(R.string.setup_title); + View view = getLayoutInflater().inflate(R.layout.setup_box, null); + builder.setView(view); + // For now, we'll just have the positive button save the preferences and dismiss the dialog + builder.setPositiveButton(R.string.setup_button, (dialog, which) -> { + // Set the preferences + prefs.edit().putBoolean("pref_background_update_check", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_background_update_check))).isChecked()).commit(); + prefs.edit().putBoolean("pref_crash_reporting", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_crash_reporting))).isChecked()).commit(); + prefs.edit().putBoolean("pref_androidacy_repo_enabled", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_androidacy_repo))).isChecked()).commit(); + prefs.edit().putBoolean("pref_magisk_alt_repo_enabled", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_magisk_alt_repo))).isChecked()).commit(); + if (BuildConfig.DEBUG) { + Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false))); + } + // Set pref_first_launch to false + MainApplication.getBootSharedPreferences().edit().putBoolean("first_launch", false).commit(); + // Restart the app + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + finish(); + startActivity(intent); + ensurePermissions(); + }); + builder.setNegativeButton(R.string.setup_button_skip, (dialog, which) -> { + MainApplication.getBootSharedPreferences().edit().putBoolean("first_launch", false).commit(); + dialog.dismiss(); + ensurePermissions(); + }); + builder.show(); + // Set the switches appropriately + ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setChecked(BuildConfig.ENABLE_AUTO_UPDATER); + ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setChecked(BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING); + // Repos are a little harder, as the enabled_repos build config is an arraylist + ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("androidacy_repo")); + ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("magisk_alt_repo")); + }); + } else { + ensurePermissions(); } } } \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index dac051b..f4e1fb9 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -2,7 +2,6 @@ package com.fox2code.mmm; import android.annotation.SuppressLint; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -50,7 +49,6 @@ import io.noties.prism4j.annotations.PrismBundle; @PrismBundle(includeAll = true, grammarLocatorClassName = ".Prism4jGrammarLocator") public class MainApplication extends FoxApplication implements androidx.work.Configuration.Provider { - private static final String TAG = "MainApplication"; private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001 private static final Shell.Builder shellBuilder; private static final long secret; @@ -60,6 +58,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con private static SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale); private static SharedPreferences bootSharedPreferences; private static String relPackageName = BuildConfig.APPLICATION_ID; + @SuppressLint("StaticFieldLeak") private static MainApplication INSTANCE; private static boolean firstBoot; @@ -68,8 +67,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con secret = new Random().nextLong(); } - // Provides the Context for the base application - public Context FoxApplication = this; @StyleRes private int managerThemeResId = R.style.Theme_MagiskModuleManager; private FoxThemeWrapper markwonThemeContext; @@ -142,7 +139,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con } public static boolean isDeveloper() { - return BuildConfig.DEBUG || getSharedPreferences().getBoolean("developer", false); + if (BuildConfig.DEBUG) return true; + return getSharedPreferences().getBoolean("developer", false); } public static boolean isDisableLowQualityModuleFilter() { @@ -165,10 +163,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con return firstBoot; } - public static boolean hasGottenRootAccess() { - return getSharedPreferences().getBoolean("has_root_access", false); - } - public static void setHasGottenRootAccess(boolean bool) { getSharedPreferences().edit().putBoolean("has_root_access", bool).apply(); } @@ -204,10 +198,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con return this.markwon = markwon; } - public FoxThemeWrapper getMarkwonThemeContext() { - return this.markwonThemeContext; - } - @NonNull @Override public androidx.work.Configuration getWorkManagerConfiguration() { @@ -233,6 +223,12 @@ public class MainApplication extends FoxApplication implements androidx.work.Con case "light": themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet_Light : R.style.Theme_MagiskModuleManager_Light; break; + case "transparent_light": + if (monet) { + Log.w("MainApplication", "Monet is not supported for transparent theme"); + } + themeResId = R.style.Theme_MagiskModuleManager_Transparent_Light; + break; } this.setManagerThemeResId(themeResId); } @@ -278,11 +274,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con if (INSTANCE == null) INSTANCE = this; relPackageName = this.getPackageName(); super.onCreate(); - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - DynamicColors.applyToActivitiesIfAvailable(this, - new DynamicColorsOptions.Builder().setPrecondition( - (activity, theme) -> isMonetEnabled()).build()); - }*/ SharedPreferences sharedPreferences = MainApplication.getSharedPreferences(); // We are only one process so it's ok to do this SharedPreferences bootPrefs = MainApplication.bootSharedPreferences = this.getSharedPreferences("mmm_boot", MODE_PRIVATE); @@ -318,14 +309,17 @@ public class MainApplication extends FoxApplication implements androidx.work.Con SentryMain.initialize(this); if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) { - Log.w("MainApplication", "Androidacy client id is empty! Please set it in androidacy" + - ".properties. Will not enable Androidacy."); + Log.w("MainApplication", "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); editor.apply(); } } + private Intent getIntent() { + return this.getPackageManager().getLaunchIntentForPackage(this.getPackageName()); + } + @Override public void onCreateFoxActivity(FoxActivity compatActivity) { super.onCreateFoxActivity(compatActivity); diff --git a/app/src/main/java/com/fox2code/mmm/NotificationType.java b/app/src/main/java/com/fox2code/mmm/NotificationType.java index abd4c17..57ed5a5 100644 --- a/app/src/main/java/com/fox2code/mmm/NotificationType.java +++ b/app/src/main/java/com/fox2code/mmm/NotificationType.java @@ -64,8 +64,7 @@ public enum NotificationType implements NotificationTypeCst { NO_INTERNET(R.string.fail_internet, R.drawable.ic_baseline_cloud_off_24) { @Override public boolean shouldRemove() { - return AppUpdateManager.getAppUpdateManager().isLastCheckSuccess() || - RepoManager.getINSTANCE().hasConnectivity(); + return RepoManager.getINSTANCE().hasConnectivity(); } }, REPO_UPDATE_FAILED(R.string.repo_update_failed, R.drawable.ic_baseline_cloud_off_24) { @@ -191,6 +190,7 @@ public enum NotificationType implements NotificationTypeCst { } public boolean shouldRemove() { + // By default, remove the notification` return false; } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index e26387b..bab4ffe 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -179,8 +179,8 @@ public final class AndroidacyActivity extends FoxActivity { // Don't open non Androidacy urls inside WebView if (request.isForMainFrame() && !AndroidacyUtil.isAndroidacyLink(request.getUrl())) { if (downloadMode || backOnResume) return true; - Log.i(TAG, "Exiting WebView " + // hideToken in case isAndroidacyLink fail. - AndroidacyUtil.hideToken(request.getUrl().toString())); + Log.i(TAG, + "Exiting WebView " + AndroidacyUtil.hideToken(request.getUrl().toString())); IntentHelper.openUri(view.getContext(), request.getUrl().toString()); return true; } @@ -258,7 +258,7 @@ public final class AndroidacyActivity extends FoxActivity { break; } } - return super.onConsoleMessage(consoleMessage); + return true; } @Override diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index e9d8f09..b9258ae 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -108,7 +108,7 @@ public final class AndroidacyRepoData extends RepoData { deviceId = output; } } - // Now, get device model, manufacturer, and Android version + // Now, get device model, manufacturer, and Android version originally from String deviceModel = android.os.Build.MODEL; String deviceManufacturer = android.os.Build.MANUFACTURER; String androidVersion = android.os.Build.VERSION.RELEASE; @@ -184,7 +184,8 @@ public final class AndroidacyRepoData extends RepoData { } String deviceId = generateDeviceId(); long time = System.currentTimeMillis(); - if (this.androidacyBlockade > time) return false; + if (this.androidacyBlockade > time) return true; // fake it till you make it. Basically, + // don'e fail just becaue we're rate limited. API and web rate limits are different. this.androidacyBlockade = time + 30_000L; try { if (this.token == null) { @@ -249,6 +250,9 @@ public final class AndroidacyRepoData extends RepoData { @Override protected List populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException { + if (BuildConfig.DEBUG) { + Log.d(TAG, "AndroidacyRepoData populate start"); + } if (!jsonObject.getString("status").equals("success")) throw new JSONException("Response is not a success!"); String name = jsonObject.optString("name", "Androidacy Modules Repo"); @@ -326,7 +330,9 @@ public final class AndroidacyRepoData extends RepoData { String config = jsonObject.optString("config", ""); moduleInfo.config = config.isEmpty() ? null : config; PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks - Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " + moduleInfo.version + " " + moduleInfo.versionCode); + // Log.d(TAG, + // "Module " + moduleInfo.name + " " + moduleInfo.id + " " + moduleInfo + // .version + " " + moduleInfo.versionCode); } Iterator moduleInfoIterator = this.moduleHashMap.values().iterator(); while (moduleInfoIterator.hasNext()) { diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java index d457947..13ca53b 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -29,7 +29,6 @@ import com.fox2code.mmm.utils.ExternalHelper; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.IntentHelper; -import com.fox2code.mmm.utils.PropUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.io.File; @@ -44,11 +43,11 @@ public class AndroidacyWebAPI { private static final int MAX_COMPAT_MODE = 1; private final AndroidacyActivity activity; private final boolean allowInstall; - private boolean allowHideNote = true; boolean consumedAction; boolean downloadMode; int effectiveCompatMode; int notifiedCompatMode; + private boolean allowHideNote = true; public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) { this.activity = activity; @@ -64,7 +63,7 @@ public class AndroidacyWebAPI { void openNativeModuleDialogRaw(String moduleUrl, String moduleId, String installTitle, String checksum, boolean canInstall) { - Log.d(TAG, "ModuleDialog, downloadUrl: " + AndroidacyUtil.hideToken(moduleUrl) + + if (BuildConfig.DEBUG) Log.d(TAG, "ModuleDialog, downloadUrl: " + AndroidacyUtil.hideToken(moduleUrl) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall); this.downloadMode = false; @@ -113,7 +112,7 @@ public class AndroidacyWebAPI { final boolean fMMTReborn = mmtReborn; builder.setPositiveButton(hasUpdate ? R.string.update_module : R.string.install_module, (x, y) -> IntentHelper.openInstaller(this.activity, - fModuleUrl, fTitle, fConfig, fChecksum, fMMTReborn)); + fModuleUrl, fTitle, fConfig, fChecksum, fMMTReborn)); } builder.setOnCancelListener(dialogInterface -> { if (!this.activity.backOnResume) @@ -146,7 +145,7 @@ public class AndroidacyWebAPI { void notifyCompatModeRaw(int value) { if (this.consumedAction) return; - Log.d(TAG, "Androidacy Compat mode: " + value); + if (BuildConfig.DEBUG) Log.d(TAG, "Androidacy Compat mode: " + value); this.notifiedCompatMode = value; if (value < 0) { value = 0; @@ -181,7 +180,7 @@ public class AndroidacyWebAPI { if (this.consumedAction) return; this.consumedAction = true; this.downloadMode = false; - Log.d(TAG, "Received openUrl request: " + url); + if (BuildConfig.DEBUG) Log.d(TAG, "Received openUrl request: " + url); if (Uri.parse(url).getScheme().equals("https")) { IntentHelper.openUrl(this.activity, url); } @@ -195,7 +194,7 @@ public class AndroidacyWebAPI { if (this.consumedAction) return; this.consumedAction = true; this.downloadMode = false; - Log.d(TAG, "Received openCustomTab request: " + url); + if (BuildConfig.DEBUG) Log.d(TAG, "Received openCustomTab request: " + url); if (Uri.parse(url).getScheme().equals("https")) { IntentHelper.openCustomTab(this.activity, url); } @@ -239,7 +238,7 @@ public class AndroidacyWebAPI { } this.consumedAction = true; this.downloadMode = false; - Log.d(TAG, "Received install request: " + + if (BuildConfig.DEBUG) Log.d(TAG, "Received install request: " + moduleUrl + " " + installTitle + " " + checksum); if (!AndroidacyUtil.isAndroidacyLink(moduleUrl)) { this.forceQuitRaw("Non Androidacy module link used on Androidacy"); @@ -299,7 +298,7 @@ public class AndroidacyWebAPI { return; } // Get moduleTitle from url -String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl); + String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl); this.openNativeModuleDialogRaw(moduleUrl, moduleId, moduleTitle, checksum, this.canInstall()); } @@ -376,6 +375,7 @@ String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl); /** * Return true if the module is an Andoridacy module. */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") @JavascriptInterface public boolean isAndroidacyModule(String moduleId) { LocalModuleInfo localModuleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId); @@ -448,7 +448,7 @@ String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl); /** * Return current android sdk-int version code, see: - * https://source.android.com/setup/start/build-numbers + * right here */ @JavascriptInterface public int getAndroidVersionCode() { diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java index dd2a703..4a69990 100644 --- a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java @@ -13,10 +13,12 @@ public class BackgroundBootListener extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (!BOOT_COMPLETED.equals(intent.getAction())) return; synchronized (BackgroundUpdateChecker.lock) { - BackgroundUpdateChecker.onMainActivityCreate(context); - if (MainApplication.isBackgroundUpdateCheckEnabled()) { - BackgroundUpdateChecker.doCheck(context); - } + new Thread(() -> { + BackgroundUpdateChecker.onMainActivityCreate(context); + if (MainApplication.isBackgroundUpdateCheckEnabled()) { + BackgroundUpdateChecker.doCheck(context); + } + }).start(); } } } diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java index 50d08fb..5f79d11 100644 --- a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java @@ -53,7 +53,7 @@ public class BackgroundUpdateChecker extends Worker { } static void doCheck(Context context) { - Thread.currentThread().setPriority(Thread.MIN_PRIORITY); + Thread.currentThread().setPriority(2); ModuleManager.getINSTANCE().scanAsync(); RepoManager.getINSTANCE().update(null); ModuleManager.getINSTANCE().runAfterScan(() -> { diff --git a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java index 6573682..3d8888a 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java +++ b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java @@ -5,10 +5,9 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.installer.InstallerInitializer; -import com.fox2code.mmm.utils.Http; -import com.fox2code.mmm.utils.NoodleDebug; import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.SyncManager; import com.topjohnwu.superuser.Shell; @@ -23,33 +22,34 @@ import java.util.HashMap; import java.util.Iterator; public final class ModuleManager extends SyncManager { - private static final String TAG = "ModuleManager"; - // New method is not really effective, this flag force app to use old method public static final boolean FORCE_NEED_FALLBACK = true; + private static final String TAG = "ModuleManager"; private static final int FLAG_MM_INVALID = ModuleInfo.FLAG_METADATA_INVALID; private static final int FLAG_MM_UNPROCESSED = ModuleInfo.FLAG_CUSTOM_INTERNAL; private static final int FLAGS_KEEP_INIT = FLAG_MM_UNPROCESSED | ModuleInfo.FLAGS_MODULE_ACTIVE | ModuleInfo.FLAG_MODULE_UPDATING_ONLY; private static final int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED; + private static final ModuleManager INSTANCE = new ModuleManager(); private final HashMap moduleInfos; private final SharedPreferences bootPrefs; private int updatableModuleCount = 0; - private static final ModuleManager INSTANCE = new ModuleManager(); + private ModuleManager() { + this.moduleInfos = new HashMap<>(); + this.bootPrefs = MainApplication.getBootSharedPreferences(); + } public static ModuleManager getINSTANCE() { return INSTANCE; } - private ModuleManager() { - this.moduleInfos = new HashMap<>(); - this.bootPrefs = MainApplication.getBootSharedPreferences(); + public static boolean isModuleActive(String moduleId) { + ModuleInfo moduleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId); + return moduleInfo != null && (moduleInfo.flags & ModuleInfo.FLAGS_MODULE_ACTIVE) != 0; } protected void scanInternal(@NonNull UpdateListener updateListener) { - NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); - noodleDebug.push("Initialize scan"); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; for (ModuleInfo v : this.moduleInfos.values()) { @@ -70,13 +70,12 @@ public final class ModuleManager extends SyncManager { if (!FORCE_NEED_FALLBACK && needFallback) { Log.e(TAG, "Failed to detect modules folder, using fallback instead."); } - noodleDebug.replace("Scan"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Scan"); if (modules != null) { - noodleDebug.push(""); for (String module : modules) { if (!new SuFile("/data/adb/modules/" + module).isDirectory()) continue; // Ignore non directory files inside modules folder - noodleDebug.replace(module); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", module); LocalModuleInfo moduleInfo = moduleInfos.get(module); if (moduleInfo == null) { moduleInfo = new LocalModuleInfo(module); @@ -114,20 +113,18 @@ public final class ModuleManager extends SyncManager { PropUtils.readProperties(moduleInfo, "/data/adb/modules/" + module + "/module.prop", true); } catch (Exception e) { - Log.d(TAG, "Failed to parse metadata!", e); + if (BuildConfig.DEBUG) Log.d(TAG, "Failed to parse metadata!", e); moduleInfo.flags |= FLAG_MM_INVALID; } } - noodleDebug.pop(); } - noodleDebug.replace("Scan update"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Scan update"); String[] modules_update = new SuFile("/data/adb/modules_update").list(); if (modules_update != null) { - noodleDebug.push(""); for (String module : modules_update) { if (!new SuFile("/data/adb/modules_update/" + module).isDirectory()) continue; // Ignore non directory files inside modules folder - noodleDebug.replace(module); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", module); LocalModuleInfo moduleInfo = moduleInfos.get(module); if (moduleInfo == null) { moduleInfo = new LocalModuleInfo(module); @@ -139,20 +136,18 @@ public final class ModuleManager extends SyncManager { PropUtils.readProperties(moduleInfo, "/data/adb/modules_update/" + module + "/module.prop", true); } catch (Exception e) { - Log.d(TAG, "Failed to parse metadata!", e); + if (BuildConfig.DEBUG) Log.d(TAG, "Failed to parse metadata!", e); moduleInfo.flags |= FLAG_MM_INVALID; } } - noodleDebug.pop(); } - noodleDebug.replace("Finalize scan"); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Finalize scan"); this.updatableModuleCount = 0; Iterator moduleInfoIterator = this.moduleInfos.values().iterator(); - noodleDebug.push(""); while (moduleInfoIterator.hasNext()) { LocalModuleInfo moduleInfo = moduleInfoIterator.next(); - noodleDebug.replace(moduleInfo.id); + if (BuildConfig.DEBUG) Log.d("NoodleDebug", moduleInfo.id); if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) { moduleInfoIterator.remove(); continue; // Don't process fallbacks if unreferenced @@ -174,12 +169,10 @@ public final class ModuleManager extends SyncManager { } moduleInfo.verify(); } - noodleDebug.pop(); if (firstScan) { editor.putBoolean("mm_first_scan", false); editor.apply(); } - noodleDebug.pop(); } public HashMap getModules() { @@ -235,7 +228,7 @@ public final class ModuleManager extends SyncManager { try { // Check for module that declare having file outside their own folder. try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( SuFileInputStream.open("/data/adb/modules/." + moduleInfo.id + "-files"), - StandardCharsets.UTF_8))) { + StandardCharsets.UTF_8))) { String line; while ((line = bufferedReader.readLine()) != null) { line = line.trim().replace(' ', '.'); @@ -248,16 +241,12 @@ public final class ModuleManager extends SyncManager { Shell.cmd("rm -rf \"" + line + "\"").exec(); } } - } catch (IOException ignored) {} + } catch (IOException ignored) { + } Shell.cmd("rm -rf /data/adb/modules/" + escapedId + "/").exec(); Shell.cmd("rm -f /data/adb/modules/." + escapedId + "-files").exec(); Shell.cmd("rm -rf /data/adb/modules_update/" + escapedId + "/").exec(); moduleInfo.flags = ModuleInfo.FLAG_METADATA_INVALID; return true; } - - public static boolean isModuleActive(String moduleId) { - ModuleInfo moduleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId); - return moduleInfo != null && (moduleInfo.flags & ModuleInfo.FLAGS_MODULE_ACTIVE) != 0; - } } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index 17ea670..02712b4 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -2,6 +2,7 @@ package com.fox2code.mmm.repo; import android.content.SharedPreferences; import android.net.Uri; +import android.util.Log; import androidx.annotation.NonNull; @@ -41,7 +42,7 @@ public class RepoData extends XRepo { public String name, website, support, donate, submitModule; private boolean forceHide, enabled; // Cache for speed - protected RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) { + public RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) { this.url = url; this.id = RepoManager.internalIdOfUrl(url); this.cacheRoot = cacheRoot; @@ -51,7 +52,7 @@ public class RepoData extends XRepo { this.defaultName = url; // Set url as default name this.forceHide = AppUpdateManager.shouldForceHide(this.id); this.enabled = (!this.forceHide) && MainApplication.getSharedPreferences() - .getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault()); + .getBoolean("pref_" + this.getPreferenceId() + "_enabled", true); this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/"; if (!this.cacheRoot.isDirectory()) { boolean mkdirs = this.cacheRoot.mkdirs(); @@ -206,14 +207,22 @@ public class RepoData extends XRepo { @Override public void setEnabled(boolean enabled) { this.enabled = enabled && !this.forceHide; + if (BuildConfig.DEBUG) { + Log.d("RepoData", + "Repo " + this.id + " enabled: " + this.enabled + " (forced: " + this.forceHide + ") with preferenceID: " + this.getPreferenceId()); + } MainApplication.getSharedPreferences().edit() .putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply(); } public void updateEnabledState() { this.forceHide = AppUpdateManager.shouldForceHide(this.id); + if (BuildConfig.DEBUG) { + Log.d("RepoData", + "Repo " + this.id + " update enabled: " + this.enabled + " (forced: " + this.forceHide + ") with preferenceID: " + this.getPreferenceId()); + } this.enabled = (!this.forceHide) && MainApplication.getSharedPreferences() - .getBoolean("pref_" + this.getPreferenceId() + "_enabled", this.isEnabledByDefault()); + .getBoolean("pref_" + this.getPreferenceId() + "_enabled", true); } public String getUrl() throws NoSuchAlgorithmException { diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index 663b0ce..659d24d 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -1,12 +1,18 @@ package com.fox2code.mmm.repo; +import android.annotation.SuppressLint; +import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; +import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.MainApplication; +import com.fox2code.mmm.R; import com.fox2code.mmm.XHooks; import com.fox2code.mmm.XRepo; import com.fox2code.mmm.androidacy.AndroidacyRepoData; @@ -14,9 +20,9 @@ import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Http; -import com.fox2code.mmm.utils.NoodleDebug; import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.SyncManager; +import com.google.android.material.snackbar.Snackbar; import java.io.File; import java.io.IOException; @@ -28,7 +34,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; - public final class RepoManager extends SyncManager { public static final String MAGISK_REPO = "https://raw.githubusercontent.com/Magisk-Modules-Repo/submission/modules/modules.json"; @@ -45,12 +50,6 @@ public final class RepoManager extends SyncManager { "https://staging-api.androidacy.com/magisk/repo"; public static final String ANDROIDACY_MAGISK_REPO_HOMEPAGE = "https://www.androidacy.com/modules-repo"; - public static final String DG_MAGISK_REPO = - "https://repo.dergoogler.com/modules.json"; - public static final String DG_MAGISK_REPO_GITHUB = - "https://googlers-magisk-repo.github.io/modules.json"; - public static final String DG_MAGISK_REPO_GITHUB_RAW = - "https://raw.githubusercontent.com/Googlers-Repo/googlers-repo.github.io/master/modules.json"; private static final String TAG = "RepoManager"; private static final String MAGISK_REPO_MANAGER = "https://magisk-modules-repo.github.io/submission/modules.json"; @@ -64,10 +63,10 @@ public final class RepoManager extends SyncManager { private final HashMap modules; private final AndroidacyRepoData androidacyRepoData; private final CustomRepoManager customRepoManager; + public String repoLastErrorName = null; private boolean hasInternet; - private boolean repoLastError = false; private boolean initialized; - public String repoLastErrorName = null; + private boolean repoLastSuccess; private RepoManager(MainApplication mainApplication) { INSTANCE = this; // Set early fox XHooks @@ -76,15 +75,12 @@ public final class RepoManager extends SyncManager { this.repoData = new LinkedHashMap<>(); this.modules = new HashMap<>(); // We do not have repo list config yet. + this.androidacyRepoData = this.addAndroidacyRepoData(); RepoData altRepo = this.addRepoData( MAGISK_ALT_REPO, "Magisk Modules Alt Repo"); altRepo.defaultWebsite = RepoManager.MAGISK_ALT_REPO_HOMEPAGE; altRepo.defaultSubmitModule = "https://github.com/Magisk-Modules-Alt-Repo/submission/issues"; - RepoData dgRepo = this.addRepoData( - DG_MAGISK_REPO_GITHUB_RAW, "Googlers Magisk Repo"); - dgRepo.defaultWebsite = "https://dergoogler.com/repo"; - this.androidacyRepoData = this.addAndroidacyRepoData(); this.customRepoManager = new CustomRepoManager(mainApplication, this); XHooks.onRepoManagerInitialize(); // Populate default cache @@ -141,25 +137,18 @@ public final class RepoManager extends SyncManager { case ANDROIDACY_MAGISK_REPO_ENDPOINT: case ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT: return "androidacy_repo"; - case DG_MAGISK_REPO: - case DG_MAGISK_REPO_GITHUB: - case DG_MAGISK_REPO_GITHUB_RAW: - return "dg_magisk_repo"; default: - return "repo_" + Hashes.hashSha1( + return "repo_" + Hashes.hashSha256( url.getBytes(StandardCharsets.UTF_8)); } } static boolean isBuiltInRepo(String repo) { switch (repo) { - case RepoManager.MAGISK_ALT_REPO: - case RepoManager.MAGISK_ALT_REPO_JSDELIVR: case RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT: case RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT: - case RepoManager.DG_MAGISK_REPO: - case RepoManager.DG_MAGISK_REPO_GITHUB: - case RepoManager.DG_MAGISK_REPO_GITHUB_RAW: + case RepoManager.MAGISK_ALT_REPO: + case RepoManager.MAGISK_ALT_REPO_JSDELIVR: return true; } return false; @@ -207,18 +196,18 @@ public final class RepoManager extends SyncManager { public RepoData addOrGet(String url, String fallBackName) { if (MAGISK_ALT_REPO_JSDELIVR.equals(url)) url = MAGISK_ALT_REPO; - if (DG_MAGISK_REPO.equals(url) || - DG_MAGISK_REPO_GITHUB.equals(url)) - url = DG_MAGISK_REPO_GITHUB_RAW; RepoData repoData; synchronized (this.syncLock) { repoData = this.repoData.get(url); if (repoData == null) { if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) || ANDROIDACY_MAGISK_REPO_ENDPOINT.equals(url)) { - if (this.androidacyRepoData != null) + //noinspection ReplaceNullCheck + if (this.androidacyRepoData != null) { return this.androidacyRepoData; - return this.addAndroidacyRepoData(); + } else { + return this.addAndroidacyRepoData(); + } } else { return this.addRepoData(url, fallBackName); } @@ -227,10 +216,8 @@ public final class RepoManager extends SyncManager { return repoData; } + @SuppressLint("StringFormatInvalid") protected void scanInternal(@NonNull UpdateListener updateListener) { - NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); - // First, check if we have internet connection - noodleDebug.push("Downloading indexes"); this.modules.clear(); updateListener.update(0D); // Using LinkedHashSet to deduplicate Androidacy entry. @@ -238,26 +225,27 @@ public final class RepoManager extends SyncManager { this.repoData.values()).toArray(new RepoData[0]); RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length]; int moduleToUpdate = 0; - noodleDebug.push(""); for (int i = 0; i < repoDatas.length; i++) { - noodleDebug.replace(repoDatas[i].getName()); + if (BuildConfig.DEBUG) Log.d("RepoManager", "Fetching: " + repoDatas[i].getName()); moduleToUpdate += (repoUpdaters[i] = new RepoUpdater(repoDatas[i])).fetchIndex(); updateListener.update(STEP1 / repoDatas.length * (i + 1)); } - noodleDebug.pop(); - noodleDebug.replace("Updating meta-data"); + if (BuildConfig.DEBUG) Log.d("RepoManag3er", "Updating meta-data"); int updatedModules = 0; boolean allowLowQualityModules = MainApplication.isDisableLowQualityModuleFilter(); - noodleDebug.push(""); for (int i = 0; i < repoUpdaters.length; i++) { + // Check if the repo is enabled + if (!repoUpdaters[i].repoData.isEnabled()) { + if (BuildConfig.DEBUG) Log.d("RepoManager", + "Skipping disabled repo: " + repoUpdaters[i].repoData.getName()); + continue; + } List repoModules = repoUpdaters[i].toUpdate(); RepoData repoData = repoDatas[i]; - noodleDebug.replace(repoData.getName()); - Log.d(TAG, "Registering " + repoData.getName()); - noodleDebug.push(""); + if (BuildConfig.DEBUG) Log.d("RepoManager", "Registering " + repoData.getName()); for (RepoModule repoModule : repoModules) { - noodleDebug.replace(repoModule.id); + if (BuildConfig.DEBUG) Log.d("RepoManager", "Fetching module: " + repoModule.id); try { if (repoModule.propUrl != null && !repoModule.propUrl.isEmpty()) { @@ -283,7 +271,6 @@ public final class RepoManager extends SyncManager { updatedModules++; updateListener.update(STEP1 + (STEP2 / moduleToUpdate * updatedModules)); } - noodleDebug.pop(); for (RepoModule repoModule : repoUpdaters[i].toApply()) { if ((repoModule.moduleInfo.flags & ModuleInfo.FLAG_METADATA_INVALID) == 0) { RepoModule registeredRepoModule = this.modules.get(repoModule.id); @@ -296,9 +283,7 @@ public final class RepoManager extends SyncManager { } } } - noodleDebug.pop(); - noodleDebug.replace("Finishing update"); - noodleDebug.push(""); + if (BuildConfig.DEBUG) Log.d("RepoManager", "Finishing update"); this.hasInternet = false; // Check if we have internet connection // Attempt to contact connectivitycheck.gstatic.com/generate_204 @@ -307,7 +292,6 @@ public final class RepoManager extends SyncManager { HttpURLConnection urlConnection = (HttpURLConnection) new URL( "https://connectivitycheck.gstatic.com/generate_204").openConnection(); urlConnection.setInstanceFollowRedirects(false); - urlConnection.setConnectTimeout(1000); urlConnection.setReadTimeout(1000); urlConnection.setUseCaches(false); urlConnection.getInputStream().close(); @@ -318,22 +302,37 @@ public final class RepoManager extends SyncManager { } catch (IOException e) { Log.e(TAG, "Failed to check internet connection", e); } - noodleDebug.pop(); if (hasInternet) { for (int i = 0; i < repoDatas.length; i++) { - noodleDebug.replace(repoUpdaters[i].repoData.getName()); - this.repoLastError = !repoUpdaters[i].finish(); - if (this.repoLastError) { + // If repo is not enabled, skip + if (!repoDatas[i].isEnabled()) { + if (BuildConfig.DEBUG) Log.d("RepoManager", + "Skipping " + repoDatas[i].getName() + " because it's disabled"); + continue; + } + if (BuildConfig.DEBUG) Log.d("RepoManager", + "Finishing: " + repoUpdaters[i].repoData.getName()); + this.repoLastSuccess = repoUpdaters[i].finish(); + if (!this.repoLastSuccess) { Log.e(TAG, "Failed to update " + repoUpdaters[i].repoData.getName()); + // Show snackbar on main looper and add some bottom padding + int finalI = i; + Activity context = MainApplication.getINSTANCE().getLastCompatActivity(); + new Handler(Looper.getMainLooper()).post(() -> { + if (context != null) { + Snackbar.make(context.findViewById(android.R.id.content), + context.getString(R.string.repo_update_failed_extended, + repoUpdaters[finalI].repoData.getName()), + Snackbar.LENGTH_LONG).show(); + } + }); this.repoLastErrorName = repoUpdaters[i].repoData.getName(); } updateListener.update(STEP1 + STEP2 + (STEP3 / repoDatas.length * (i + 1))); } } - noodleDebug.pop(); Log.i(TAG, "Got " + this.modules.size() + " modules!"); updateListener.update(1D); - noodleDebug.pop(); // pop "Finishing update" } public void updateEnabledStates() { @@ -408,6 +407,6 @@ public final class RepoManager extends SyncManager { } public boolean isLastUpdateSuccess() { - return this.repoLastError; + return this.repoLastSuccess; } } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java index c7145ac..befc5c3 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java @@ -1,20 +1,11 @@ package com.fox2code.mmm.repo; import android.util.Log; -import android.view.View; -import android.view.Window; -import android.widget.Toast; -import androidx.annotation.Nullable; - -import com.fox2code.mmm.MainActivity; -import com.fox2code.mmm.MainApplication; +import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Http; -import com.fox2code.mmm.utils.HttpException; -import com.google.android.material.snackbar.Snackbar; -import org.jetbrains.annotations.Contract; import org.json.JSONObject; import java.io.IOException; @@ -52,12 +43,12 @@ public class RepoUpdater { } this.indexRaw = Http.doHttpGet(this.repoData.getUrl(), false); // Ensure it's a valid json and response code is 200 - if (Arrays.hashCode(this.indexRaw) == 0) { + /*if (Arrays.hashCode(this.indexRaw) == 0) { this.indexRaw = null; this.toUpdate = Collections.emptyList(); this.toApply = this.repoData.moduleHashMap.values(); return 0; - } + }*/ this.toUpdate = this.repoData.populate(new JSONObject( new String(this.indexRaw, StandardCharsets.UTF_8))); // Since we reuse instances this should work @@ -83,7 +74,7 @@ public class RepoUpdater { } public boolean finish() { - final boolean success = this.indexRaw != null; + boolean success = this.indexRaw != null; // If repo is not enabled we don't need to do anything, just return true if (!this.repoData.isEnabled()) { return true; @@ -91,6 +82,9 @@ public class RepoUpdater { if (this.indexRaw != null) { try { Files.write(this.repoData.metaDataCache, this.indexRaw); + if (BuildConfig.DEBUG) { + Log.d(TAG, "Wrote index of " + this.repoData.id); + } } catch (IOException e) { e.printStackTrace(); } 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 4466717..907e635 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -1,6 +1,9 @@ package com.fox2code.mmm.settings; +import static java.lang.Integer.parseInt; + import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.app.AlarmManager; import android.app.Application; import android.app.PendingIntent; @@ -9,6 +12,8 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.Signature; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -61,14 +66,17 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.internal.TextWatcherAdapter; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.google.common.hash.Hashing; import com.mikepenz.aboutlibraries.LibsBuilder; import com.topjohnwu.superuser.internal.UiThreadHandler; import org.json.JSONException; import java.io.IOException; +import java.io.RandomAccessFile; import java.security.NoSuchAlgorithmException; import java.util.HashSet; +import java.util.Locale; import java.util.Objects; import java.util.Random; @@ -78,6 +86,50 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper(); private static int devModeStep = 0; + // Shamelessly adapted from https://github.com/DrKLO/Telegram/blob/2c71f6c92b45386f0c2b25f1442596462404bb39/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java#L1254 + public final static int PERFORMANCE_CLASS_LOW = 0; + public final static int PERFORMANCE_CLASS_AVERAGE = 1; + public final static int PERFORMANCE_CLASS_HIGH = 2; + + @PerformanceClass + public static int getDevicePerformanceClass() { + int devicePerformanceClass; + int androidVersion = Build.VERSION.SDK_INT; + int cpuCount = Runtime.getRuntime().availableProcessors(); + int memoryClass = + ((ActivityManager) MainApplication.getINSTANCE().getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); + int totalCpuFreq = 0; + int freqResolved = 0; + for (int i = 0; i < cpuCount; i++) { + try { + RandomAccessFile reader = new RandomAccessFile(String.format(Locale.ENGLISH, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i), "r"); + String line = reader.readLine(); + if (line != null) { + totalCpuFreq += parseInt(line) / 1000; + freqResolved++; + } + reader.close(); + } catch (Throwable ignore) {} + } + int maxCpuFreq = freqResolved == 0 ? -1 : (int) Math.ceil(totalCpuFreq / (float) freqResolved); + + if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion <= 21 || cpuCount <= 4 && maxCpuFreq <= 1300 && memoryClass <= 128 && androidVersion <= 24) { + devicePerformanceClass = PERFORMANCE_CLASS_LOW; + } else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 2050 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) { + devicePerformanceClass = PERFORMANCE_CLASS_AVERAGE; + } else { + devicePerformanceClass = PERFORMANCE_CLASS_HIGH; + } + + if (BuildConfig.DEBUG) { + Log.d(TAG, "getDevicePerformanceClass: androidVersion=" + androidVersion + " cpuCount=" + cpuCount + " memoryClass=" + memoryClass + " maxCpuFreq=" + maxCpuFreq + " devicePerformanceClass=" + devicePerformanceClass); + } + + return devicePerformanceClass; + } + + public @interface PerformanceClass {} + @Override protected void onCreate(Bundle savedInstanceState) { devModeStep = 0; @@ -123,6 +175,25 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return true; }); ListPreference themePreference = findPreference("pref_theme"); + // If transparent theme(s) are set, disable monet + if (themePreference.getValue().equals("transparent_light")) { + if (BuildConfig.DEBUG) { + Log.d(TAG, "Transparent theme is set, disabling monet"); + } + findPreference("pref_enable_monet").setEnabled(false); + // Toggle monet off + ((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false); + SharedPreferences.Editor editor = + getPreferenceManager().getSharedPreferences().edit(); + editor.putBoolean("pref_enable_monet", false).apply(); + // Set summary + findPreference("pref_enable_monet").setSummary(R.string.monet_disabled_summary); + // Same for blur + findPreference("pref_enable_blur").setEnabled(false); + ((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false); + editor.putBoolean("pref_enable_blur", false).apply(); + findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary); + } themePreference.setSummaryProvider(p -> themePreference.getEntry()); themePreference.setOnPreferenceClickListener(p -> { // You need to reboot your device at least once to be able to access dev-mode @@ -130,12 +201,65 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return false; }); themePreference.setOnPreferenceChangeListener((preference, newValue) -> { - devModeStep = 0; - UiThreadHandler.handler.postDelayed(() -> { - MainApplication.getINSTANCE().updateTheme(); - FoxActivity.getFoxActivity(this).setThemeRecreate( - MainApplication.getINSTANCE().getManagerThemeResId()); - }, 1); + if (BuildConfig.DEBUG) { + Log.d(TAG, "Theme changed, refreshing activity. New value: " + newValue); + } + // Immediately save + SharedPreferences.Editor editor = + getPreferenceManager().getSharedPreferences().edit(); + editor.putString("pref_theme", (String) newValue).apply(); + // If theme contains "transparent" then disable monet + if (newValue.toString().contains("transparent")) { + if (BuildConfig.DEBUG) { + Log.d(TAG, "Transparent theme is being set, disabling monet"); + } + // Show a dialogue warning the user about issues with transparent themes and + // that blur/monet will be disabled + new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.transparent_theme_dialogue_title) + .setMessage(R.string.transparent_theme_dialogue_message) + .setPositiveButton(R.string.ok, (dialog, which) -> { + // Toggle monet off + ((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false); + editor.putBoolean("pref_enable_monet", false).apply(); + // Set summary + findPreference("pref_enable_monet").setSummary(R.string.monet_disabled_summary); + // Same for blur + ((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false); + editor.putBoolean("pref_enable_blur", false).apply(); + findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary); + // Refresh activity + devModeStep = 0; + UiThreadHandler.handler.postDelayed(() -> { + MainApplication.getINSTANCE().updateTheme(); + FoxActivity.getFoxActivity(this).setThemeRecreate( + MainApplication.getINSTANCE().getManagerThemeResId()); + }, 1); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> { + // Revert to system theme + ((ListPreference) findPreference("pref_theme")).setValue("system"); + // Refresh activity + devModeStep = 0; + UiThreadHandler.handler.postDelayed(() -> { + MainApplication.getINSTANCE().updateTheme(); + FoxActivity.getFoxActivity(this).setThemeRecreate( + MainApplication.getINSTANCE().getManagerThemeResId()); + }, 1); + }) + .show(); + } else { + findPreference("pref_enable_monet").setEnabled(true); + findPreference("pref_enable_monet").setSummary(null); + findPreference("pref_enable_blur").setEnabled(true); + findPreference("pref_enable_blur").setSummary(null); + devModeStep = 0; + UiThreadHandler.handler.postDelayed(() -> { + MainApplication.getINSTANCE().updateTheme(); + FoxActivity.getFoxActivity(this).setThemeRecreate( + MainApplication.getINSTANCE().getManagerThemeResId()); + }, 1); + } return true; }); // Crash reporting @@ -157,13 +281,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { int mPendingIntentId = 123456; // If < 23, FLAG_IMMUTABLE is not available PendingIntent mPendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT); - } + mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, + mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); if (BuildConfig.DEBUG) { @@ -177,9 +296,36 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return true; }); Preference enableBlur = findPreference("pref_enable_blur"); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - enableBlur.setSummary(R.string.require_android_6); - enableBlur.setEnabled(false); + // Disable blur on low performance devices + if (getDevicePerformanceClass() < PERFORMANCE_CLASS_AVERAGE) { + // Show a warning + enableBlur.setOnPreferenceChangeListener((preference, newValue) -> { + if (newValue.equals(true)) { + new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.low_performance_device_dialogue_title) + .setMessage(R.string.low_performance_device_dialogue_message) + .setPositiveButton(R.string.ok, (dialog, which) -> { + // Toggle blur on + ((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(true); + SharedPreferences.Editor editor = + getPreferenceManager().getSharedPreferences().edit(); + editor.putBoolean("pref_enable_blur", true).apply(); + // Set summary + findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> { + // Revert to blur on + ((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false); + SharedPreferences.Editor editor = + getPreferenceManager().getSharedPreferences().edit(); + editor.putBoolean("pref_enable_blur", false).apply(); + // Set summary + findPreference("pref_enable_blur").setSummary(null); + }) + .show(); + } + return true; + }); } Preference disableMonet = findPreference("pref_enable_monet"); @@ -252,6 +398,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG || InstallerInitializer.peekMagiskPath() == null) { // Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app + Log.d(TAG, String.format("Sentry installed: %s, debug: %s, magisk path: %s", + SentryMain.IS_SENTRY_INSTALLED, BuildConfig.DEBUG, InstallerInitializer.peekMagiskPath())); Objects.requireNonNull((Preference) findPreference("pref_crash")).setVisible(false); } else { findPreference("pref_crash").setOnPreferenceClickListener(preference -> { @@ -387,9 +535,24 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { openFragment(libsBuilder.supportFragment(), R.string.licenses); return true; }); - findPreference("pref_pkg_info").setSummary(BuildConfig.APPLICATION_ID + - " v" + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")" + - getRepackageState()); // State may not be "I am just running from myself as myself" + // Determine if this is an official build based on the signature + boolean isOfficial = false; + try { + // Get the signature of the key used to sign the app + @SuppressLint("PackageManagerGetSignatures") Signature[] signatures = requireContext().getPackageManager().getPackageInfo(requireContext().getPackageName(), PackageManager.GET_SIGNATURES).signatures; + String officialSignatureHash = + "7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6"; + String ourSignatureHash = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString(); + isOfficial = ourSignatureHash.equals(officialSignatureHash); + } catch (PackageManager.NameNotFoundException ignored) { + } + String flavor = BuildConfig.FLAVOR; + String type = BuildConfig.BUILD_TYPE; + // Set the summary of pref_pkg_info to something like Github-debug v1.0 (123) (Official) + String pkgInfo = getString(R.string.pref_pkg_info_summary, flavor + "-" + type, + BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, isOfficial ? + getString(R.string.official) : getString(R.string.unofficial)); + findPreference("pref_pkg_info").setSummary(pkgInfo); } @SuppressLint("RestrictedApi") @@ -445,7 +608,6 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { setPreferencesFromResource(R.xml.repo_preferences, rootKey); setRepoData(RepoManager.MAGISK_ALT_REPO); setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT); - setRepoData(RepoManager.DG_MAGISK_REPO_GITHUB); updateCustomRepoList(true); onCreatePreferencesAndroidacy(); } @@ -464,6 +626,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { // Use MaterialAlertDialogBuilder new MaterialAlertDialogBuilder(this.requireContext()) .setTitle(R.string.warning) + .setCancelable(false) .setMessage(R.string.androidacy_test_mode_warning) .setPositiveButton(android.R.string.ok, (dialog, which) -> { // User clicked OK button @@ -474,13 +637,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { int mPendingIntentId = 123456; // If < 23, FLAG_IMMUTABLE is not available PendingIntent mPendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT); - } + mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, + mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); if (BuildConfig.DEBUG) { @@ -502,6 +660,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { // Show dialog to restart app with ok button new MaterialAlertDialogBuilder(this.requireContext()) .setTitle(R.string.warning) + .setCancelable(false) .setMessage(R.string.androidacy_test_mode_disable_warning) .setNeutralButton(android.R.string.ok, (dialog, which) -> { // User clicked OK button @@ -510,13 +669,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { int mPendingIntentId = 123456; // If < 23, FLAG_IMMUTABLE is not available PendingIntent mPendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT); - } + mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, + mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); if (BuildConfig.DEBUG) { @@ -536,6 +690,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { androidacyRepoEnabled.setOnPreferenceClickListener(preference -> { new MaterialAlertDialogBuilder(this.requireContext()) .setTitle(R.string.androidacy_repo_disabled) + .setCancelable(false) .setMessage(R.string.androidacy_repo_disabled_message) .setPositiveButton(R.string.download_full_app, (dialog, which) -> { // User clicked OK button. Open GitHub releases page @@ -572,9 +727,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { }); prefAndroidacyRepoApiKey.setPositiveButtonText(R.string.save_api_key); prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> { - if (originalApiKeyRef[0].equals(newValue)) return true; // Skip if nothing changed. - // Curious if this actually works - so crash the app on purpose - // throw new RuntimeException("This is a test crash"); + if (originalApiKeyRef[0].equals(newValue)) return true; // get original api key String apiKey = String.valueOf(newValue); // Show snack bar with indeterminate progress @@ -593,6 +746,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { // Show dialog to restart app with ok button new MaterialAlertDialogBuilder(this.requireContext()) .setTitle(R.string.restart) + .setCancelable(false) .setMessage(R.string.api_key_restart) .setNeutralButton(android.R.string.ok, (dialog, which) -> { // User clicked OK button @@ -601,13 +755,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { int mPendingIntentId = 123456; // If < 23, FLAG_IMMUTABLE is not available PendingIntent mPendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT); - } + mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, + mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); if (BuildConfig.DEBUG) { @@ -650,6 +799,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { // Show dialog to restart app with ok button new MaterialAlertDialogBuilder(this.requireContext()) .setTitle(R.string.restart) + .setCancelable(false) .setMessage(R.string.api_key_restart) .setNeutralButton(android.R.string.ok, (dialog, which) -> { // User clicked OK button @@ -658,13 +808,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { int mPendingIntentId = 123456; // If < 23, FLAG_IMMUTABLE is not available PendingIntent mPendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, - mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT); - } + mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, + mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); if (BuildConfig.DEBUG) { @@ -793,12 +938,20 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { preference.setTitle(repoData.getName()); preference = findPreference(preferenceName + "_enabled"); if (preference != null) { - ((TwoStatePreference) preference).setChecked(repoData.isEnabled()); - preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled); - preference.setOnPreferenceChangeListener((p, newValue) -> { - p.setTitle(((Boolean) newValue) ? R.string.repo_enabled : R.string.repo_disabled); - return true; - }); + // Handle custom repo separately + if (repoData instanceof CustomRepoData) { + preference.setTitle(R.string.custom_repo_always_on); + // Disable the preference + preference.setEnabled(false); + return; + } else { + ((TwoStatePreference) preference).setChecked(repoData.isEnabled()); + preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled); + preference.setOnPreferenceChangeListener((p, newValue) -> { + p.setTitle(((Boolean) newValue) ? R.string.repo_enabled : R.string.repo_disabled); + return true; + }); + } } preference = findPreference(preferenceName + "_website"); String homepage = repoData.getWebsite(); diff --git a/app/src/main/java/com/fox2code/mmm/utils/Hashes.java b/app/src/main/java/com/fox2code/mmm/utils/Hashes.java index c346af6..26e39fa 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Hashes.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Hashes.java @@ -24,6 +24,7 @@ public class Hashes { } public static String hashMd5(byte[] input) { + Log.w(TAG, "hashMd5: This method is insecure, use hashSha256 instead"); try { MessageDigest md = MessageDigest.getInstance("MD5"); diff --git a/app/src/main/java/com/fox2code/mmm/utils/Http.java b/app/src/main/java/com/fox2code/mmm/utils/Http.java index 5be3187..39e04dd 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Http.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Http.java @@ -15,11 +15,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.fox2code.mmm.BuildConfig; +import com.fox2code.mmm.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.androidacy.AndroidacyUtil; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.repo.RepoManager; -import com.google.net.cronet.okhttptransport.CronetCallFactory; import com.google.net.cronet.okhttptransport.CronetInterceptor; import org.chromium.net.CronetEngine; @@ -140,13 +140,6 @@ public class Http { return chain.proceed(request.build()); }); // Add cronet interceptor - // install cronet - /*try { - // Detect if cronet is installed - CronetProviderInstaller.installProvider(mainApplication); - } catch (Exception e) { - Log.e(TAG, "Failed to install cronet", e); - }*/ // init cronet try { // Load the cronet library @@ -165,8 +158,13 @@ public class Http { } builder.setStoragePath(mainApplication.getCacheDir().getAbsolutePath() + "/cronet"); builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 10 * 1024 * 1024); - CronetEngine engine = - builder.build(); + // Add quic hint + builder.addQuicHint("github.com", 443, 443); + builder.addQuicHint("githubusercontent.com", 443, 443); + builder.addQuicHint("jsdelivr.net", 443, 443); + builder.addQuicHint("androidacy.com", 443, 443); + builder.addQuicHint("sentry.io", 443, 443); + CronetEngine engine = builder.build(); httpclientBuilder.addInterceptor(CronetInterceptor.newBuilder(engine).build()); } catch (Exception e) { Log.e(TAG, "Failed to init cronet", e); @@ -234,10 +232,10 @@ public class Http { @SuppressWarnings("resource") public static byte[] doHttpGet(String url, boolean allowCache) throws IOException { checkNeedBlockAndroidacyRequest(url); - Response response = - (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute(); + Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute(); // 200/204 == success, 304 == cache valid if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { + Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code()); checkNeedCaptchaAndroidacy(url, response.code()); // If it's a 401, and an androidacy link, it's probably an invalid token if (response.code() == 401 && AndroidacyUtil.isAndroidacyLink(url)) { @@ -260,13 +258,17 @@ public class Http { @SuppressWarnings("resource") private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException { + if (BuildConfig.DEBUG) Log.d(TAG, "POST " + url + " " + data); checkNeedBlockAndroidacyRequest(url); - Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute(); + Response response; + response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute(); if (response.isRedirect()) { return response.request().url().uri().toString(); } // 200/204 == success, 304 == cache valid if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { + if (BuildConfig.DEBUG) + Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code() + ", body: " + response.body().string()); checkNeedCaptchaAndroidacy(url, response.code()); throw new HttpException(response.code()); } @@ -280,10 +282,11 @@ public class Http { } public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException { - Log.d("Http", "Progress URL: " + url); + if (BuildConfig.DEBUG) Log.d("Http", "Progress URL: " + url); checkNeedBlockAndroidacyRequest(url); Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute(); if (response.code() != 200 && response.code() != 204) { + Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code()); checkNeedCaptchaAndroidacy(url, response.code()); throw new HttpException(response.code()); } @@ -337,6 +340,54 @@ public class Http { return hasWebView; } + public static void ensureCacheDirs(MainActivity mainActivity) { + // Recursively ensure cache dirs for webview exist under our cache dir + File cacheDir = mainActivity.getCacheDir(); + File webviewCacheDir = new File(cacheDir, "WebView"); + if (!webviewCacheDir.exists()) { + if (!webviewCacheDir.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + File webviewCacheDirCache = new File(webviewCacheDir, "Default"); + if (!webviewCacheDirCache.exists()) { + if (!webviewCacheDirCache.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + File webviewCacheDirCacheCodeCache = new File(webviewCacheDirCache, "HTTP Cache"); + if (!webviewCacheDirCacheCodeCache.exists()) { + if (!webviewCacheDirCacheCodeCache.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + File webviewCacheDirCacheCodeCacheIndex = new File(webviewCacheDirCacheCodeCache, "Code Cache"); + if (!webviewCacheDirCacheCodeCacheIndex.exists()) { + if (!webviewCacheDirCacheCodeCacheIndex.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + File webviewCacheDirCacheCodeCacheIndexIndex = new File(webviewCacheDirCacheCodeCacheIndex, "Index"); + if (!webviewCacheDirCacheCodeCacheIndexIndex.exists()) { + if (!webviewCacheDirCacheCodeCacheIndexIndex.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + // Create the js and wasm dirs + File webviewCacheDirCacheCodeCacheIndexIndexJs = new File(webviewCacheDirCacheCodeCache, "js"); + if (!webviewCacheDirCacheCodeCacheIndexIndexJs.exists()) { + if (!webviewCacheDirCacheCodeCacheIndexIndexJs.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + File webviewCacheDirCacheCodeCacheIndexIndexWasm = new File(webviewCacheDirCacheCodeCache, "wasm"); + if (!webviewCacheDirCacheCodeCacheIndexIndexWasm.exists()) { + if (!webviewCacheDirCacheCodeCacheIndexIndexWasm.mkdirs()) { + Log.e(TAG, "Failed to create webview cache dir"); + } + } + } + public interface ProgressListener { void onUpdate(int downloaded, int total, boolean done); } diff --git a/app/src/main/java/com/fox2code/mmm/utils/NoodleDebug.java b/app/src/main/java/com/fox2code/mmm/utils/NoodleDebug.java deleted file mode 100644 index 7ca52b4..0000000 --- a/app/src/main/java/com/fox2code/mmm/utils/NoodleDebug.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.fox2code.mmm.utils; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.util.Log; -import android.widget.TextView; - -import androidx.annotation.IdRes; -import androidx.annotation.NonNull; - -import java.lang.ref.WeakReference; -import java.util.LinkedList; -import java.util.Objects; - -public class NoodleDebug { - private static final String TAG = "NoodleDebug"; - private static final WeakReference NULL_THREAD_REF = new WeakReference<>(null); - private static final ThreadLocal THREAD_NOODLE = new ThreadLocal<>(); - @SuppressLint("StaticFieldLeak") // <- Null initialized - private static final NoodleDebug NULL = new NoodleDebug() { - @Override - public NoodleDebug bind() { - getNoodleDebug().unbind(); - THREAD_NOODLE.remove(); - return this; - } - - @Override - public void setEnabled(boolean enabled) {} - - @Override - protected void markDirty() {} - }; - private final Activity activity; - private final TextView textView; - private final LinkedList tokens; - private final StringBuilder debug; - private WeakReference thread; - private boolean enabled, updating; - - private NoodleDebug() { - this.activity = null; - this.textView = null; - this.tokens = new LinkedList<>(); - this.debug = new StringBuilder(0); - this.thread = NULL_THREAD_REF; - } - - public NoodleDebug(Activity activity,@IdRes int textViewId) { - this(activity, activity.findViewById(textViewId)); - } - - public NoodleDebug(Activity activity, TextView textView) { - this.activity = Objects.requireNonNull(activity); - this.textView = Objects.requireNonNull(textView); - this.tokens = new LinkedList<>(); - this.debug = new StringBuilder(64); - this.thread = NULL_THREAD_REF; - } - - public NoodleDebug bind() { - synchronized (this.tokens) { - Thread thread; - if ((thread = this.thread.get()) != null) { - Log.e(TAG, "Trying to bind to thread \"" + Thread.currentThread().getName() + - "\" while already bound to \"" + thread.getName() + "\""); - return NULL; - } - this.tokens.clear(); - } - if (this.enabled) { - this.thread = new WeakReference<>(Thread.currentThread()); - THREAD_NOODLE.set(this); - } else { - this.thread = NULL_THREAD_REF; - THREAD_NOODLE.remove(); - } - return this; - } - - public void unbind() { - this.thread = NULL_THREAD_REF; - boolean markDirty; - synchronized (this.tokens) { - markDirty = !this.tokens.isEmpty(); - this.tokens.clear(); - } - if (markDirty) this.markDirty(); - } - - public boolean isBound() { - return this.thread.get() != null; - } - - public void push(String token) { - if (!this.enabled) return; - synchronized (this.tokens) { - this.tokens.add(token); - } - if (!token.isEmpty()) - this.markDirty(); - } - - public void pop() { - if (!this.enabled) return; - String last; - synchronized (this.tokens) { - last = this.tokens.removeLast(); - } - if (!last.isEmpty()) - this.markDirty(); - } - - public void replace(String token) { - if (!this.enabled) return; - String last; - synchronized (this.tokens) { - last = this.tokens.removeLast(); - this.tokens.add(token); - } - if (!last.equals(token)) - this.markDirty(); - } - - public void setEnabled(boolean enabled) { - if (this.enabled && !enabled) { - this.thread = NULL_THREAD_REF; - synchronized (this.tokens) { - this.tokens.clear(); - } - this.markDirty(); - } - this.enabled = enabled; - } - - protected void markDirty() { - assert this.activity != null; - assert this.textView != null; - if (this.updating) return; - this.updating = true; - this.activity.runOnUiThread(() -> { - String debugText; - synchronized (this.tokens) { - StringBuilder debug = this.debug; - debug.setLength(0); - boolean first = true; - for (String text : this.tokens) { - if (text.isEmpty()) continue; - if (first) first = false; - else debug.append(" > "); - debug.append(text); - } - debugText = debug.toString(); - } - this.updating = false; - this.textView.setText(debugText); - }); - } - - @NonNull - public static NoodleDebug getNoodleDebug() { - NoodleDebug noodleDebug = THREAD_NOODLE.get(); - if (noodleDebug == null) return NULL; - if (noodleDebug.thread.get() != Thread.currentThread() || - noodleDebug.activity.isDestroyed()) { - THREAD_NOODLE.remove(); - return NULL; - } - return noodleDebug; - } -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b6d0647..2b43e94 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -15,12 +15,13 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" > + app:layout_constraintTop_toTopOf="parent"> + + - + app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" />--> + + + + style="?attr/materialCardViewElevatedStyle"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/gh_root_ca b/app/src/main/res/raw/gh_root_ca new file mode 100644 index 0000000..fd4341d --- /dev/null +++ b/app/src/main/res/raw/gh_root_ca @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/gstatic_root_ca b/app/src/main/res/raw/gstatic_root_ca new file mode 100644 index 0000000..f4ce4ca --- /dev/null +++ b/app/src/main/res/raw/gstatic_root_ca @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/sentry_io_root_ca b/app/src/main/res/raw/sentry_io_root_ca new file mode 100644 index 0000000..fd4341d --- /dev/null +++ b/app/src/main/res/raw/sentry_io_root_ca @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/sentry_root_ca b/app/src/main/res/raw/sentry_root_ca new file mode 100644 index 0000000..b85c803 --- /dev/null +++ b/app/src/main/res/raw/sentry_root_ca @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/app/src/main/res/values-cs/arrays.xml b/app/src/main/res/values-cs/arrays.xml index e425a54..8cff8f1 100644 --- a/app/src/main/res/values-cs/arrays.xml +++ b/app/src/main/res/values-cs/arrays.xml @@ -3,6 +3,7 @@ Dle systému Tmavá AMOLED Black + Transparent (light) Světlá diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml index 3df0c29..54da743 100644 --- a/app/src/main/res/values-de/arrays.xml +++ b/app/src/main/res/values-de/arrays.xml @@ -4,6 +4,7 @@ Systemvorgabe Dunkel AMOLED Black + Transparent (light) Hell diff --git a/app/src/main/res/values-el/arrays.xml b/app/src/main/res/values-el/arrays.xml index d953610..ac18184 100644 --- a/app/src/main/res/values-el/arrays.xml +++ b/app/src/main/res/values-el/arrays.xml @@ -3,6 +3,7 @@ Προεπιλογή συστήματως Σκωτεινό AMOLED Black + Transparent (light) Ανοιχτό \ No newline at end of file diff --git a/app/src/main/res/values-es-rMX/arrays.xml b/app/src/main/res/values-es-rMX/arrays.xml index 08b4fe0..5248cbd 100644 --- a/app/src/main/res/values-es-rMX/arrays.xml +++ b/app/src/main/res/values-es-rMX/arrays.xml @@ -3,6 +3,7 @@ Sistema Oscuro AMOLED Black + Transparent (light) Claro \ No newline at end of file diff --git a/app/src/main/res/values-et/arrays.xml b/app/src/main/res/values-et/arrays.xml index e27dae9..a509f66 100644 --- a/app/src/main/res/values-et/arrays.xml +++ b/app/src/main/res/values-et/arrays.xml @@ -3,6 +3,7 @@ Süsteem Tume AMOLED Black + Transparent (light) Hele diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml index f9319ba..b407b43 100644 --- a/app/src/main/res/values-fr/arrays.xml +++ b/app/src/main/res/values-fr/arrays.xml @@ -3,6 +3,7 @@ Système Sombre AMOLED Black + Transparent (light) Clair diff --git a/app/src/main/res/values-id/arrays.xml b/app/src/main/res/values-id/arrays.xml index e9685f6..944a65e 100644 --- a/app/src/main/res/values-id/arrays.xml +++ b/app/src/main/res/values-id/arrays.xml @@ -3,6 +3,7 @@ Sistem Gelap AMOLED Black + Transparent (light) Terang diff --git a/app/src/main/res/values-it/arrays.xml b/app/src/main/res/values-it/arrays.xml index d43a2c2..30a7657 100644 --- a/app/src/main/res/values-it/arrays.xml +++ b/app/src/main/res/values-it/arrays.xml @@ -3,6 +3,7 @@ Sistema Scuro AMOLED Black + Transparent (light) Chiaro \ No newline at end of file diff --git a/app/src/main/res/values-ja/arrays.xml b/app/src/main/res/values-ja/arrays.xml index fa16044..8229c0d 100644 --- a/app/src/main/res/values-ja/arrays.xml +++ b/app/src/main/res/values-ja/arrays.xml @@ -3,6 +3,7 @@ システムの設定を使用 ダーク AMOLED Black + Transparent (light) ライト diff --git a/app/src/main/res/values-pl/arrays.xml b/app/src/main/res/values-pl/arrays.xml index d857dc5..4e2a658 100644 --- a/app/src/main/res/values-pl/arrays.xml +++ b/app/src/main/res/values-pl/arrays.xml @@ -3,6 +3,7 @@ Zgodny z systemem Ciemny Czarny + Transparent (light) Jasny Nie pytaj ponownie diff --git a/app/src/main/res/values-pt-rBR/arrays.xml b/app/src/main/res/values-pt-rBR/arrays.xml index 9478cc2..b52106b 100644 --- a/app/src/main/res/values-pt-rBR/arrays.xml +++ b/app/src/main/res/values-pt-rBR/arrays.xml @@ -3,6 +3,7 @@ Sistema Escuro AMOLED Black + Transparent (light) Claro \ No newline at end of file diff --git a/app/src/main/res/values-ro/arrays.xml b/app/src/main/res/values-ro/arrays.xml index d8b9b1b..a0a80e8 100644 --- a/app/src/main/res/values-ro/arrays.xml +++ b/app/src/main/res/values-ro/arrays.xml @@ -5,6 +5,7 @@ Sistem Întunecată AMOLED Black + Transparent (light) Luminoasă diff --git a/app/src/main/res/values-ru/arrays.xml b/app/src/main/res/values-ru/arrays.xml index 7829b4e..050de99 100644 --- a/app/src/main/res/values-ru/arrays.xml +++ b/app/src/main/res/values-ru/arrays.xml @@ -3,6 +3,7 @@ Как в системе Тёмная AMOLED Black + Transparent (light) Светлая diff --git a/app/src/main/res/values-sk/arrays.xml b/app/src/main/res/values-sk/arrays.xml index e32d923..c9dafec 100644 --- a/app/src/main/res/values-sk/arrays.xml +++ b/app/src/main/res/values-sk/arrays.xml @@ -3,6 +3,7 @@ Podľa systému Tmavá AMOLED Black + Transparent (light) Svetlá diff --git a/app/src/main/res/values-tr/arrays.xml b/app/src/main/res/values-tr/arrays.xml index 8ba5628..cdfda29 100644 --- a/app/src/main/res/values-tr/arrays.xml +++ b/app/src/main/res/values-tr/arrays.xml @@ -3,6 +3,7 @@ Sistem Koyu AMOLED Black + Transparent (light) Açık \ No newline at end of file diff --git a/app/src/main/res/values-v26/themes.xml b/app/src/main/res/values-v26/themes.xml new file mode 100644 index 0000000..048aff5 --- /dev/null +++ b/app/src/main/res/values-v26/themes.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml index 3476aec..3bf41aa 100644 --- a/app/src/main/res/values-v31/themes.xml +++ b/app/src/main/res/values-v31/themes.xml @@ -26,13 +26,22 @@