Merge branch 'Fox2Code:master' into master

pull/27/head
Jia-Bin 3 years ago committed by GitHub
commit 82d42824ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,8 +10,8 @@ android {
applicationId "com.fox2code.mmm" applicationId "com.fox2code.mmm"
minSdk 21 minSdk 21
targetSdk 32 targetSdk 32
versionCode 30 versionCode 32
versionName "0.3.2" versionName "0.4.0-rc1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -64,7 +64,7 @@ dependencies {
// Utils // Utils
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-brotli: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 // Markdown
implementation "io.noties.markwon:core:4.6.2" implementation "io.noties.markwon:core:4.6.2"
@ -74,6 +74,9 @@ dependencies {
annotationProcessor "io.noties:prism4j-bundler:2.0.0" annotationProcessor "io.noties:prism4j-bundler:2.0.0"
implementation "com.caverock:androidsvg:1.4" implementation "com.caverock:androidsvg:1.4"
// Utils for compat
compileOnly "org.robolectric:android-all:11-robolectric-6757853"
// Test // Test
testImplementation 'junit:junit:4.+' testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'

@ -2,25 +2,42 @@ package com.fox2code.mmm;
import android.util.Log; import android.util.Log;
import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; 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.nio.charset.StandardCharsets;
import java.util.HashMap;
// See https://docs.github.com/en/rest/reference/repos#releases // See https://docs.github.com/en/rest/reference/repos#releases
public class AppUpdateManager { 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 String TAG = "AppUpdateManager";
private static final AppUpdateManager INSTANCE = new AppUpdateManager(); private static final AppUpdateManager INSTANCE = new AppUpdateManager();
private static final String RELEASES_API_URL = private static final String RELEASES_API_URL =
"https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases"; "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() { public static AppUpdateManager getAppUpdateManager() {
return INSTANCE; return INSTANCE;
} }
private final HashMap<String, Integer> compatDataId = new HashMap<>();
private final Object updateLock = new Object(); private final Object updateLock = new Object();
private final File compatFile;
private String latestRelease; private String latestRelease;
private String latestPreRelease; private String latestPreRelease;
private long lastChecked; private long lastChecked;
@ -28,12 +45,20 @@ public class AppUpdateManager {
private boolean lastCheckSuccess; private boolean lastCheckSuccess;
private AppUpdateManager() { private AppUpdateManager() {
this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt");
this.latestRelease = MainApplication.getBootSharedPreferences() this.latestRelease = MainApplication.getBootSharedPreferences()
.getString("updater_latest_release", BuildConfig.VERSION_NAME); .getString("updater_latest_release", BuildConfig.VERSION_NAME);
this.latestPreRelease = MainApplication.getBootSharedPreferences() this.latestPreRelease = MainApplication.getBootSharedPreferences()
.getString("updater_latest_pre_release", BuildConfig.VERSION_NAME); .getString("updater_latest_pre_release", BuildConfig.VERSION_NAME);
this.lastChecked = 0; this.lastChecked = 0;
this.preReleaseNewer = true; 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 // Return true if should show a notification
@ -95,6 +120,31 @@ public class AppUpdateManager {
return this.peekShouldUpdate(); 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() { public boolean peekShouldUpdate() {
return !(BuildConfig.VERSION_NAME.equals(this.latestRelease) || return !(BuildConfig.VERSION_NAME.equals(this.latestRelease) ||
(this.preReleaseNewer && (this.preReleaseNewer &&
@ -109,4 +159,46 @@ public class AppUpdateManager {
public boolean isLastCheckSuccess() { public boolean isLastCheckSuccess() {
return lastCheckSuccess; 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);
}
} }

@ -117,6 +117,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
MainActivity.this.searchView.clearFocus(); MainActivity.this.searchView.clearFocus();
} }
}); });
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.searchView.setMinimumHeight(CompatDisplay.dpToPixel(16)); this.searchView.setMinimumHeight(CompatDisplay.dpToPixel(16));
this.searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH | this.searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH |
EditorInfo.IME_FLAG_NO_FULLSCREEN); EditorInfo.IME_FLAG_NO_FULLSCREEN);
@ -179,6 +180,8 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
} else { } else {
if (AppUpdateManager.getAppUpdateManager().checkUpdate(true)) if (AppUpdateManager.getAppUpdateManager().checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (AppUpdateManager.getAppUpdateManager().isLastCheckSuccess())
AppUpdateManager.getAppUpdateManager().checkUpdateCompat();
if (max != 0) { if (max != 0) {
int current = 0; int current = 0;
for (LocalModuleInfo localModuleInfo : for (LocalModuleInfo localModuleInfo :
@ -241,9 +244,10 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
swipeRefreshLayoutOrigStartOffset + combinedBarsHeight, swipeRefreshLayoutOrigStartOffset + combinedBarsHeight,
swipeRefreshLayoutOrigEndOffset + combinedBarsHeight); swipeRefreshLayoutOrigEndOffset + combinedBarsHeight);
this.moduleViewListBuilder.setHeaderPx( this.moduleViewListBuilder.setHeaderPx(
actionBarHeight + CompatDisplay.dpToPixel(4)); actionBarHeight + CompatDisplay.dpToPixel(8));
this.moduleViewListBuilder.setFooterPx( this.moduleViewListBuilder.setFooterPx(
bottomInset + this.searchCard.getHeight()); bottomInset + this.searchCard.getHeight());
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.moduleViewListBuilder.updateInsets(); this.moduleViewListBuilder.updateInsets();
this.actionBarBlur.invalidate(); this.actionBarBlur.invalidate();
this.overScrollInsetTop = combinedBarsHeight; this.overScrollInsetTop = combinedBarsHeight;

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

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

@ -31,13 +31,17 @@ public enum NotificationType implements NotificationTypeCst {
return !MainApplication.isShowcaseMode(); 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 @Override
public boolean shouldRemove() { public boolean shouldRemove() {
return InstallerInitializer.peekMagiskPath() != null; 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 @Override
public boolean shouldRemove() { public boolean shouldRemove() {
return InstallerInitializer.peekMagiskPath() == null || return InstallerInitializer.peekMagiskPath() == null ||
@ -128,7 +132,11 @@ public enum NotificationType implements NotificationTypeCst {
public final boolean special; public final boolean special;
NotificationType(@StringRes int textId, int iconId) { 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) { NotificationType(@StringRes int textId, int iconId, int backgroundAttr, int foregroundAttr) {

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

@ -10,6 +10,7 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemProperties;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
@ -88,8 +89,7 @@ public class CompatActivity extends AppCompatActivity {
if (!this.onCreateCalled) { if (!this.onCreateCalled) {
this.getLayoutInflater().setFactory2(new LayoutInflaterFactory(this.getDelegate()) this.getLayoutInflater().setFactory2(new LayoutInflaterFactory(this.getDelegate())
.addOnViewCreatedListener(WindowInsetsHelper.Companion.getLISTENER())); .addOnViewCreatedListener(WindowInsetsHelper.Companion.getLISTENER()));
this.hasHardwareNavBar = ViewConfiguration.get(this).hasPermanentMenuKey() || this.hasHardwareNavBar = this.hasHardwareNavBar0();
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
this.onCreateCalledOnce = true; this.onCreateCalledOnce = true;
} }
Application application = this.getApplication(); Application application = this.getApplication();
@ -104,6 +104,7 @@ public class CompatActivity extends AppCompatActivity {
@Override @Override
protected void onResume() { protected void onResume() {
this.hasHardwareNavBar = this.hasHardwareNavBar0();
super.onResume(); super.onResume();
this.refreshUI(); this.refreshUI();
} }
@ -287,9 +288,13 @@ public class CompatActivity extends AppCompatActivity {
public boolean hasHardwareNavBar() { public boolean hasHardwareNavBar() {
// If onCreate has not been called yet, cached value is not valid // If onCreate has not been called yet, cached value is not valid
return this.onCreateCalledOnce ? this.hasHardwareNavBar : return this.onCreateCalledOnce ? this.hasHardwareNavBar : this.hasHardwareNavBar0();
ViewConfiguration.get(this).hasPermanentMenuKey() || }
KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
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, public void setActionBarExtraMenuButton(@DrawableRes int drawableResId,

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

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

@ -1,7 +1,11 @@
package com.fox2code.mmm.utils; 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.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.manager.ModuleInfo;
import com.topjohnwu.superuser.io.SuFileInputStream; 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) { public static void applyFallbacks(ModuleInfo moduleInfo) {
if (moduleInfo.support == null || moduleInfo.support.isEmpty()) { if (moduleInfo.support == null || moduleInfo.support.isEmpty()) {
moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id); moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id);
@ -299,7 +319,8 @@ public class PropUtils {
|| moduleInfo.author == null || !TextUtils.isGraphic(moduleInfo.author) || moduleInfo.author == null || !TextUtils.isGraphic(moduleInfo.author)
|| (description = moduleInfo.description) == null || !TextUtils.isGraphic(description) || (description = moduleInfo.description) == null || !TextUtils.isGraphic(description)
|| description.toLowerCase(Locale.ROOT).equals(moduleInfo.name.toLowerCase(Locale.ROOT)) || 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) { private static boolean isInvalidValue(String name) {

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

Loading…
Cancel
Save