Skip to content
This repository has been archived by the owner on May 4, 2023. It is now read-only.

Commit

Permalink
Release 0.1.0
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Fox2Code committed Oct 23, 2021
1 parent 6a608ff commit 6574115
Show file tree
Hide file tree
Showing 19 changed files with 222 additions and 34 deletions.
3 changes: 2 additions & 1 deletion DEVELOPERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ This the manager support these new properties
```properties
# Fox's Mmm supported properties
minApi=<int>
maxApi=<int>
minMagisk=<int>
support=<url>
donate=<url>
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`)
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)

Expand Down
10 changes: 8 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down Expand Up @@ -39,6 +39,10 @@ aboutLibraries {
}
}

configurations {
implementation.exclude group: 'org.jetbrains' , module: 'annotations'
}

dependencies {
// UI
implementation 'androidx.appcompat:appcompat:1.3.1'
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/assets/module_installer_compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/com/fox2code/mmm/ActionButtonType.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ public void doAction(ImageButton button, ModuleHolder moduleHolder) {
@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);
Expand All @@ -70,7 +72,9 @@ public void doAction(ImageButton button, ModuleHolder moduleHolder) {

@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)) {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/fox2code/mmm/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/fox2code/mmm/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newStat
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);
Expand Down Expand Up @@ -159,7 +159,7 @@ public void refreshUI() {
@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);
Expand Down
84 changes: 81 additions & 3 deletions app/src/main/java/com/fox2code/mmm/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
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;
Expand All @@ -27,7 +29,17 @@
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 =
Expand All @@ -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()
Expand Down Expand Up @@ -75,6 +88,32 @@ public static boolean isForceDarkTerminal() {
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);
}
Expand Down Expand Up @@ -104,18 +143,48 @@ public static String formatTime(long timeStamp) {
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
Expand Down Expand Up @@ -144,13 +213,22 @@ public boolean isLightTheme() {
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")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public void appendRemoteModules() {
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 >
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/fox2code/mmm/NotificationType.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public boolean shouldRemove() {
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) {
Expand Down
38 changes: 25 additions & 13 deletions app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,32 @@ protected void onCreate(Bundle savedInstanceState) {

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();
Expand Down Expand Up @@ -261,7 +273,7 @@ public boolean dispatchKeyEvent(KeyEvent event) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public void onReceive(Context context, Intent intent) {
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
MainApplication.notifyBootListenerCompleted();
ModuleManager.getINSTANCE().scan();
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/fox2code/mmm/manager/ModuleInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading

0 comments on commit 6574115

Please sign in to comment.