diff --git a/DEVELOPERS.md b/DEVELOPERS.md index cdce7e9..acb92b7 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -9,6 +9,7 @@ Note: official repo do not accept new modules anymore, submit Index: - [Special notes](DEVELOPERS.md#special-notes) - [Properties](DEVELOPERS.md#properties) +- [ANSI Styling](DEVELOPERS.md#ansi-styling) - [Installer commands](DEVELOPERS.md#installer-commands) ## Special notes @@ -72,11 +73,19 @@ 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 +## ANSI Styling + +FoxMMM declare `ANSI_SUPPORT` to `true` if ANSI is supported. + +It use [AndroidANSI](https://github.com/Fox2Code/AndroidANSI) library, +please check it's [README.md](https://github.com/Fox2Code/AndroidANSI/blob/master/README.md) +for the list of supported codes. + ## Installer commands -The Fox's Mmm also allow better control over it's installer interface +FoxMmm also allow better control over it's installer interface -Fox's Mmm define the variable `MMM_EXT_SUPPORT` to expose it's extensions support +FoxMmm define the variable `MMM_EXT_SUPPORT` to expose it's extensions support All the commands start with it `#!`, by default the manager process command as log output unless `#!useExt` is sent to indicate that the app is ready to use commands @@ -97,6 +106,7 @@ Commands: - `hideLoading`: Hide the indeterminate progress bar if previously shown - `setSupportLink `: Set support link to show when the install finish (Note: Modules installed from repo will not show the config button if a link is set) +- `disableANSI`: Disable ANSI support if enabled Variables: - `MMM_EXT_SUPPORT` declared if extensions are supported @@ -131,10 +141,9 @@ mmm_exec hideLoading mmm_exec setSupportLink https://github.com/Fox2Code/FoxMagiskModuleManager ``` -You may look at the [example module](example_module) code or -download the [module zip](example_module.zip) and try it yourself +[You may look at the examples modules and their codes.](examples) -Have fun with the API making the user install experience a unique experience +Have fun with the API making your user install experience a unique experience Also there is the source of the app icon [here](https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.type=clipart&foreground.clipart=extension&foreground.space.trim=0&foreground.space.pad=0.25&foreColor=rgb(255%2C%20255%2C%20255)&backColor=rgb(255%2C%20152%2C%200)&crop=0&backgroundShape=circle&effects=elevate&name=ic_launcher) diff --git a/app/build.gradle b/app/build.gradle index d79f16f..0a66b26 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,8 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3' implementation 'com.github.topjohnwu.libsu:io:5.0.1' - implementation 'com.github.Fox2Code:RosettaX:46ec630055' + implementation 'com.github.Fox2Code:RosettaX:1.0.1' + implementation 'com.github.Fox2Code:AndroidANSI:1.0.1' // Markdown implementation "io.noties.markwon:core:4.6.2" diff --git a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java index fc3c635..7c32fe4 100644 --- a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java +++ b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java @@ -21,10 +21,12 @@ import java.util.HashMap; // See https://docs.github.com/en/rest/reference/repos#releases public class AppUpdateManager { public static int FLAG_COMPAT_LOW_QUALITY = 0x01; - public static int FLAG_COMPAT_NO_EXT = 0x02; - public static int FLAG_COMPAT_MAGISK_CMD = 0x04; - public static int FLAG_COMPAT_NEED_32BIT = 0x08; - public static int FLAG_COMPAT_MALWARE = 0x10; + public static int FLAG_COMPAT_NO_EXT = 0x02; + public static int FLAG_COMPAT_MAGISK_CMD = 0x04; + public static int FLAG_COMPAT_NEED_32BIT = 0x08; + public static int FLAG_COMPAT_MALWARE = 0x10; + public static int FLAG_COMPAT_NO_ANSI = 0x20; + public static int FLAG_COMPAT_FORCE_ANSI = 0x40; private static final String TAG = "AppUpdateManager"; private static final AppUpdateManager INSTANCE = new AppUpdateManager(); private static final String RELEASES_API_URL = @@ -201,6 +203,12 @@ public class AppUpdateManager { case "malware": value |= FLAG_COMPAT_MALWARE; break; + case "noANSI": + value |= FLAG_COMPAT_NO_ANSI; + break; + case "forceANSI": + value |= FLAG_COMPAT_FORCE_ANSI; + break; } } compatDataId.put(line.substring(0, i), value); diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java index aaab697..863abb1 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -15,7 +15,8 @@ import android.widget.Toast; import androidx.recyclerview.widget.RecyclerView; -import com.fox2code.mmm.module.ActionButtonType; +import com.fox2code.androidansi.AnsiConstants; +import com.fox2code.androidansi.AnsiParser; import com.fox2code.mmm.AppUpdateManager; import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.Constants; @@ -23,6 +24,7 @@ import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; import com.fox2code.mmm.XHooks; import com.fox2code.mmm.compat.CompatActivity; +import com.fox2code.mmm.module.ActionButtonType; import com.fox2code.mmm.utils.FastException; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Hashes; @@ -119,7 +121,8 @@ public class InstallerActivity extends CompatActivity { this.progressIndicator = findViewById(R.id.progress_bar); this.rebootFloatingButton = findViewById(R.id.install_terminal_reboot_fab); this.installerTerminal = new InstallerTerminal( - installTerminal = findViewById(R.id.install_terminal), foreground); + installTerminal = findViewById(R.id.install_terminal), + this.isLightTheme(), foreground); (horizontalScroller != null ? horizontalScroller : installTerminal) .setBackground(new ColorDrawable(background)); this.progressIndicator.setVisibility(View.GONE); @@ -249,8 +252,26 @@ public class InstallerActivity extends CompatActivity { "! Failed to extract test install script", ""); return; } + this.installerTerminal.enableAnsi(); + // Extract customize.sh manually in rootless mode because unzip might not exists + try (ZipFile zipFile = new ZipFile(file)) { + ZipEntry zipEntry = zipFile.getEntry("customize.sh"); + if (zipEntry != null) { + try (FileOutputStream fileOutputStream = new FileOutputStream( + new File(file.getParentFile(), "customize.sh"))) { + Files.copy(zipFile.getInputStream(zipEntry), fileOutputStream); + } + } + } catch (Exception e) { + Log.d(TAG, "Failed ot extract install script via java code", e); + } installerMonitor = new InstallerMonitor(installScript); installJob = Shell.cmd("export MMM_EXT_SUPPORT=1", + "export MMM_USER_LANGUAGE=" + (MainApplication.isForceEnglish() ? "en-US" : + Resources.getSystem().getConfiguration().locale.toLanguageTag()), + "export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME, + "export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"), + AnsiConstants.ANSI_CMD_SUPPORT, "cd \"" + this.moduleCache.getAbsolutePath() + "\"", "sh \"" + installScript.getAbsolutePath() + "\"" + " 3 0 \"" + file.getAbsolutePath() + "\"") @@ -372,15 +393,25 @@ public class InstallerActivity extends CompatActivity { installerMonitor = new InstallerMonitor(installExecutable); if (moduleId != null) installerMonitor.setForCleanUp(moduleId); if (noExtensions) { + if ((compatFlags & AppUpdateManager.FLAG_COMPAT_FORCE_ANSI) != 0) + this.installerTerminal.enableAnsi(); + else this.installerTerminal.disableAnsi(); installJob = Shell.cmd(arch32, "export BOOTMODE=true", // No Extensions + this.installerTerminal.isAnsiEnabled() ? + AnsiConstants.ANSI_CMD_SUPPORT : "true", "cd \"" + this.moduleCache.getAbsolutePath() + "\"", installCommand).to(installerController, installerMonitor); } else { + if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NO_ANSI) != 0) + this.installerTerminal.enableAnsi(); + else this.installerTerminal.disableAnsi(); installJob = Shell.cmd(arch32, "export MMM_EXT_SUPPORT=1", "export MMM_USER_LANGUAGE=" + (MainApplication.isForceEnglish() ? "en-US" : Resources.getSystem().getConfiguration().locale.toLanguageTag()), "export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME, "export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"), + this.installerTerminal.isAnsiEnabled() ? + AnsiConstants.ANSI_CMD_SUPPORT : "true", "export BOOTMODE=true", anyKernel3 ? "export AK3TMPFS=" + InstallerInitializer.peekMagiskPath() + "/ak3tmpfs" : "cd \"" + this.moduleCache.getAbsolutePath() + "\"", @@ -389,8 +420,7 @@ public class InstallerActivity extends CompatActivity { } boolean success = installJob.exec().isSuccess(); // Wait one UI cycle before disabling controller or processing results - UiThreadHandler.runAndWait(() -> { - }); // to avoid race conditions + UiThreadHandler.runAndWait(() -> {}); // to avoid race conditions installerController.disable(); String message = "- Install successful"; if (!success) { @@ -433,6 +463,7 @@ public class InstallerActivity extends CompatActivity { this.useExt = true; return; } + s = AnsiParser.patchEscapeSequence(s); if (this.useExt && s.startsWith("#!")) { this.processCommand(s.substring(2)); } else if (this.useRecovery && s.startsWith("progress ")) { @@ -464,7 +495,7 @@ public class InstallerActivity extends CompatActivity { final String arg; final String command; int i = rawCommand.indexOf(' '); - if (i != -1) { + if (i != -1 && rawCommand.length() != i + 1) { arg = rawCommand.substring(i + 1).trim(); command = rawCommand.substring(0, i); } else { @@ -534,6 +565,9 @@ public class InstallerActivity extends CompatActivity { arg.indexOf('/', 8) > 8)) this.supportLink = arg; break; + case "disableANSI": + this.terminal.disableAnsi(); + break; } } @@ -625,6 +659,7 @@ public class InstallerActivity extends CompatActivity { @SuppressWarnings("SameParameterValue") private void setInstallStateFinished(boolean success, String message, String optionalLink) { + this.installerTerminal.disableAnsi(); if (success && toDelete != null && !toDelete.delete()) { SuFile suFile = new SuFile(toDelete.getAbsolutePath()); if (suFile.exists() && !suFile.delete()) diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java index 52b2b31..fa22bde 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java @@ -1,6 +1,7 @@ package com.fox2code.mmm.installer; import android.graphics.Typeface; +import android.text.Spannable; import android.view.ViewGroup; import android.widget.TextView; @@ -8,20 +9,25 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.fox2code.androidansi.AnsiContext; + import java.util.ArrayList; public class InstallerTerminal extends RecyclerView.Adapter { private final RecyclerView recyclerView; - private final ArrayList terminal; + private final ArrayList terminal; + private final AnsiContext ansiContext; private final Object lock = new Object(); private final int foreground; + private boolean ansiEnabled = false; - public InstallerTerminal(RecyclerView recyclerView,int foreground) { + public InstallerTerminal(RecyclerView recyclerView, boolean isLightTheme,int foreground) { recyclerView.setLayoutManager( new LinearLayoutManager(recyclerView.getContext())); this.recyclerView = recyclerView; this.foreground = foreground; this.terminal = new ArrayList<>(); + this.ansiContext = (isLightTheme ? AnsiContext.LIGHT : AnsiContext.DARK).copy(); this.recyclerView.setAdapter(this); } @@ -33,7 +39,7 @@ public class InstallerTerminal extends RecyclerView.Adapter diff --git a/example_module.zip b/examples/example_module.zip similarity index 100% rename from example_module.zip rename to examples/example_module.zip diff --git a/example_module/customize.sh b/examples/example_module/customize.sh similarity index 100% rename from example_module/customize.sh rename to examples/example_module/customize.sh diff --git a/example_module/module.prop b/examples/example_module/module.prop similarity index 100% rename from example_module/module.prop rename to examples/example_module/module.prop