Merge pull request #242 from Androidacy/master

1.0.1 update
pull/246/head v1.0.1
Androidacy Service Account 2 years ago committed by GitHub
commit 8ac1d3bce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,19 +5,19 @@
Important news Important news
</summary> </summary>
I have health problems that made me work slow on everything. I have health problems that made me work slow on everything. I don't like sharing my health problmes
I don't like sharing my health problmes but it has been to much recently for me to keep it for myself. 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. 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 had theses problems even before I started FoxMMM, the only reason no one noticed is because I can
I can work or go to any school because of how much pain and exhaustion I feel everyday. 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. 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 Even tho I'm very slow at doing anything, the only thing that made me look like I was working on
on this project at a normal speed like someone that work is because 75% of my time was on this project. 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, 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 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. 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, Even tho I received money from my parent and the governement for my health problems, I didn't know
I didn't know what to do with it cause anything I could have bought had no use for me what to do with it cause anything I could have bought had no use for me because my extreme pain made
because my extreme pain made me unable to use anything. (Even video games) 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.
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: 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. - 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 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 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 - Me missing obvious bugs and being able to do simple task properly
(Well maybe this last one is harder to proove via commit history) (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 But sine many peoples are faking health issues for clout, if any data-scientist want to do an
to do an analysis to proove what it would make my day, and I would be happy to give money analysis to proove what it would make my day, and I would be happy to give money if someone does
if someone does that because I don't know what do to with my money at this point. 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 This is really sickening peoples need to give proof for their mental/health issues because some
issues because some peoples fake having thoses issues for clout. 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, If you want to use my health problems for coult, I don't care as long as you are respectful, at
at least you won't be hurting peoples with mental/health issues by faking having thoses issues. 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 I'll probably delete this section once my health would be gotten better, or at least good enough for
least good enough for me to not be stuck on my bed at least once a day because of pain. me to not be stuck on my bed at least once a day because of pain.
</details> </details>
@ -68,9 +68,10 @@ Main activity:
[<img src="screenshot-dark.jpg" width="250"/>](screenshot-dark.jpg) [<img src="screenshot-dark.jpg" width="250"/>](screenshot-dark.jpg)
[<img src="screenshot-light.jpg" width="250"/>](screenshot-light.jpg) [<img src="screenshot-light.jpg" width="250"/>](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** **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 ## Requirements
Minimum: Minimum:
- Android 5.0+ - Android 5.0+
- Magisk 19.0+ - Magisk 19.0+
- An internet connection - An internet connection
Recommended: Recommended:
- Android 6.0+ - Android 6.0+
- Magisk 21.2+ - Magisk 21.2+
- An internet connection - 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 ## 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. and download and install the latest `.apk` on your device.
## Repositories Available ## Repositories Available
The app currently use these two repos as module sources, each with their own benefits and
The app currently use these two repos as module sources, each with their own benefits and drawback: drawbacks:
(Note: Each module repo can be disabled in the settings of the app) (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**) (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) - Accepting new modules [here](https://github.com/Magisk-Modules-Alt-Repo/submission)
- Less restrictive than the original repo - Less restrictive than the original repo
- Officially supported by Fox's mmm - 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) [![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/) #### [https://www.androidacy.com/modules-repo/](https://www.androidacy.com/modules-repo/)
- Accepting new modules [here](https://www.androidacy.com/module-repository-applications/) - Accepting new modules [here](https://www.androidacy.com/module-repository-applications/)
- Modules downloadable easily outside the app - Modules downloadable easily outside the app
- Officially supported by Fox's mmm - 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 - Added features like module reviews, automatic VirusTotal scans, and more
Support: 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) [![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 If a module is in multiple repos, the manager will just pick the most up to date version of the
of the module, if a module is in multiple repos it will just use first registered repo. 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 Note: If you or a friend uploaded a module and it doesn't appear in your module list you can disable
list you can disable the low quality filter in the app settings. the low quality filter in the app settings.
Go to the [developer documentation](DEVELOPERS.md) for more info. Go to the [developer documentation](docs/DEVELOPERS.md) for more info.
## For developers ## 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 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 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 ## For translators
We use Weblate for translations: https://translate.nift4.org/engage/foxmmm/ We use Weblate for translations: https://translate.nift4.org/engage/foxmmm/
(Make sure to check your spam folder when registering) (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) 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) 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. Translators are not expected to have any previous coding experience.
## License ## License
See [LICENSE](LICENCE). Library licenses can be found in the licenses section of the app.
Fox's Magisk Manager, the icon, and names are copyright
Cronet is licensed under the Apache License, Version 2.0. Static libraries are licensed under 2021-present [Fox2Code](https://github.com/Fox2Code). The Androidacy name, logo, integration, and
the BSD license. See [LICENSE](https://chromium.googlesource.com/chromium/src/+/master/LICENSE) later portions of the code are copyright
for more information. Libraries were built using the microg build script which can be found [here](https://github.com/microg/cronet-build). 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.
## I want to add my own repo
Modules are not covered by this license, please check the license of each module before using it.
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. Some third party backend services may be proprietary, please check their terms of service before
- Modules in the repo must be monitored, and malicious modules must be removed. using them.
- 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)) ## Disclaimer
In addition of these initial condition the repo must follow these rules: In no event shall the developer be liable for any special, direct, indirect, consequential, or
- Repos must process and take-down off their repo module where it's removal was requested incidental damages or any damages whatsoever, whether in an action of contract, negligence or other
by their original author, even if their licences legally allow their distributions. tort, arising out of or in connection with the use of the app or the contents of the app. The
- Repos may collect and store "mixed anonymous data" without user permission developer reserves the right to make additions, deletions, or modification to the contents on the
(Anonymous means no personal data, usernames, email, or IP addresses) app at any time without prior notice.
(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, This app is not affiliated with Magisk or its developers, nor with any of the module repos or
security reason, and must not be used for any other purpose without user explicit consent. developers of the modules.
(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 ## Add your own repos
- 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) See [the documentation](docs/add-repo.md)
- Modules owners must be aware of any change made of the distributed version of their modules.
## Issues with a repo
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 you have a problem with a repo, please contact the repo owner **first**. If you are unable to
If all of these conditions are met you can open an issue for review. reach them or they are not willing to help, you can contact us as a last resort.
(And don't forget to include a link to the `modules.json`)
Default repo owners:
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. - Androidacy: [Telegram](https://telegram.dog/androidacy_discussions)
Last update of theses rules are: 4 May 2022 - Magisk-Modules-Alt-Repo: [Telegram](https://github.com/Magisk-Modules-Alt-Repo/submission/issues)
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!

@ -8,7 +8,7 @@ plugins {
android { android {
namespace "com.fox2code.mmm" namespace "com.fox2code.mmm"
compileSdk 33 compileSdk 33
buildToolsVersion '30.0.3' buildToolsVersion '33.0.0'
signingConfigs { signingConfigs {
release { release {
// Everything comes from local.properties // Everything comes from local.properties
@ -25,20 +25,42 @@ android {
defaultConfig { defaultConfig {
applicationId "com.fox2code.mmm" applicationId "com.fox2code.mmm"
minSdk 21 minSdk 23
targetSdk 33 targetSdk 33
versionCode 60 versionCode 61
versionName "0.6.8" versionName "1.0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.release 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 { buildTypes {
release { release {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
} }
debug { debug {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
@ -57,6 +79,17 @@ android {
dimension "type" dimension "type"
buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true" buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true"
buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "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 // Get the androidacy client ID from the androidacy.properties
Properties properties = new Properties() Properties properties = new Properties()
// If androidacy.properties doesn't exist, use the default client ID which is heavily // 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 // Disable crash reporting for F-Droid flavor by default
buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false" 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 // Repo with ads or tracking feature are disabled by default for the
// F-Droid flavor. // F-Droid flavor.
buildConfigField("java.util.List<String>", buildConfigField("java.util.List<String>",
@ -99,7 +142,7 @@ android {
// Get the androidacy client ID from the androidacy.properties // Get the androidacy client ID from the androidacy.properties
Properties properties = new 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 // to 50 requests per minute
if (project.rootProject.file('androidacy.properties').exists()) { if (project.rootProject.file('androidacy.properties').exists()) {
properties.load(project.rootProject.file('androidacy.properties').newDataInputStream()) properties.load(project.rootProject.file('androidacy.properties').newDataInputStream())
@ -111,8 +154,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_11
} }
lint { lint {
disable 'MissingTranslation' disable 'MissingTranslation'
@ -121,14 +164,14 @@ android {
} }
aboutLibraries { aboutLibraries {
additionalLicenses = ["LGPL_3_0_only"] additionalLicenses = ["LGPL_3_0_only", "Apache_2_0"]
} }
// "true" is not allowed inside this block, use "hasSentryConfig" instead. // "true" is not allowed inside this block, use "hasSentryConfig" instead.
// This is because gradle doesn't allow to enable/disable plugins conditionally // This is because gradle doesn't allow to enable/disable plugins conditionally
sentry { sentry {
// Disable sentry on F-Droid flavor // Disable sentry on F-Droid flavor
ignoredFlavors = hasSentryConfig ? [] : ["default", "fdroid"] ignoredFlavors = []
// Disables or enables the handling of Proguard mapping for Sentry. // Disables or enables the handling of Proguard mapping for Sentry.
// If enabled the plugin will generate a UUID and will take care of // 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. // Does auto instrumentation for specified features through bytecode manipulation.
// Default is enabled. // Default is enabled.
tracingInstrumentation { tracingInstrumentation {
enabled = hasSentryConfig enabled = true
} }
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations). // Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
// Default is enabled. // Default is enabled.
// Only available v3.1.0 and above. // Only available v3.1.0 and above.
autoInstallation { autoInstallation {
enabled = hasSentryConfig enabled = true
// Specifies a version of the sentry-android SDK and fragment, timber and okhttp integrations. // Specifies a version of the sentry-android SDK and fragment, timber and okhttp integrations.
// //
@ -188,13 +231,6 @@ sentry {
configurations { configurations {
implementation.exclude group: 'org.jetbrains', module: 'annotations' 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 { dependencies {
@ -212,7 +248,7 @@ dependencies {
implementation "dev.rikka.rikkax.insets:insets:1.3.0" implementation "dev.rikka.rikkax.insets:insets:1.3.0"
implementation 'com.github.Dimezis:BlurView:version-2.0.2' implementation 'com.github.Dimezis:BlurView:version-2.0.2'
implementation 'com.github.KieronQuinn:MonetCompat:0.4.1' 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 // Update the version code in the root build.gradle
implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}"
@ -220,21 +256,14 @@ dependencies {
implementation 'androidx.work:work-runtime:2.7.1' implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.10' implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.10'
// Chromium cronet from microG // Chromium cronet from androidacy
implementation fileTree(dir: 'libs', include: '*.jar') 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 // Force prefer our own version of Cronet
implementation 'com.github.topjohnwu.libsu:io:5.0.1' implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.9' implementation 'com.github.Fox2Code:RosettaX:1.0.9'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1' implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
implementation 'io.sentry:sentry-android:6.9.2'
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'
}
// Markdown // Markdown
implementation "io.noties.markwon:core:4.6.2" 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:image:4.6.2"
implementation "io.noties.markwon:syntax-highlight:4.6.2" implementation "io.noties.markwon:syntax-highlight:4.6.2"
implementation 'com.google.net.cronet:cronet-okhttp:0.1.0' implementation 'com.google.net.cronet:cronet-okhttp:0.1.0'
// Ignore all org.chromium.net dependencies
annotationProcessor "io.noties:prism4j-bundler:2.0.0" annotationProcessor "io.noties:prism4j-bundler:2.0.0"
implementation "com.caverock:androidsvg:1.4" 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 // Test
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4' 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) { if (hasSentryConfig) {
@ -265,8 +300,7 @@ if (hasSentryConfig) {
} }
final String sentrySrc = hasSentryConfig ? 'src/sentry/java' : 'src/sentryless/java' final String sentrySrc = hasSentryConfig ? 'src/sentry/java' : 'src/sentryless/java'
final String sentryManifestSrc = hasSentryConfig ? final String sentryManifestSrc = hasSentryConfig ? 'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml'
'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml'
android { android {
sourceSets { sourceSets {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -29,7 +29,7 @@
<application <application
android:name=".MainApplication" android:name=".MainApplication"
android:allowBackup="true" android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/full_backup_content" android:fullBackupContent="@xml/full_backup_content"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

@ -21,26 +21,19 @@ import java.util.HashMap;
// See https://docs.github.com/en/rest/reference/repos#releases // See https://docs.github.com/en/rest/reference/repos#releases
public class AppUpdateManager { public class AppUpdateManager {
public static final int FLAG_COMPAT_LOW_QUALITY = 0x0001; public static final int FLAG_COMPAT_LOW_QUALITY = 0x0001;
public static final int FLAG_COMPAT_NO_EXT = 0x0002; public static final int FLAG_COMPAT_NO_EXT = 0x0002;
public static final int FLAG_COMPAT_MAGISK_CMD = 0x0004; public static final int FLAG_COMPAT_MAGISK_CMD = 0x0004;
public static final int FLAG_COMPAT_NEED_32BIT = 0x0008; public static final int FLAG_COMPAT_NEED_32BIT = 0x0008;
public static final int FLAG_COMPAT_MALWARE = 0x0010; public static final int FLAG_COMPAT_MALWARE = 0x0010;
public static final int FLAG_COMPAT_NO_ANSI = 0x0020; public static final int FLAG_COMPAT_NO_ANSI = 0x0020;
public static final int FLAG_COMPAT_FORCE_ANSI = 0x0040; public static final int FLAG_COMPAT_FORCE_ANSI = 0x0040;
public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080; public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080;
public static final int FLAG_COMPAT_MMT_REBORN = 0x0100; public static final int FLAG_COMPAT_MMT_REBORN = 0x0100;
public static final int FLAG_COMPAT_ZIP_WRAPPER = 0x0200; public static final int FLAG_COMPAT_ZIP_WRAPPER = 0x0200;
private static final String TAG = "AppUpdateManager"; private static final String TAG = "AppUpdateManager";
private static final AppUpdateManager INSTANCE = new AppUpdateManager(); private static final AppUpdateManager INSTANCE = new AppUpdateManager();
private static final String RELEASES_API_URL = private static final String RELEASES_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases";
"https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases"; private static final String COMPAT_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/issues/4";
private static final String COMPAT_API_URL =
"https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/issues/4";
public static AppUpdateManager getAppUpdateManager() {
return INSTANCE;
}
private final HashMap<String, Integer> compatDataId = new HashMap<>(); private final HashMap<String, Integer> compatDataId = new HashMap<>();
private final Object updateLock = new Object(); private final Object updateLock = new Object();
private final File compatFile; private final File compatFile;
@ -49,13 +42,10 @@ public class AppUpdateManager {
private long lastChecked; private long lastChecked;
private boolean preReleaseNewer; private boolean preReleaseNewer;
private boolean lastCheckSuccess; private boolean lastCheckSuccess;
private AppUpdateManager() { private AppUpdateManager() {
this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt"); this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt");
this.latestRelease = MainApplication.getBootSharedPreferences() this.latestRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_release", BuildConfig.VERSION_NAME);
.getString("updater_latest_release", BuildConfig.VERSION_NAME); this.latestPreRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_pre_release", BuildConfig.VERSION_NAME);
this.latestPreRelease = MainApplication.getBootSharedPreferences()
.getString("updater_latest_pre_release", BuildConfig.VERSION_NAME);
this.lastChecked = 0; this.lastChecked = 0;
this.preReleaseNewer = true; this.preReleaseNewer = true;
if (this.compatFile.isFile()) { 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 // Return true if should show a notification
public boolean checkUpdate(boolean force) { public boolean checkUpdate(boolean force) {
if (!BuildConfig.ENABLE_AUTO_UPDATER) if (!BuildConfig.ENABLE_AUTO_UPDATER) return false;
return false; if (!force && this.peekShouldUpdate()) return true;
if (!force && this.peekShouldUpdate())
return true;
long lastChecked = this.lastChecked; long lastChecked = this.lastChecked;
if (lastChecked != 0 && if (lastChecked != 0 &&
// Avoid spam calls by putting a 60 seconds timer // Avoid spam calls by putting a 60 seconds timer
lastChecked < System.currentTimeMillis() - 60000L) lastChecked < System.currentTimeMillis() - 60000L)
return force && this.peekShouldUpdate(); return force && this.peekShouldUpdate();
synchronized (this.updateLock) { synchronized (this.updateLock) {
if (lastChecked != this.lastChecked) if (lastChecked != this.lastChecked) return this.peekShouldUpdate();
return this.peekShouldUpdate();
boolean preReleaseNewer = true; boolean preReleaseNewer = true;
try { try {
JSONArray releases = new JSONArray(new String(Http.doHttpGet( JSONArray releases = new JSONArray(new String(Http.doHttpGet(RELEASES_API_URL, false), StandardCharsets.UTF_8));
RELEASES_API_URL, false), StandardCharsets.UTF_8));
String latestRelease = null, latestPreRelease = null; String latestRelease = null, latestPreRelease = null;
for (int i = 0; i < releases.length(); i++) { for (int i = 0; i < releases.length(); i++) {
JSONObject release = releases.getJSONObject(i); JSONObject release = releases.getJSONObject(i);
@ -92,22 +92,18 @@ public class AppUpdateManager {
if (release.getBoolean("draft")) continue; if (release.getBoolean("draft")) continue;
boolean preRelease = release.getBoolean("prerelease"); boolean preRelease = release.getBoolean("prerelease");
String version = release.getString("tag_name"); String version = release.getString("tag_name");
if (version.startsWith("v")) if (version.startsWith("v")) version = version.substring(1);
version = version.substring(1);
if (preRelease) { if (preRelease) {
if (latestPreRelease == null) if (latestPreRelease == null) latestPreRelease = version;
latestPreRelease = version;
} else if (latestRelease == null) { } else if (latestRelease == null) {
latestRelease = version; latestRelease = version;
if (latestPreRelease == null) if (latestPreRelease == null) preReleaseNewer = false;
preReleaseNewer = false;
} }
if (latestRelease != null && latestPreRelease != null) { if (latestRelease != null && latestPreRelease != null) {
break; // We read everything we needed to read. break; // We read everything we needed to read.
} }
} }
if (latestRelease != null) if (latestRelease != null) this.latestRelease = latestRelease;
this.latestRelease = latestRelease;
if (latestPreRelease != null) { if (latestPreRelease != null) {
this.latestPreRelease = latestPreRelease; this.latestPreRelease = latestPreRelease;
this.preReleaseNewer = preReleaseNewer; this.preReleaseNewer = preReleaseNewer;
@ -115,9 +111,9 @@ public class AppUpdateManager {
this.latestPreRelease = ""; this.latestPreRelease = "";
this.preReleaseNewer = false; this.preReleaseNewer = false;
} }
Log.d(TAG, "Latest release: " + latestRelease); if (BuildConfig.DEBUG) Log.d(TAG, "Latest release: " + latestRelease);
Log.d(TAG, "Latest pre-release: " + latestPreRelease); if (BuildConfig.DEBUG) Log.d(TAG, "Latest pre-release: " + latestPreRelease);
Log.d(TAG, "Latest pre-release newer: " + preReleaseNewer); if (BuildConfig.DEBUG) Log.d(TAG, "Latest pre-release newer: " + preReleaseNewer);
this.lastChecked = System.currentTimeMillis(); this.lastChecked = System.currentTimeMillis();
this.lastCheckSuccess = true; this.lastCheckSuccess = true;
} catch (Exception ioe) { } catch (Exception ioe) {
@ -131,45 +127,42 @@ public class AppUpdateManager {
public void checkUpdateCompat() { public void checkUpdateCompat() {
if (this.compatFile.exists()) { if (this.compatFile.exists()) {
long lastUpdate = this.compatFile.lastModified(); long lastUpdate = this.compatFile.lastModified();
if (lastUpdate <= System.currentTimeMillis() && if (lastUpdate <= System.currentTimeMillis() && lastUpdate + 600_000L > System.currentTimeMillis()) {
lastUpdate + 600_000L > System.currentTimeMillis()) {
return; // Skip update return; // Skip update
} }
} }
try { try {
JSONObject object = new JSONObject(new String(Http.doHttpGet( JSONObject object = new JSONObject(new String(Http.doHttpGet(COMPAT_API_URL, false), StandardCharsets.UTF_8));
COMPAT_API_URL, false), StandardCharsets.UTF_8));
if (object.isNull("body")) { if (object.isNull("body")) {
compatDataId.clear(); compatDataId.clear();
Files.write(compatFile, new byte[0]); Files.write(compatFile, new byte[0]);
return; return;
} }
byte[] rawData = object.getString("body") byte[] rawData = object.getString("body").getBytes(StandardCharsets.UTF_8);
.getBytes(StandardCharsets.UTF_8);
this.parseCompatibilityFlags(new ByteArrayInputStream(rawData)); this.parseCompatibilityFlags(new ByteArrayInputStream(rawData));
Files.write(compatFile, rawData); Files.write(compatFile, rawData);
if (!BuildConfig.ENABLE_AUTO_UPDATER) if (!BuildConfig.ENABLE_AUTO_UPDATER) this.lastCheckSuccess = true;
this.lastCheckSuccess = true;
} catch (Exception e) { } catch (Exception e) {
if (!BuildConfig.ENABLE_AUTO_UPDATER) if (!BuildConfig.ENABLE_AUTO_UPDATER) this.lastCheckSuccess = false;
this.lastCheckSuccess = false;
Log.e("AppUpdateManager", "Failed to update compat list", e); Log.e("AppUpdateManager", "Failed to update compat list", e);
} }
} }
public boolean peekShouldUpdate() { public boolean peekShouldUpdate() {
if (!BuildConfig.ENABLE_AUTO_UPDATER) if (!BuildConfig.ENABLE_AUTO_UPDATER) return false;
return false; // Convert both BuildConfig.VERSION_NAME and latestRelease to int
return !(BuildConfig.VERSION_NAME.equals(this.latestRelease) || int currentVersion = 0, latestVersion = 0;
(this.preReleaseNewer && try {
BuildConfig.VERSION_NAME.equals(this.latestPreRelease))); 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() { public boolean peekHasUpdate() {
if (!BuildConfig.ENABLE_AUTO_UPDATER) if (!BuildConfig.ENABLE_AUTO_UPDATER) return false;
return false; return !BuildConfig.VERSION_NAME.equals(this.preReleaseNewer ? this.latestPreRelease : this.latestRelease);
return !BuildConfig.VERSION_NAME.equals(this.preReleaseNewer ?
this.latestPreRelease : this.latestRelease);
} }
public boolean isLastCheckSuccess() { public boolean isLastCheckSuccess() {
@ -178,8 +171,7 @@ public class AppUpdateManager {
private void parseCompatibilityFlags(InputStream inputStream) throws IOException { private void parseCompatibilityFlags(InputStream inputStream) throws IOException {
compatDataId.clear(); compatDataId.clear();
BufferedReader bufferedReader = new BufferedReader( BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line; String line;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
line = line.trim(); line = line.trim();
@ -231,16 +223,4 @@ public class AppUpdateManager {
Integer compatFlags = compatDataId.get(moduleId); Integer compatFlags = compatDataId.get(moduleId);
return compatFlags == null ? 0 : compatFlags; 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;
}
} }

@ -13,17 +13,23 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.text.InputType;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView; import androidx.cardview.widget.CardView;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils; 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.ExternalHelper;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper; import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.NoodleDebug;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.android.material.progressindicator.LinearProgressIndicator; 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; import eightbitlab.com.blurview.BlurView;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper { public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
private static final String TAG = "MainActivity"; private static final String TAG = "MainActivity";
private static final int PRECISION = 10000; private static final int PRECISION = 10000;
public static boolean noodleDebugState = BuildConfig.DEBUG;
public final ModuleViewListBuilder moduleViewListBuilder; public final ModuleViewListBuilder moduleViewListBuilder;
public LinearProgressIndicator progressIndicator; public LinearProgressIndicator progressIndicator;
private ModuleViewAdapter moduleViewAdapter; private ModuleViewAdapter moduleViewAdapter;
@ -71,8 +86,9 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
private RecyclerView moduleList; private RecyclerView moduleList;
private CardView searchCard; private CardView searchCard;
private SearchView searchView; private SearchView searchView;
private NoodleDebug noodleDebug;
private boolean initMode; private boolean initMode;
private boolean doSetupNowRunning;
private boolean urlFactoryInstalled = false;
public MainActivity() { public MainActivity() {
this.moduleViewListBuilder = new ModuleViewListBuilder(this); this.moduleViewListBuilder = new ModuleViewListBuilder(this);
@ -88,7 +104,18 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
this.initMode = true; 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); BackgroundUpdateChecker.onMainActivityCreate(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> { 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.moduleList = findViewById(R.id.module_list);
this.searchCard = findViewById(R.id.search_card); this.searchCard = findViewById(R.id.search_card);
this.searchView = findViewById(R.id.search_bar); this.searchView = findViewById(R.id.search_bar);
this.noodleDebug = new NoodleDebug(this, R.id.noodle_debug);
this.moduleViewAdapter = new ModuleViewAdapter(); this.moduleViewAdapter = new ModuleViewAdapter();
this.moduleList.setAdapter(this.moduleViewAdapter); this.moduleList.setAdapter(this.moduleViewAdapter);
this.moduleList.setLayoutManager(new LinearLayoutManager(this)); this.moduleList.setLayoutManager(new LinearLayoutManager(this));
@ -158,11 +184,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode()) if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
noodleDebug.push("Ensure Permissions");
ensurePermissions();
noodleDebug.pop();
ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules); ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules);
this.commonNext(); this.commonNext();
@ -171,21 +192,32 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override @Override
public void onFailure(int error) { public void onFailure(int error) {
Log.i(TAG, "Failed to get magisk path!"); Log.i(TAG, "Failed to get magisk path!");
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification()); moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
this.commonNext(); this.commonNext();
} }
public void 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; swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
updateScreenInsets(); // Fix an edge case updateScreenInsets(); // Fix an edge case
if (MainApplication.isShowcaseMode()) if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
if (!Http.hasWebView()) // Check Http for WebView availability if (!Http.hasWebView()) // Check Http for WebView availability
moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW); moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW);
noodleDebug.push("Apply");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
runOnUiThread(() -> { runOnUiThread(() -> {
progressIndicator.setIndeterminate(false); progressIndicator.setIndeterminate(false);
@ -193,17 +225,33 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
// Fix insets not being accounted for correctly // Fix insets not being accounted for correctly
updateScreenInsets(getResources().getConfiguration()); 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!"); Log.i(TAG, "Scanning for modules!");
noodleDebug.replace("Initialize Update"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Initialize Update");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount(); final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) { if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
Log.w(TAG, "Need update on create?"); Log.w(TAG, "Need update on create?");
} }
noodleDebug.replace("Check Update Compat"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update Compat");
AppUpdateManager.getAppUpdateManager().checkUpdateCompat(); 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))); 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); 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()) { if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) { } else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
@ -211,16 +259,21 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
} else { } else {
// Compatibility data still needs to be updated // Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); 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)) if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
noodleDebug.replace("Check Json Update"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Json Update");
if (max != 0) { 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; int current = 0;
noodleDebug.push(""); // noodleDebug.push("");
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) { if (localModuleInfo.updateJson != null) {
noodleDebug.replace(localModuleInfo.id); if (BuildConfig.DEBUG) Log.d("NoodleDebug", localModuleInfo.id);
try { try {
localModuleInfo.checkModuleUpdate(); localModuleInfo.checkModuleUpdate();
} catch (Exception e) { } 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)); runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
} }
} }
noodleDebug.pop();
} }
} }
runOnUiThread(() -> { runOnUiThread(() -> {
@ -241,16 +293,101 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
setActionBarBackground(null); setActionBarBackground(null);
updateScreenInsets(getResources().getConfiguration()); updateScreenInsets(getResources().getConfiguration());
}); });
noodleDebug.replace("Apply"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules); 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); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
noodleDebug.pop();
Log.i(TAG, "Finished app opening state!"); Log.i(TAG, "Finished app opening state!");
noodleDebug.unbind(); // noodleDebug.unbind();
} }
}, true); }, true);
ExternalHelper.INSTANCE.refreshHelper(this); ExternalHelper.INSTANCE.refreshHelper(this);
this.initMode = false; 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() { private void cardIconifyUpdate() {
@ -319,7 +456,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.updateBlurState(); this.updateBlurState();
this.moduleViewListBuilder.setQuery(null); this.moduleViewListBuilder.setQuery(null);
Log.i(TAG, "Item After"); Log.i(TAG, "Item After");
noodleDebugState = MainApplication.isDeveloper();
this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter); this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter);
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() { InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override @Override
@ -328,8 +464,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode()) if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules); ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules);
this.commonNext(); this.commonNext();
@ -338,14 +472,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override @Override
public void onFailure(int error) { public void onFailure(int error) {
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification()); moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
this.commonNext(); this.commonNext();
} }
public void commonNext() { public void commonNext() {
Log.i(TAG, "Common Before"); Log.i(TAG, "Common Before");
NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug();
if (MainApplication.isShowcaseMode()) if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
@ -354,26 +485,23 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false)) else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
RepoManager.getINSTANCE().updateEnabledStates(); RepoManager.getINSTANCE().updateEnabledStates();
noodleDebug.push("");
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) { if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
runOnUiThread(() -> { runOnUiThread(() -> {
progressIndicator.setIndeterminate(false); progressIndicator.setIndeterminate(false);
progressIndicator.setMax(PRECISION); 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))); RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> progressIndicator.setProgressCompat((int) (value * PRECISION), true)));
runOnUiThread(() -> { runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true); progressIndicator.setProgressCompat(PRECISION, true);
progressIndicator.setVisibility(View.GONE); progressIndicator.setVisibility(View.GONE);
}); });
} }
noodleDebug.replace("Apply"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules); RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
Log.i(TAG, "Common Before applyTo"); Log.i(TAG, "Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
noodleDebug.pop();
Log.i(TAG, "Common After"); Log.i(TAG, "Common After");
noodleDebug.unbind();
} }
}); });
this.initMode = false; this.initMode = false;
@ -390,33 +518,33 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.swipeRefreshLayout.setRefreshing(false); this.swipeRefreshLayout.setRefreshing(false);
return; // Do not double scan return; // Do not double scan
} }
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Refresh");
this.progressIndicator.setVisibility(View.VISIBLE); this.progressIndicator.setVisibility(View.VISIBLE);
this.progressIndicator.setProgressCompat(0, false); this.progressIndicator.setProgressCompat(0, false);
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L; this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
// this.swipeRefreshLayout.setRefreshing(true); ?? // this.swipeRefreshLayout.setRefreshing(true); ??
new Thread(() -> { new Thread(() -> {
noodleDebug.setEnabled(noodleDebugState);
NoodleDebug noodleDebug = this.noodleDebug.bind();
Http.cleanDnsCache(); // Allow DNS reload from network Http.cleanDnsCache(); // Allow DNS reload from network
noodleDebug.push("Check Update"); // noodleDebug.push("Check Update");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount(); 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))); 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); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) { if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.REPO_UPDATE_FAILED);
} else { } else {
// Compatibility data still needs to be updated // Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); 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)) if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
// noodleDebug.replace("Check Json Update"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Json Update");
if (max != 0) { if (max != 0) {
int current = 0; int current = 0;
noodleDebug.push("");
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) { if (localModuleInfo.updateJson != null) {
noodleDebug.replace(localModuleInfo.id); if (BuildConfig.DEBUG) Log.d("NoodleDebug", localModuleInfo.id);
try { try {
localModuleInfo.checkModuleUpdate(); localModuleInfo.checkModuleUpdate();
} catch (Exception e) { } 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)); 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(() -> { runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE); this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false); this.swipeRefreshLayout.setRefreshing(false);
}); });
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
}
RepoManager.getINSTANCE().updateEnabledStates(); RepoManager.getINSTANCE().updateEnabledStates();
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules); RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
// noodleDebug.pop(); /*
// noodleDebug.unbind(); noodleDebug.unbind();
*/
}, "Repo update thread").start(); }, "Repo update thread").start();
} }
@ -487,33 +612,50 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
private void ensurePermissions() { 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 // 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 (!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) { 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 if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Request Notification Permission");
// notifications for updates if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
runOnUiThread(() -> { // Show a dialog explaining why we need this permission, which is to show
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); // notifications for updates
builder.setTitle(R.string.permission_notification_title); runOnUiThread(() -> {
builder.setMessage(R.string.permission_notification_message); if (BuildConfig.DEBUG)
// Don't ask again checkbox Log.d("NoodleDebug", "Show Notification Permission Dialog");
View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
CheckBox checkBox = view.findViewById(R.id.checkbox); builder.setTitle(R.string.permission_notification_title);
checkBox.setText(R.string.dont_ask_again); builder.setMessage(R.string.permission_notification_message);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply()); // Don't ask again checkbox
builder.setView(view); View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null);
builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> { CheckBox checkBox = view.findViewById(R.id.checkbox);
// Request the permission checkBox.setText(R.string.dont_ask_again);
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0); checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply());
}); builder.setView(view);
builder.setNegativeButton(R.string.cancel, (dialog, which) -> { builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog // Request the permission
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
prefs.edit().putBoolean("pref_background_update_check", false).apply(); });
dialog.dismiss(); 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 // 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()) { } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && !NotificationManagerCompat.from(this).areNotificationsEnabled()) {
runOnUiThread(() -> { runOnUiThread(() -> {
@ -533,16 +675,77 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
Uri uri = Uri.fromParts("package", getPackageName(), null); Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri); intent.setData(uri);
startActivity(intent); startActivity(intent);
doSetupNowRunning = false;
}); });
builder.setNegativeButton(R.string.cancel, (dialog, which) -> { builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog // Set pref_background_update_check to false and dismiss dialog
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("pref_background_update_check", false).apply(); prefs.edit().putBoolean("pref_background_update_check", false).apply();
dialog.dismiss(); dialog.dismiss();
doSetupNowRunning = false;
}); });
builder.show(); 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();
} }
} }
} }

@ -2,7 +2,6 @@ package com.fox2code.mmm;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
@ -50,7 +49,6 @@ import io.noties.prism4j.annotations.PrismBundle;
@PrismBundle(includeAll = true, grammarLocatorClassName = ".Prism4jGrammarLocator") @PrismBundle(includeAll = true, grammarLocatorClassName = ".Prism4jGrammarLocator")
public class MainApplication extends FoxApplication implements androidx.work.Configuration.Provider { 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 String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
private static final Shell.Builder shellBuilder; private static final Shell.Builder shellBuilder;
private static final long secret; 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 SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale);
private static SharedPreferences bootSharedPreferences; private static SharedPreferences bootSharedPreferences;
private static String relPackageName = BuildConfig.APPLICATION_ID; private static String relPackageName = BuildConfig.APPLICATION_ID;
@SuppressLint("StaticFieldLeak")
private static MainApplication INSTANCE; private static MainApplication INSTANCE;
private static boolean firstBoot; private static boolean firstBoot;
@ -68,8 +67,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
secret = new Random().nextLong(); secret = new Random().nextLong();
} }
// Provides the Context for the base application
public Context FoxApplication = this;
@StyleRes @StyleRes
private int managerThemeResId = R.style.Theme_MagiskModuleManager; private int managerThemeResId = R.style.Theme_MagiskModuleManager;
private FoxThemeWrapper markwonThemeContext; private FoxThemeWrapper markwonThemeContext;
@ -142,7 +139,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
} }
public static boolean isDeveloper() { 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() { public static boolean isDisableLowQualityModuleFilter() {
@ -165,10 +163,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
return firstBoot; return firstBoot;
} }
public static boolean hasGottenRootAccess() {
return getSharedPreferences().getBoolean("has_root_access", false);
}
public static void setHasGottenRootAccess(boolean bool) { public static void setHasGottenRootAccess(boolean bool) {
getSharedPreferences().edit().putBoolean("has_root_access", bool).apply(); 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; return this.markwon = markwon;
} }
public FoxThemeWrapper getMarkwonThemeContext() {
return this.markwonThemeContext;
}
@NonNull @NonNull
@Override @Override
public androidx.work.Configuration getWorkManagerConfiguration() { public androidx.work.Configuration getWorkManagerConfiguration() {
@ -233,6 +223,12 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
case "light": case "light":
themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet_Light : R.style.Theme_MagiskModuleManager_Light; themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet_Light : R.style.Theme_MagiskModuleManager_Light;
break; 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); this.setManagerThemeResId(themeResId);
} }
@ -278,11 +274,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
if (INSTANCE == null) INSTANCE = this; if (INSTANCE == null) INSTANCE = this;
relPackageName = this.getPackageName(); relPackageName = this.getPackageName();
super.onCreate(); 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(); SharedPreferences sharedPreferences = MainApplication.getSharedPreferences();
// We are only one process so it's ok to do this // We are only one process so it's ok to do this
SharedPreferences bootPrefs = MainApplication.bootSharedPreferences = this.getSharedPreferences("mmm_boot", MODE_PRIVATE); 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); SentryMain.initialize(this);
if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) { if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) {
Log.w("MainApplication", "Androidacy client id is empty! Please set it in androidacy" + Log.w("MainApplication", "Androidacy client id is empty! Please set it in androidacy" + ".properties. Will not enable Androidacy.");
".properties. Will not enable Androidacy.");
SharedPreferences.Editor editor = sharedPreferences.edit(); SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("pref_androidacy_repo_enabled", false); editor.putBoolean("pref_androidacy_repo_enabled", false);
editor.apply(); editor.apply();
} }
} }
private Intent getIntent() {
return this.getPackageManager().getLaunchIntentForPackage(this.getPackageName());
}
@Override @Override
public void onCreateFoxActivity(FoxActivity compatActivity) { public void onCreateFoxActivity(FoxActivity compatActivity) {
super.onCreateFoxActivity(compatActivity); super.onCreateFoxActivity(compatActivity);

@ -64,8 +64,7 @@ public enum NotificationType implements NotificationTypeCst {
NO_INTERNET(R.string.fail_internet, R.drawable.ic_baseline_cloud_off_24) { NO_INTERNET(R.string.fail_internet, R.drawable.ic_baseline_cloud_off_24) {
@Override @Override
public boolean shouldRemove() { public boolean shouldRemove() {
return AppUpdateManager.getAppUpdateManager().isLastCheckSuccess() || return RepoManager.getINSTANCE().hasConnectivity();
RepoManager.getINSTANCE().hasConnectivity();
} }
}, },
REPO_UPDATE_FAILED(R.string.repo_update_failed, R.drawable.ic_baseline_cloud_off_24) { 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() { public boolean shouldRemove() {
// By default, remove the notification`
return false; return false;
} }

@ -179,8 +179,8 @@ public final class AndroidacyActivity extends FoxActivity {
// Don't open non Androidacy urls inside WebView // Don't open non Androidacy urls inside WebView
if (request.isForMainFrame() && !AndroidacyUtil.isAndroidacyLink(request.getUrl())) { if (request.isForMainFrame() && !AndroidacyUtil.isAndroidacyLink(request.getUrl())) {
if (downloadMode || backOnResume) return true; if (downloadMode || backOnResume) return true;
Log.i(TAG, "Exiting WebView " + // hideToken in case isAndroidacyLink fail. Log.i(TAG,
AndroidacyUtil.hideToken(request.getUrl().toString())); "Exiting WebView " + AndroidacyUtil.hideToken(request.getUrl().toString()));
IntentHelper.openUri(view.getContext(), request.getUrl().toString()); IntentHelper.openUri(view.getContext(), request.getUrl().toString());
return true; return true;
} }
@ -258,7 +258,7 @@ public final class AndroidacyActivity extends FoxActivity {
break; break;
} }
} }
return super.onConsoleMessage(consoleMessage); return true;
} }
@Override @Override

@ -108,7 +108,7 @@ public final class AndroidacyRepoData extends RepoData {
deviceId = output; 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 deviceModel = android.os.Build.MODEL;
String deviceManufacturer = android.os.Build.MANUFACTURER; String deviceManufacturer = android.os.Build.MANUFACTURER;
String androidVersion = android.os.Build.VERSION.RELEASE; String androidVersion = android.os.Build.VERSION.RELEASE;
@ -184,7 +184,8 @@ public final class AndroidacyRepoData extends RepoData {
} }
String deviceId = generateDeviceId(); String deviceId = generateDeviceId();
long time = System.currentTimeMillis(); 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; this.androidacyBlockade = time + 30_000L;
try { try {
if (this.token == null) { if (this.token == null) {
@ -249,6 +250,9 @@ public final class AndroidacyRepoData extends RepoData {
@Override @Override
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException { protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException {
if (BuildConfig.DEBUG) {
Log.d(TAG, "AndroidacyRepoData populate start");
}
if (!jsonObject.getString("status").equals("success")) if (!jsonObject.getString("status").equals("success"))
throw new JSONException("Response is not a success!"); throw new JSONException("Response is not a success!");
String name = jsonObject.optString("name", "Androidacy Modules Repo"); String name = jsonObject.optString("name", "Androidacy Modules Repo");
@ -326,7 +330,9 @@ public final class AndroidacyRepoData extends RepoData {
String config = jsonObject.optString("config", ""); String config = jsonObject.optString("config", "");
moduleInfo.config = config.isEmpty() ? null : config; moduleInfo.config = config.isEmpty() ? null : config;
PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks 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<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator(); Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
while (moduleInfoIterator.hasNext()) { while (moduleInfoIterator.hasNext()) {

@ -29,7 +29,6 @@ import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Hashes;
import com.fox2code.mmm.utils.IntentHelper; import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.PropUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File; import java.io.File;
@ -44,11 +43,11 @@ public class AndroidacyWebAPI {
private static final int MAX_COMPAT_MODE = 1; private static final int MAX_COMPAT_MODE = 1;
private final AndroidacyActivity activity; private final AndroidacyActivity activity;
private final boolean allowInstall; private final boolean allowInstall;
private boolean allowHideNote = true;
boolean consumedAction; boolean consumedAction;
boolean downloadMode; boolean downloadMode;
int effectiveCompatMode; int effectiveCompatMode;
int notifiedCompatMode; int notifiedCompatMode;
private boolean allowHideNote = true;
public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) { public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) {
this.activity = activity; this.activity = activity;
@ -64,7 +63,7 @@ public class AndroidacyWebAPI {
void openNativeModuleDialogRaw(String moduleUrl, String moduleId, String installTitle, void openNativeModuleDialogRaw(String moduleUrl, String moduleId, String installTitle,
String checksum, boolean canInstall) { 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 + ", moduleId: " + moduleId + ", installTitle: " + installTitle +
", checksum: " + checksum + ", canInstall: " + canInstall); ", checksum: " + checksum + ", canInstall: " + canInstall);
this.downloadMode = false; this.downloadMode = false;
@ -113,7 +112,7 @@ public class AndroidacyWebAPI {
final boolean fMMTReborn = mmtReborn; final boolean fMMTReborn = mmtReborn;
builder.setPositiveButton(hasUpdate ? builder.setPositiveButton(hasUpdate ?
R.string.update_module : R.string.install_module, (x, y) -> IntentHelper.openInstaller(this.activity, 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 -> { builder.setOnCancelListener(dialogInterface -> {
if (!this.activity.backOnResume) if (!this.activity.backOnResume)
@ -146,7 +145,7 @@ public class AndroidacyWebAPI {
void notifyCompatModeRaw(int value) { void notifyCompatModeRaw(int value) {
if (this.consumedAction) return; if (this.consumedAction) return;
Log.d(TAG, "Androidacy Compat mode: " + value); if (BuildConfig.DEBUG) Log.d(TAG, "Androidacy Compat mode: " + value);
this.notifiedCompatMode = value; this.notifiedCompatMode = value;
if (value < 0) { if (value < 0) {
value = 0; value = 0;
@ -181,7 +180,7 @@ public class AndroidacyWebAPI {
if (this.consumedAction) return; if (this.consumedAction) return;
this.consumedAction = true; this.consumedAction = true;
this.downloadMode = false; 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")) { if (Uri.parse(url).getScheme().equals("https")) {
IntentHelper.openUrl(this.activity, url); IntentHelper.openUrl(this.activity, url);
} }
@ -195,7 +194,7 @@ public class AndroidacyWebAPI {
if (this.consumedAction) return; if (this.consumedAction) return;
this.consumedAction = true; this.consumedAction = true;
this.downloadMode = false; 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")) { if (Uri.parse(url).getScheme().equals("https")) {
IntentHelper.openCustomTab(this.activity, url); IntentHelper.openCustomTab(this.activity, url);
} }
@ -239,7 +238,7 @@ public class AndroidacyWebAPI {
} }
this.consumedAction = true; this.consumedAction = true;
this.downloadMode = false; this.downloadMode = false;
Log.d(TAG, "Received install request: " + if (BuildConfig.DEBUG) Log.d(TAG, "Received install request: " +
moduleUrl + " " + installTitle + " " + checksum); moduleUrl + " " + installTitle + " " + checksum);
if (!AndroidacyUtil.isAndroidacyLink(moduleUrl)) { if (!AndroidacyUtil.isAndroidacyLink(moduleUrl)) {
this.forceQuitRaw("Non Androidacy module link used on Androidacy"); this.forceQuitRaw("Non Androidacy module link used on Androidacy");
@ -299,7 +298,7 @@ public class AndroidacyWebAPI {
return; return;
} }
// Get moduleTitle from url // Get moduleTitle from url
String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl); String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl);
this.openNativeModuleDialogRaw(moduleUrl, moduleId, moduleTitle, checksum, this.canInstall()); 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. * Return true if the module is an Andoridacy module.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
@JavascriptInterface @JavascriptInterface
public boolean isAndroidacyModule(String moduleId) { public boolean isAndroidacyModule(String moduleId) {
LocalModuleInfo localModuleInfo = ModuleManager.getINSTANCE().getModules().get(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: * Return current android sdk-int version code, see:
* https://source.android.com/setup/start/build-numbers * <a href="https://source.android.com/setup/start/build-numbers">right here</a>
*/ */
@JavascriptInterface @JavascriptInterface
public int getAndroidVersionCode() { public int getAndroidVersionCode() {

@ -13,10 +13,12 @@ public class BackgroundBootListener extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (!BOOT_COMPLETED.equals(intent.getAction())) return; if (!BOOT_COMPLETED.equals(intent.getAction())) return;
synchronized (BackgroundUpdateChecker.lock) { synchronized (BackgroundUpdateChecker.lock) {
BackgroundUpdateChecker.onMainActivityCreate(context); new Thread(() -> {
if (MainApplication.isBackgroundUpdateCheckEnabled()) { BackgroundUpdateChecker.onMainActivityCreate(context);
BackgroundUpdateChecker.doCheck(context); if (MainApplication.isBackgroundUpdateCheckEnabled()) {
} BackgroundUpdateChecker.doCheck(context);
}
}).start();
} }
} }
} }

@ -53,7 +53,7 @@ public class BackgroundUpdateChecker extends Worker {
} }
static void doCheck(Context context) { static void doCheck(Context context) {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY); Thread.currentThread().setPriority(2);
ModuleManager.getINSTANCE().scanAsync(); ModuleManager.getINSTANCE().scanAsync();
RepoManager.getINSTANCE().update(null); RepoManager.getINSTANCE().update(null);
ModuleManager.getINSTANCE().runAfterScan(() -> { ModuleManager.getINSTANCE().runAfterScan(() -> {

@ -5,10 +5,9 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.installer.InstallerInitializer; 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.PropUtils;
import com.fox2code.mmm.utils.SyncManager; import com.fox2code.mmm.utils.SyncManager;
import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.Shell;
@ -23,33 +22,34 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
public final class ModuleManager extends SyncManager { 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 // New method is not really effective, this flag force app to use old method
public static final boolean FORCE_NEED_FALLBACK = true; 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_INVALID = ModuleInfo.FLAG_METADATA_INVALID;
private static final int FLAG_MM_UNPROCESSED = ModuleInfo.FLAG_CUSTOM_INTERNAL; private static final int FLAG_MM_UNPROCESSED = ModuleInfo.FLAG_CUSTOM_INTERNAL;
private static final int FLAGS_KEEP_INIT = FLAG_MM_UNPROCESSED | private static final int FLAGS_KEEP_INIT = FLAG_MM_UNPROCESSED |
ModuleInfo.FLAGS_MODULE_ACTIVE | ModuleInfo.FLAG_MODULE_UPDATING_ONLY; 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 int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED;
private static final ModuleManager INSTANCE = new ModuleManager();
private final HashMap<String, LocalModuleInfo> moduleInfos; private final HashMap<String, LocalModuleInfo> moduleInfos;
private final SharedPreferences bootPrefs; private final SharedPreferences bootPrefs;
private int updatableModuleCount = 0; 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() { public static ModuleManager getINSTANCE() {
return INSTANCE; return INSTANCE;
} }
private ModuleManager() { public static boolean isModuleActive(String moduleId) {
this.moduleInfos = new HashMap<>(); ModuleInfo moduleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId);
this.bootPrefs = MainApplication.getBootSharedPreferences(); return moduleInfo != null && (moduleInfo.flags & ModuleInfo.FLAGS_MODULE_ACTIVE) != 0;
} }
protected void scanInternal(@NonNull UpdateListener updateListener) { protected void scanInternal(@NonNull UpdateListener updateListener) {
NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug();
noodleDebug.push("Initialize scan");
boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true);
SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null;
for (ModuleInfo v : this.moduleInfos.values()) { for (ModuleInfo v : this.moduleInfos.values()) {
@ -70,13 +70,12 @@ public final class ModuleManager extends SyncManager {
if (!FORCE_NEED_FALLBACK && needFallback) { if (!FORCE_NEED_FALLBACK && needFallback) {
Log.e(TAG, "Failed to detect modules folder, using fallback instead."); Log.e(TAG, "Failed to detect modules folder, using fallback instead.");
} }
noodleDebug.replace("Scan"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Scan");
if (modules != null) { if (modules != null) {
noodleDebug.push("");
for (String module : modules) { for (String module : modules) {
if (!new SuFile("/data/adb/modules/" + module).isDirectory()) if (!new SuFile("/data/adb/modules/" + module).isDirectory())
continue; // Ignore non directory files inside modules folder continue; // Ignore non directory files inside modules folder
noodleDebug.replace(module); if (BuildConfig.DEBUG) Log.d("NoodleDebug", module);
LocalModuleInfo moduleInfo = moduleInfos.get(module); LocalModuleInfo moduleInfo = moduleInfos.get(module);
if (moduleInfo == null) { if (moduleInfo == null) {
moduleInfo = new LocalModuleInfo(module); moduleInfo = new LocalModuleInfo(module);
@ -114,20 +113,18 @@ public final class ModuleManager extends SyncManager {
PropUtils.readProperties(moduleInfo, PropUtils.readProperties(moduleInfo,
"/data/adb/modules/" + module + "/module.prop", true); "/data/adb/modules/" + module + "/module.prop", true);
} catch (Exception e) { } 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; 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(); String[] modules_update = new SuFile("/data/adb/modules_update").list();
if (modules_update != null) { if (modules_update != null) {
noodleDebug.push("");
for (String module : modules_update) { for (String module : modules_update) {
if (!new SuFile("/data/adb/modules_update/" + module).isDirectory()) if (!new SuFile("/data/adb/modules_update/" + module).isDirectory())
continue; // Ignore non directory files inside modules folder continue; // Ignore non directory files inside modules folder
noodleDebug.replace(module); if (BuildConfig.DEBUG) Log.d("NoodleDebug", module);
LocalModuleInfo moduleInfo = moduleInfos.get(module); LocalModuleInfo moduleInfo = moduleInfos.get(module);
if (moduleInfo == null) { if (moduleInfo == null) {
moduleInfo = new LocalModuleInfo(module); moduleInfo = new LocalModuleInfo(module);
@ -139,20 +136,18 @@ public final class ModuleManager extends SyncManager {
PropUtils.readProperties(moduleInfo, PropUtils.readProperties(moduleInfo,
"/data/adb/modules_update/" + module + "/module.prop", true); "/data/adb/modules_update/" + module + "/module.prop", true);
} catch (Exception e) { } 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; moduleInfo.flags |= FLAG_MM_INVALID;
} }
} }
noodleDebug.pop();
} }
noodleDebug.replace("Finalize scan"); if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Finalize scan");
this.updatableModuleCount = 0; this.updatableModuleCount = 0;
Iterator<LocalModuleInfo> moduleInfoIterator = Iterator<LocalModuleInfo> moduleInfoIterator =
this.moduleInfos.values().iterator(); this.moduleInfos.values().iterator();
noodleDebug.push("");
while (moduleInfoIterator.hasNext()) { while (moduleInfoIterator.hasNext()) {
LocalModuleInfo moduleInfo = moduleInfoIterator.next(); LocalModuleInfo moduleInfo = moduleInfoIterator.next();
noodleDebug.replace(moduleInfo.id); if (BuildConfig.DEBUG) Log.d("NoodleDebug", moduleInfo.id);
if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) { if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) {
moduleInfoIterator.remove(); moduleInfoIterator.remove();
continue; // Don't process fallbacks if unreferenced continue; // Don't process fallbacks if unreferenced
@ -174,12 +169,10 @@ public final class ModuleManager extends SyncManager {
} }
moduleInfo.verify(); moduleInfo.verify();
} }
noodleDebug.pop();
if (firstScan) { if (firstScan) {
editor.putBoolean("mm_first_scan", false); editor.putBoolean("mm_first_scan", false);
editor.apply(); editor.apply();
} }
noodleDebug.pop();
} }
public HashMap<String, LocalModuleInfo> getModules() { public HashMap<String, LocalModuleInfo> getModules() {
@ -235,7 +228,7 @@ public final class ModuleManager extends SyncManager {
try { // Check for module that declare having file outside their own folder. try { // Check for module that declare having file outside their own folder.
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
SuFileInputStream.open("/data/adb/modules/." + moduleInfo.id + "-files"), SuFileInputStream.open("/data/adb/modules/." + moduleInfo.id + "-files"),
StandardCharsets.UTF_8))) { StandardCharsets.UTF_8))) {
String line; String line;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
line = line.trim().replace(' ', '.'); line = line.trim().replace(' ', '.');
@ -248,16 +241,12 @@ public final class ModuleManager extends SyncManager {
Shell.cmd("rm -rf \"" + line + "\"").exec(); Shell.cmd("rm -rf \"" + line + "\"").exec();
} }
} }
} catch (IOException ignored) {} } catch (IOException ignored) {
}
Shell.cmd("rm -rf /data/adb/modules/" + escapedId + "/").exec(); Shell.cmd("rm -rf /data/adb/modules/" + escapedId + "/").exec();
Shell.cmd("rm -f /data/adb/modules/." + escapedId + "-files").exec(); Shell.cmd("rm -f /data/adb/modules/." + escapedId + "-files").exec();
Shell.cmd("rm -rf /data/adb/modules_update/" + escapedId + "/").exec(); Shell.cmd("rm -rf /data/adb/modules_update/" + escapedId + "/").exec();
moduleInfo.flags = ModuleInfo.FLAG_METADATA_INVALID; moduleInfo.flags = ModuleInfo.FLAG_METADATA_INVALID;
return true; 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;
}
} }

@ -2,6 +2,7 @@ package com.fox2code.mmm.repo;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -41,7 +42,7 @@ public class RepoData extends XRepo {
public String name, website, support, donate, submitModule; public String name, website, support, donate, submitModule;
private boolean forceHide, enabled; // Cache for speed 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.url = url;
this.id = RepoManager.internalIdOfUrl(url); this.id = RepoManager.internalIdOfUrl(url);
this.cacheRoot = cacheRoot; this.cacheRoot = cacheRoot;
@ -51,7 +52,7 @@ public class RepoData extends XRepo {
this.defaultName = url; // Set url as default name this.defaultName = url; // Set url as default name
this.forceHide = AppUpdateManager.shouldForceHide(this.id); this.forceHide = AppUpdateManager.shouldForceHide(this.id);
this.enabled = (!this.forceHide) && MainApplication.getSharedPreferences() 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() + "/"; this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/";
if (!this.cacheRoot.isDirectory()) { if (!this.cacheRoot.isDirectory()) {
boolean mkdirs = this.cacheRoot.mkdirs(); boolean mkdirs = this.cacheRoot.mkdirs();
@ -206,14 +207,22 @@ public class RepoData extends XRepo {
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
this.enabled = enabled && !this.forceHide; 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() MainApplication.getSharedPreferences().edit()
.putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply(); .putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply();
} }
public void updateEnabledState() { public void updateEnabledState() {
this.forceHide = AppUpdateManager.shouldForceHide(this.id); 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() this.enabled = (!this.forceHide) && MainApplication.getSharedPreferences()
.getBoolean("pref_" + this.getPreferenceId() + "_enabled", this.isEnabledByDefault()); .getBoolean("pref_" + this.getPreferenceId() + "_enabled", true);
} }
public String getUrl() throws NoSuchAlgorithmException { public String getUrl() throws NoSuchAlgorithmException {

@ -1,12 +1,18 @@
package com.fox2code.mmm.repo; package com.fox2code.mmm.repo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks; import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.XRepo; import com.fox2code.mmm.XRepo;
import com.fox2code.mmm.androidacy.AndroidacyRepoData; 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.Files;
import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Hashes;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.NoodleDebug;
import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.PropUtils;
import com.fox2code.mmm.utils.SyncManager; import com.fox2code.mmm.utils.SyncManager;
import com.google.android.material.snackbar.Snackbar;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -28,7 +34,6 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
public final class RepoManager extends SyncManager { public final class RepoManager extends SyncManager {
public static final String MAGISK_REPO = public static final String MAGISK_REPO =
"https://raw.githubusercontent.com/Magisk-Modules-Repo/submission/modules/modules.json"; "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"; "https://staging-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_MAGISK_REPO_HOMEPAGE = public static final String ANDROIDACY_MAGISK_REPO_HOMEPAGE =
"https://www.androidacy.com/modules-repo"; "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 TAG = "RepoManager";
private static final String MAGISK_REPO_MANAGER = private static final String MAGISK_REPO_MANAGER =
"https://magisk-modules-repo.github.io/submission/modules.json"; "https://magisk-modules-repo.github.io/submission/modules.json";
@ -64,10 +63,10 @@ public final class RepoManager extends SyncManager {
private final HashMap<String, RepoModule> modules; private final HashMap<String, RepoModule> modules;
private final AndroidacyRepoData androidacyRepoData; private final AndroidacyRepoData androidacyRepoData;
private final CustomRepoManager customRepoManager; private final CustomRepoManager customRepoManager;
public String repoLastErrorName = null;
private boolean hasInternet; private boolean hasInternet;
private boolean repoLastError = false;
private boolean initialized; private boolean initialized;
public String repoLastErrorName = null; private boolean repoLastSuccess;
private RepoManager(MainApplication mainApplication) { private RepoManager(MainApplication mainApplication) {
INSTANCE = this; // Set early fox XHooks INSTANCE = this; // Set early fox XHooks
@ -76,15 +75,12 @@ public final class RepoManager extends SyncManager {
this.repoData = new LinkedHashMap<>(); this.repoData = new LinkedHashMap<>();
this.modules = new HashMap<>(); this.modules = new HashMap<>();
// We do not have repo list config yet. // We do not have repo list config yet.
this.androidacyRepoData = this.addAndroidacyRepoData();
RepoData altRepo = this.addRepoData( RepoData altRepo = this.addRepoData(
MAGISK_ALT_REPO, "Magisk Modules Alt Repo"); MAGISK_ALT_REPO, "Magisk Modules Alt Repo");
altRepo.defaultWebsite = RepoManager.MAGISK_ALT_REPO_HOMEPAGE; altRepo.defaultWebsite = RepoManager.MAGISK_ALT_REPO_HOMEPAGE;
altRepo.defaultSubmitModule = altRepo.defaultSubmitModule =
"https://github.com/Magisk-Modules-Alt-Repo/submission/issues"; "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); this.customRepoManager = new CustomRepoManager(mainApplication, this);
XHooks.onRepoManagerInitialize(); XHooks.onRepoManagerInitialize();
// Populate default cache // Populate default cache
@ -141,25 +137,18 @@ public final class RepoManager extends SyncManager {
case ANDROIDACY_MAGISK_REPO_ENDPOINT: case ANDROIDACY_MAGISK_REPO_ENDPOINT:
case ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT: case ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT:
return "androidacy_repo"; return "androidacy_repo";
case DG_MAGISK_REPO:
case DG_MAGISK_REPO_GITHUB:
case DG_MAGISK_REPO_GITHUB_RAW:
return "dg_magisk_repo";
default: default:
return "repo_" + Hashes.hashSha1( return "repo_" + Hashes.hashSha256(
url.getBytes(StandardCharsets.UTF_8)); url.getBytes(StandardCharsets.UTF_8));
} }
} }
static boolean isBuiltInRepo(String repo) { static boolean isBuiltInRepo(String repo) {
switch (repo) { switch (repo) {
case RepoManager.MAGISK_ALT_REPO:
case RepoManager.MAGISK_ALT_REPO_JSDELIVR:
case RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT: case RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT:
case RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT: case RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT:
case RepoManager.DG_MAGISK_REPO: case RepoManager.MAGISK_ALT_REPO:
case RepoManager.DG_MAGISK_REPO_GITHUB: case RepoManager.MAGISK_ALT_REPO_JSDELIVR:
case RepoManager.DG_MAGISK_REPO_GITHUB_RAW:
return true; return true;
} }
return false; return false;
@ -207,18 +196,18 @@ public final class RepoManager extends SyncManager {
public RepoData addOrGet(String url, String fallBackName) { public RepoData addOrGet(String url, String fallBackName) {
if (MAGISK_ALT_REPO_JSDELIVR.equals(url)) if (MAGISK_ALT_REPO_JSDELIVR.equals(url))
url = MAGISK_ALT_REPO; url = MAGISK_ALT_REPO;
if (DG_MAGISK_REPO.equals(url) ||
DG_MAGISK_REPO_GITHUB.equals(url))
url = DG_MAGISK_REPO_GITHUB_RAW;
RepoData repoData; RepoData repoData;
synchronized (this.syncLock) { synchronized (this.syncLock) {
repoData = this.repoData.get(url); repoData = this.repoData.get(url);
if (repoData == null) { if (repoData == null) {
if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) || if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) ||
ANDROIDACY_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.androidacyRepoData;
return this.addAndroidacyRepoData(); } else {
return this.addAndroidacyRepoData();
}
} else { } else {
return this.addRepoData(url, fallBackName); return this.addRepoData(url, fallBackName);
} }
@ -227,10 +216,8 @@ public final class RepoManager extends SyncManager {
return repoData; return repoData;
} }
@SuppressLint("StringFormatInvalid")
protected void scanInternal(@NonNull UpdateListener updateListener) { protected void scanInternal(@NonNull UpdateListener updateListener) {
NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug();
// First, check if we have internet connection
noodleDebug.push("Downloading indexes");
this.modules.clear(); this.modules.clear();
updateListener.update(0D); updateListener.update(0D);
// Using LinkedHashSet to deduplicate Androidacy entry. // Using LinkedHashSet to deduplicate Androidacy entry.
@ -238,26 +225,27 @@ public final class RepoManager extends SyncManager {
this.repoData.values()).toArray(new RepoData[0]); this.repoData.values()).toArray(new RepoData[0]);
RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length]; RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length];
int moduleToUpdate = 0; int moduleToUpdate = 0;
noodleDebug.push("");
for (int i = 0; i < repoDatas.length; i++) { 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] = moduleToUpdate += (repoUpdaters[i] =
new RepoUpdater(repoDatas[i])).fetchIndex(); new RepoUpdater(repoDatas[i])).fetchIndex();
updateListener.update(STEP1 / repoDatas.length * (i + 1)); updateListener.update(STEP1 / repoDatas.length * (i + 1));
} }
noodleDebug.pop(); if (BuildConfig.DEBUG) Log.d("RepoManag3er", "Updating meta-data");
noodleDebug.replace("Updating meta-data");
int updatedModules = 0; int updatedModules = 0;
boolean allowLowQualityModules = MainApplication.isDisableLowQualityModuleFilter(); boolean allowLowQualityModules = MainApplication.isDisableLowQualityModuleFilter();
noodleDebug.push("");
for (int i = 0; i < repoUpdaters.length; i++) { 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<RepoModule> repoModules = repoUpdaters[i].toUpdate(); List<RepoModule> repoModules = repoUpdaters[i].toUpdate();
RepoData repoData = repoDatas[i]; RepoData repoData = repoDatas[i];
noodleDebug.replace(repoData.getName()); if (BuildConfig.DEBUG) Log.d("RepoManager", "Registering " + repoData.getName());
Log.d(TAG, "Registering " + repoData.getName());
noodleDebug.push("");
for (RepoModule repoModule : repoModules) { for (RepoModule repoModule : repoModules) {
noodleDebug.replace(repoModule.id); if (BuildConfig.DEBUG) Log.d("RepoManager", "Fetching module: " + repoModule.id);
try { try {
if (repoModule.propUrl != null && if (repoModule.propUrl != null &&
!repoModule.propUrl.isEmpty()) { !repoModule.propUrl.isEmpty()) {
@ -283,7 +271,6 @@ public final class RepoManager extends SyncManager {
updatedModules++; updatedModules++;
updateListener.update(STEP1 + (STEP2 / moduleToUpdate * updatedModules)); updateListener.update(STEP1 + (STEP2 / moduleToUpdate * updatedModules));
} }
noodleDebug.pop();
for (RepoModule repoModule : repoUpdaters[i].toApply()) { for (RepoModule repoModule : repoUpdaters[i].toApply()) {
if ((repoModule.moduleInfo.flags & ModuleInfo.FLAG_METADATA_INVALID) == 0) { if ((repoModule.moduleInfo.flags & ModuleInfo.FLAG_METADATA_INVALID) == 0) {
RepoModule registeredRepoModule = this.modules.get(repoModule.id); RepoModule registeredRepoModule = this.modules.get(repoModule.id);
@ -296,9 +283,7 @@ public final class RepoManager extends SyncManager {
} }
} }
} }
noodleDebug.pop(); if (BuildConfig.DEBUG) Log.d("RepoManager", "Finishing update");
noodleDebug.replace("Finishing update");
noodleDebug.push("");
this.hasInternet = false; this.hasInternet = false;
// Check if we have internet connection // Check if we have internet connection
// Attempt to contact connectivitycheck.gstatic.com/generate_204 // Attempt to contact connectivitycheck.gstatic.com/generate_204
@ -307,7 +292,6 @@ public final class RepoManager extends SyncManager {
HttpURLConnection urlConnection = (HttpURLConnection) new URL( HttpURLConnection urlConnection = (HttpURLConnection) new URL(
"https://connectivitycheck.gstatic.com/generate_204").openConnection(); "https://connectivitycheck.gstatic.com/generate_204").openConnection();
urlConnection.setInstanceFollowRedirects(false); urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(1000);
urlConnection.setReadTimeout(1000); urlConnection.setReadTimeout(1000);
urlConnection.setUseCaches(false); urlConnection.setUseCaches(false);
urlConnection.getInputStream().close(); urlConnection.getInputStream().close();
@ -318,22 +302,37 @@ public final class RepoManager extends SyncManager {
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Failed to check internet connection", e); Log.e(TAG, "Failed to check internet connection", e);
} }
noodleDebug.pop();
if (hasInternet) { if (hasInternet) {
for (int i = 0; i < repoDatas.length; i++) { for (int i = 0; i < repoDatas.length; i++) {
noodleDebug.replace(repoUpdaters[i].repoData.getName()); // If repo is not enabled, skip
this.repoLastError = !repoUpdaters[i].finish(); if (!repoDatas[i].isEnabled()) {
if (this.repoLastError) { 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()); 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(); this.repoLastErrorName = repoUpdaters[i].repoData.getName();
} }
updateListener.update(STEP1 + STEP2 + (STEP3 / repoDatas.length * (i + 1))); updateListener.update(STEP1 + STEP2 + (STEP3 / repoDatas.length * (i + 1)));
} }
} }
noodleDebug.pop();
Log.i(TAG, "Got " + this.modules.size() + " modules!"); Log.i(TAG, "Got " + this.modules.size() + " modules!");
updateListener.update(1D); updateListener.update(1D);
noodleDebug.pop(); // pop "Finishing update"
} }
public void updateEnabledStates() { public void updateEnabledStates() {
@ -408,6 +407,6 @@ public final class RepoManager extends SyncManager {
} }
public boolean isLastUpdateSuccess() { public boolean isLastUpdateSuccess() {
return this.repoLastError; return this.repoLastSuccess;
} }
} }

@ -1,20 +1,11 @@
package com.fox2code.mmm.repo; package com.fox2code.mmm.repo;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
import androidx.annotation.Nullable; import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Http; 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 org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
@ -52,12 +43,12 @@ public class RepoUpdater {
} }
this.indexRaw = Http.doHttpGet(this.repoData.getUrl(), false); this.indexRaw = Http.doHttpGet(this.repoData.getUrl(), false);
// Ensure it's a valid json and response code is 200 // 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.indexRaw = null;
this.toUpdate = Collections.emptyList(); this.toUpdate = Collections.emptyList();
this.toApply = this.repoData.moduleHashMap.values(); this.toApply = this.repoData.moduleHashMap.values();
return 0; return 0;
} }*/
this.toUpdate = this.repoData.populate(new JSONObject( this.toUpdate = this.repoData.populate(new JSONObject(
new String(this.indexRaw, StandardCharsets.UTF_8))); new String(this.indexRaw, StandardCharsets.UTF_8)));
// Since we reuse instances this should work // Since we reuse instances this should work
@ -83,7 +74,7 @@ public class RepoUpdater {
} }
public boolean finish() { 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 repo is not enabled we don't need to do anything, just return true
if (!this.repoData.isEnabled()) { if (!this.repoData.isEnabled()) {
return true; return true;
@ -91,6 +82,9 @@ public class RepoUpdater {
if (this.indexRaw != null) { if (this.indexRaw != null) {
try { try {
Files.write(this.repoData.metaDataCache, this.indexRaw); Files.write(this.repoData.metaDataCache, this.indexRaw);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Wrote index of " + this.repoData.id);
}
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

@ -1,6 +1,9 @@
package com.fox2code.mmm.settings; package com.fox2code.mmm.settings;
import static java.lang.Integer.parseInt;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.Application; import android.app.Application;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -9,6 +12,8 @@ import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.internal.TextWatcherAdapter;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.MaterialAutoCompleteTextView; import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.google.common.hash.Hashing;
import com.mikepenz.aboutlibraries.LibsBuilder; import com.mikepenz.aboutlibraries.LibsBuilder;
import com.topjohnwu.superuser.internal.UiThreadHandler; import com.topjohnwu.superuser.internal.UiThreadHandler;
import org.json.JSONException; import org.json.JSONException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
@ -78,6 +86,50 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper(); private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper();
private static int devModeStep = 0; 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
devModeStep = 0; devModeStep = 0;
@ -123,6 +175,25 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true; return true;
}); });
ListPreference themePreference = findPreference("pref_theme"); 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.setSummaryProvider(p -> themePreference.getEntry());
themePreference.setOnPreferenceClickListener(p -> { themePreference.setOnPreferenceClickListener(p -> {
// You need to reboot your device at least once to be able to access dev-mode // 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; return false;
}); });
themePreference.setOnPreferenceChangeListener((preference, newValue) -> { themePreference.setOnPreferenceChangeListener((preference, newValue) -> {
devModeStep = 0; if (BuildConfig.DEBUG) {
UiThreadHandler.handler.postDelayed(() -> { Log.d(TAG, "Theme changed, refreshing activity. New value: " + newValue);
MainApplication.getINSTANCE().updateTheme(); }
FoxActivity.getFoxActivity(this).setThemeRecreate( // Immediately save
MainApplication.getINSTANCE().getManagerThemeResId()); SharedPreferences.Editor editor =
}, 1); 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; return true;
}); });
// Crash reporting // Crash reporting
@ -157,13 +281,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456; int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available // If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent; PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -177,9 +296,36 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true; return true;
}); });
Preference enableBlur = findPreference("pref_enable_blur"); Preference enableBlur = findPreference("pref_enable_blur");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Disable blur on low performance devices
enableBlur.setSummary(R.string.require_android_6); if (getDevicePerformanceClass() < PERFORMANCE_CLASS_AVERAGE) {
enableBlur.setEnabled(false); // 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"); Preference disableMonet = findPreference("pref_enable_monet");
@ -252,6 +398,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG || if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG ||
InstallerInitializer.peekMagiskPath() == null) { InstallerInitializer.peekMagiskPath() == null) {
// Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app // 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); Objects.requireNonNull((Preference) findPreference("pref_crash")).setVisible(false);
} else { } else {
findPreference("pref_crash").setOnPreferenceClickListener(preference -> { findPreference("pref_crash").setOnPreferenceClickListener(preference -> {
@ -387,9 +535,24 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
openFragment(libsBuilder.supportFragment(), R.string.licenses); openFragment(libsBuilder.supportFragment(), R.string.licenses);
return true; return true;
}); });
findPreference("pref_pkg_info").setSummary(BuildConfig.APPLICATION_ID + // Determine if this is an official build based on the signature
" v" + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")" + boolean isOfficial = false;
getRepackageState()); // State may not be "I am just running from myself as myself" 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") @SuppressLint("RestrictedApi")
@ -445,7 +608,6 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
setPreferencesFromResource(R.xml.repo_preferences, rootKey); setPreferencesFromResource(R.xml.repo_preferences, rootKey);
setRepoData(RepoManager.MAGISK_ALT_REPO); setRepoData(RepoManager.MAGISK_ALT_REPO);
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT); setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
setRepoData(RepoManager.DG_MAGISK_REPO_GITHUB);
updateCustomRepoList(true); updateCustomRepoList(true);
onCreatePreferencesAndroidacy(); onCreatePreferencesAndroidacy();
} }
@ -464,6 +626,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Use MaterialAlertDialogBuilder // Use MaterialAlertDialogBuilder
new MaterialAlertDialogBuilder(this.requireContext()) new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setCancelable(false)
.setMessage(R.string.androidacy_test_mode_warning) .setMessage(R.string.androidacy_test_mode_warning)
.setPositiveButton(android.R.string.ok, (dialog, which) -> { .setPositiveButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button // User clicked OK button
@ -474,13 +637,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456; int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available // If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent; PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -502,6 +660,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Show dialog to restart app with ok button // Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext()) new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setCancelable(false)
.setMessage(R.string.androidacy_test_mode_disable_warning) .setMessage(R.string.androidacy_test_mode_disable_warning)
.setNeutralButton(android.R.string.ok, (dialog, which) -> { .setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button // User clicked OK button
@ -510,13 +669,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456; int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available // If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent; PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -536,6 +690,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
androidacyRepoEnabled.setOnPreferenceClickListener(preference -> { androidacyRepoEnabled.setOnPreferenceClickListener(preference -> {
new MaterialAlertDialogBuilder(this.requireContext()) new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.androidacy_repo_disabled) .setTitle(R.string.androidacy_repo_disabled)
.setCancelable(false)
.setMessage(R.string.androidacy_repo_disabled_message) .setMessage(R.string.androidacy_repo_disabled_message)
.setPositiveButton(R.string.download_full_app, (dialog, which) -> { .setPositiveButton(R.string.download_full_app, (dialog, which) -> {
// User clicked OK button. Open GitHub releases page // 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.setPositiveButtonText(R.string.save_api_key);
prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> { prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> {
if (originalApiKeyRef[0].equals(newValue)) return true; // Skip if nothing changed. if (originalApiKeyRef[0].equals(newValue)) return true;
// Curious if this actually works - so crash the app on purpose
// throw new RuntimeException("This is a test crash");
// get original api key // get original api key
String apiKey = String.valueOf(newValue); String apiKey = String.valueOf(newValue);
// Show snack bar with indeterminate progress // 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 // Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext()) new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.restart) .setTitle(R.string.restart)
.setCancelable(false)
.setMessage(R.string.api_key_restart) .setMessage(R.string.api_key_restart)
.setNeutralButton(android.R.string.ok, (dialog, which) -> { .setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button // User clicked OK button
@ -601,13 +755,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456; int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available // If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent; PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -650,6 +799,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Show dialog to restart app with ok button // Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext()) new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.restart) .setTitle(R.string.restart)
.setCancelable(false)
.setMessage(R.string.api_key_restart) .setMessage(R.string.api_key_restart)
.setNeutralButton(android.R.string.ok, (dialog, which) -> { .setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button // User clicked OK button
@ -658,13 +808,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456; int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available // If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent; PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -793,12 +938,20 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
preference.setTitle(repoData.getName()); preference.setTitle(repoData.getName());
preference = findPreference(preferenceName + "_enabled"); preference = findPreference(preferenceName + "_enabled");
if (preference != null) { if (preference != null) {
((TwoStatePreference) preference).setChecked(repoData.isEnabled()); // Handle custom repo separately
preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled); if (repoData instanceof CustomRepoData) {
preference.setOnPreferenceChangeListener((p, newValue) -> { preference.setTitle(R.string.custom_repo_always_on);
p.setTitle(((Boolean) newValue) ? R.string.repo_enabled : R.string.repo_disabled); // Disable the preference
return true; 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"); preference = findPreference(preferenceName + "_website");
String homepage = repoData.getWebsite(); String homepage = repoData.getWebsite();

@ -24,6 +24,7 @@ public class Hashes {
} }
public static String hashMd5(byte[] input) { public static String hashMd5(byte[] input) {
Log.w(TAG, "hashMd5: This method is insecure, use hashSha256 instead");
try { try {
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");

@ -15,11 +15,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.androidacy.AndroidacyUtil; import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoManager;
import com.google.net.cronet.okhttptransport.CronetCallFactory;
import com.google.net.cronet.okhttptransport.CronetInterceptor; import com.google.net.cronet.okhttptransport.CronetInterceptor;
import org.chromium.net.CronetEngine; import org.chromium.net.CronetEngine;
@ -140,13 +140,6 @@ public class Http {
return chain.proceed(request.build()); return chain.proceed(request.build());
}); });
// Add cronet interceptor // 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 // init cronet
try { try {
// Load the cronet library // Load the cronet library
@ -165,8 +158,13 @@ public class Http {
} }
builder.setStoragePath(mainApplication.getCacheDir().getAbsolutePath() + "/cronet"); builder.setStoragePath(mainApplication.getCacheDir().getAbsolutePath() + "/cronet");
builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 10 * 1024 * 1024); builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 10 * 1024 * 1024);
CronetEngine engine = // Add quic hint
builder.build(); 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()); httpclientBuilder.addInterceptor(CronetInterceptor.newBuilder(engine).build());
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Failed to init cronet", e); Log.e(TAG, "Failed to init cronet", e);
@ -234,10 +232,10 @@ public class Http {
@SuppressWarnings("resource") @SuppressWarnings("resource")
public static byte[] doHttpGet(String url, boolean allowCache) throws IOException { public static byte[] doHttpGet(String url, boolean allowCache) throws IOException {
checkNeedBlockAndroidacyRequest(url); checkNeedBlockAndroidacyRequest(url);
Response response = Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
(allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
// 200/204 == success, 304 == cache valid // 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { 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()); checkNeedCaptchaAndroidacy(url, response.code());
// If it's a 401, and an androidacy link, it's probably an invalid token // If it's a 401, and an androidacy link, it's probably an invalid token
if (response.code() == 401 && AndroidacyUtil.isAndroidacyLink(url)) { if (response.code() == 401 && AndroidacyUtil.isAndroidacyLink(url)) {
@ -260,13 +258,17 @@ public class Http {
@SuppressWarnings("resource") @SuppressWarnings("resource")
private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException { private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException {
if (BuildConfig.DEBUG) Log.d(TAG, "POST " + url + " " + data);
checkNeedBlockAndroidacyRequest(url); 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()) { if (response.isRedirect()) {
return response.request().url().uri().toString(); return response.request().url().uri().toString();
} }
// 200/204 == success, 304 == cache valid // 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { 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()); checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code()); throw new HttpException(response.code());
} }
@ -280,10 +282,11 @@ public class Http {
} }
public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException { 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); checkNeedBlockAndroidacyRequest(url);
Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute(); Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute();
if (response.code() != 200 && response.code() != 204) { if (response.code() != 200 && response.code() != 204) {
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code());
checkNeedCaptchaAndroidacy(url, response.code()); checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code()); throw new HttpException(response.code());
} }
@ -337,6 +340,54 @@ public class Http {
return hasWebView; 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 { public interface ProgressListener {
void onUpdate(int downloaded, int total, boolean done); void onUpdate(int downloaded, int total, boolean done);
} }

@ -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<Thread> NULL_THREAD_REF = new WeakReference<>(null);
private static final ThreadLocal<NoodleDebug> 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<String> tokens;
private final StringBuilder debug;
private WeakReference<Thread> 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;
}
}

@ -15,12 +15,13 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" > app:layout_constraintTop_toTopOf="parent">
<!-- FrameLayout is the best way to fix blurring --> <!-- FrameLayout is the best way to fix blurring -->
<FrameLayout <FrameLayout
android:id="@+id/blur_frame" android:id="@+id/blur_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list" android:id="@+id/module_list"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -36,6 +37,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:id="@+id/action_bar_padding" android:id="@+id/action_bar_padding"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -44,49 +46,59 @@
<com.google.android.material.progressindicator.LinearProgressIndicator <com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar" android:id="@+id/progress_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true" android:indeterminate="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" /> app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" />
<TextView <!--<TextView
android:id="@+id/noodle_debug" android:id="@+id/noodle_debug"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" /> app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" />-->
<LinearLayout <LinearLayout
android:id="@+id/search_container" android:id="@+id/search_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:gravity="right" android:gravity="right"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_fitsSystemWindowsInsets="bottom" app:layout_fitsSystemWindowsInsets="bottom"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
<!-- <!--
setting high app:cardCornerRadius is not supported on some versions setting high app:cardCornerRadius is not supported on some versions
so we must use code to get a round appearance. so we must use code to get a round appearance.
--> -->
<!-- Material down arrow icon -->
<com.google.android.material.button.MaterialButton
android:id="@+id/down_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="@color/transparent"
android:visibility="gone" />
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/search_card" android:id="@+id/search_card"
android:layout_gravity="center"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:shape="ring" android:layout_gravity="center"
android:background="@null" android:background="@null"
android:shape="ring"
app:cardCornerRadius="@dimen/card_corner_radius" app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
app:strokeColor="@android:color/transparent" app:strokeColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeWidth="0dp"> app:strokeWidth="0dp">
<androidx.appcompat.widget.SearchView <androidx.appcompat.widget.SearchView
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"

@ -16,7 +16,7 @@
android:id="@+id/card_view" android:id="@+id/card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?attr/materialCardViewFilledStyle"> style="?attr/materialCardViewElevatedStyle">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="fill" android:orientation="vertical" android:paddingLeft="20dp" android:paddingRight="20dp" android:scrollbars="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/setup_message" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/repos"
android:textAppearance="@android:style/TextAppearance.Material.Headline" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/setup_androidacy_repo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:checked="false"
android:key="pref_androidacy_repo_enabled"
android:text="@string/setup_androidacy_repo"
android:textAppearance="@android:style/TextAppearance.Material.Small" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/setup_magisk_alt_repo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/setup_magisk_alt_repo"
android:checked="false"
android:key="pref_magisk_alt_repo_enabled"
android:textAppearance="@android:style/TextAppearance.Material.Small" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/setup_custom_repos"
android:textAppearance="@android:style/TextAppearance.Material.Caption" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/misc"
android:textAppearance="@android:style/TextAppearance.Material.Headline" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/setup_crash_reporting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:checked="false"
android:key="pref_crash_reporting_enabled"
android:text="@string/setup_crash_reporting"
android:textAppearance="@android:style/TextAppearance.Material.Small" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/setup_background_update_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:checked="false"
android:key="pref_background_update_check"
android:text="@string/setup_background_update_check"
android:textAppearance="@android:style/TextAppearance.Material.Small" />
<!-- Placeholder for future settings -->
<!--<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/setup_app_analytics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:checked="false"
android:key="pref_app_analytics"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:text="@string/setup_app_analytics" />-->
</LinearLayout>
</ScrollView>
</LinearLayout>

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

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

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

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

@ -3,6 +3,7 @@
<item>Dle systému</item> <item>Dle systému</item>
<item>Tmavá</item> <item>Tmavá</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Světlá</item> <item>Světlá</item>
</string-array> </string-array>
</resources> </resources>

@ -4,6 +4,7 @@
<item>Systemvorgabe</item> <item>Systemvorgabe</item>
<item>Dunkel</item> <item>Dunkel</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Hell</item> <item>Hell</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Προεπιλογή συστήματως</item> <item>Προεπιλογή συστήματως</item>
<item>Σκωτεινό</item> <item>Σκωτεινό</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Ανοιχτό</item> <item>Ανοιχτό</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Sistema</item> <item>Sistema</item>
<item>Oscuro</item> <item>Oscuro</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Claro</item> <item>Claro</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Süsteem</item> <item>Süsteem</item>
<item>Tume</item> <item>Tume</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Hele</item> <item>Hele</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Système</item> <item>Système</item>
<item>Sombre</item> <item>Sombre</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Clair</item> <item>Clair</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Sistem</item> <item>Sistem</item>
<item>Gelap</item> <item>Gelap</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Terang</item> <item>Terang</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Sistema</item> <item>Sistema</item>
<item>Scuro</item> <item>Scuro</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Chiaro</item> <item>Chiaro</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>システムの設定を使用</item> <item>システムの設定を使用</item>
<item>ダーク</item> <item>ダーク</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>ライト</item> <item>ライト</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Zgodny z systemem</item> <item>Zgodny z systemem</item>
<item>Ciemny</item> <item>Ciemny</item>
<item>Czarny</item> <item>Czarny</item>
<item>Transparent (light)</item>
<item>Jasny</item> <item>Jasny</item>
</string-array> </string-array>
<string-array name="permission_notification_dont_ask_again">Nie pytaj ponownie</string-array> <string-array name="permission_notification_dont_ask_again">Nie pytaj ponownie</string-array>

@ -3,6 +3,7 @@
<item>Sistema</item> <item>Sistema</item>
<item>Escuro</item> <item>Escuro</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Claro</item> <item>Claro</item>
</string-array> </string-array>
</resources> </resources>

@ -5,6 +5,7 @@
<item>Sistem</item> <item>Sistem</item>
<item>Întunecată</item> <item>Întunecată</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Luminoasă</item> <item>Luminoasă</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Как в системе</item> <item>Как в системе</item>
<item>Тёмная</item> <item>Тёмная</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Светлая</item> <item>Светлая</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Podľa systému</item> <item>Podľa systému</item>
<item>Tmavá</item> <item>Tmavá</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Svetlá</item> <item>Svetlá</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>Sistem</item> <item>Sistem</item>
<item>Koyu</item> <item>Koyu</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>ık</item> <item>ık</item>
</string-array> </string-array>
</resources> </resources>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MagiskModuleManager.Transparent.Light" parent="Theme.MagiskModuleManager.Light">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<!-- Make as much as sanely possible of the window transparent -->
<item name="android:background">@android:color/transparent</item>
<item name="android:colorBackground">@android:color/transparent</item>
<item name="android:primaryContentAlpha">0.0</item>
<item name="android:secondaryContentAlpha">0.0</item>
</style>
</resources>

@ -26,13 +26,22 @@
<style name="Theme.MagiskModuleManager.Monet.Black" parent="Theme.MagiskModuleManager.Monet.Dark"> <style name="Theme.MagiskModuleManager.Monet.Black" parent="Theme.MagiskModuleManager.Monet.Dark">
<item name="colorBackgroundFloating">@color/black</item> <item name="colorBackgroundFloating">@color/black</item>
<item name="android:windowBackground">@color/black</item> <item name="android:windowBackground">@color/black</item>
<item name="boxBackgroundColor">@color/black</item> <item name="boxBackgroundColor">@color/dark_backgroundColor</item>
<item name="cardBackgroundColor">@color/black</item> <item name="cardBackgroundColor">@color/cardview_dark_background</item>
<item name="backgroundColor">@color/black</item> <item name="backgroundColor">@color/black</item>
<item name="colorSurface">@color/black</item> <item name="colorSurface">@color/black</item>
<!-- Darker variants of the colors --> <!-- Darker variants of the monet colors -->
<item name="colorPrimaryVariant">@color/system_accent2_900</item> <item name="colorPrimary">@color/system_accent2_300</item>
<item name="colorSecondaryVariant">@color/system_accent2_900</item> <item name="colorPrimaryVariant">@color/system_accent2_700</item>
<item name="colorSecondary">@color/system_accent2_400</item>
<item name="colorSecondaryVariant">@color/system_accent2_600</item>
<!-- Override chip style -->
<item name="chipBackgroundColor">@color/system_accent2_700</item>
<item name="chipSurfaceColor">@color/system_accent2_700</item>
<!-- <item name="colorOnPrimary">@color/system_accent2_100</item>
<item name="colorOnSecondary">@color/system_accent2_100</item>
<item name="colorOnBackground">@color/system_accent2_100</item>
<item name="colorOnSurface">@color/system_accent2_100</item> -->
</style> </style>
<style name="Widget.Material3.Chip.Choice.Dark" parent="Widget.Material3.Chip.Assist"> <style name="Widget.Material3.Chip.Choice.Dark" parent="Widget.Material3.Chip.Assist">

@ -4,6 +4,7 @@
<item>Hệ thống</item> <item>Hệ thống</item>
<item>Tối</item> <item>Tối</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Sáng</item> <item>Sáng</item>
</string-array> </string-array>
</resources> </resources>

@ -1,8 +1,9 @@
<resources> <resources>
<string-array name="theme_values_names"> <string-array name="theme_values_names">
<item>跟随系统</item> <item>跟随系统</item>
<item>深色模式</item> <item>深色模式</item>
<item>纯黑模式</item> <item>纯黑模式</item>
<item>Transparent (light)</item>
<item>明亮模式</item> <item>明亮模式</item>
</string-array> </string-array>
</resources> </resources>

@ -3,6 +3,7 @@
<item>跟隨系統</item> <item>跟隨系統</item>
<item>深色主題</item> <item>深色主題</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>淺色主題</item> <item>淺色主題</item>
</string-array> </string-array>
</resources> </resources>

@ -1,9 +1,12 @@
<resources> <resources>
<!-- If translating, please remove "theme_values" --> <!-- If translating, please remove "theme_values" -->
<!-- Transparent dark theme has, erm, issues -->
<string-array name="theme_values" translatable="false"> <string-array name="theme_values" translatable="false">
<item>system</item> <item>system</item>
<item>dark</item> <item>dark</item>
<item>black</item> <item>black</item>
<item>transparent_light</item>
<!-- <item>transparent_dark</item> -->
<item>light</item> <item>light</item>
</string-array> </string-array>
@ -11,6 +14,7 @@
<item>System</item> <item>System</item>
<item>Dark</item> <item>Dark</item>
<item>AMOLED Black</item> <item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Light</item> <item>Light</item>
</string-array> </string-array>
<string-array name="permission_notification_dont_ask_again">Don't prompt again</string-array> <string-array name="permission_notification_dont_ask_again">Don't prompt again</string-array>

@ -7,6 +7,7 @@
<color name="teal_700">#FF018786</color> <color name="teal_700">#FF018786</color>
<color name="orange_700">#EF6C00</color> <color name="orange_700">#EF6C00</color>
<color name="orange_200">#FFA726</color> <color name="orange_200">#FFA726</color>
<color name="orange_500">#FF9800</color>
<color name="black_transparent">#70000000</color> <color name="black_transparent">#70000000</color>
<color name="white_transparent">#70FFFFFF</color> <color name="white_transparent">#70FFFFFF</color>
<color name="transparent">#00000000</color> <color name="transparent">#00000000</color>

@ -132,7 +132,7 @@
<string name="remove_repo">Remove Repo</string> <string name="remove_repo">Remove Repo</string>
<string name="custom_url">Custom url</string> <string name="custom_url">Custom url</string>
<string name="link_copied">Link copied</string> <string name="link_copied">Link copied</string>
<string name="androidacy_repo_info">This repository may display some non-intrusive advertising to cover server and development costs.</string> <string name="androidacy_repo_info">This repository may display some non-intrusive dvertising to cover server and development costs. Features reviews, automatic virus scans, and more.</string>
<string name="backup_module_list">Backup modules</string> <string name="backup_module_list">Backup modules</string>
<string name="restore_module_list">Restore modules</string> <string name="restore_module_list">Restore modules</string>
<string name="require_internet">This operation require an internet connection</string> <string name="require_internet">This operation require an internet connection</string>
@ -198,4 +198,43 @@
download the GitHub release if you\'d like to benefit from features like module reviews, automatic security checks, and more.</string> download the GitHub release if you\'d like to benefit from features like module reviews, automatic security checks, and more.</string>
<string name="download_full_app">Download full version</string> <string name="download_full_app">Download full version</string>
<string name="repo_update_failed">Some repos have failed to update</string> <string name="repo_update_failed">Some repos have failed to update</string>
<string name="repo_update_failed_extended">Update of %1$s failed. Please try again later.</string>
<string name="monet_disabled_summary">Monet is not compatible with transparent themes.</string>
<string name="blur_disabled_summary">Blur is not compatible with transparent themes.</string>
<string name="transparent_theme_dialogue_title">You are setting a transparent theme</string>
<string name="transparent_theme_dialogue_message">Transparent themes may have some inconsistencies and may not work on all ROMs. In additon, monet and blur will be disabled. You can change back at any time.</string>
<string name="custom_repo_always_on">Custom repos are always on until you remove them.</string>
<string name="sentry_dialogue_message">We encountered an error! Please help us improve the app
by adding some information about the error below.\nName and email are optional but will
allow us to contact you if needed for more information.</string>
<string name="sentry_dialogue_title">Oops! Looks like the app closed unexpectedly.</string>
<string name="name">Your name</string>
<string name="email">Your email</string>
<string name="additional_info">Tell us what happened</string>
<string name="submit">Submit</string>
<string name="sentry_dialogue_failed_toast">Could not submit feedback due to an error</string>
<string name="sentry_dialogue_success">Submitted feedback successfully. We\'ll review it
shortly</string>
<string name="sentry_dialogue_no_description">Could not submit feedback as no
description was provided</string>
<string name="go_to_online_repo">Scroll to online repo</string>
<string name="pref_pkg_info_summary">%1$s v%2$s (%3$o) | %4$s Build</string>
<string name="official">Official</string>
<string name="unofficial">Unofficial</string>
<string name="setup_title">First time setup</string>
<string name="setup_message">Welcome! This app will help you install and manage Magisk modules. To get started, please select the options below. These and more can be configured from settings later.</string>
<string name="setup_button">Finish setup</string>
<string name="setup_background_update_check">Allow us to check for updates in the background.\nThis feature may use more battery.</string>
<string name="setup_androidacy_repo">Enable the Androidacy repo\nFeatures user reviews, automatic virus scans, fast updates, a wide selection, and is backed by Androidacy.</string>
<!-- Maybe once alt repo fixes their shit and stops allowing crappy modules or outright
kanged modules, we'll reword this. -->
<string name="setup_magisk_alt_repo">Enable the Magisk Alt Repo\nMuch more lax than the original. Has a lot of modules at the cost of some safety.</string>
<string name="setup_crash_reporting">Enable Sentry crash reporting and performance monitoring.\nAll reports are striclty anonymous and confidential.</string>
<string name="setup_custom_repos">You can add custom repos later in settings.</string>
<string name="repos">Repos</string>
<string name="misc">Miscellaneous</string>
<string name="setup_button_skip">Skip</string>
<string name="low_performance_device_dialogue_title">Enabling blur on lower-end device</string>
<string name="low_performance_device_dialogue_message">You are trying to enable blur on a device that may not perform well with it.\nYou may enable it, but this may lead to a poor user experience and we recommend you don\'t.</string>
<string name="alt_repo_info">This repo has less restrictions and reviews, which may lead to lower quality modules. Pretty barebones but has a lot of modules.</string>
</resources> </resources>

@ -92,8 +92,10 @@
<item name="backgroundColor">@color/black</item> <item name="backgroundColor">@color/black</item>
<item name="colorSurface">@color/black</item> <item name="colorSurface">@color/black</item>
<!-- Darker variants of the colors --> <!-- Darker variants of the colors -->
<item name="colorPrimaryVariant">@color/system_accent2_900</item> <item name="colorPrimary">@color/orange_500</item>
<item name="colorSecondaryVariant">@color/system_accent2_900</item> <item name="colorPrimaryVariant">@color/orange_700</item>
<item name="colorSecondary">@color/orange_500</item>
<item name="colorSecondaryVariant">@color/orange_700</item>
</style> </style>
<style name="Widget.Material.Chip.Choice.Dark" parent="Widget.MaterialComponents.Chip.Action"> <style name="Widget.Material.Chip.Choice.Dark" parent="Widget.MaterialComponents.Chip.Action">

@ -6,4 +6,29 @@
<certificates src="@raw/androidacy_root_ca" /> <certificates src="@raw/androidacy_root_ca" />
</trust-anchors> </trust-anchors>
</domain-config> </domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">github.com</domain>
<domain includeSubdomains="true">githubusercontent.com</domain>
<trust-anchors>
<certificates src="@raw/gh_root_ca" />
</trust-anchors>
</domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">gstatic.com</domain>
<trust-anchors>
<certificates src="@raw/gstatic_root_ca" />
</trust-anchors>
</domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">ingest.sentry.io</domain>
<trust-anchors>
<certificates src="@raw/sentry_root_ca" />
</trust-anchors>
</domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="false">sentry.io</domain>
<trust-anchors>
<certificates src="@raw/sentry_io_root_ca" />
</trust-anchors>
</domain-config>
</network-security-config> </network-security-config>

@ -1,35 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"> <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:key="pref_magisk_alt_repo"
app:title="@string/loading">
<SwitchPreferenceCompat
app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_magisk_alt_repo_enabled"
app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24"
app:key="pref_magisk_alt_repo_website"
app:singleLineTitle="false"
app:title="@string/website" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_support_24"
app:key="pref_magisk_alt_repo_support"
app:singleLineTitle="false"
app:title="@string/support" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_monetization_on_24"
app:key="pref_magisk_alt_repo_donate"
app:singleLineTitle="false"
app:title="@string/donate" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_upload_file_24"
app:key="pref_magisk_alt_repo_submit"
app:singleLineTitle="false"
app:title="@string/submit_modules" />
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:key="pref_androidacy_repo" app:key="pref_androidacy_repo"
app:title="@string/loading"> app:title="@string/loading">
@ -88,44 +58,54 @@
app:summary="@string/androidacy_repo_info" /> app:summary="@string/androidacy_repo_info" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:key="pref_dg_magisk_repo" app:key="pref_magisk_alt_repo"
app:title="@string/loading"> app:title="@string/loading">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:icon="@drawable/ic_baseline_extension_24" app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_dg_magisk_repo_enabled" app:key="pref_magisk_alt_repo_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled" app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" /> app:switchTextOn="@string/repo_enabled" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24" app:icon="@drawable/ic_baseline_language_24"
app:key="pref_dg_magisk_repo_website" app:key="pref_magisk_alt_repo_website"
app:singleLineTitle="false" app:singleLineTitle="false"
app:title="@string/website" /> app:title="@string/website" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_support_24" app:icon="@drawable/ic_baseline_support_24"
app:key="pref_dg_magisk_repo_support" app:key="pref_magisk_alt_repo_support"
app:singleLineTitle="false" app:singleLineTitle="false"
app:title="@string/support" /> app:title="@string/support" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_monetization_on_24" app:icon="@drawable/ic_baseline_monetization_on_24"
app:key="pref_dg_magisk_repo_donate" app:key="pref_magisk_alt_repo_donate"
app:singleLineTitle="false" app:singleLineTitle="false"
app:title="@string/donate" /> app:title="@string/donate" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_upload_file_24" app:icon="@drawable/ic_baseline_upload_file_24"
app:key="pref_dg_magisk_repo_submit" app:key="pref_magisk_alt_repo_submit"
app:singleLineTitle="false" app:singleLineTitle="false"
app:title="@string/submit_modules" /> app:title="@string/submit_modules" />
<Preference
app:enabled="false"
app:icon="@drawable/ic_baseline_info_24"
app:key="pref_alt_repo_disclaimer"
app:singleLineTitle="false"
app:summary="@string/alt_repo_info" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:key="pref_custom_repo_0" app:key="pref_custom_repo_0"
app:title="@string/loading"> app:title="@string/loading">
<!-- Custom repos can't be enabled/disabled. Instead, they must be deleted. Show a disabled
switch to indicate that. -->
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24" app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_0_enabled" app:key="pref_custom_repo_0_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled" app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" /> app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24" app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_0_website" app:key="pref_custom_repo_0_website"
@ -156,11 +136,13 @@
app:key="pref_custom_repo_1" app:key="pref_custom_repo_1"
app:title="@string/loading"> app:title="@string/loading">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24" app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_1_enabled" app:key="pref_custom_repo_1_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled" app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" /> app:switchTextOn="@string/custom_repo_always_on" />/>
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24" app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_1_website" app:key="pref_custom_repo_1_website"
@ -191,11 +173,13 @@
app:key="pref_custom_repo_2" app:key="pref_custom_repo_2"
app:title="@string/loading"> app:title="@string/loading">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24" app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_2_enabled" app:key="pref_custom_repo_2_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled" app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" /> app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24" app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_2_website" app:key="pref_custom_repo_2_website"
@ -226,11 +210,13 @@
app:key="pref_custom_repo_3" app:key="pref_custom_repo_3"
app:title="@string/loading"> app:title="@string/loading">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24" app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_3_enabled" app:key="pref_custom_repo_3_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled" app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" /> app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24" app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_3_website" app:key="pref_custom_repo_3_website"
@ -261,11 +247,13 @@
app:key="pref_custom_repo_4" app:key="pref_custom_repo_4"
app:title="@string/loading"> app:title="@string/loading">
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24" app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_4_enabled" app:key="pref_custom_repo_4_enabled"
app:singleLineTitle="false" app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled" app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" /> app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference <com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24" app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_4_website" app:key="pref_custom_repo_4_website"

@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="QueryAllPackagesPermission"> tools:ignore="QueryAllPackagesPermission">
<uses-sdk tools:overrideLibrary="io.sentry.android" />
<application android:icon="@mipmap/ic_launcher" android:enableOnBackInvokedCallback="true" <application android:icon="@mipmap/ic_launcher" android:enableOnBackInvokedCallback="true"
tools:targetApi="tiramisu"> tools:targetApi="tiramisu">

@ -1,29 +1,18 @@
package com.fox2code.mmm.sentry; package com.fox2code.mmm.sentry;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.androidacy.AndroidacyUtil; import com.fox2code.mmm.androidacy.AndroidacyUtil;
import org.jetbrains.annotations.NotNull; import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Writer;
import io.sentry.Breadcrumb;
import io.sentry.Hint;
import io.sentry.JsonObjectWriter;
import io.sentry.NoOpLogger;
import io.sentry.Sentry; import io.sentry.Sentry;
import io.sentry.SentryOptions;
import io.sentry.TypeCheckHint;
import io.sentry.android.core.SentryAndroid; import io.sentry.android.core.SentryAndroid;
import io.sentry.android.fragment.FragmentLifecycleIntegration; import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.okhttp.SentryOkHttpInterceptor;
import io.sentry.hints.DiskFlushNotification;
public class SentryMain { public class SentryMain {
public static final boolean IS_SENTRY_INSTALLED = true; public static final boolean IS_SENTRY_INSTALLED = true;
@ -33,13 +22,30 @@ public class SentryMain {
* Initialize Sentry * Initialize Sentry
* Sentry is used for crash reporting and performance monitoring. The SDK is explcitly configured not to send PII, and server side scrubbing of sensitive data is enabled (which also removes IP addresses) * Sentry is used for crash reporting and performance monitoring. The SDK is explcitly configured not to send PII, and server side scrubbing of sensitive data is enabled (which also removes IP addresses)
*/ */
@SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"})
public static void initialize(final MainApplication mainApplication) { public static void initialize(final MainApplication mainApplication) {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
SharedPreferences.Editor editor = mainApplication.getSharedPreferences("sentry", Context.MODE_PRIVATE).edit();
editor.putString("lastExitReason", "crash");
editor.putLong("lastExitTime", System.currentTimeMillis());
editor.apply();
// If we just let the default uncaught exception handler handle the
// exception, the app will hang and never close.
// So we need to kill the app ourselves.
System.exit(1);
});
SentryAndroid.init(mainApplication, options -> { SentryAndroid.init(mainApplication, options -> {
// If crash reporting is disabled, stop here. // If crash reporting is disabled, stop here.
if (!MainApplication.isCrashReportingEnabled()) { if (!MainApplication.isCrashReportingEnabled()) {
options.setDsn(""); options.setDsn("");
} else { } else {
options.addIntegration(new FragmentLifecycleIntegration(mainApplication, true, true)); options.addIntegration(new FragmentLifecycleIntegration(mainApplication, true, true));
options.setCollectAdditionalContext(true);
options.setAttachThreads(true);
options.setAttachStacktrace(true);
options.setEnableNdk(true);
// Intercept okhttp requests to add sentry headers
options.addInAppInclude("com.fox2code.mmm");
// Sentry sends ABSOLUTELY NO Personally Identifiable Information (PII) by default. // Sentry sends ABSOLUTELY NO Personally Identifiable Information (PII) by default.
// Already set to false by default, just set it again to make peoples feel safer. // Already set to false by default, just set it again to make peoples feel safer.
options.setSendDefaultPii(false); options.setSendDefaultPii(false);
@ -49,64 +55,25 @@ public class SentryMain {
// Add a callback that will be used before the event is sent to Sentry. // Add a callback that will be used before the event is sent to Sentry.
// With this callback, you can modify the event or, when returning null, also discard the event. // With this callback, you can modify the event or, when returning null, also discard the event.
options.setBeforeSend((event, hint) -> { options.setBeforeSend((event, hint) -> {
if (BuildConfig.DEBUG) { // Debug sentry events for debug. // Save lastEventId to private shared preferences
StringBuilder stringBuilder = new StringBuilder("Sentry report debug: "); SharedPreferences sharedPreferences = MainApplication.getINSTANCE().getSharedPreferences("sentry", Context.MODE_PRIVATE);
try { String lastEventId = Objects.requireNonNull(event.getEventId()).toString();
event.serialize(new JsonObjectWriter(new Writer() { SharedPreferences.Editor editor = sharedPreferences.edit();
@Override editor.putString("lastEventId", lastEventId);
public void write(char[] cbuf) { editor.apply();
stringBuilder.append(cbuf); return event;
} });
// Filter breadrcrumb content from crash report.
@Override options.setBeforeBreadcrumb((breadcrumb, hint) -> {
public void write(String str) { String url = (String) breadcrumb.getData("url");
stringBuilder.append(str); if (url == null || url.isEmpty()) return breadcrumb;
} if ("cloudflare-dns.com".equals(Uri.parse(url).getHost())) return null;
if (AndroidacyUtil.isAndroidacyLink(url)) {
@Override breadcrumb.setData("url", AndroidacyUtil.hideToken(url));
public void write(char[] chars, int i, int i1) {
stringBuilder.append(chars, i, i1);
}
@Override
public void write(String str, int off, int len) {
stringBuilder.append(str, off, len);
}
@Override
public void flush() {
}
@Override
public void close() {
}
}, 4), NoOpLogger.getInstance());
} catch (IOException ignored) {
}
Log.i(TAG, stringBuilder.toString());
}
if (MainApplication.isCrashReportingEnabled()) {
return event;
} else {
// We need to do this to avoid crash delay on crash when the event is dropped
DiskFlushNotification diskFlushNotification = hint.getAs(
TypeCheckHint.SENTRY_TYPE_CHECK_HINT, DiskFlushNotification.class);
if (diskFlushNotification != null) diskFlushNotification.markFlushed();
return null;
} }
return breadcrumb;
}); });
} }
// Filter breadrcrumb content from crash report.
options.setBeforeBreadcrumb((breadcrumb, hint) -> {
String url = (String) breadcrumb.getData("url");
if (url == null || url.isEmpty()) return breadcrumb;
if ("cloudflare-dns.com".equals(Uri.parse(url).getHost()))
return null;
if (AndroidacyUtil.isAndroidacyLink(url)) {
breadcrumb.setData("url", AndroidacyUtil.hideToken(url));
}
return breadcrumb;
});
}); });
} }

@ -6,7 +6,7 @@ buildscript {
gradlePluginPortal() gradlePluginPortal()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
project.ext.latestAboutLibsRelease = "10.5.0" project.ext.latestAboutLibsRelease = "10.5.2"
project.ext.sentryConfigFile = new File(rootDir, "sentry.properties").getAbsoluteFile() project.ext.sentryConfigFile = new File(rootDir, "sentry.properties").getAbsoluteFile()
project.ext.hasSentryConfig = sentryConfigFile.exists() project.ext.hasSentryConfig = sentryConfigFile.exists()
project.ext.sentryCli = [ project.ext.sentryCli = [

@ -100,7 +100,7 @@ Note²: For `minMagisk`, `XX.Y` is parsed as `XXY00`, so you can just put the Ma
(For example `- Hello world` will be transformed to `[*] Hello world`, do not apply to modules installed from storage) (For example `- Hello world` will be transformed to `[*] Hello world`, do not apply to modules installed from storage)
Note: Fox's Mmm use fallback Note: Fox's Mmm use fallback
[here](app/src/main/java/com/fox2code/mmm/utils/PropUtils.java#L36) [here](../app/src/main/java/com/fox2code/mmm/utils/PropUtils.java#L36)
for some modules for some modules
Theses values are only used if not defined in the `module.prop` files Theses values are only used if not defined in the `module.prop` files
So the original module maker can still override them So the original module maker can still override them
@ -173,7 +173,7 @@ mmm_exec hideLoading
mmm_exec setSupportLink https://github.com/Fox2Code/FoxMagiskModuleManager mmm_exec setSupportLink https://github.com/Fox2Code/FoxMagiskModuleManager
``` ```
[You may look at the examples modules and their codes.](examples) [You may look at the examples modules and their codes.](../examples)
## Developer mode ## Developer mode

@ -0,0 +1,47 @@
## Adding custom repositories
You have two options:
### Option 1: Add your repo to the app for yourself:
1. You need the modules.json link of your repo, if you don't have one please contact the repo owner.
2. Open the app and go to the settings.
3. Go to repos at the top.
4. Scroll to the bottom and click on the "Add repo" button.
5. Paste the modules.json link and click on the "Add" button.
6. You can now download modules from your repo.
### Option 2: Add your repo to the app for everyone:
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))
These guidelines are not mandatory, but not following them may result in your repo being removed or
not being added in the first place.
<details>
<summary>Click to see the guidelines</summary>
- Repos must process and take-down off their repo module where it's removal was provably
requested by
their
original author
- Repos may not collect and/or distribute any personally identifiable data (including IP
addresses) 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 and/or have a
way to remove their modules from the repository
- Modules owners must be aware of any change made of the distributed version of their modules.
- Repos should make an effort to keep users safe, via a review process, or by using a
whitelist/blacklist
</details>
In all scenarios, insofar that their policies are not in conflict with the above, the repo owner's
poloicies take precedence over the above guidelines. We encourage users to check the guidelines of
the repo they are using.

@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:Reserv
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true # android.enableJetifier=true
# Fox builds props mods # Fox builds props mods
org.gradle.parallel=true org.gradle.parallel=true

@ -1,6 +1,6 @@
#Sun Jun 05 10:40:53 EDT 2022 #Sun Jun 05 10:40:53 EDT 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
Loading…
Cancel
Save