Merge branch 'Fox2Code:master' into master

pull/91/head
007 2 years ago committed by GitHub
commit 2f8bc37349
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,8 +10,8 @@ android {
applicationId "com.fox2code.mmm"
minSdk 21
targetSdk 32
versionCode 30
versionName "0.3.2"
versionCode 32
versionName "0.4.0-rc1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -35,7 +35,6 @@ android {
lint {
disable 'MissingTranslation'
}
namespace 'com.fox2code.mmm'
}
aboutLibraries {
@ -65,7 +64,7 @@ dependencies {
// Utils
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3'
implementation 'com.github.topjohnwu.libsu:io:3.2.1'
implementation 'com.github.topjohnwu.libsu:io:4.0.0'
// Markdown
implementation "io.noties.markwon:core:4.6.2"
@ -75,6 +74,9 @@ dependencies {
annotationProcessor "io.noties:prism4j-bundler:2.0.0"
implementation "com.caverock:androidsvg:1.4"
// Utils for compat (Needs to be outsourced ASAP)
// compileOnly "org.robolectric:android-all:11-robolectric-6757853"
// Test
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.fox2code.mmm"
tools:ignore="QueryAllPackagesPermission">
<!-- Wifi is not the only way to get an internet connection -->

@ -0,0 +1,16 @@
package android.os;
import androidx.annotation.Keep;
import com.topjohnwu.superuser.ShellUtils;
@Keep
public class SystemProperties {
@Keep
public static String get(String key) {
String prop = ShellUtils.fastCmd("getprop " + key).trim();
if (prop.endsWith("\n"))
prop = prop.substring(0, prop.length() - 1).trim();
return prop;
}
}

@ -2,25 +2,42 @@ package com.fox2code.mmm;
import android.util.Log;
import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Http;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
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;
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/releases";
public static AppUpdateManager getAppUpdateManager() {
return INSTANCE;
}
private final HashMap<String, Integer> compatDataId = new HashMap<>();
private final Object updateLock = new Object();
private final File compatFile;
private String latestRelease;
private String latestPreRelease;
private long lastChecked;
@ -28,12 +45,20 @@ public class AppUpdateManager {
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.lastChecked = 0;
this.preReleaseNewer = true;
if (this.compatFile.isFile()) {
try {
this.parseCompatibilityFlags(new FileInputStream(this.compatFile));
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Return true if should show a notification
@ -95,6 +120,31 @@ public class AppUpdateManager {
return this.peekShouldUpdate();
}
public void checkUpdateCompat() {
if (this.compatFile.exists()) {
long lastUpdate = this.compatFile.lastModified();
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));
if (object.isNull("body")) {
compatDataId.clear();
Files.write(compatFile, new byte[0]);
return;
}
byte[] rawData = object.getString("body")
.getBytes(StandardCharsets.UTF_8);
this.parseCompatibilityFlags(new ByteArrayInputStream(rawData));
Files.write(compatFile, rawData);
} catch (Exception e) {
Log.e("AppUpdateManager", "Failed to update compat list", e);
}
}
public boolean peekShouldUpdate() {
return !(BuildConfig.VERSION_NAME.equals(this.latestRelease) ||
(this.preReleaseNewer &&
@ -109,4 +159,46 @@ public class AppUpdateManager {
public boolean isLastCheckSuccess() {
return lastCheckSuccess;
}
private void parseCompatibilityFlags(InputStream inputStream) throws IOException {
compatDataId.clear();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line;
while ((line = bufferedReader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
int i = line.indexOf('/');
if (i == -1) continue;
int value = 0;
for (String arg : line.substring(i + 1).split(",")) {
switch (arg) {
default:
break;
case "lowQuality":
value |= FLAG_COMPAT_LOW_QUALITY;
break;
case "noExt":
value |= FLAG_COMPAT_NO_EXT;
break;
case "magiskCmd":
value |= FLAG_COMPAT_MAGISK_CMD;
break;
case "need32bit":
value |= FLAG_COMPAT_NEED_32BIT;
break;
}
}
compatDataId.put(line.substring(0, i), value);
}
}
public int getCompatibilityFlags(String moduleId) {
Integer compatFlags = compatDataId.get(moduleId);
return compatFlags == null ? 0 : compatFlags;
}
public static int getFlagsForModule(String moduleId) {
return INSTANCE.getCompatibilityFlags(moduleId);
}
}

@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@ -22,6 +23,7 @@ import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.compat.CompatDisplay;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
@ -32,6 +34,7 @@ import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import eightbitlab.com.blurview.BlurView;
import eightbitlab.com.blurview.BlurViewFacade;
import eightbitlab.com.blurview.RenderScriptBlur;
public class MainActivity extends CompatActivity implements SwipeRefreshLayout.OnRefreshListener,
@ -72,10 +75,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
setContentView(R.layout.activity_main);
this.setTitle(R.string.app_name);
this.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
setActionBarBackground(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = this.getWindow().getAttributes();
@ -106,7 +107,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.actionBarBlur.setupWith(this.moduleList).setFrameClearDrawable(
this.getWindow().getDecorView().getBackground())
.setBlurAlgorithm(new RenderScriptBlur(this))
.setBlurRadius(5F).setBlurAutoUpdate(true)
.setBlurRadius(4F).setBlurAutoUpdate(true)
.setHasFixedTransformationMatrix(true);
this.updateBlurState();
this.moduleList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@ -116,6 +117,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
MainActivity.this.searchView.clearFocus();
}
});
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.searchView.setMinimumHeight(CompatDisplay.dpToPixel(16));
this.searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH |
EditorInfo.IME_FLAG_NO_FULLSCREEN);
this.searchView.setOnQueryTextListener(this);
@ -177,6 +180,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
} else {
if (AppUpdateManager.getAppUpdateManager().checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (AppUpdateManager.getAppUpdateManager().isLastCheckSuccess())
AppUpdateManager.getAppUpdateManager().checkUpdateCompat();
if (max != 0) {
int current = 0;
for (LocalModuleInfo localModuleInfo :
@ -238,32 +243,38 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.swipeRefreshLayout.setProgressViewOffset(false,
swipeRefreshLayoutOrigStartOffset + combinedBarsHeight,
swipeRefreshLayoutOrigEndOffset + combinedBarsHeight);
this.moduleViewListBuilder.setHeaderPx(actionBarHeight);
this.moduleViewListBuilder.setHeaderPx(
actionBarHeight + CompatDisplay.dpToPixel(8));
this.moduleViewListBuilder.setFooterPx(
bottomInset + this.searchCard.getHeight());
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.moduleViewListBuilder.updateInsets();
this.actionBarBlur.invalidate();
this.overScrollInsetTop = combinedBarsHeight;
this.overScrollInsetBottom = bottomInset;
Log.d(TAG, "( " + bottomInset + ", " +
this.searchCard.getHeight() + ")");
}
private void updateBlurState() {
boolean isLightMode = this.isLightTheme();
int colorBackground;
try {
colorBackground = this.getColorCompat(
android.R.attr.windowBackground);
} catch (Resources.NotFoundException e) {
colorBackground = this.getColorCompat(isLightMode ?
R.color.white : R.color.black);
}
if (MainApplication.isBlurEnabled()) {
this.actionBarBlur.setBlurEnabled(true);
int transparent = this.getColorCompat(R.color.transparent);
this.actionBarBackground.setColor(transparent);
this.actionBarBackground.setColor(ColorUtils
.setAlphaComponent(colorBackground, 0x02));
this.actionBarBackground.setColor(Color.TRANSPARENT);
} else {
this.actionBarBlur.setBlurEnabled(false);
boolean isLightMode = this.isLightTheme();
int colorOpaque;
try {
colorOpaque = this.getColorCompat(
android.R.attr.windowBackground);
} catch (Resources.NotFoundException e) {
colorOpaque = this.getColorCompat(isLightMode ?
R.color.white : R.color.black);
}
this.actionBarBackground.setColor(colorOpaque);
this.actionBarBlur.setOverlayColor(Color.TRANSPARENT);
this.actionBarBackground.setColor(colorBackground);
}
}

@ -104,6 +104,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
}
}
});
this.buttonAction.setClickable(false);
this.switchMaterial.setEnabled(false);
this.switchMaterial.setOnCheckedChangeListener((v, checked) -> {
if (this.initState) return; // Skip if non user

@ -76,6 +76,7 @@ public class ModuleViewListBuilder {
RepoManager repoManager = RepoManager.getINSTANCE();
repoManager.runAfterUpdate(() -> {
Log.i(TAG, "A2: " + repoManager.getModules().size());
boolean no32bitSupport = Build.SUPPORTED_32_BIT_ABIS.length == 0;
for (RepoModule repoModule : repoManager.getModules().values()) {
if (!repoModule.repoData.isEnabled()) continue;
ModuleInfo moduleInfo = repoModule.moduleInfo;
@ -84,9 +85,11 @@ public class ModuleViewListBuilder {
// Only check Magisk compatibility if root is present
(InstallerInitializer.peekMagiskPath() != null &&
repoModule.moduleInfo.minMagisk >
InstallerInitializer.peekMagiskVersion()
)))
continue; // Skip adding incompatible modules
InstallerInitializer.peekMagiskVersion())) ||
// If 64bit only system, skip 32bit only modules
(no32bitSupport && (AppUpdateManager.getFlagsForModule(repoModule.id)
& AppUpdateManager.FLAG_COMPAT_NEED_32BIT) != 0)
) continue; // Skip adding incompatible modules
ModuleHolder moduleHolder = this.mappedModuleHolders.get(repoModule.id);
if (moduleHolder == null) {
this.mappedModuleHolders.put(repoModule.id,

@ -31,13 +31,17 @@ public enum NotificationType implements NotificationTypeCst {
return !MainApplication.isShowcaseMode();
}
},
NO_ROOT(R.string.fail_root_magisk, R.drawable.ic_baseline_numbers_24) {
NO_ROOT(R.string.fail_root_magisk, R.drawable.ic_baseline_numbers_24, v -> {
IntentHelper.openUrl(v.getContext(), "https://github.com/topjohnwu/Magisk");
}) {
@Override
public boolean shouldRemove() {
return InstallerInitializer.peekMagiskPath() != null;
}
},
MAGISK_OUTDATED(R.string.magisk_outdated, R.drawable.ic_baseline_update_24) {
MAGISK_OUTDATED(R.string.magisk_outdated, R.drawable.ic_baseline_update_24, v -> {
IntentHelper.openUrl(v.getContext(), "https://github.com/topjohnwu/Magisk");
}) {
@Override
public boolean shouldRemove() {
return InstallerInitializer.peekMagiskPath() == null ||
@ -128,7 +132,11 @@ public enum NotificationType implements NotificationTypeCst {
public final boolean special;
NotificationType(@StringRes int textId, int iconId) {
this(textId, iconId, R.attr.colorError, R.attr.colorOnPrimary); //R.attr.colorOnError);
this(textId, iconId, R.attr.colorError, R.attr.colorOnPrimary);
}
NotificationType(@StringRes int textId, int iconId, View.OnClickListener onClickListener) {
this(textId, iconId, R.attr.colorError, R.attr.colorOnPrimary, onClickListener);
}
NotificationType(@StringRes int textId, int iconId, int backgroundAttr, int foregroundAttr) {

@ -81,9 +81,9 @@ public class AndroidacyActivity extends CompatActivity {
setActionBarBackground(null);
this.setDisplayHomeAsUpEnabled(true);
if (title == null || title.isEmpty()) {
this.setTitle(title);
} else {
this.setTitle("Androidacy");
} else {
this.setTitle(title);
}
if (allowInstall || title == null || title.isEmpty()) {
this.hideActionBar();
@ -120,9 +120,9 @@ public class AndroidacyActivity extends CompatActivity {
public boolean shouldOverrideUrlLoading(
@NonNull WebView view, @NonNull WebResourceRequest request) {
// Don't open non Androidacy urls inside WebView
if (request.isForMainFrame() && !(request.getUrl().getScheme().equals("intent") ||
AndroidacyUtil.isAndroidacyLink(request.getUrl()))) {
IntentHelper.openUrl(view.getContext(), request.getUrl().toString());
if (request.isForMainFrame() &&
!AndroidacyUtil.isAndroidacyLink(request.getUrl())) {
IntentHelper.openUri(view.getContext(), request.getUrl().toString());
return true;
}
return false;

@ -10,11 +10,15 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemProperties;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import androidx.annotation.AttrRes;
import androidx.annotation.CallSuper;
@ -65,8 +69,10 @@ public class CompatActivity extends AppCompatActivity {
private MenuItem.OnMenuItemClickListener menuClickListener;
private CharSequence menuContentDescription;
@StyleRes private int setThemeDynamic = 0;
private boolean onCreateCalledOnce = false;
private boolean onCreateCalled = false;
private boolean isRefreshUi = false;
private boolean hasHardwareNavBar;
private int drawableResId;
private MenuItem menuItem;
// CompatConfigHelper
@ -82,6 +88,8 @@ public class CompatActivity extends AppCompatActivity {
if (!this.onCreateCalled) {
this.getLayoutInflater().setFactory2(new LayoutInflaterFactory(this.getDelegate())
.addOnViewCreatedListener(WindowInsetsHelper.Companion.getLISTENER()));
this.hasHardwareNavBar = this.hasHardwareNavBar0();
this.onCreateCalledOnce = true;
}
Application application = this.getApplication();
if (application instanceof ApplicationCallbacks) {
@ -95,6 +103,7 @@ public class CompatActivity extends AppCompatActivity {
@Override
protected void onResume() {
this.hasHardwareNavBar = this.hasHardwareNavBar0();
super.onResume();
this.refreshUI();
}
@ -114,7 +123,7 @@ public class CompatActivity extends AppCompatActivity {
@CallSuper
public void refreshUI() {
// Avoid recursive calls
if (this.isRefreshUi) return;
if (this.isRefreshUi || !this.onCreateCalled) return;
Application application = this.getApplication();
if (application instanceof ApplicationCallbacks) {
this.isRefreshUi = true;
@ -196,6 +205,22 @@ public class CompatActivity extends AppCompatActivity {
}
}
public View getActionBarView() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
return compatActionBar.getCustomView();
} else {
android.app.ActionBar actionBar = this.getActionBar();
return actionBar != null ? actionBar.getCustomView() : null;
}
}
@Dimension @Px
public int getActionBarHeight() {
androidx.appcompat.app.ActionBar compatActionBar;
@ -242,21 +267,35 @@ public class CompatActivity extends AppCompatActivity {
return height;
}
public int getNavigationBarHeight() { // How to improve this?
public int getNavigationBarHeight() {
int height = WindowInsetsCompat.CONSUMED.getInsets(
WindowInsetsCompat.Type.navigationBars()).bottom;
if (height == 0) { // Fallback to system resources
int id = Resources.getSystem().getIdentifier(
"config_showNavigationBar", "bool", "android");
if (id > 0 && Resources.getSystem().getBoolean(id)) {
Log.d(TAG, "Nav 1: " + id);
if ((id > 0 && Resources.getSystem().getBoolean(id))
|| !this.hasHardwareNavBar()) {
id = Resources.getSystem().getIdentifier(
"navigation_bar_height", "dimen", "android");
Log.d(TAG, "Nav 2: " + id);
if (id > 0) return Resources.getSystem().getDimensionPixelSize(id);
}
}
return height;
}
public boolean hasHardwareNavBar() {
// If onCreate has not been called yet, cached value is not valid
return this.onCreateCalledOnce ? this.hasHardwareNavBar : this.hasHardwareNavBar0();
}
private boolean hasHardwareNavBar0() {
return (ViewConfiguration.get(this).hasPermanentMenuKey() ||
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)) &&
!"0".equals(SystemProperties.get("qemu.hw.mainkeys"));
}
public void setActionBarExtraMenuButton(@DrawableRes int drawableResId,
MenuItem.OnMenuItemClickListener menuClickListener) {
this.setActionBarExtraMenuButton(drawableResId,

@ -16,6 +16,7 @@ import android.widget.Toast;
import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.mmm.ActionButtonType;
import com.fox2code.mmm.AppUpdateManager;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
@ -26,6 +27,7 @@ import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Hashes;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.PropUtils;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.topjohnwu.superuser.CallbackList;
import com.topjohnwu.superuser.Shell;
@ -261,23 +263,39 @@ public class InstallerActivity extends CompatActivity {
.to(installerController, installerMonitor);
} else {
String arch32 = "true"; // Do nothing by default
boolean needs32bit = false;
String moduleId = null;
try (ZipFile zipFile = new ZipFile(file)) {
if (zipFile.getEntry( // Check if module hard require 32bit support
"common/addon/Volume-Key-Selector/tools/arm64/keycheck") == null &&
zipFile.getEntry(
"common/addon/Volume-Key-Selector/install.sh") != null) {
needs32bit = true;
}
moduleId = PropUtils.readModuleId(zipFile
.getInputStream(zipFile.getEntry("module.prop")));
} catch (IOException ignored) {}
int compatFlags = AppUpdateManager.getFlagsForModule(moduleId);
if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NEED_32BIT) != 0)
needs32bit = true;
if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NO_EXT) != 0)
noExtensions = true;
if (moduleId != null && (moduleId.isEmpty() ||
moduleId.contains("/") || moduleId.contains("\0") ||
(moduleId.startsWith(".") && moduleId.endsWith(".")))) {
this.setInstallStateFinished(false,
"! This module contain a dangerous moduleId",
null);
return;
}
if (Build.SUPPORTED_32_BIT_ABIS.length == 0) {
boolean needs32bit = false;
try (ZipFile zipFile = new ZipFile(file)) {
if (zipFile.getEntry( // Check if module hard require 32bit support
"common/addon/Volume-Key-Selector/tools/arm64/keycheck") == null &&
zipFile.getEntry(
"common/addon/Volume-Key-Selector/install.sh") != null) {
needs32bit = true;
}
} catch (IOException ignored) {}
if (needs32bit) {
this.setInstallStateFinished(false,
"! This module can't be installed on a 64bit only system",
null);
return;
}
} else {
} else if (needs32bit || (compatFlags & AppUpdateManager.FLAG_COMPAT_NO_EXT) == 0) {
// Restore Magisk legacy stuff for retro compatibility
if (Build.SUPPORTED_32_BIT_ABIS[0].contains("arm"))
arch32 = "export ARCH32=arm";
@ -288,7 +306,8 @@ public class InstallerActivity extends CompatActivity {
File installExecutable;
if (InstallerInitializer.peekMagiskVersion() >=
Constants.MAGISK_VER_CODE_INSTALL_COMMAND &&
(noExtensions || MainApplication.isUsingMagiskCommand())) {
((compatFlags & AppUpdateManager.FLAG_COMPAT_MAGISK_CMD) != 0 ||
noExtensions || MainApplication.isUsingMagiskCommand())) {
installCommand = "magisk --install-module \"" + file.getAbsolutePath() + "\"";
installExecutable = new File(InstallerInitializer.peekMagiskPath()
.equals("/sbin") ? "/sbin/magisk" : "/system/bin/magisk");
@ -296,13 +315,14 @@ public class InstallerActivity extends CompatActivity {
installExecutable = this.extractInstallScript("module_installer_compat.sh");
if (installExecutable == null) {
this.setInstallStateFinished(false,
"! Failed to extract module install script", "");
"! Failed to extract module install script", null);
return;
}
installCommand = "sh \"" + installExecutable.getAbsolutePath() + "\"" +
" /dev/null 1 \"" + file.getAbsolutePath() + "\"";
}
installerMonitor = new InstallerMonitor(installExecutable);
if (moduleId != null) installerMonitor.setForCleanUp(moduleId);
if (noExtensions) {
installJob = Shell.su(arch32, // No Extensions
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
@ -462,6 +482,7 @@ public class InstallerActivity extends CompatActivity {
private static final String DEFAULT_ERR = "! Install failed";
private final String installScriptErr;
public String lastCommand = "";
public String forCleanUp;
public InstallerMonitor(File installScript) {
super(Runnable::run);
@ -476,6 +497,10 @@ public class InstallerActivity extends CompatActivity {
this.lastCommand = s;
}
public void setForCleanUp(String forCleanUp) {
this.forCleanUp = forCleanUp;
}
private String doCleanUp() {
String installScriptErr = this.installScriptErr;
// This block is mainly to help fixing customize.sh syntax errors
@ -490,6 +515,10 @@ public class InstallerActivity extends CompatActivity {
Log.e(TAG, "Failed to delete failed update");
return "Error: " + installScriptErr.substring(i + 1);
}
} else if (this.forCleanUp != null) {
SuFile moduleUpdate = new SuFile("/data/adb/modules_update/" + this.forCleanUp);
if (moduleUpdate.exists() && !moduleUpdate.deleteRecursive())
Log.e(TAG, "Failed to delete failed update");
}
return DEFAULT_ERR;
}

@ -18,6 +18,7 @@ import com.fox2code.mmm.R;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -64,8 +65,11 @@ public class MarkdownActivity extends CompatActivity {
}
Log.i(TAG, "Url for markdown " + url);
setContentView(R.layout.markdown_view);
ViewGroup markdownBackground = findViewById(R.id.markdownBackground);
TextView textView = findViewById(R.id.markdownView);
final ViewGroup markdownBackground = findViewById(R.id.markdownBackground);
final TextView textView = findViewById(R.id.markdownView);
final TextView footer = findViewById(R.id.markdownFooter);
UiThreadHandler.handler.postDelayed(() -> // Fix footer height
footer.setMinHeight(this.getNavigationBarHeight()), 1L);
new Thread(() -> {
try {
Log.d(TAG, "Downloading");

@ -112,7 +112,8 @@ public class SettingsActivity extends CompatActivity {
.withLicenseShown(true).withAboutMinimalDesign(false)
.withUiListener(new OverScrollManager.LibsOverScroll());
Preference update = findPreference("pref_update");
update.setVisible(AppUpdateManager.getAppUpdateManager().peekHasUpdate());
update.setVisible(BuildConfig.DEBUG ||
AppUpdateManager.getAppUpdateManager().peekHasUpdate());
update.setOnPreferenceClickListener(p -> {
devModeStep = 0;
IntentHelper.openUrl(p.getContext(),

@ -30,8 +30,21 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
public class IntentHelper {
private static final String TAG = "IntentHelper";
public static void openUri(Context context, String uri) {
if (uri.startsWith("intent://")) {
try {
startActivity(context, Intent.parseUri(uri, Intent.URI_INTENT_SCHEME), false);
} catch (URISyntaxException | ActivityNotFoundException e) {
Log.e(TAG, "Failed launch of " + uri, e);
}
} else openUrl(context, uri);
}
public static void openUrl(Context context, String url) {
openUrl(context, url, false);
}
@ -227,7 +240,7 @@ public class IntentHelper {
callback.onReceived(destination, null, RESPONSE_ERROR);
return;
}
Log.d("IntentHelper", "FilePicker returned " + uri);
Log.d(TAG, "FilePicker returned " + uri);
if ("http".equals(uri.getScheme()) ||
"https".equals(uri.getScheme())) {
callback.onReceived(destination, uri, RESPONSE_URL);
@ -259,7 +272,7 @@ public class IntentHelper {
Files.copy(inputStream, outputStream);
success = true;
} catch (Exception e) {
Log.e("IntentHelper", "failed copy of " + uri, e);
Log.e(TAG, "failed copy of " + uri, e);
Toast.makeText(compatActivity,
R.string.file_picker_failure,
Toast.LENGTH_SHORT).show();
@ -267,7 +280,7 @@ public class IntentHelper {
Files.closeSilently(inputStream);
Files.closeSilently(outputStream);
if (!success && destination.exists() && !destination.delete())
Log.e("IntentHelper", "Failed to delete artefact!");
Log.e(TAG, "Failed to delete artefact!");
}
callback.onReceived(destination, uri, success ? RESPONSE_FILE : RESPONSE_ERROR);
});

@ -1,7 +1,11 @@
package com.fox2code.mmm.utils;
import static com.fox2code.mmm.AppUpdateManager.FLAG_COMPAT_LOW_QUALITY;
import static com.fox2code.mmm.AppUpdateManager.getFlagsForModule;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.fox2code.mmm.manager.ModuleInfo;
import com.topjohnwu.superuser.io.SuFileInputStream;
@ -274,6 +278,22 @@ public class PropUtils {
}
}
public static String readModuleId(InputStream inputStream) {
String moduleId = null;
try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.startsWith("id=")) {
moduleId = line.substring(3).trim();
}
}
} catch (IOException e) {
Log.d("PropUtils", "Failed to get moduleId", e);
}
return moduleId;
}
public static void applyFallbacks(ModuleInfo moduleInfo) {
if (moduleInfo.support == null || moduleInfo.support.isEmpty()) {
moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id);
@ -299,7 +319,8 @@ public class PropUtils {
|| moduleInfo.author == null || !TextUtils.isGraphic(moduleInfo.author)
|| (description = moduleInfo.description) == null || !TextUtils.isGraphic(description)
|| description.toLowerCase(Locale.ROOT).equals(moduleInfo.name.toLowerCase(Locale.ROOT))
|| description.length() < Math.min(Math.max(moduleInfo.name.length() + 4, 16), 24);
|| description.length() < Math.min(Math.max(moduleInfo.name.length() + 4, 16), 24)
|| (getFlagsForModule(moduleInfo.id) & FLAG_COMPAT_LOW_QUALITY) != 0;
}
private static boolean isInvalidValue(String name) {

@ -57,16 +57,21 @@
android:gravity="right"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:fitsSystemWindowsInsets="bottom"
app:layout_fitsSystemWindowsInsets="bottom"
tools:ignore="RtlHardcoded">
<androidx.cardview.widget.CardView
<!--
setting high app:cardCornerRadius is not supported on some versions
so we must use code to get a round appearance.
-->
<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:background="@null"
app:cardCornerRadius="75dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardPreventCornerOverlap="true"
app:strokeColor="@android:color/transparent"
app:cardElevation="0dp"
app:strokeWidth="0dp">
@ -75,7 +80,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null" />
</androidx.cardview.widget.CardView>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -7,7 +7,7 @@
android:background="@color/black"
android:id="@+id/markdownBackground"
app:fitsSystemWindowsInsets="top|left|right">
<androidx.cardview.widget.CardView
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
@ -33,5 +33,5 @@
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</androidx.cardview.widget.CardView>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

@ -10,7 +10,7 @@
android:layout_marginBottom="2dp"
android:gravity="center_vertical"
android:background="@null">
<androidx.cardview.widget.CardView
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
@ -190,5 +190,5 @@
tools:ignore="RtlHardcoded" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

@ -0,0 +1,7 @@
<resources>
<string-array name="theme_values_names">
<item>Sistem</item>
<item>Gelap</item>
<item>Terang</item>
</string-array>
</resources>

@ -0,0 +1,96 @@
<resources>
<string name="app_name">Fox\'s Magisk Module Manager</string>
<string name="app_name_short">Fox\'s Mmm</string>
<string name="fail_root_magisk">Tidak dapat mengakses root atau Magisk</string>
<string name="loading">Memuat…</string>
<string name="updatable">Dapat diperbarui</string>
<string name="installed">Terpasang</string>
<string name="online_repo">Repo Online</string>
<string name="showcase_mode">Aplikasi berada dalam mode lockdown</string>
<string name="failed_download">Tidak dapat mengunduh file.</string>
<string name="slow_modules">Modul membutuhkan waktu yang terlalu lama untuk melakukan boot, pertimbangkanglah untuk mematikan beberapa modul</string>
<string name="fail_internet">Tidak dapat terhubung dengan internet</string>
<string name="title_activity_settings">SettingsActivity</string>
<string name="app_update_available">Tersedia pembaruan aplikasi</string>
<string name="app_update">Perbarui</string>
<string name="no_desc_found">Tidak dapat menemukan deskripsi.</string>
<string name="download_module">Unduh modul</string>
<string name="install_module">Instal modul</string>
<string name="update_module">Perbarui modul</string>
<string name="changelog">Changelog</string>
<string name="website">Situs</string>
<string name="support">Dukungan</string>
<string name="donate">Donasi</string>
<string name="submit_modules">Kirim sebuah modul</string>
<string name="require_android_6">Membutuhkan Android 6.0+</string>
<!-- Module section translation -->
<string name="module_last_update">Pembaruan terakhir:</string>
<string name="module_repo">Repo:</string>
<string name="module_by">oleh</string>
<string name="module_downloads">Diunduh:</string>
<string name="module_stars">Bintang:</string>
<!-- Preference Titles -->
<!-- Note: Lockdown mode used to be called showcase mode -->
<string name="manage_repos_pref">Kelola repo</string>
<string name="showcase_mode_pref">Mode lockdown</string>
<string name="showcase_mode_desc">Mode lockdown mencegah manager dari melakukan tindakan terhadap modul</string>
<string name="pref_category_settings">Peraturan</string>
<string name="pref_category_info">Info</string>
<string name="show_licenses">Perlihatkan lisensi</string>
<string name="licenses">Lisensi</string>
<string name="show_incompatible_pref">Tampilkan modul tidak kompatibel</string>
<string name="show_incompatible_desc">Tampilkan modul yang tidak kompatibel dengan perangkat anda berdasarkan metadata mereka</string>
<string name="magisk_outdated">Versi Magisk usang!</string>
<string name="pref_category_repos">Repos</string>
<string name="repo_main_desc">Repository yang menampung modul Magisk</string>
<string name="repo_main_alt">Sebuah alternatif Magisk-Modules-Repo dengan restriksi lebih ringan.</string>
<string name="master_delete">Hapus file modul?</string>
<string name="master_delete_no">Simpan file</string>
<string name="master_delete_yes">Hapus file</string>
<string name="master_delete_fail">Tidak dapat menghapus file modul</string>
<string name="theme_pref">Tema</string>
<string name="theme_mode_pref">Mode tema</string>
<string name="module_id_prefix">Id modul: </string>
<string name="install_from_storage">Instal modul dari penyimpanan</string>
<string name="invalid_format">Modul yang diseleksi mempunyai format yang tidak valid</string>
<string name="local_install_title">Instal lokal</string>
<string name="source_code">Kode sumber</string>
<string name="magisk_builtin_module">Modul bawaan Magisk</string>
<string name="substratum_builtin_module">Modul bawaan Substratum</string>
<string name="force_dark_terminal_title">Paksa mode gelap pada terminal</string>
<string name="file_picker_failure">Pemilih file anda sekarang gagal memberikan akses kepada file.</string>
<string name="remote_install_title">Instal jarak jauh</string>
<string name="file_picker_wierd">Pemilih file anda mengembalikan tanggapan yang tidak standar.</string>
<string name="use_magisk_install_command_pref">Gunakan perintah instal modul magisk</string>
<string name="use_magisk_install_command_desc">
Waktu diuji ini menyebabkan masalah kepada alat diagnosis kesalahan instal modul,
jadi saya menyembunyikan opsi ini dibelakan mode pengembang, aktifkan pada risiko anda sendiri!
</string>
<string name="dev_mode_enabled">Mode pengembang aktif</string>
<string name="force_english_pref">Paksa Bahasa Inggris</string>
<string name="disable_low_quality_module_filter_pref">Nonaktifkan filter modul qualitas rendah</string>
<string name="disable_low_quality_module_filter_desc">
Beberapa modul tidak menyatakan metadata mereka dengan benar,menyebabkan glitch visual,
dan/atau mengindikasikan modul qualitas rendah, nonaktifkan pada risiko anda sendiri!
</string>
<string name="dns_over_https_pref">DNS melalui HTTPS</string>
<string name="dns_over_https_desc">
Mungkin dapat memperbaiki masalah koneksi didalam beberapa kasus.
(Tidak berlaku untuk WebView)
</string>
<string name="disable_extensions_pref">Nonaktifkan ekstensi</string>
<string name="disable_extensions_desc">
Nonaktifkan ekstensi Fox\'s Mmm, ini mencegah modul dari menggunakan
ekstensi terminal, berguna jika sebuah modul menyalahgunakan ekstensi Fox\'s Mmm.
</string>
<string name="wrap_text_pref">Wrap teks</string>
<string name="wrap_text_desc">
Wrap teks ke sebuah garis baru daripada menaruh
semua teks pada garis yang sama pada saat menginstal sebuah modul
</string>
<string name="enable_blur_pref">Aktifkan blur</string>
<string name="repo_enabled">Repo diaktifkan</string>
<string name="repo_disabled">Repo dinonaktifkan</string>
</resources>

@ -22,6 +22,7 @@
<string name="support">支持</string>
<string name="donate">捐赠</string>
<string name="submit_modules">提交一个模块</string>
<string name="require_android_6">需要 Android 6.0+</string>
<!-- Module section translation -->
<string name="module_last_update">最后更新:</string>
@ -50,6 +51,7 @@
<string name="master_delete_yes">删除文件</string>
<string name="master_delete_fail">无法删除模块文件</string>
<string name="theme_pref">主题</string>
<string name="theme_mode_pref">主题模式</string>
<string name="module_id_prefix">模块ID: </string>
<string name="install_from_storage">从存储空间安装模块</string>
<string name="invalid_format">所选模块的格式无效</string>
@ -88,6 +90,7 @@
将文本换行至新行,
而不是将所有文本放在同一行
</string>
<string name="enable_blur_pref">启用模糊</string>
<string name="repo_enabled">启用仓库</string>
<string name="repo_disabled">禁用仓库</string>
</resources>

@ -22,6 +22,7 @@
<string name="support">支援</string>
<string name="donate">捐贈</string>
<string name="submit_modules">提交一個模組</string>
<string name="require_android_6">要求 Android 6.0 以上版本</string>
<!-- Module section translation -->
<string name="module_last_update">最後更新:</string>
@ -50,6 +51,7 @@
<string name="master_delete_yes">刪除安裝檔</string>
<string name="master_delete_fail">刪除安裝檔失敗</string>
<string name="theme_pref">主題</string>
<string name="theme_mode_pref">主題模式</string>
<string name="module_id_prefix">模組 ID: </string>
<string name="install_from_storage">從本機安裝</string>
<string name="invalid_format">所選模組的格式無效或檔案損毀</string>
@ -88,6 +90,7 @@
將文字換行至新行,
而不是將所有文字放在同一行。
</string>
<string name="enable_blur_pref">啟用模糊</string>
<string name="repo_enabled">啟用倉庫</string>
<string name="repo_disabled">禁用倉庫</string>
</resources>
</resources>
Loading…
Cancel
Save