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

Commit d2e6a49

Browse files
committed
Add ANSI Support
1 parent 78408ea commit d2e6a49

File tree

11 files changed

+117
-30
lines changed

11 files changed

+117
-30
lines changed

DEVELOPERS.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Note: official repo do not accept new modules anymore, submit
99
Index:
1010
- [Special notes](DEVELOPERS.md#special-notes)
1111
- [Properties](DEVELOPERS.md#properties)
12+
- [ANSI Styling](DEVELOPERS.md#ansi-styling)
1213
- [Installer commands](DEVELOPERS.md#installer-commands)
1314

1415
## Special notes
@@ -72,11 +73,19 @@ for some modules
7273
Theses values are only used if not defined in the `module.prop` files
7374
So the original module maker can still override them
7475

76+
## ANSI Styling
77+
78+
FoxMMM declare `ANSI_SUPPORT` to `true` if ANSI is supported.
79+
80+
It use [AndroidANSI](https://github.com/Fox2Code/AndroidANSI) library,
81+
please check it's [README.md](https://github.com/Fox2Code/AndroidANSI/blob/master/README.md)
82+
for the list of supported codes.
83+
7584
## Installer commands
7685

77-
The Fox's Mmm also allow better control over it's installer interface
86+
FoxMmm also allow better control over it's installer interface
7887

79-
Fox's Mmm define the variable `MMM_EXT_SUPPORT` to expose it's extensions support
88+
FoxMmm define the variable `MMM_EXT_SUPPORT` to expose it's extensions support
8089

8190
All the commands start with it `#!`, by default the manager process command as log output
8291
unless `#!useExt` is sent to indicate that the app is ready to use commands
@@ -97,6 +106,7 @@ Commands:
97106
- `hideLoading`: Hide the indeterminate progress bar if previously shown
98107
- `setSupportLink <url>`: Set support link to show when the install finish
99108
(Note: Modules installed from repo will not show the config button if a link is set)
109+
- `disableANSI`: Disable ANSI support if enabled
100110

101111
Variables:
102112
- `MMM_EXT_SUPPORT` declared if extensions are supported
@@ -131,10 +141,9 @@ mmm_exec hideLoading
131141
mmm_exec setSupportLink https://github.com/Fox2Code/FoxMagiskModuleManager
132142
```
133143

134-
You may look at the [example module](example_module) code or
135-
download the [module zip](example_module.zip) and try it yourself
144+
[You may look at the examples modules and their codes.](examples)
136145

137-
Have fun with the API making the user install experience a unique experience
146+
Have fun with the API making your user install experience a unique experience
138147

139148
Also there is the source of the app icon
140149
[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)

app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ dependencies {
9898
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
9999
implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3'
100100
implementation 'com.github.topjohnwu.libsu:io:5.0.1'
101-
implementation 'com.github.Fox2Code:RosettaX:46ec630055'
101+
implementation 'com.github.Fox2Code:RosettaX:1.0.1'
102+
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
102103

103104
// Markdown
104105
implementation "io.noties.markwon:core:4.6.2"

app/src/main/java/com/fox2code/mmm/AppUpdateManager.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
// See https://docs.github.com/en/rest/reference/repos#releases
2222
public class AppUpdateManager {
2323
public static int FLAG_COMPAT_LOW_QUALITY = 0x01;
24-
public static int FLAG_COMPAT_NO_EXT = 0x02;
25-
public static int FLAG_COMPAT_MAGISK_CMD = 0x04;
26-
public static int FLAG_COMPAT_NEED_32BIT = 0x08;
27-
public static int FLAG_COMPAT_MALWARE = 0x10;
24+
public static int FLAG_COMPAT_NO_EXT = 0x02;
25+
public static int FLAG_COMPAT_MAGISK_CMD = 0x04;
26+
public static int FLAG_COMPAT_NEED_32BIT = 0x08;
27+
public static int FLAG_COMPAT_MALWARE = 0x10;
28+
public static int FLAG_COMPAT_NO_ANSI = 0x20;
29+
public static int FLAG_COMPAT_FORCE_ANSI = 0x40;
2830
private static final String TAG = "AppUpdateManager";
2931
private static final AppUpdateManager INSTANCE = new AppUpdateManager();
3032
private static final String RELEASES_API_URL =
@@ -201,6 +203,12 @@ private void parseCompatibilityFlags(InputStream inputStream) throws IOException
201203
case "malware":
202204
value |= FLAG_COMPAT_MALWARE;
203205
break;
206+
case "noANSI":
207+
value |= FLAG_COMPAT_NO_ANSI;
208+
break;
209+
case "forceANSI":
210+
value |= FLAG_COMPAT_FORCE_ANSI;
211+
break;
204212
}
205213
}
206214
compatDataId.put(line.substring(0, i), value);

app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515

1616
import androidx.recyclerview.widget.RecyclerView;
1717

18-
import com.fox2code.mmm.module.ActionButtonType;
18+
import com.fox2code.androidansi.AnsiConstants;
19+
import com.fox2code.androidansi.AnsiParser;
1920
import com.fox2code.mmm.AppUpdateManager;
2021
import com.fox2code.mmm.BuildConfig;
2122
import com.fox2code.mmm.Constants;
2223
import com.fox2code.mmm.MainApplication;
2324
import com.fox2code.mmm.R;
2425
import com.fox2code.mmm.XHooks;
2526
import com.fox2code.mmm.compat.CompatActivity;
27+
import com.fox2code.mmm.module.ActionButtonType;
2628
import com.fox2code.mmm.utils.FastException;
2729
import com.fox2code.mmm.utils.Files;
2830
import com.fox2code.mmm.utils.Hashes;
@@ -119,7 +121,8 @@ protected void onCreate(Bundle savedInstanceState) {
119121
this.progressIndicator = findViewById(R.id.progress_bar);
120122
this.rebootFloatingButton = findViewById(R.id.install_terminal_reboot_fab);
121123
this.installerTerminal = new InstallerTerminal(
122-
installTerminal = findViewById(R.id.install_terminal), foreground);
124+
installTerminal = findViewById(R.id.install_terminal),
125+
this.isLightTheme(), foreground);
123126
(horizontalScroller != null ? horizontalScroller : installTerminal)
124127
.setBackground(new ColorDrawable(background));
125128
this.progressIndicator.setVisibility(View.GONE);
@@ -249,8 +252,26 @@ private void doInstall(File file, boolean noExtensions, boolean rootless) {
249252
"! Failed to extract test install script", "");
250253
return;
251254
}
255+
this.installerTerminal.enableAnsi();
256+
// Extract customize.sh manually in rootless mode because unzip might not exists
257+
try (ZipFile zipFile = new ZipFile(file)) {
258+
ZipEntry zipEntry = zipFile.getEntry("customize.sh");
259+
if (zipEntry != null) {
260+
try (FileOutputStream fileOutputStream = new FileOutputStream(
261+
new File(file.getParentFile(), "customize.sh"))) {
262+
Files.copy(zipFile.getInputStream(zipEntry), fileOutputStream);
263+
}
264+
}
265+
} catch (Exception e) {
266+
Log.d(TAG, "Failed ot extract install script via java code", e);
267+
}
252268
installerMonitor = new InstallerMonitor(installScript);
253269
installJob = Shell.cmd("export MMM_EXT_SUPPORT=1",
270+
"export MMM_USER_LANGUAGE=" + (MainApplication.isForceEnglish() ? "en-US" :
271+
Resources.getSystem().getConfiguration().locale.toLanguageTag()),
272+
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
273+
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
274+
AnsiConstants.ANSI_CMD_SUPPORT,
254275
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
255276
"sh \"" + installScript.getAbsolutePath() + "\"" +
256277
" 3 0 \"" + file.getAbsolutePath() + "\"")
@@ -372,15 +393,25 @@ private void doInstall(File file, boolean noExtensions, boolean rootless) {
372393
installerMonitor = new InstallerMonitor(installExecutable);
373394
if (moduleId != null) installerMonitor.setForCleanUp(moduleId);
374395
if (noExtensions) {
396+
if ((compatFlags & AppUpdateManager.FLAG_COMPAT_FORCE_ANSI) != 0)
397+
this.installerTerminal.enableAnsi();
398+
else this.installerTerminal.disableAnsi();
375399
installJob = Shell.cmd(arch32, "export BOOTMODE=true", // No Extensions
400+
this.installerTerminal.isAnsiEnabled() ?
401+
AnsiConstants.ANSI_CMD_SUPPORT : "true",
376402
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
377403
installCommand).to(installerController, installerMonitor);
378404
} else {
405+
if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NO_ANSI) != 0)
406+
this.installerTerminal.enableAnsi();
407+
else this.installerTerminal.disableAnsi();
379408
installJob = Shell.cmd(arch32, "export MMM_EXT_SUPPORT=1",
380409
"export MMM_USER_LANGUAGE=" + (MainApplication.isForceEnglish() ? "en-US" :
381410
Resources.getSystem().getConfiguration().locale.toLanguageTag()),
382411
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
383412
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
413+
this.installerTerminal.isAnsiEnabled() ?
414+
AnsiConstants.ANSI_CMD_SUPPORT : "true",
384415
"export BOOTMODE=true", anyKernel3 ? "export AK3TMPFS=" +
385416
InstallerInitializer.peekMagiskPath() + "/ak3tmpfs" :
386417
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
@@ -389,8 +420,7 @@ private void doInstall(File file, boolean noExtensions, boolean rootless) {
389420
}
390421
boolean success = installJob.exec().isSuccess();
391422
// Wait one UI cycle before disabling controller or processing results
392-
UiThreadHandler.runAndWait(() -> {
393-
}); // to avoid race conditions
423+
UiThreadHandler.runAndWait(() -> {}); // to avoid race conditions
394424
installerController.disable();
395425
String message = "- Install successful";
396426
if (!success) {
@@ -433,6 +463,7 @@ public void onAddElement(String s) {
433463
this.useExt = true;
434464
return;
435465
}
466+
s = AnsiParser.patchEscapeSequence(s);
436467
if (this.useExt && s.startsWith("#!")) {
437468
this.processCommand(s.substring(2));
438469
} else if (this.useRecovery && s.startsWith("progress ")) {
@@ -464,7 +495,7 @@ private void processCommand(String rawCommand) {
464495
final String arg;
465496
final String command;
466497
int i = rawCommand.indexOf(' ');
467-
if (i != -1) {
498+
if (i != -1 && rawCommand.length() != i + 1) {
468499
arg = rawCommand.substring(i + 1).trim();
469500
command = rawCommand.substring(0, i);
470501
} else {
@@ -534,6 +565,9 @@ private void processCommand(String rawCommand) {
534565
arg.indexOf('/', 8) > 8))
535566
this.supportLink = arg;
536567
break;
568+
case "disableANSI":
569+
this.terminal.disableAnsi();
570+
break;
537571
}
538572
}
539573

@@ -625,6 +659,7 @@ private File extractInstallScript(String script) {
625659

626660
@SuppressWarnings("SameParameterValue")
627661
private void setInstallStateFinished(boolean success, String message, String optionalLink) {
662+
this.installerTerminal.disableAnsi();
628663
if (success && toDelete != null && !toDelete.delete()) {
629664
SuFile suFile = new SuFile(toDelete.getAbsolutePath());
630665
if (suFile.exists() && !suFile.delete())

app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
package com.fox2code.mmm.installer;
22

33
import android.graphics.Typeface;
4+
import android.text.Spannable;
45
import android.view.ViewGroup;
56
import android.widget.TextView;
67

78
import androidx.annotation.NonNull;
89
import androidx.recyclerview.widget.LinearLayoutManager;
910
import androidx.recyclerview.widget.RecyclerView;
1011

12+
import com.fox2code.androidansi.AnsiContext;
13+
1114
import java.util.ArrayList;
1215

1316
public class InstallerTerminal extends RecyclerView.Adapter<InstallerTerminal.TextViewHolder> {
1417
private final RecyclerView recyclerView;
15-
private final ArrayList<String> terminal;
18+
private final ArrayList<ProcessedLine> terminal;
19+
private final AnsiContext ansiContext;
1620
private final Object lock = new Object();
1721
private final int foreground;
22+
private boolean ansiEnabled = false;
1823

19-
public InstallerTerminal(RecyclerView recyclerView,int foreground) {
24+
public InstallerTerminal(RecyclerView recyclerView, boolean isLightTheme,int foreground) {
2025
recyclerView.setLayoutManager(
2126
new LinearLayoutManager(recyclerView.getContext()));
2227
this.recyclerView = recyclerView;
2328
this.foreground = foreground;
2429
this.terminal = new ArrayList<>();
30+
this.ansiContext = (isLightTheme ? AnsiContext.LIGHT : AnsiContext.DARK).copy();
2531
this.recyclerView.setAdapter(this);
2632
}
2733

@@ -33,7 +39,7 @@ public TextViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType
3339

3440
@Override
3541
public void onBindViewHolder(@NonNull TextViewHolder holder, int position) {
36-
holder.setText(this.terminal.get(position));
42+
this.terminal.get(position).setText(holder.textView);
3743
}
3844

3945
@Override
@@ -45,27 +51,20 @@ public void addLine(String line) {
4551
synchronized (lock) {
4652
boolean bottom = !this.recyclerView.canScrollVertically(1);
4753
int index = this.terminal.size();
48-
this.terminal.add(line);
54+
this.terminal.add(this.process(line));
4955
this.notifyItemInserted(index);
5056
if (bottom) this.recyclerView.scrollToPosition(index);
5157
}
5258
}
5359

54-
public void setLine(int index, String line) {
55-
synchronized (lock) {
56-
this.terminal.set(index, line);
57-
this.notifyItemChanged(index);
58-
}
59-
}
60-
6160
public void setLastLine(String line) {
6261
synchronized (lock) {
6362
int size = this.terminal.size();
6463
if (size == 0) {
65-
this.terminal.add(line);
64+
this.terminal.add(this.process(line));
6665
this.notifyItemInserted(0);
6766
} else {
68-
this.terminal.set(size - 1, line);
67+
this.terminal.set(size - 1, this.process(line));
6968
this.notifyItemChanged(size - 1);
7069
}
7170
}
@@ -75,7 +74,7 @@ public String getLastLine() {
7574
synchronized (lock) {
7675
int size = this.terminal.size();
7776
return size == 0 ? "" :
78-
this.terminal.get(size - 1);
77+
this.terminal.get(size - 1).line;
7978
}
8079
}
8180

@@ -111,6 +110,25 @@ public void scrollDown() {
111110
}
112111
}
113112

113+
public void enableAnsi() {
114+
this.ansiEnabled = true;
115+
}
116+
117+
public void disableAnsi() {
118+
this.ansiEnabled = false;
119+
this.ansiContext.reset();
120+
}
121+
122+
public boolean isAnsiEnabled() {
123+
return this.ansiEnabled;
124+
}
125+
126+
private ProcessedLine process(String line) {
127+
if (line.isEmpty()) return new ProcessedLine(" ", null);
128+
return new ProcessedLine(line, this.ansiEnabled ?
129+
this.ansiContext.parseAsSpannable(line) : null);
130+
}
131+
114132
public static class TextViewHolder extends RecyclerView.ViewHolder {
115133
private final TextView textView;
116134

@@ -128,4 +146,19 @@ private void setText(String text) {
128146
this.textView.setText(text.isEmpty() ? " " : text);
129147
}
130148
}
149+
150+
private static class ProcessedLine {
151+
public final String line;
152+
public final Spannable spannable;
153+
154+
ProcessedLine(String line, Spannable spannable) {
155+
this.line = line;
156+
this.spannable = spannable;
157+
}
158+
159+
public void setText(TextView textView) {
160+
textView.setText(this.spannable == null ?
161+
this.line: this.spannable);
162+
}
163+
}
131164
}

app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import com.topjohnwu.superuser.internal.UiThreadHandler;
3232

3333
import java.util.HashSet;
34-
import java.util.Locale;
3534

3635
public class SettingsActivity extends CompatActivity {
3736
private static int devModeStep = 0;

app/src/main/res/layout/installer.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
android:layout_width="match_parent"
1414
android:id="@+id/install_horizontal_scroller"
1515
android:background="@color/black"
16+
android:overScrollMode="never"
1617
app:layout_constraintBottom_toBottomOf="parent"
1718
app:layout_constraintEnd_toEndOf="parent"
1819
app:layout_constraintStart_toStartOf="parent"

app/src/main/res/layout/installer_wrap.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
android:layout_width="match_parent"
1515
android:layout_height="match_parent"
1616
android:textSize="16sp"
17+
android:overScrollMode="never"
1718
app:layout_constraintEnd_toEndOf="parent"
1819
app:layout_constraintStart_toStartOf="parent"
1920
app:layout_constraintTop_toTopOf="parent" />

0 commit comments

Comments
 (0)