Merge pull request #242 from Androidacy/master

1.0.1 update
pull/246/head v1.0.1
Androidacy Service Account 1 year 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
</summary>
I have health problems that made me work slow on everything.
I don't like sharing my health problmes but it has been to much recently for me to keep it for myself.
I have health problems that made me work slow on everything. I don't like sharing my health problmes
but it has been to much recently for me to keep it for myself.
This has been too much for me recently, so my moderators (same that on telegram)
This has been too much for me recently, so my moderators (same that on telegram)
will be handling the project for me for a while.
I had theses problems even before I started FoxMMM, the only reason no one noticed is because
I can work or go to any school because of how much pain and exhaustion I feel everyday.
I had theses problems even before I started FoxMMM, the only reason no one noticed is because I can
work or go to any school because of how much pain and exhaustion I feel everyday.
The only two thing that helped me reduce the pain is making code and playing with firends.
Even tho I'm very slow at doing anything, the only thing that made me look like I was working
on this project at a normal speed like someone that work is because 75% of my time was on this project.
Even tho I'm very slow at doing anything, the only thing that made me look like I was working on
this project at a normal speed like someone that work is because 75% of my time was on this project.
There was also some times I couldn't work on this projects for multiple days because of my health,
sometimes I was forcing myself to change one line of code from this project because doing nothing
@ -25,33 +25,33 @@ was more painful that trying something while in pain.
Spending time with my friend and working on this project was a sort of pain killer for me.
Even tho I received money from my parent and the governement for my health problems,
I didn't know what to do with it cause anything I could have bought had no use for me
because my extreme pain made me unable to use anything. (Even video games)
My health issues also prevented me to do any project of greater complexcity that this,
and without community support I would have been physically unable to continue this project.
Even tho I received money from my parent and the governement for my health problems, I didn't know
what to do with it cause anything I could have bought had no use for me because my extreme pain made
me unable to use anything. (Even video games)
My health issues also prevented me to do any project of greater complexcity that this, and without
community support I would have been physically unable to continue this project.
There was clues of my health problems, right on this project, and theses are the following:
- My commit time of day being random proving I have no jobs.
- Me not commiting for entire week, or having only commited one line in a week.
- Me taking too much time to publish release after I did the relase commit.
- Me missing obvious bugs and being able to do simple task properly
(Well maybe this last one is harder to proove via commit history)
But sine many peoples are faking health issues for clout, if any data-scientist want
to do an analysis to proove what it would make my day, and I would be happy to give money
if someone does that because I don't know what do to with my money at this point.
But sine many peoples are faking health issues for clout, if any data-scientist want to do an
analysis to proove what it would make my day, and I would be happy to give money if someone does
that because I don't know what do to with my money at this point.
This is really sickening peoples need to give proof for their mental/health
issues because some peoples fake having thoses issues for clout.
This is really sickening peoples need to give proof for their mental/health issues because some
peoples fake having thoses issues for clout.
If you want to use my health problems for coult, I don't care as long as you are respectful,
at least you won't be hurting peoples with mental/health issues by faking having thoses issues.
If you want to use my health problems for coult, I don't care as long as you are respectful, at
least you won't be hurting peoples with mental/health issues by faking having thoses issues.
I'll probably delete this section once my health would be gotten better, or at
least good enough for me to not be stuck on my bed at least once a day because of pain.
I'll probably delete this section once my health would be gotten better, or at least good enough for
me to not be stuck on my bed at least once a day because of pain.
</details>
@ -68,9 +68,10 @@ Main activity:
[<img src="screenshot-dark.jpg" width="250"/>](screenshot-dark.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**
@ -80,11 +81,13 @@ The official Magisk has dropped support to download online modules, so I made Fo
## Requirements
Minimum:
- Android 5.0+
- Magisk 19.0+
- An internet connection
Recommended:
- Android 6.0+
- Magisk 21.2+
- An internet connection
@ -93,17 +96,18 @@ Note: This app may require the use of a VPN in countries with a state wide firew
## For users
To install the app go to [releases](https://github.com/Fox2Code/FoxMagiskModuleManager/releases),
To install the app go to [releases](https://github.com/Fox2Code/FoxMagiskModuleManager/releases),
and download and install the latest `.apk` on your device.
## Repositories Available
The app currently use these two repos as module sources, each with their own benefits and drawback:
The app currently use these two repos as module sources, each with their own benefits and
drawbacks:
(Note: Each module repo can be disabled in the settings of the app)
(Note²: I do not own or actively monitor any of the repos or modules, **download at your own risk**)
#### [https://github.com/Magisk-Modules-Alt-Repo](https://github.com/Magisk-Modules-Alt-Repo)
#### [https://github.com/Magisk-Modules-Alt-Repo](https://github.com/Magisk-Modules-Alt-Repo)
- Accepting new modules [here](https://github.com/Magisk-Modules-Alt-Repo/submission)
- Less restrictive than the original repo
- Officially supported by Fox's mmm
@ -113,22 +117,25 @@ Support:
[![GitHub issues](https://img.shields.io/github/issues/Magisk-Modules-Alt-Repo/submission)](https://github.com/Magisk-Modules-Alt-Repo/submission/issues)
#### [https://www.androidacy.com/modules-repo/](https://www.androidacy.com/modules-repo/)
- Accepting new modules [here](https://www.androidacy.com/module-repository-applications/)
- Modules downloadable easily outside the app
- Officially supported by Fox's mmm
- Contains ads to help cover server costs
- May show ads to help cover infrastrcture costs.
- [Read more](https://www.androidacycom/doing-it-alone-the-what-the-how-and-the-why/)
| [Privacy policy](https://www.androidacy.com/privacy/)
- Added features like module reviews, automatic VirusTotal scans, and more
Support:
[![Telegram Group](https://img.shields.io/endpoint?color=neon&style=flat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fandroidacy_discussions)](https://telegram.dog/androidacy_discussions)
If a module is in multiple repos, the manager will just pick the most up to date version
of the module, if a module is in multiple repos it will just use first registered repo.
If a module is in multiple repos, the manager will just pick the most up to date version of the
module, if a module is in multiple repos it will just use first registered repo.
Note: If you or a friend uploaded a module and it doesn't appear in your module
list you can disable the low quality filter in the app settings.
Go to the [developer documentation](DEVELOPERS.md) for more info.
Note: If you or a friend uploaded a module and it doesn't appear in your module list you can disable
the low quality filter in the app settings.
Go to the [developer documentation](docs/DEVELOPERS.md) for more info.
## For developers
@ -142,14 +149,15 @@ And if you want to be event fancier you can setup `config` to your own config ap
It also add new ways to control the installer ui via a new `#!` command system
It allow module developers to have a more customizable install experience
For more information please check the [developer documentation](DEVELOPERS.md)
For more information please check the [developer documentation](docs/DEVELOPERS.md)
## For translators
We use Weblate for translations: https://translate.nift4.org/engage/foxmmm/
(Make sure to check your spam folder when registering)
If you do not want to register on the self-hosted Weblate instance, you can do a pull request on GitHub:
If you do not want to register on the self-hosted Weblate instance, you can do a pull request on
GitHub:
See [`app/src/main/res/values/strings.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/strings.xml)
and [`app/src/main/res/values/arrays.xml`](https://github.com/Fox2Code/FoxMagiskModuleManager/blob/master/app/src/main/res/values/arrays.xml)
@ -159,46 +167,39 @@ If your language is right to left don't forget to set `lang_support_rtl` to `tru
Translators are not expected to have any previous coding experience.
## License
See [LICENSE](LICENCE). Library licenses can be found in the licenses section of the app.
Cronet is licensed under the Apache License, Version 2.0. Static libraries are licensed under
the BSD license. See [LICENSE](https://chromium.googlesource.com/chromium/src/+/master/LICENSE)
for more information. Libraries were built using the microg build script which can be found [here](https://github.com/microg/cronet-build).
## I want to add my own repo
To add you own repo to Fox's mmm it need to follow theses conditions:
- The module repo or at least one of it's owners must be known.
- Modules in the repo must be monitored, and malicious modules must be removed.
- Module repo must have a valid, working, automatically or frequently updated `modules.json`
([Example](https://github.com/Magisk-Modules-Alt-Repo/json/blob/main/modules.json))
In addition of these initial condition the repo must follow these rules:
- Repos must process and take-down off their repo module where it's removal was requested
by their original author, even if their licences legally allow their distributions.
- Repos may collect and store "mixed anonymous data" without user permission
(Anonymous means no personal data, usernames, email, or IP addresses)
(Mixed means users data must be split and not that separate data is not linkable together)
- Temporary storage of IPs address without user consent is allowed for rate limiting, GeoIP,
security reason, and must not be used for any other purpose without user explicit consent.
(GeoIP is the process of getting the country of an IP address)
- Repos may not collect and/or distribute any personal data without informing users that they do so and offering a way to opt out
- Modules owners must be aware that their modules are being hosted on the repository
(This rule doesn't apply for modules from `Magisk-Modules-Repo` last updated before 2022)
- Modules owners must be aware of any change made of the distributed version of their modules.
Please note Androidacy has their Module Repository Policies outlined [on their website](https://www.androidacy.com/module-requirements/?utm_source=foxmmm-readme&utm_medium=web). Please refer to that document for the latest changes regarding their Repository.
If all of these conditions are met you can open an issue for review.
(And don't forget to include a link to the `modules.json`)
If an existing repo is not respecting theses rules please open an issue.
If a repo is repeatedly violating these rule will be removed from the app.
Last update of theses rules are: 4 May 2022
Please note that these rules does not apply retroactively.
If your post an issue about rules violation they must violate both the version of
the rules at the moment of the incident and the latest version of the rules.
(This paragraph doesn't apply for license violation, legal requests, or illegal behaviour.)
In addition, we advise you to contact the repo host beforehand to attempt to resolve any issues. This helps avoid unnecessary conflict, and most of the time will get your issue solved quickly!
Fox's Magisk Manager, the icon, and names are copyright
2021-present [Fox2Code](https://github.com/Fox2Code). The Androidacy name, logo, integration, and
later portions of the code are copyright
2022-present [Androidacy](https://www.androidacy.com/?utm_source=fox-repo&utm_medium=web). See
[LICENSE](LICENCE) for details. Library licenses can be found in the licenses section of the app.
Modules are not covered by this license, please check the license of each module before using it.
Some third party backend services may be proprietary, please check their terms of service before
using them.
## Disclaimer
In no event shall the developer be liable for any special, direct, indirect, consequential, or
incidental damages or any damages whatsoever, whether in an action of contract, negligence or other
tort, arising out of or in connection with the use of the app or the contents of the app. The
developer reserves the right to make additions, deletions, or modification to the contents on the
app at any time without prior notice.
This app is not affiliated with Magisk or its developers, nor with any of the module repos or
developers of the modules.
## Add your own repos
See [the documentation](docs/add-repo.md)
## Issues with a repo
If you have a problem with a repo, please contact the repo owner **first**. If you are unable to
reach them or they are not willing to help, you can contact us as a last resort.
Default repo owners:
- Androidacy: [Telegram](https://telegram.dog/androidacy_discussions)
- Magisk-Modules-Alt-Repo: [Telegram](https://github.com/Magisk-Modules-Alt-Repo/submission/issues)

@ -8,7 +8,7 @@ plugins {
android {
namespace "com.fox2code.mmm"
compileSdk 33
buildToolsVersion '30.0.3'
buildToolsVersion '33.0.0'
signingConfigs {
release {
// Everything comes from local.properties
@ -25,20 +25,42 @@ android {
defaultConfig {
applicationId "com.fox2code.mmm"
minSdk 21
minSdk 23
targetSdk 33
versionCode 60
versionName "0.6.8"
versionCode 61
versionName "1.0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.release
}
splits {
// Configures multiple APKs based on ABI.
abi {
// Enables building multiple APKs per ABI.
enable true
// By default all ABIs are included, so use reset() and include to specify that we only
// want APKs for x86 and x86_64.
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies a list of ABIs that Gradle should create APKs for.
include "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
// Specifies that we do not want to also generate a universal APK that includes all ABIs.
universalApk true
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
'proguard-rules.pro'
}
debug {
applicationIdSuffix '.debug'
@ -57,6 +79,17 @@ android {
dimension "type"
buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true"
buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true"
if (hasSentryConfig) {
Properties properties = new Properties()
try (FileInputStream fis = new FileInputStream(sentryConfigFile)) {
properties.load(fis)
}
buildConfigField "String", "SENTRY_TOKEN", '"' + properties.getProperty("auth." + "token") + '"'
} else {
buildConfigField "String", "SENTRY_TOKEN", '""'
}
// Get the androidacy client ID from the androidacy.properties
Properties properties = new Properties()
// If androidacy.properties doesn't exist, use the default client ID which is heavily
@ -91,6 +124,16 @@ android {
// Disable crash reporting for F-Droid flavor by default
buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false"
if (hasSentryConfig) {
Properties properties = new Properties()
try (FileInputStream fis = new FileInputStream(sentryConfigFile)) {
properties.load(fis)
}
buildConfigField "String", "SENTRY_TOKEN", '"' + properties.getProperty("auth." + "token") + '"'
} else {
buildConfigField "String", "SENTRY_TOKEN", '""'
}
// Repo with ads or tracking feature are disabled by default for the
// F-Droid flavor.
buildConfigField("java.util.List<String>",
@ -99,7 +142,7 @@ android {
// Get the androidacy client ID from the androidacy.properties
Properties properties = new Properties()
// If androidacy.properties doesn't exist, use the default client ID which is limited
// If androidacy.properties doesn't exist, use the fdroid client ID which is limited
// to 50 requests per minute
if (project.rootProject.file('androidacy.properties').exists()) {
properties.load(project.rootProject.file('androidacy.properties').newDataInputStream())
@ -111,8 +154,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
lint {
disable 'MissingTranslation'
@ -121,14 +164,14 @@ android {
}
aboutLibraries {
additionalLicenses = ["LGPL_3_0_only"]
additionalLicenses = ["LGPL_3_0_only", "Apache_2_0"]
}
// "true" is not allowed inside this block, use "hasSentryConfig" instead.
// This is because gradle doesn't allow to enable/disable plugins conditionally
sentry {
// Disable sentry on F-Droid flavor
ignoredFlavors = hasSentryConfig ? [] : ["default", "fdroid"]
ignoredFlavors = []
// Disables or enables the handling of Proguard mapping for Sentry.
// If enabled the plugin will generate a UUID and will take care of
@ -164,14 +207,14 @@ sentry {
// Does auto instrumentation for specified features through bytecode manipulation.
// Default is enabled.
tracingInstrumentation {
enabled = hasSentryConfig
enabled = true
}
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
// Default is enabled.
// Only available v3.1.0 and above.
autoInstallation {
enabled = hasSentryConfig
enabled = true
// Specifies a version of the sentry-android SDK and fragment, timber and okhttp integrations.
//
@ -188,13 +231,6 @@ sentry {
configurations {
implementation.exclude group: 'org.jetbrains', module: 'annotations'
if (!hasSentryConfig) {
implementation.exclude group: 'io.sentry', module: 'sentry-android'
implementation.exclude group: 'io.sentry', module: 'sentry-android-fragment'
implementation.exclude group: 'io.sentry', module: 'sentry-android-okhttp'
implementation.exclude group: 'io.sentry', module: 'sentry-android-core'
implementation.exclude group: 'io.sentry', module: 'sentry-android-ndk'
}
}
dependencies {
@ -212,7 +248,7 @@ dependencies {
implementation "dev.rikka.rikkax.insets:insets:1.3.0"
implementation 'com.github.Dimezis:BlurView:version-2.0.2'
implementation 'com.github.KieronQuinn:MonetCompat:0.4.1'
implementation 'com.github.Fox2Code:FoxCompat:0.1.5'
implementation 'com.github.Fox2Code:FoxCompat:0.1.6'
// Update the version code in the root build.gradle
implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}"
@ -220,21 +256,14 @@ dependencies {
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.10'
// Chromium cronet from microG
implementation fileTree(dir: 'libs', include: '*.jar')
// Chromium cronet from androidacy
implementation 'com.androidacy:cronet-common:108.0.5359.95'
implementation 'com.androidacy:cronet-native:108.0.5359.95'
// Force prefer our own version of Cronet
implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.9'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
if (hasSentryConfig) {
// Error reporting
defaultImplementation 'io.sentry:sentry-android:6.9.2'
defaultImplementation 'io.sentry:sentry-android-fragment:6.9.2'
defaultImplementation 'io.sentry:sentry-android-okhttp:6.9.2'
defaultImplementation 'io.sentry:sentry-android-core:6.9.2'
defaultImplementation 'io.sentry:sentry-android-ndk:6.9.2'
}
implementation 'io.sentry:sentry-android:6.9.2'
// Markdown
implementation "io.noties.markwon:core:4.6.2"
@ -242,13 +271,19 @@ dependencies {
implementation "io.noties.markwon:image:4.6.2"
implementation "io.noties.markwon:syntax-highlight:4.6.2"
implementation 'com.google.net.cronet:cronet-okhttp:0.1.0'
// Ignore all org.chromium.net dependencies
annotationProcessor "io.noties:prism4j-bundler:2.0.0"
implementation "com.caverock:androidsvg:1.4"
// Icons
// implementation "com.mikepenz:iconics-core:3.2.5"
//implementation "androidx.appcompat:appcompat:${versions.appCompat}"
// implementation 'com.mikepenz:community-material-typeface:7.0.96.0-kotlin@aar'
// Test
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
// Add okhttp logging interceptor if debug build
debugImplementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.10'
}
if (hasSentryConfig) {
@ -265,8 +300,7 @@ if (hasSentryConfig) {
}
final String sentrySrc = hasSentryConfig ? 'src/sentry/java' : 'src/sentryless/java'
final String sentryManifestSrc = hasSentryConfig ?
'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml'
final String sentryManifestSrc = hasSentryConfig ? 'src/sentry/AndroidManifest.xml' : 'src/sentryless/AndroidManifest.xml'
android {
sourceSets {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

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

@ -13,17 +13,23 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
@ -46,16 +52,25 @@ import com.fox2code.mmm.utils.BlurUtils;
import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.NoodleDebug;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Objects;
import eightbitlab.com.blurview.BlurView;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
private static final String TAG = "MainActivity";
private static final int PRECISION = 10000;
public static boolean noodleDebugState = BuildConfig.DEBUG;
public final ModuleViewListBuilder moduleViewListBuilder;
public LinearProgressIndicator progressIndicator;
private ModuleViewAdapter moduleViewAdapter;
@ -71,8 +86,9 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
private RecyclerView moduleList;
private CardView searchCard;
private SearchView searchView;
private NoodleDebug noodleDebug;
private boolean initMode;
private boolean doSetupNowRunning;
private boolean urlFactoryInstalled = false;
public MainActivity() {
this.moduleViewListBuilder = new ModuleViewListBuilder(this);
@ -88,7 +104,18 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override
protected void onCreate(Bundle savedInstanceState) {
this.initMode = true;
noodleDebugState = MainApplication.isDeveloper();
// Ensure HTTP Cache directories are created
Http.ensureCacheDirs(this);
if (!urlFactoryInstalled) {
try {
ExperimentalCronetEngine cronetEngine = new ExperimentalCronetEngine.Builder(this).build();
CronetURLStreamHandlerFactory cronetURLStreamHandlerFactory = new CronetURLStreamHandlerFactory(cronetEngine);
URL.setURLStreamHandlerFactory(cronetURLStreamHandlerFactory);
urlFactoryInstalled = true;
} catch (Throwable t) {
Log.e(TAG, "Failed to install CronetURLStreamHandlerFactory", t);
}
}
BackgroundUpdateChecker.onMainActivityCreate(this);
super.onCreate(savedInstanceState);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
@ -116,7 +143,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.moduleList = findViewById(R.id.module_list);
this.searchCard = findViewById(R.id.search_card);
this.searchView = findViewById(R.id.search_bar);
this.noodleDebug = new NoodleDebug(this, R.id.noodle_debug);
this.moduleViewAdapter = new ModuleViewAdapter();
this.moduleList.setAdapter(this.moduleViewAdapter);
this.moduleList.setLayoutManager(new LinearLayoutManager(this));
@ -158,11 +184,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
noodleDebug.push("Ensure Permissions");
ensurePermissions();
noodleDebug.pop();
ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules);
this.commonNext();
@ -171,21 +192,32 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override
public void onFailure(int error) {
Log.i(TAG, "Failed to get magisk path!");
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
this.commonNext();
}
public void commonNext() {
NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug();
doSetupNowRunning = true;
doSetupNow();
// Wait for doSetupNow to finish
while (doSetupNowRunning) {
try {
//noinspection BusyWait
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
/*if (BuildConfig.DEBUG) {
SharedPreferences prefs = MainApplication.getSharedPreferences();
if (BuildConfig.DEBUG) Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false)));
}*/
swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
updateScreenInsets(); // Fix an edge case
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
if (!Http.hasWebView()) // Check Http for WebView availability
moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW);
noodleDebug.push("Apply");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
runOnUiThread(() -> {
progressIndicator.setIndeterminate(false);
@ -193,17 +225,33 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
// Fix insets not being accounted for correctly
updateScreenInsets(getResources().getConfiguration());
});
// On every preferences change, log the change if debug is enabled
if (BuildConfig.DEBUG) {
Log.i("PrefsListener", "onCreate: Preferences: " + MainApplication.getSharedPreferences().getAll());
// Log all preferences changes
MainApplication.getSharedPreferences().registerOnSharedPreferenceChangeListener((prefs, key) -> Log.i("PrefsListener", "onSharedPreferenceChanged: " + key + " = " + prefs.getAll().get(key)));
}
Log.i(TAG, "Scanning for modules!");
noodleDebug.replace("Initialize Update");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Initialize Update");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
Log.w(TAG, "Need update on create?");
}
noodleDebug.replace("Check Update Compat");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update Compat");
AppUpdateManager.getAppUpdateManager().checkUpdateCompat();
noodleDebug.replace("Check Update");
/*if (BuildConfig.DEBUG) {
SharedPreferences prefs = MainApplication.getSharedPreferences();
Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false)));
}*/
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update");
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
/*if (BuildConfig.DEBUG) {
SharedPreferences prefs = MainApplication.getSharedPreferences();
Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false)));
}*/
if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
@ -211,16 +259,21 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
} else {
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
noodleDebug.replace("Check App Update");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
noodleDebug.replace("Check Json Update");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Json Update");
if (max != 0) {
/*if (BuildConfig.DEBUG) {
SharedPreferences prefs = MainApplication.getSharedPreferences();
Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false)));
}*/
int current = 0;
noodleDebug.push("");
// noodleDebug.push("");
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) {
noodleDebug.replace(localModuleInfo.id);
if (BuildConfig.DEBUG) Log.d("NoodleDebug", localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (Exception e) {
@ -231,7 +284,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
}
}
noodleDebug.pop();
}
}
runOnUiThread(() -> {
@ -241,16 +293,101 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
setActionBarBackground(null);
updateScreenInsets(getResources().getConfiguration());
});
noodleDebug.replace("Apply");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
/*if (BuildConfig.DEBUG) {
SharedPreferences prefs = MainApplication.getSharedPreferences();
Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false)));
}*/
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
noodleDebug.pop();
Log.i(TAG, "Finished app opening state!");
noodleDebug.unbind();
// noodleDebug.unbind();
}
}, true);
ExternalHelper.INSTANCE.refreshHelper(this);
this.initMode = false;
// Show an material alert dialog if lastEventId is not "" or null in the private sentry shared preferences
//noinspection ConstantConditions
if (MainApplication.isCrashReportingEnabled() && !BuildConfig.SENTRY_TOKEN.isEmpty()) {
SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE);
String lastExitReason = preferences.getString("lastExitReason", "");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Last Exit Reason: " + lastExitReason);
if (lastExitReason.equals("crash")) {
String lastEventId = preferences.getString("lastEventId", "");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Last Event ID: " + lastEventId);
if (!lastEventId.equals("")) {
// Three edit texts for the user to enter their email, name and a description of the issue
EditText email = new EditText(this);
email.setHint(R.string.email);
email.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
EditText name = new EditText(this);
name.setHint(R.string.name);
name.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
EditText description = new EditText(this);
description.setHint(R.string.additional_info);
// Set description to be multiline and auto resize
description.setSingleLine(false);
description.setMaxHeight(1000);
// Make description required-
new MaterialAlertDialogBuilder(this).setCancelable(false).setTitle(R.string.sentry_dialogue_title).setMessage(R.string.sentry_dialogue_message).setView(new LinearLayout(this) {{
setOrientation(LinearLayout.VERTICAL);
setPadding(40, 20, 40, 10);
addView(email);
addView(name);
addView(description);
}}).setPositiveButton(R.string.submit, (dialog, which) -> {
// Make sure the user has entered a description
if (description.getText().toString().equals("")) {
Toast.makeText(this, R.string.sentry_dialogue_no_description, Toast.LENGTH_LONG).show();
dialog.cancel();
}
preferences.edit().remove("lastEventId").apply();
preferences.edit().putString("lastExitReason", "").apply();
// Prevent strict mode violation
new Thread(() -> {
try {
HttpURLConnection connection = (HttpURLConnection) new URL("https" + "://sentry.io/api/0/projects/androidacy-i6/foxmmm/user-feedback/").openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + BuildConfig.SENTRY_TOKEN);
// Setups the JSON body
String nameString = name.getText().toString();
String emailString = email.getText().toString();
if (nameString.equals("")) nameString = "Anonymous";
if (emailString.equals("")) emailString = "Anonymous";
JSONObject body = new JSONObject();
body.put("event_id", lastEventId);
body.put("name", nameString);
body.put("email", emailString);
body.put("comments", description.getText().toString());
// Send the request
connection.setDoOutput(true);
connection.getOutputStream().write(body.toString().getBytes());
connection.connect();
// For debug builds, log the response code and response body
if (BuildConfig.DEBUG) {
Log.d("NoodleDebug", "Response Code: " + connection.getResponseCode());
}
// Check if the request was successful
if (connection.getResponseCode() == 200) {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG).show());
} else {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
} catch (IOException | JSONException ignored) {
// Show a toast if the user feedback could not be submitted
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
}).start();
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
preferences.edit().remove("lastEventId").apply();
preferences.edit().putString("lastExitReason", "").apply();
Log.w(TAG, "User cancelled sentry dialog");
}).show();
}
}
}
}
private void cardIconifyUpdate() {
@ -319,7 +456,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.updateBlurState();
this.moduleViewListBuilder.setQuery(null);
Log.i(TAG, "Item After");
noodleDebugState = MainApplication.isDeveloper();
this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter);
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
@ -328,8 +464,6 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules);
this.commonNext();
@ -338,14 +472,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override
public void onFailure(int error) {
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
noodleDebug.setEnabled(noodleDebugState);
noodleDebug.bind();
this.commonNext();
}
public void commonNext() {
Log.i(TAG, "Common Before");
NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug();
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
@ -354,26 +485,23 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
RepoManager.getINSTANCE().updateEnabledStates();
noodleDebug.push("");
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
runOnUiThread(() -> {
progressIndicator.setIndeterminate(false);
progressIndicator.setMax(PRECISION);
});
noodleDebug.replace("Check Update");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Update");
RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> progressIndicator.setProgressCompat((int) (value * PRECISION), true)));
runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true);
progressIndicator.setVisibility(View.GONE);
});
}
noodleDebug.replace("Apply");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
Log.i(TAG, "Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
noodleDebug.pop();
Log.i(TAG, "Common After");
noodleDebug.unbind();
}
});
this.initMode = false;
@ -390,33 +518,33 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.swipeRefreshLayout.setRefreshing(false);
return; // Do not double scan
}
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Refresh");
this.progressIndicator.setVisibility(View.VISIBLE);
this.progressIndicator.setProgressCompat(0, false);
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
// this.swipeRefreshLayout.setRefreshing(true); ??
new Thread(() -> {
noodleDebug.setEnabled(noodleDebugState);
NoodleDebug noodleDebug = this.noodleDebug.bind();
Http.cleanDnsCache(); // Allow DNS reload from network
noodleDebug.push("Check Update");
// noodleDebug.push("Check Update");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.REPO_UPDATE_FAILED);
} else {
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
// noodleDebug.replace("Check App Update");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
// noodleDebug.replace("Check Json Update");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Check Json Update");
if (max != 0) {
int current = 0;
noodleDebug.push("");
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) {
noodleDebug.replace(localModuleInfo.id);
if (BuildConfig.DEBUG) Log.d("NoodleDebug", localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (Exception e) {
@ -427,23 +555,20 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
}
}
noodleDebug.pop();
}
}
// noodleDebug.replace("Apply");
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Apply");
runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false);
});
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
}
RepoManager.getINSTANCE().updateEnabledStates();
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
// noodleDebug.pop();
// noodleDebug.unbind();
/*
noodleDebug.unbind();
*/
}, "Repo update thread").start();
}
@ -487,33 +612,50 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@SuppressLint("RestrictedApi")
private void ensurePermissions() {
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Ensure Permissions");
// First, check if user has said don't ask again by checking if pref_dont_ask_again_notification_permission is true
if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("pref_dont_ask_again_notification_permission", false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// Show a dialog explaining why we need this permission, which is to show
// notifications for updates
runOnUiThread(() -> {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.permission_notification_title);
builder.setMessage(R.string.permission_notification_message);
// Don't ask again checkbox
View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null);
CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(R.string.dont_ask_again);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply());
builder.setView(view);
builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> {
// Request the permission
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("pref_background_update_check", false).apply();
dialog.dismiss();
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Request Notification Permission");
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
// Show a dialog explaining why we need this permission, which is to show
// notifications for updates
runOnUiThread(() -> {
if (BuildConfig.DEBUG)
Log.d("NoodleDebug", "Show Notification Permission Dialog");
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.permission_notification_title);
builder.setMessage(R.string.permission_notification_message);
// Don't ask again checkbox
View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null);
CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(R.string.dont_ask_again);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply());
builder.setView(view);
builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> {
// Request the permission
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("pref_background_update_check", false).apply();
dialog.dismiss();
});
builder.show();
if (BuildConfig.DEBUG)
Log.d("NoodleDebug", "Show Notification Permission Dialog Done");
});
builder.show();
});
} else {
// Request the permission
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Request Notification Permission");
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
if (BuildConfig.DEBUG) {
// Log if granted via onRequestPermissionsResult
Log.d("NoodleDebug", "Request Notification Permission Done. Result: " + (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED));
}
doSetupNowRunning = false;
}
// Next branch is for < android 13 and user has blocked notifications
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && !NotificationManagerCompat.from(this).areNotificationsEnabled()) {
runOnUiThread(() -> {
@ -533,16 +675,77 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
doSetupNowRunning = false;
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("pref_background_update_check", false).apply();
dialog.dismiss();
doSetupNowRunning = false;
});
builder.show();
});
} else {
doSetupNowRunning = false;
}
} else {
if (BuildConfig.DEBUG)
Log.d("NoodleDebug", "Notification Permission Already Granted or Don't Ask Again");
doSetupNowRunning = false;
}
}
// Method to show a setup box on first launch
@SuppressLint({"InflateParams", "RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref"})
private void doSetupNow() {
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Do setup now");
// Check if this is the first launch
SharedPreferences prefs = MainApplication.getSharedPreferences();
boolean firstLaunch = MainApplication.getBootSharedPreferences().getBoolean("first_launch", true);
if (BuildConfig.DEBUG) Log.d("Noodle", "First launch: " + firstLaunch);
if (firstLaunch) {
// Show setup box
runOnUiThread(() -> {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setCancelable(false);
builder.setTitle(R.string.setup_title);
View view = getLayoutInflater().inflate(R.layout.setup_box, null);
builder.setView(view);
// For now, we'll just have the positive button save the preferences and dismiss the dialog
builder.setPositiveButton(R.string.setup_button, (dialog, which) -> {
// Set the preferences
prefs.edit().putBoolean("pref_background_update_check", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_background_update_check))).isChecked()).commit();
prefs.edit().putBoolean("pref_crash_reporting", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_crash_reporting))).isChecked()).commit();
prefs.edit().putBoolean("pref_androidacy_repo_enabled", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_androidacy_repo))).isChecked()).commit();
prefs.edit().putBoolean("pref_magisk_alt_repo_enabled", ((MaterialSwitch) Objects.requireNonNull(((AlertDialog) dialog).findViewById(R.id.setup_magisk_alt_repo))).isChecked()).commit();
if (BuildConfig.DEBUG) {
Log.d("MainActivity", String.format("Background update check: %s, Crash reporting: %s, Androidacy repo: %s, Magisk alt repo: %s", prefs.getBoolean("pref_background_update_check", false), prefs.getBoolean("pref_crash_reporting", false), prefs.getBoolean("pref_androidacy_repo_enabled", false), prefs.getBoolean("pref_magisk_alt_repo_enabled", false)));
}
// Set pref_first_launch to false
MainApplication.getBootSharedPreferences().edit().putBoolean("first_launch", false).commit();
// Restart the app
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
finish();
startActivity(intent);
ensurePermissions();
});
builder.setNegativeButton(R.string.setup_button_skip, (dialog, which) -> {
MainApplication.getBootSharedPreferences().edit().putBoolean("first_launch", false).commit();
dialog.dismiss();
ensurePermissions();
});
builder.show();
// Set the switches appropriately
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setChecked(BuildConfig.ENABLE_AUTO_UPDATER);
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setChecked(BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING);
// Repos are a little harder, as the enabled_repos build config is an arraylist
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("androidacy_repo"));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("magisk_alt_repo"));
});
} else {
ensurePermissions();
}
}
}

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

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

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

@ -108,7 +108,7 @@ public final class AndroidacyRepoData extends RepoData {
deviceId = output;
}
}
// Now, get device model, manufacturer, and Android version
// Now, get device model, manufacturer, and Android version originally from
String deviceModel = android.os.Build.MODEL;
String deviceManufacturer = android.os.Build.MANUFACTURER;
String androidVersion = android.os.Build.VERSION.RELEASE;
@ -184,7 +184,8 @@ public final class AndroidacyRepoData extends RepoData {
}
String deviceId = generateDeviceId();
long time = System.currentTimeMillis();
if (this.androidacyBlockade > time) return false;
if (this.androidacyBlockade > time) return true; // fake it till you make it. Basically,
// don'e fail just becaue we're rate limited. API and web rate limits are different.
this.androidacyBlockade = time + 30_000L;
try {
if (this.token == null) {
@ -249,6 +250,9 @@ public final class AndroidacyRepoData extends RepoData {
@Override
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException {
if (BuildConfig.DEBUG) {
Log.d(TAG, "AndroidacyRepoData populate start");
}
if (!jsonObject.getString("status").equals("success"))
throw new JSONException("Response is not a success!");
String name = jsonObject.optString("name", "Androidacy Modules Repo");
@ -326,7 +330,9 @@ public final class AndroidacyRepoData extends RepoData {
String config = jsonObject.optString("config", "");
moduleInfo.config = config.isEmpty() ? null : config;
PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks
Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " + moduleInfo.version + " " + moduleInfo.versionCode);
// Log.d(TAG,
// "Module " + moduleInfo.name + " " + moduleInfo.id + " " + moduleInfo
// .version + " " + moduleInfo.versionCode);
}
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
while (moduleInfoIterator.hasNext()) {

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

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

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

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

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

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

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

@ -1,6 +1,9 @@
package com.fox2code.mmm.settings;
import static java.lang.Integer.parseInt;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
@ -9,6 +12,8 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -61,14 +66,17 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.internal.TextWatcherAdapter;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.google.common.hash.Hashing;
import com.mikepenz.aboutlibraries.LibsBuilder;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import org.json.JSONException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
@ -78,6 +86,50 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper();
private static int devModeStep = 0;
// Shamelessly adapted from https://github.com/DrKLO/Telegram/blob/2c71f6c92b45386f0c2b25f1442596462404bb39/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java#L1254
public final static int PERFORMANCE_CLASS_LOW = 0;
public final static int PERFORMANCE_CLASS_AVERAGE = 1;
public final static int PERFORMANCE_CLASS_HIGH = 2;
@PerformanceClass
public static int getDevicePerformanceClass() {
int devicePerformanceClass;
int androidVersion = Build.VERSION.SDK_INT;
int cpuCount = Runtime.getRuntime().availableProcessors();
int memoryClass =
((ActivityManager) MainApplication.getINSTANCE().getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int totalCpuFreq = 0;
int freqResolved = 0;
for (int i = 0; i < cpuCount; i++) {
try {
RandomAccessFile reader = new RandomAccessFile(String.format(Locale.ENGLISH, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i), "r");
String line = reader.readLine();
if (line != null) {
totalCpuFreq += parseInt(line) / 1000;
freqResolved++;
}
reader.close();
} catch (Throwable ignore) {}
}
int maxCpuFreq = freqResolved == 0 ? -1 : (int) Math.ceil(totalCpuFreq / (float) freqResolved);
if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion <= 21 || cpuCount <= 4 && maxCpuFreq <= 1300 && memoryClass <= 128 && androidVersion <= 24) {
devicePerformanceClass = PERFORMANCE_CLASS_LOW;
} else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 2050 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) {
devicePerformanceClass = PERFORMANCE_CLASS_AVERAGE;
} else {
devicePerformanceClass = PERFORMANCE_CLASS_HIGH;
}
if (BuildConfig.DEBUG) {
Log.d(TAG, "getDevicePerformanceClass: androidVersion=" + androidVersion + " cpuCount=" + cpuCount + " memoryClass=" + memoryClass + " maxCpuFreq=" + maxCpuFreq + " devicePerformanceClass=" + devicePerformanceClass);
}
return devicePerformanceClass;
}
public @interface PerformanceClass {}
@Override
protected void onCreate(Bundle savedInstanceState) {
devModeStep = 0;
@ -123,6 +175,25 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
ListPreference themePreference = findPreference("pref_theme");
// If transparent theme(s) are set, disable monet
if (themePreference.getValue().equals("transparent_light")) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Transparent theme is set, disabling monet");
}
findPreference("pref_enable_monet").setEnabled(false);
// Toggle monet off
((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false);
SharedPreferences.Editor editor =
getPreferenceManager().getSharedPreferences().edit();
editor.putBoolean("pref_enable_monet", false).apply();
// Set summary
findPreference("pref_enable_monet").setSummary(R.string.monet_disabled_summary);
// Same for blur
findPreference("pref_enable_blur").setEnabled(false);
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false);
editor.putBoolean("pref_enable_blur", false).apply();
findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary);
}
themePreference.setSummaryProvider(p -> themePreference.getEntry());
themePreference.setOnPreferenceClickListener(p -> {
// You need to reboot your device at least once to be able to access dev-mode
@ -130,12 +201,65 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return false;
});
themePreference.setOnPreferenceChangeListener((preference, newValue) -> {
devModeStep = 0;
UiThreadHandler.handler.postDelayed(() -> {
MainApplication.getINSTANCE().updateTheme();
FoxActivity.getFoxActivity(this).setThemeRecreate(
MainApplication.getINSTANCE().getManagerThemeResId());
}, 1);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Theme changed, refreshing activity. New value: " + newValue);
}
// Immediately save
SharedPreferences.Editor editor =
getPreferenceManager().getSharedPreferences().edit();
editor.putString("pref_theme", (String) newValue).apply();
// If theme contains "transparent" then disable monet
if (newValue.toString().contains("transparent")) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Transparent theme is being set, disabling monet");
}
// Show a dialogue warning the user about issues with transparent themes and
// that blur/monet will be disabled
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.transparent_theme_dialogue_title)
.setMessage(R.string.transparent_theme_dialogue_message)
.setPositiveButton(R.string.ok, (dialog, which) -> {
// Toggle monet off
((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false);
editor.putBoolean("pref_enable_monet", false).apply();
// Set summary
findPreference("pref_enable_monet").setSummary(R.string.monet_disabled_summary);
// Same for blur
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false);
editor.putBoolean("pref_enable_blur", false).apply();
findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary);
// Refresh activity
devModeStep = 0;
UiThreadHandler.handler.postDelayed(() -> {
MainApplication.getINSTANCE().updateTheme();
FoxActivity.getFoxActivity(this).setThemeRecreate(
MainApplication.getINSTANCE().getManagerThemeResId());
}, 1);
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Revert to system theme
((ListPreference) findPreference("pref_theme")).setValue("system");
// Refresh activity
devModeStep = 0;
UiThreadHandler.handler.postDelayed(() -> {
MainApplication.getINSTANCE().updateTheme();
FoxActivity.getFoxActivity(this).setThemeRecreate(
MainApplication.getINSTANCE().getManagerThemeResId());
}, 1);
})
.show();
} else {
findPreference("pref_enable_monet").setEnabled(true);
findPreference("pref_enable_monet").setSummary(null);
findPreference("pref_enable_blur").setEnabled(true);
findPreference("pref_enable_blur").setSummary(null);
devModeStep = 0;
UiThreadHandler.handler.postDelayed(() -> {
MainApplication.getINSTANCE().updateTheme();
FoxActivity.getFoxActivity(this).setThemeRecreate(
MainApplication.getINSTANCE().getManagerThemeResId());
}, 1);
}
return true;
});
// Crash reporting
@ -157,13 +281,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
@ -177,9 +296,36 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
Preference enableBlur = findPreference("pref_enable_blur");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
enableBlur.setSummary(R.string.require_android_6);
enableBlur.setEnabled(false);
// Disable blur on low performance devices
if (getDevicePerformanceClass() < PERFORMANCE_CLASS_AVERAGE) {
// Show a warning
enableBlur.setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue.equals(true)) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.low_performance_device_dialogue_title)
.setMessage(R.string.low_performance_device_dialogue_message)
.setPositiveButton(R.string.ok, (dialog, which) -> {
// Toggle blur on
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(true);
SharedPreferences.Editor editor =
getPreferenceManager().getSharedPreferences().edit();
editor.putBoolean("pref_enable_blur", true).apply();
// Set summary
findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary);
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Revert to blur on
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false);
SharedPreferences.Editor editor =
getPreferenceManager().getSharedPreferences().edit();
editor.putBoolean("pref_enable_blur", false).apply();
// Set summary
findPreference("pref_enable_blur").setSummary(null);
})
.show();
}
return true;
});
}
Preference disableMonet = findPreference("pref_enable_monet");
@ -252,6 +398,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG ||
InstallerInitializer.peekMagiskPath() == null) {
// Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app
Log.d(TAG, String.format("Sentry installed: %s, debug: %s, magisk path: %s",
SentryMain.IS_SENTRY_INSTALLED, BuildConfig.DEBUG, InstallerInitializer.peekMagiskPath()));
Objects.requireNonNull((Preference) findPreference("pref_crash")).setVisible(false);
} else {
findPreference("pref_crash").setOnPreferenceClickListener(preference -> {
@ -387,9 +535,24 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
openFragment(libsBuilder.supportFragment(), R.string.licenses);
return true;
});
findPreference("pref_pkg_info").setSummary(BuildConfig.APPLICATION_ID +
" v" + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")" +
getRepackageState()); // State may not be "I am just running from myself as myself"
// Determine if this is an official build based on the signature
boolean isOfficial = false;
try {
// Get the signature of the key used to sign the app
@SuppressLint("PackageManagerGetSignatures") Signature[] signatures = requireContext().getPackageManager().getPackageInfo(requireContext().getPackageName(), PackageManager.GET_SIGNATURES).signatures;
String officialSignatureHash =
"7bec7c4462f4aac616612d9f56a023ee3046e83afa956463b5fab547fd0a0be6";
String ourSignatureHash = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString();
isOfficial = ourSignatureHash.equals(officialSignatureHash);
} catch (PackageManager.NameNotFoundException ignored) {
}
String flavor = BuildConfig.FLAVOR;
String type = BuildConfig.BUILD_TYPE;
// Set the summary of pref_pkg_info to something like Github-debug v1.0 (123) (Official)
String pkgInfo = getString(R.string.pref_pkg_info_summary, flavor + "-" + type,
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, isOfficial ?
getString(R.string.official) : getString(R.string.unofficial));
findPreference("pref_pkg_info").setSummary(pkgInfo);
}
@SuppressLint("RestrictedApi")
@ -445,7 +608,6 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
setPreferencesFromResource(R.xml.repo_preferences, rootKey);
setRepoData(RepoManager.MAGISK_ALT_REPO);
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
setRepoData(RepoManager.DG_MAGISK_REPO_GITHUB);
updateCustomRepoList(true);
onCreatePreferencesAndroidacy();
}
@ -464,6 +626,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Use MaterialAlertDialogBuilder
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.warning)
.setCancelable(false)
.setMessage(R.string.androidacy_test_mode_warning)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button
@ -474,13 +637,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
@ -502,6 +660,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.warning)
.setCancelable(false)
.setMessage(R.string.androidacy_test_mode_disable_warning)
.setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button
@ -510,13 +669,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
@ -536,6 +690,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
androidacyRepoEnabled.setOnPreferenceClickListener(preference -> {
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.androidacy_repo_disabled)
.setCancelable(false)
.setMessage(R.string.androidacy_repo_disabled_message)
.setPositiveButton(R.string.download_full_app, (dialog, which) -> {
// User clicked OK button. Open GitHub releases page
@ -572,9 +727,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
prefAndroidacyRepoApiKey.setPositiveButtonText(R.string.save_api_key);
prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> {
if (originalApiKeyRef[0].equals(newValue)) return true; // Skip if nothing changed.
// Curious if this actually works - so crash the app on purpose
// throw new RuntimeException("This is a test crash");
if (originalApiKeyRef[0].equals(newValue)) return true;
// get original api key
String apiKey = String.valueOf(newValue);
// Show snack bar with indeterminate progress
@ -593,6 +746,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.restart)
.setCancelable(false)
.setMessage(R.string.api_key_restart)
.setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button
@ -601,13 +755,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
@ -650,6 +799,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Show dialog to restart app with ok button
new MaterialAlertDialogBuilder(this.requireContext())
.setTitle(R.string.restart)
.setCancelable(false)
.setMessage(R.string.api_key_restart)
.setNeutralButton(android.R.string.ok, (dialog, which) -> {
// User clicked OK button
@ -658,13 +808,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int mPendingIntentId = 123456;
// If < 23, FLAG_IMMUTABLE is not available
PendingIntent mPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
}
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
@ -793,12 +938,20 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
preference.setTitle(repoData.getName());
preference = findPreference(preferenceName + "_enabled");
if (preference != null) {
((TwoStatePreference) preference).setChecked(repoData.isEnabled());
preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled);
preference.setOnPreferenceChangeListener((p, newValue) -> {
p.setTitle(((Boolean) newValue) ? R.string.repo_enabled : R.string.repo_disabled);
return true;
});
// Handle custom repo separately
if (repoData instanceof CustomRepoData) {
preference.setTitle(R.string.custom_repo_always_on);
// Disable the preference
preference.setEnabled(false);
return;
} else {
((TwoStatePreference) preference).setChecked(repoData.isEnabled());
preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled);
preference.setOnPreferenceChangeListener((p, newValue) -> {
p.setTitle(((Boolean) newValue) ? R.string.repo_enabled : R.string.repo_disabled);
return true;
});
}
}
preference = findPreference(preferenceName + "_website");
String homepage = repoData.getWebsite();

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

@ -15,11 +15,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.repo.RepoManager;
import com.google.net.cronet.okhttptransport.CronetCallFactory;
import com.google.net.cronet.okhttptransport.CronetInterceptor;
import org.chromium.net.CronetEngine;
@ -140,13 +140,6 @@ public class Http {
return chain.proceed(request.build());
});
// Add cronet interceptor
// install cronet
/*try {
// Detect if cronet is installed
CronetProviderInstaller.installProvider(mainApplication);
} catch (Exception e) {
Log.e(TAG, "Failed to install cronet", e);
}*/
// init cronet
try {
// Load the cronet library
@ -165,8 +158,13 @@ public class Http {
}
builder.setStoragePath(mainApplication.getCacheDir().getAbsolutePath() + "/cronet");
builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 10 * 1024 * 1024);
CronetEngine engine =
builder.build();
// Add quic hint
builder.addQuicHint("github.com", 443, 443);
builder.addQuicHint("githubusercontent.com", 443, 443);
builder.addQuicHint("jsdelivr.net", 443, 443);
builder.addQuicHint("androidacy.com", 443, 443);
builder.addQuicHint("sentry.io", 443, 443);
CronetEngine engine = builder.build();
httpclientBuilder.addInterceptor(CronetInterceptor.newBuilder(engine).build());
} catch (Exception e) {
Log.e(TAG, "Failed to init cronet", e);
@ -234,10 +232,10 @@ public class Http {
@SuppressWarnings("resource")
public static byte[] doHttpGet(String url, boolean allowCache) throws IOException {
checkNeedBlockAndroidacyRequest(url);
Response response =
(allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code());
checkNeedCaptchaAndroidacy(url, response.code());
// If it's a 401, and an androidacy link, it's probably an invalid token
if (response.code() == 401 && AndroidacyUtil.isAndroidacyLink(url)) {
@ -260,13 +258,17 @@ public class Http {
@SuppressWarnings("resource")
private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException {
if (BuildConfig.DEBUG) Log.d(TAG, "POST " + url + " " + data);
checkNeedBlockAndroidacyRequest(url);
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute();
Response response;
response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute();
if (response.isRedirect()) {
return response.request().url().uri().toString();
}
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
if (BuildConfig.DEBUG)
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code() + ", body: " + response.body().string());
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
@ -280,10 +282,11 @@ public class Http {
}
public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException {
Log.d("Http", "Progress URL: " + url);
if (BuildConfig.DEBUG) Log.d("Http", "Progress URL: " + url);
checkNeedBlockAndroidacyRequest(url);
Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute();
if (response.code() != 200 && response.code() != 204) {
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code());
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
@ -337,6 +340,54 @@ public class Http {
return hasWebView;
}
public static void ensureCacheDirs(MainActivity mainActivity) {
// Recursively ensure cache dirs for webview exist under our cache dir
File cacheDir = mainActivity.getCacheDir();
File webviewCacheDir = new File(cacheDir, "WebView");
if (!webviewCacheDir.exists()) {
if (!webviewCacheDir.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
File webviewCacheDirCache = new File(webviewCacheDir, "Default");
if (!webviewCacheDirCache.exists()) {
if (!webviewCacheDirCache.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCache = new File(webviewCacheDirCache, "HTTP Cache");
if (!webviewCacheDirCacheCodeCache.exists()) {
if (!webviewCacheDirCacheCodeCache.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCacheIndex = new File(webviewCacheDirCacheCodeCache, "Code Cache");
if (!webviewCacheDirCacheCodeCacheIndex.exists()) {
if (!webviewCacheDirCacheCodeCacheIndex.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCacheIndexIndex = new File(webviewCacheDirCacheCodeCacheIndex, "Index");
if (!webviewCacheDirCacheCodeCacheIndexIndex.exists()) {
if (!webviewCacheDirCacheCodeCacheIndexIndex.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
// Create the js and wasm dirs
File webviewCacheDirCacheCodeCacheIndexIndexJs = new File(webviewCacheDirCacheCodeCache, "js");
if (!webviewCacheDirCacheCodeCacheIndexIndexJs.exists()) {
if (!webviewCacheDirCacheCodeCacheIndexIndexJs.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCacheIndexIndexWasm = new File(webviewCacheDirCacheCodeCache, "wasm");
if (!webviewCacheDirCacheCodeCacheIndexIndexWasm.exists()) {
if (!webviewCacheDirCacheCodeCacheIndexIndexWasm.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
}
}
}
public interface ProgressListener {
void onUpdate(int downloaded, int total, boolean done);
}

@ -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_constraintEnd_toEndOf="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
android:id="@+id/blur_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list"
android:layout_width="match_parent"
@ -36,6 +37,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/action_bar_padding"
android:layout_width="match_parent"
@ -44,49 +46,59 @@
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" />
<TextView
<!--<TextView
android:id="@+id/noodle_debug"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" />
app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" />-->
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:gravity="right"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_fitsSystemWindowsInsets="bottom"
tools:ignore="RtlHardcoded">
<!--
setting high app:cardCornerRadius is not supported on some versions
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
android:id="@+id/search_card"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:shape="ring"
android:layout_gravity="center"
android:background="@null"
android:shape="ring"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="true"
app:strokeColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_bar"
android:layout_width="match_parent"

@ -16,7 +16,7 @@
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/materialCardViewFilledStyle">
style="?attr/materialCardViewElevatedStyle">
<androidx.constraintlayout.widget.ConstraintLayout
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>Tmavá</item>
<item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Světlá</item>
</string-array>
</resources>

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -3,6 +3,7 @@
<item>Sistem</item>
<item>Koyu</item>
<item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>ık</item>
</string-array>
</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">
<item name="colorBackgroundFloating">@color/black</item>
<item name="android:windowBackground">@color/black</item>
<item name="boxBackgroundColor">@color/black</item>
<item name="cardBackgroundColor">@color/black</item>
<item name="boxBackgroundColor">@color/dark_backgroundColor</item>
<item name="cardBackgroundColor">@color/cardview_dark_background</item>
<item name="backgroundColor">@color/black</item>
<item name="colorSurface">@color/black</item>
<!-- Darker variants of the colors -->
<item name="colorPrimaryVariant">@color/system_accent2_900</item>
<item name="colorSecondaryVariant">@color/system_accent2_900</item>
<!-- Darker variants of the monet colors -->
<item name="colorPrimary">@color/system_accent2_300</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 name="Widget.Material3.Chip.Choice.Dark" parent="Widget.Material3.Chip.Assist">

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

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

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

@ -1,9 +1,12 @@
<resources>
<!-- If translating, please remove "theme_values" -->
<!-- Transparent dark theme has, erm, issues -->
<string-array name="theme_values" translatable="false">
<item>system</item>
<item>dark</item>
<item>black</item>
<item>transparent_light</item>
<!-- <item>transparent_dark</item> -->
<item>light</item>
</string-array>
@ -11,6 +14,7 @@
<item>System</item>
<item>Dark</item>
<item>AMOLED Black</item>
<item>Transparent (light)</item>
<item>Light</item>
</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="orange_700">#EF6C00</color>
<color name="orange_200">#FFA726</color>
<color name="orange_500">#FF9800</color>
<color name="black_transparent">#70000000</color>
<color name="white_transparent">#70FFFFFF</color>
<color name="transparent">#00000000</color>

@ -132,7 +132,7 @@
<string name="remove_repo">Remove Repo</string>
<string name="custom_url">Custom url</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="restore_module_list">Restore modules</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>
<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_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>

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

@ -6,4 +6,29 @@
<certificates src="@raw/androidacy_root_ca" />
</trust-anchors>
</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>

@ -1,35 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<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
app:key="pref_androidacy_repo"
app:title="@string/loading">
@ -88,44 +58,54 @@
app:summary="@string/androidacy_repo_info" />
</PreferenceCategory>
<PreferenceCategory
app:key="pref_dg_magisk_repo"
app:key="pref_magisk_alt_repo"
app:title="@string/loading">
<SwitchPreferenceCompat
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:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
<com.fox2code.mmm.settings.LongClickablePreference
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:title="@string/website" />
<com.fox2code.mmm.settings.LongClickablePreference
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:title="@string/support" />
<com.fox2code.mmm.settings.LongClickablePreference
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:title="@string/donate" />
<com.fox2code.mmm.settings.LongClickablePreference
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: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
app:key="pref_custom_repo_0"
app:title="@string/loading">
<!-- Custom repos can't be enabled/disabled. Instead, they must be deleted. Show a disabled
switch to indicate that. -->
<SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_0_enabled"
app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_0_website"
@ -156,11 +136,13 @@
app:key="pref_custom_repo_1"
app:title="@string/loading">
<SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_1_enabled"
app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
app:switchTextOn="@string/custom_repo_always_on" />/>
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_1_website"
@ -191,11 +173,13 @@
app:key="pref_custom_repo_2"
app:title="@string/loading">
<SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_2_enabled"
app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_2_website"
@ -226,11 +210,13 @@
app:key="pref_custom_repo_3"
app:title="@string/loading">
<SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_3_enabled"
app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_3_website"
@ -261,11 +247,13 @@
app:key="pref_custom_repo_4"
app:title="@string/loading">
<SwitchPreferenceCompat
app:defaultValue="true"
app:enabled="false"
app:icon="@drawable/ic_baseline_extension_24"
app:key="pref_custom_repo_4_enabled"
app:singleLineTitle="false"
app:switchTextOff="@string/repo_disabled"
app:switchTextOn="@string/repo_enabled" />
app:switchTextOn="@string/custom_repo_always_on" />
<com.fox2code.mmm.settings.LongClickablePreference
app:icon="@drawable/ic_baseline_language_24"
app:key="pref_custom_repo_4_website"

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

@ -1,29 +1,18 @@
package com.fox2code.mmm.sentry;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
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.SentryOptions;
import io.sentry.TypeCheckHint;
import io.sentry.android.core.SentryAndroid;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.okhttp.SentryOkHttpInterceptor;
import io.sentry.hints.DiskFlushNotification;
public class SentryMain {
public static final boolean IS_SENTRY_INSTALLED = true;
@ -33,13 +22,30 @@ public class SentryMain {
* 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)
*/
@SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"})
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 -> {
// If crash reporting is disabled, stop here.
if (!MainApplication.isCrashReportingEnabled()) {
options.setDsn("");
} else {
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.
// Already set to false by default, just set it again to make peoples feel safer.
options.setSendDefaultPii(false);
@ -49,64 +55,25 @@ public class SentryMain {
// 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.
options.setBeforeSend((event, hint) -> {
if (BuildConfig.DEBUG) { // Debug sentry events for debug.
StringBuilder stringBuilder = new StringBuilder("Sentry report debug: ");
try {
event.serialize(new JsonObjectWriter(new Writer() {
@Override
public void write(char[] cbuf) {
stringBuilder.append(cbuf);
}
@Override
public void write(String str) {
stringBuilder.append(str);
}
@Override
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;
// Save lastEventId to private shared preferences
SharedPreferences sharedPreferences = MainApplication.getINSTANCE().getSharedPreferences("sentry", Context.MODE_PRIVATE);
String lastEventId = Objects.requireNonNull(event.getEventId()).toString();
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("lastEventId", lastEventId);
editor.apply();
return event;
});
// 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;
});
}
// 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()
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.hasSentryConfig = sentryConfigFile.exists()
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)
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
Theses values are only used if not defined in the `module.prop` files
So the original module maker can still override them
@ -173,7 +173,7 @@ mmm_exec hideLoading
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

@ -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
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# android.enableJetifier=true
# Fox builds props mods
org.gradle.parallel=true

@ -1,6 +1,6 @@
#Sun Jun 05 10:40:53 EDT 2022
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStoreBase=GRADLE_USER_HOME
Loading…
Cancel
Save