diff --git a/app/build.gradle b/app/build.gradle index 1661b02..0f58843 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } @@ -64,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" @@ -74,6 +74,9 @@ dependencies { annotationProcessor "io.noties:prism4j-bundler:2.0.0" implementation "com.caverock:androidsvg:1.4" + // Utils for compat + compileOnly "org.robolectric:android-all:11-robolectric-6757853" + // Test testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java index 1050811..9ce29e7 100644 --- a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java +++ b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java @@ -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 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); + } } diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 2bd3ce3..563b4fc 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -117,6 +117,7 @@ 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); @@ -179,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 : @@ -241,9 +244,10 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O swipeRefreshLayoutOrigStartOffset + combinedBarsHeight, swipeRefreshLayoutOrigEndOffset + combinedBarsHeight); this.moduleViewListBuilder.setHeaderPx( - actionBarHeight + CompatDisplay.dpToPixel(4)); + 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; diff --git a/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java b/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java index 0b44297..accb39c 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java @@ -104,6 +104,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter { if (this.initState) return; // Skip if non user diff --git a/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java b/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java index ec074a2..02417ad 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java @@ -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, diff --git a/app/src/main/java/com/fox2code/mmm/NotificationType.java b/app/src/main/java/com/fox2code/mmm/NotificationType.java index 71fc825..06b4330 100644 --- a/app/src/main/java/com/fox2code/mmm/NotificationType.java +++ b/app/src/main/java/com/fox2code/mmm/NotificationType.java @@ -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) { diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index c7d905d..8b10024 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -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; diff --git a/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java b/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java index a7031b8..0df73eb 100644 --- a/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java +++ b/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java @@ -10,6 +10,7 @@ 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; @@ -88,8 +89,7 @@ public class CompatActivity extends AppCompatActivity { if (!this.onCreateCalled) { this.getLayoutInflater().setFactory2(new LayoutInflaterFactory(this.getDelegate()) .addOnViewCreatedListener(WindowInsetsHelper.Companion.getLISTENER())); - this.hasHardwareNavBar = ViewConfiguration.get(this).hasPermanentMenuKey() || - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); + this.hasHardwareNavBar = this.hasHardwareNavBar0(); this.onCreateCalledOnce = true; } Application application = this.getApplication(); @@ -104,6 +104,7 @@ public class CompatActivity extends AppCompatActivity { @Override protected void onResume() { + this.hasHardwareNavBar = this.hasHardwareNavBar0(); super.onResume(); this.refreshUI(); } @@ -287,9 +288,13 @@ public class CompatActivity extends AppCompatActivity { public boolean hasHardwareNavBar() { // If onCreate has not been called yet, cached value is not valid - return this.onCreateCalledOnce ? this.hasHardwareNavBar : - ViewConfiguration.get(this).hasPermanentMenuKey() || - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); + 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, 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 448c55f..cc21da1 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -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; } diff --git a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java index c10d62e..a022b1a 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java +++ b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java @@ -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); }); diff --git a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java index 5a9deed..8b78628 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java +++ b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java @@ -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) { diff --git a/app/src/main/res/values-id/arrays.xml b/app/src/main/res/values-id/arrays.xml new file mode 100644 index 0000000..d1e9488 --- /dev/null +++ b/app/src/main/res/values-id/arrays.xml @@ -0,0 +1,7 @@ + + + Sistem + Gelap + Terang + + diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 0000000..3815c05 --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,96 @@ + + Fox\'s Magisk Module Manager + Fox\'s Mmm + Tidak dapat mengakses root atau Magisk + Memuat… + Dapat diperbarui + Terpasang + Repo Online + Aplikasi berada dalam mode lockdown + Tidak dapat mengunduh file. + Modul membutuhkan waktu yang terlalu lama untuk melakukan boot, pertimbangkanglah untuk mematikan beberapa modul + Tidak dapat terhubung dengan internet + SettingsActivity + Tersedia pembaruan aplikasi + Perbarui + Tidak dapat menemukan deskripsi. + Unduh modul + Instal modul + Perbarui modul + Changelog + Situs + Dukungan + Donasi + Kirim sebuah modul + Membutuhkan Android 6.0+ + + + Pembaruan terakhir: + Repo: + oleh + Diunduh: + Bintang: + + + + Kelola repo + Mode lockdown + Mode lockdown mencegah manager dari melakukan tindakan terhadap modul + Peraturan + Info + Perlihatkan lisensi + Lisensi + Tampilkan modul tidak kompatibel + Tampilkan modul yang tidak kompatibel dengan perangkat anda berdasarkan metadata mereka + Versi Magisk usang! + Repos + Repository yang menampung modul Magisk + Sebuah alternatif Magisk-Modules-Repo dengan restriksi lebih ringan. + Hapus file modul? + Simpan file + Hapus file + Tidak dapat menghapus file modul + Tema + Mode tema + Id modul: + Instal modul dari penyimpanan + Modul yang diseleksi mempunyai format yang tidak valid + Instal lokal + Kode sumber + Modul bawaan Magisk + Modul bawaan Substratum + Paksa mode gelap pada terminal + Pemilih file anda sekarang gagal memberikan akses kepada file. + Instal jarak jauh + Pemilih file anda mengembalikan tanggapan yang tidak standar. + Gunakan perintah instal modul magisk + + Waktu diuji ini menyebabkan masalah kepada alat diagnosis kesalahan instal modul, + jadi saya menyembunyikan opsi ini dibelakan mode pengembang, aktifkan pada risiko anda sendiri! + + Mode pengembang aktif + Paksa Bahasa Inggris + Nonaktifkan filter modul qualitas rendah + + Beberapa modul tidak menyatakan metadata mereka dengan benar,menyebabkan glitch visual, + dan/atau mengindikasikan modul qualitas rendah, nonaktifkan pada risiko anda sendiri! + + DNS melalui HTTPS + + Mungkin dapat memperbaiki masalah koneksi didalam beberapa kasus. + (Tidak berlaku untuk WebView) + + Nonaktifkan ekstensi + + Nonaktifkan ekstensi Fox\'s Mmm, ini mencegah modul dari menggunakan + ekstensi terminal, berguna jika sebuah modul menyalahgunakan ekstensi Fox\'s Mmm. + + Wrap teks + + Wrap teks ke sebuah garis baru daripada menaruh + semua teks pada garis yang sama pada saat menginstal sebuah modul + + Aktifkan blur + Repo diaktifkan + Repo dinonaktifkan + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index bf920b3..239ff79 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -22,6 +22,7 @@ 支持 捐赠 提交一个模块 + 需要 Android 6.0+ 最后更新: @@ -50,6 +51,7 @@ 删除文件 无法删除模块文件 主题 + 主题模式 模块ID: 从存储空间安装模块 所选模块的格式无效 @@ -88,6 +90,7 @@ 将文本换行至新行, 而不是将所有文本放在同一行 + 启用模糊 启用仓库 禁用仓库