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 @@