Release 0.1.0

- Improved module deletion safety
- Improved compat installer script
- Added `maxApi` metadata support for modules
- Added support links fallbacks for some popular modules
- Added Markdown syntax highlight support into module info view
  (See Prism4j repo for the list of supported languages)
- Added hidden dev mode + hidden setting to enable magisk command install
pull/27/head 0.1.0
Fox2Code 5 years ago
parent 6a608ffbea
commit 6574115a85

@ -24,6 +24,7 @@ This the manager support these new properties
```properties
# Fox's Mmm supported properties
minApi=<int>
maxApi=<int>
minMagisk=<int>
support=<url>
donate=<url>
@ -31,7 +32,7 @@ config=<package>
```
(Note: All urls must start with `https://`, or else will be ignored)
- `minApi` tell the manager which is the minimum SDK version required for the module
- `minApi` and `maxApi` tell the manager which is the SDK version range the module support
(See: [Codenames, Tags, and Build Numbers](https://source.android.com/setup/start/build-numbers))
- `minMagisk` tell the manager which is the minimum Magisk version required for the module
(Often for magisk `xx.y` the version code is `xxy00`)

@ -5,6 +5,16 @@ So I made my own app to do that! :3
**This app is not officially supported by Magisk or it's developers**
## Requirements
Minimum:
- Android 5.0+
- Magisk 19.0+
Recommended:
- Android 6.0+
- Magisk 21.2+
## For users
Related commits:
@ -24,12 +34,14 @@ If a module is in both repo, the manager will just pick the most up to date vers
## For developers
The manager can read new meta keys to allow modules to customize their entry
The manager can read new meta keys to allow modules to customize their own entry
It use `module.prop` the `minApi=<int>` and `minMagisk=<int>` properties to detect compatibility
And use the `support=<url>` and `donate=<url>` key to detect module related links
It also use `minApi`, `maxApi` and `minMagisk` in the `module.prop` to detect compatibility
And support the `support` and `donate` properties to allow them to add their own support links
(Note: the manager use fallback values for some modules, see developer documentation for more info)
It also add new ways to control the installer ui via a new `#!` command system
It also add new ways to control the installer ui via a new `#!` command system
It allow module developers to have a more customizable install experience
For more information please check the [developer documentation](DEVELOPERS.md)

@ -10,8 +10,8 @@ android {
applicationId "com.fox2code.mmm"
minSdk 21
targetSdk 30
versionCode 3
versionName "0.0.3"
versionCode 4
versionName "0.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -39,6 +39,10 @@ aboutLibraries {
}
}
configurations {
implementation.exclude group: 'org.jetbrains' , module: 'annotations'
}
dependencies {
// UI
implementation 'androidx.appcompat:appcompat:1.3.1'
@ -57,6 +61,8 @@ dependencies {
implementation "io.noties.markwon:core:4.6.2"
implementation "io.noties.markwon:html:4.6.2"
implementation "io.noties.markwon:image:4.6.2"
implementation "io.noties.markwon:syntax-highlight:4.6.2"
annotationProcessor "io.noties:prism4j-bundler:2.0.0"
implementation "com.caverock:androidsvg:1.4"
// Test

@ -29,6 +29,19 @@ mount /data 2>/dev/null
. /data/adb/magisk/util_functions.sh
[ $MAGISK_VER_CODE -lt 19000 ] && require_new_magisk
# Add grep_get_prop implementation if missing
if ! type grep_get_prop &>/dev/null; then
grep_get_prop() {
local result=$(grep_prop $@)
if [ -z "$result" ]; then
# Fallback to getprop
getprop "$1"
else
echo $result
fi
}
fi
if [ $MAGISK_VER_CODE -ge 20400 ]; then
# New Magisk have complete installation logic within util_functions.sh
install_module
@ -84,6 +97,7 @@ mount_partitions
# Detect version and architecture
api_level_arch_detect
API=$(grep_get_prop ro.build.version.sdk)
# Setup busybox and binaries
$BOOTMODE && boot_actions || recovery_actions

@ -52,8 +52,10 @@ public enum ActionButtonType {
@Override
public void update(ImageButton button, ModuleHolder moduleHolder) {
int icon = moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING) ?
R.drawable.ic_baseline_delete_outline_24 :
moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE) ?
R.drawable.ic_baseline_delete_outline_24 : (
// We can't trust active flag on first boot
MainApplication.isFirstBoot() ||
moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)) ?
R.drawable.ic_baseline_delete_24 :
R.drawable.ic_baseline_delete_forever_24;
button.setImageResource(icon);
@ -70,7 +72,9 @@ public enum ActionButtonType {
@Override
public boolean doActionLong(ImageButton button, ModuleHolder moduleHolder) {
if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)) return false;
// We can't trust active flag on first boot
if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)
|| MainApplication.isFirstBoot()) return false;
new AlertDialog.Builder(button.getContext()).setTitle(R.string.master_delete)
.setPositiveButton(R.string.master_delete_yes, (v, i) -> {
if (!ModuleManager.getINSTANCE().masterClear(moduleHolder.moduleInfo)) {

@ -4,6 +4,7 @@ public class Constants {
public static final int MAGISK_VER_CODE_FLAT_MODULES = 19000;
public static final int MAGISK_VER_CODE_UTIL_INSTALL = 20400;
public static final int MAGISK_VER_CODE_PATH_SUPPORT = 21000;
public static final int MAGISK_VER_CODE_INSTALL_COMMAND = 21200;
public static final int MAGISK_VER_CODE_MAGISK_ZYGOTE = 23002;
public static final String INTENT_INSTALL_INTERNAL =
BuildConfig.APPLICATION_ID + ".intent.action.INSTALL_MODULE_INTERNAL";

@ -90,7 +90,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
public void onPathReceived(String path) {
Log.i(TAG, "Got magisk path: " + path);
if (InstallerInitializer.peekMagiskVersion() <
Constants.MAGISK_VER_CODE_PATH_SUPPORT)
Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
@ -159,7 +159,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
@Override
public void onPathReceived(String path) {
if (InstallerInitializer.peekMagiskVersion() <
Constants.MAGISK_VER_CODE_PATH_SUPPORT)
Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);

@ -6,7 +6,9 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.SystemClock;
import android.text.SpannableStringBuilder;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
@ -27,7 +29,17 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import io.noties.markwon.syntax.Prism4jTheme;
import io.noties.markwon.syntax.Prism4jThemeDarkula;
import io.noties.markwon.syntax.Prism4jThemeDefault;
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
import io.noties.prism4j.Prism4j;
import io.noties.prism4j.annotations.PrismBundle;
@PrismBundle(
includeAll = true,
grammarLocatorClassName = ".Prism4jGrammarLocator"
)
public class MainApplication extends Application implements CompatActivity.ApplicationCallbacks {
private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
private static Locale timeFormatLocale =
@ -38,6 +50,7 @@ public class MainApplication extends Application implements CompatActivity.Appli
private static final int secret;
private static SharedPreferences bootSharedPreferences;
private static MainApplication INSTANCE;
private static boolean firstBoot;
static {
Shell.setDefaultBuilder(shellBuilder = Shell.Builder.create()
@ -75,6 +88,32 @@ public class MainApplication extends Application implements CompatActivity.Appli
return getSharedPreferences().getBoolean("pref_force_dark_terminal", false);
}
public static boolean isDeveloper() {
return BuildConfig.DEBUG ||
getSharedPreferences().getBoolean("developer", false);
}
public static boolean isUsingMagiskCommand() {
return InstallerInitializer.peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND
&& getSharedPreferences().getBoolean("pref_use_magisk_install_command", false)
&& isDeveloper();
}
public static boolean isFirstBoot() {
return firstBoot;
}
public static void notifyBootListenerCompleted() {
if (MainApplication.bootSharedPreferences != null) {
MainApplication.bootSharedPreferences.edit()
.putBoolean("first_boot", false).apply();
} else if (MainApplication.INSTANCE != null) {
MainApplication.getSharedPreferences().edit()
.putBoolean("first_boot", false).apply();
}
firstBoot = false;
}
public static boolean hasGottenRootAccess() {
return getSharedPreferences().getBoolean("has_root_access", false);
}
@ -104,18 +143,48 @@ public class MainApplication extends Application implements CompatActivity.Appli
public Markwon getMarkwon() {
if (this.markwon != null)
return this.markwon;
ContextThemeWrapper contextThemeWrapper = this.markwonThemeContext =
new ContextThemeWrapper(this, this.managerThemeResId);
ContextThemeWrapper contextThemeWrapper = this.markwonThemeContext;
if (contextThemeWrapper == null)
contextThemeWrapper = this.markwonThemeContext =
new ContextThemeWrapper(this, this.managerThemeResId);
Markwon markwon = Markwon.builder(contextThemeWrapper).usePlugin(HtmlPlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(
new Prism4j(new Prism4jGrammarLocator()), new Prism4jSwitchTheme()))
.usePlugin(ImagesPlugin.create().addSchemeHandler(
OkHttpNetworkSchemeHandler.create(Http.getHttpclientWithCache()))).build();
return this.markwon = markwon;
}
private class Prism4jSwitchTheme implements Prism4jTheme {
private final Prism4jTheme light = new Prism4jThemeDefault(Color.TRANSPARENT);
private final Prism4jTheme dark = new Prism4jThemeDarkula(Color.TRANSPARENT);
private Prism4jTheme getTheme() {
return isLightTheme() ? this.light : this.dark;
}
@Override
public int background() {
return this.getTheme().background();
}
@Override
public int textColor() {
return this.getTheme().textColor();
}
@Override
public void apply(@NonNull String language, @NonNull Prism4j.Syntax syntax,
@NonNull SpannableStringBuilder builder, int start, int end) {
this.getTheme().apply(language, syntax, builder, start, end);
}
}
public void setManagerThemeResId(@StyleRes int resId) {
this.managerThemeResId = resId;
if (this.markwonThemeContext != null)
this.markwonThemeContext.setTheme(resId);
this.markwon = null;
}
@StyleRes
@ -144,13 +213,22 @@ public class MainApplication extends Application implements CompatActivity.Appli
public void onCreate() {
INSTANCE = this;
super.onCreate();
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);
long lastBoot = System.currentTimeMillis() - SystemClock.elapsedRealtime();
long lastBootPrefs = bootPrefs.getLong("last_boot", 0);
if (lastBootPrefs == 0 || Math.abs(lastBoot - lastBootPrefs) > 100) {
bootPrefs.edit().clear().putLong("last_boot", lastBoot).apply();
boolean firstBoot = sharedPreferences.getBoolean("first_boot", true);
bootPrefs.edit().clear().putLong("last_boot", lastBoot)
.putBoolean("first_boot", firstBoot).apply();
if (firstBoot) {
sharedPreferences.edit().putBoolean("first_boot", false).apply();
}
MainApplication.firstBoot = firstBoot;
} else {
MainApplication.firstBoot = bootPrefs.getBoolean("first_boot", false);
}
@StyleRes int themeResId;
switch (getSharedPreferences().getString("pref_theme", "system")) {

@ -72,7 +72,9 @@ public class ModuleViewListBuilder {
repoManager.runAfterUpdate(() -> {
Log.i(TAG, "A2: " + repoManager.getModules().size());
for (RepoModule repoModule : repoManager.getModules().values()) {
if (!showIncompatible && (repoModule.moduleInfo.minApi > Build.VERSION.SDK_INT ||
ModuleInfo moduleInfo = repoModule.moduleInfo;
if (!showIncompatible && (moduleInfo.minApi > Build.VERSION.SDK_INT ||
(moduleInfo.maxApi != 0 && moduleInfo.maxApi < Build.VERSION.SDK_INT) ||
// Only check Magisk compatibility if root is present
(InstallerInitializer.peekMagiskPath() != null &&
repoModule.moduleInfo.minMagisk >

@ -42,7 +42,7 @@ public enum NotificationType implements NotificationTypeCst {
public boolean shouldRemove() {
return InstallerInitializer.peekMagiskPath() == null ||
InstallerInitializer.peekMagiskVersion() >=
Constants.MAGISK_VER_CODE_PATH_SUPPORT;
Constants.MAGISK_VER_CODE_INSTALL_COMMAND;
}
},
NO_INTERNET(R.string.fail_internet, R.drawable.ic_baseline_cloud_off_24) {

@ -136,20 +136,32 @@ public class InstallerActivity extends CompatActivity {
private void doInstall(File file) {
Log.i(TAG, "Installing: " + moduleCache.getName());
File installScript = this.extractCompatScript();
if (installScript == null) {
this.setInstallStateFinished(false,
"! Failed to extract module install script", "");
return;
}
InstallerController installerController = new InstallerController(
this.progressIndicator, this.installerTerminal, file.getAbsoluteFile());
InstallerMonitor installerMonitor = new InstallerMonitor(installScript);
boolean success = Shell.su("export MMM_EXT_SUPPORT=1",
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
"sh \"" + installScript.getAbsolutePath() + "\"" +
" /dev/null 1 \"" + file.getAbsolutePath() + "\"")
.to(installerController, installerMonitor).exec().isSuccess();
InstallerMonitor installerMonitor;
Shell.Job installJob;
if (MainApplication.isUsingMagiskCommand()) {
installerMonitor = new InstallerMonitor(new File(InstallerInitializer
.peekMagiskPath().equals("/sbin") ? "/sbin/magisk" : "/system/bin/magisk"));
installJob = Shell.su("export MMM_EXT_SUPPORT=1",
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
"magisk --install-module \"" + file.getAbsolutePath() + "\"")
.to(installerController, installerMonitor);
} else {
File installScript = this.extractCompatScript();
if (installScript == null) {
this.setInstallStateFinished(false,
"! Failed to extract module install script", "");
return;
}
installerMonitor = new InstallerMonitor(installScript);
installJob = Shell.su("export MMM_EXT_SUPPORT=1",
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
"sh \"" + installScript.getAbsolutePath() + "\"" +
" /dev/null 1 \"" + file.getAbsolutePath() + "\"")
.to(installerController, installerMonitor);
}
boolean success = installJob.exec().isSuccess();
// Wait one UI cycle before disabling controller or processing results
UiThreadHandler.runAndWait(() -> {}); // to avoid race conditions
installerController.disable();
@ -261,7 +273,7 @@ public class InstallerActivity extends CompatActivity {
public static class InstallerMonitor extends CallbackList<String> {
private static final String DEFAULT_ERR = "! Install failed";
private final String installScriptPath;
public String lastCommand;
public String lastCommand = "";
public InstallerMonitor(File installScript) {
super(Runnable::run);

@ -19,6 +19,7 @@ public class ModuleBootReceive extends BroadcastReceiver {
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
MainApplication.notifyBootListenerCompleted();
ModuleManager.getINSTANCE().scan();
}

@ -28,6 +28,7 @@ public class ModuleInfo {
// Community restrictions
public int minMagisk;
public int minApi;
public int maxApi;
// Module status (0 if not from Module Manager)
public int flags;

@ -1,6 +1,8 @@
package com.fox2code.mmm.settings;
import android.os.Bundle;
import android.text.method.Touch;
import android.widget.Toast;
import androidx.annotation.StyleRes;
import androidx.fragment.app.FragmentTransaction;
@ -8,9 +10,12 @@ import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.IntentHelper;
@ -18,8 +23,11 @@ import com.mikepenz.aboutlibraries.LibsBuilder;
import com.topjohnwu.superuser.internal.UiThreadHandler;
public class SettingsActivity extends CompatActivity {
private static int devModeStep = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
devModeStep = 0;
super.onCreate(savedInstanceState);
this.setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.settings_activity);
@ -41,7 +49,13 @@ public class SettingsActivity extends CompatActivity {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
ListPreference themePreference = findPreference("pref_theme");
themePreference.setSummaryProvider(p -> themePreference.getEntry());
themePreference.setOnPreferenceClickListener(p -> {
// You need to reboot your device at least once to be able to access dev-mode
if (!MainApplication.isFirstBoot()) devModeStep = 1;
return false;
});
themePreference.setOnPreferenceChangeListener((preference, newValue) -> {
devModeStep = 0;
@StyleRes int themeResId;
switch (String.valueOf(newValue)) {
default:
@ -62,6 +76,10 @@ public class SettingsActivity extends CompatActivity {
if ("dark".equals(themePreference.getValue())) {
findPreference("pref_force_dark_terminal").setEnabled(false);
}
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND
|| !MainApplication.isDeveloper()) {
findPreference("pref_use_magisk_install_command").setVisible(false);
}
setRepoNameResolution("pref_repo_main", RepoManager.MAGISK_REPO,
"Magisk Modules Repo (Official)", RepoManager.MAGISK_REPO_HOMEPAGE);
@ -71,10 +89,19 @@ public class SettingsActivity extends CompatActivity {
.withFields(R.string.class.getFields()).withShowLoadingProgress(false)
.withLicenseShown(true).withAboutMinimalDesign(false);
findPreference("pref_source_code").setOnPreferenceClickListener(p -> {
if (devModeStep == 2 && (BuildConfig.DEBUG || !MainApplication.isDeveloper())) {
devModeStep = 0;
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", true).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show();
return true;
}
IntentHelper.openUrl(p.getContext(), "https://github.com/Fox2Code/FoxMagiskModuleManager");
return true;
});
findPreference("pref_show_licenses").setOnPreferenceClickListener(p -> {
devModeStep = devModeStep == 1 ? 2 : 0;
CompatActivity compatActivity = getCompatActivity(this);
compatActivity.setOnBackPressedCallback(this);
compatActivity.setTitle(R.string.licenses);

@ -50,15 +50,15 @@ public class Http {
InetAddress.getByName("2606:4700:4700::0064"),
InetAddress.getByName("2606:4700:4700::6400")
).resolvePrivateAddresses(true).build());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (UnknownHostException|RuntimeException e) {
Log.e("Http", "Failed to init DoH", e);
}
httpClient = httpclientBuilder.build();
MainApplication mainApplication = MainApplication.getINSTANCE();
if (mainApplication != null) {
httpclientBuilder.cache(new Cache(
new File(mainApplication.getCacheDir(), "http_cache"),
1024L * 1024L)); // 1Mo of cache
2L * 1024L * 1024L)); // 2Mib of cache
httpclientBuilder.cookieJar(new CDNCookieJar());
httpClientCachable = httpclientBuilder.build();
} else {

@ -12,6 +12,7 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
public class PropUtils {
private static final HashMap<String, String> moduleSupportsFallbacks = new HashMap<>();
private static final HashMap<String, String> moduleConfigsFallbacks = new HashMap<>();
private static final HashMap<String, Integer> moduleMinApiFallbacks = new HashMap<>();
private static final int RIRU_MIN_API;
@ -19,6 +20,11 @@ public class PropUtils {
// Note: These fallback values may not be up-to-date
// They are only used if modules don't define the metadata
static {
// Support are pages or groups where the user can get support for the module
moduleSupportsFallbacks.put("aospill", "https://t.me/PannekoX");
moduleSupportsFallbacks.put("quickstepswitcher", "https://t.me/QuickstepSwitcherSupport");
moduleSupportsFallbacks.put("riru_edxposed", "https://t.me/EdXposed");
moduleSupportsFallbacks.put("riru_lsposed", "https://github.com/LSPosed/LSPosed/issues/");
// Config are application installed by modules that allow them to be configured
moduleConfigsFallbacks.put("quickstepswitcher", "xyz.paphonb.quickstepswitcher");
moduleConfigsFallbacks.put("riru_edxposed", "org.meowcat.edxposed.manager");
@ -107,6 +113,13 @@ public class PropUtils {
moduleInfo.minApi = 0;
}
break;
case "maxApi":
try {
moduleInfo.maxApi = Integer.parseInt(value);
} catch (Exception e) {
moduleInfo.maxApi = 0;
}
break;
}
}
}
@ -130,6 +143,9 @@ public class PropUtils {
|| moduleInfo.id.startsWith("riru-"))
moduleInfo.minApi = RIRU_MIN_API;
}
if (moduleInfo.support == null) {
moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id);
}
if (moduleInfo.config == null) {
moduleInfo.config = moduleConfigsFallbacks.get(moduleInfo.id);
}

@ -41,4 +41,10 @@
<string name="file_picker_failure">Your current file picker failed to give access to the file.</string>
<string name="remote_install_title">Remote install</string>
<string name="file_picker_wierd">Your file picker returned a non standard response.</string>
<string name="use_magisk_install_command_pref">Use magisk module install command</string>
<string name="use_magisk_install_command_desc">
During test it caused problems to the module install error diagnosis tool,
so I hid this option behind dev-mode, enable this at your own risk!
</string>
<string name="dev_mode_enabled">Developer mode enabled</string>
</resources>

@ -29,6 +29,13 @@
app:icon="@drawable/ic_baseline_hide_source_24"
app:title="@string/show_incompatible_pref"
app:summary="@string/show_incompatible_desc"/>
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_use_magisk_install_command"
app:icon="@drawable/ic_baseline_numbers_24"
app:title="@string/use_magisk_install_command_pref"
app:summary="@string/use_magisk_install_command_desc"/>
</PreferenceCategory>
<PreferenceCategory

@ -1,6 +1,6 @@
#Sun Sep 19 17:36:18 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

Loading…
Cancel
Save