diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 477dca9..a19273f 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -79,6 +79,7 @@ support= donate= config= changeBoot= +mmtReborn= ``` Note: All urls must start with `https://`, or else will be ignored Note²: For `minMagisk`, `XX.Y` is parsed as `XXY00`, so you can just put the Magisk version name. @@ -93,6 +94,8 @@ Note²: For `minMagisk`, `XX.Y` is parsed as `XXY00`, so you can just put the Ma - `config` package name of the application that configure your module (Note: The icon won't appear in the module list if the module or target app is not installed) - `changeBoot` tell the manager the module may change the boot image +- `mmtReborn` tell the manager to use [MMT-Reborn](https://github.com/iamlooper/MMT-Reborn) logging style + (For example `- Hello world` will be transformed to `[*] Hello world`, do not apply to modules installed from storage) Note: Fox's Mmm use fallback [here](app/src/main/java/com/fox2code/mmm/utils/PropUtils.java#L36) diff --git a/app/src/main/assets/module_installer_compat.sh b/app/src/main/assets/module_installer_compat.sh index 4a21aeb..f62435c 100644 --- a/app/src/main/assets/module_installer_compat.sh +++ b/app/src/main/assets/module_installer_compat.sh @@ -53,15 +53,15 @@ settings() { fi } -if [ $MAGISK_VER_CODE -ge 20400 ]; then +if [ $MAGISK_VER_CODE -ge 20400 ] && [ -z "$MMM_MMT_REBORN" ]; then # New Magisk have complete installation logic within util_functions.sh install_module exit 0 fi -################# -# Legacy Support -################# +####################################################### +# Legacy Support + compat mode for MMT Reborn template +####################################################### TMPDIR=/dev/tmp PERSISTDIR=/sbin/.magisk/mirror/persist @@ -98,6 +98,7 @@ abort() { rm -rf $TMPDIR 2>/dev/null mkdir -p $TMPDIR +chcon u:object_r:system_file:s0 $TMPDIR || true cd $TMPDIR # Preperation for flashable zips @@ -121,7 +122,9 @@ $BOOTMODE && boot_actions || recovery_actions unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2 [ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" -$BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules +[ -z "$NVBASE" ] && NVBASE="/data/adb" +MODDIRNAME=modules +$BOOTMODE && MODDIRNAME=modules_update MODULEROOT=$NVBASE/$MODDIRNAME MODID=`grep_prop id $TMPDIR/module.prop` MODNAME=`grep_prop name $TMPDIR/module.prop` @@ -166,6 +169,50 @@ if is_legacy_script; then ui_print "- Setting permissions" set_permissions +elif [ -n "$MMM_MMT_REBORN" ]; then + # https://github.com/iamlooper/MMT-Reborn + ui_print "[*] Using FoxMMM MMT-Reborn compatibility mode" + + load_vksel() { source "$MODPATH/addon/Volume-Key-Selector/install.sh"; } + + rmtouch() { [[ -e "$1" ]] && rm -rf "$1" 2>/dev/null; } + + unzip -o "$ZIPFILE" -d "$MODPATH" >&2 + + # Load install script + source "$MODPATH/setup.sh" + + # Remove all old files before doing installation if want to + "$CLEANSERVICE" && rm -rf "/data/adb/modules/$MODID" + + # Enable debugging if true + "$DEBUG" && set -x || set +x + + # Print mod info + info_print + + # Auto vskel load + "$AUTOVKSEL" && load_vksel + + # Main + init_main + + # Skip mount + "$SKIPMOUNT" && touch "$MODPATH/skip_mount" + + # Set permissions + set_permissions + + # Remove stuffs that don't belong to modules + rmtouch "$MODPATH/META-INF" + rmtouch "$MODPATH/addon" + rmtouch "$MODPATH/setup.sh" + rmtouch "$MODPATH/LICENSE" + rmtouch "$MODPATH/README.md" + rmtouch "$MODPATH/system/bin/placeholder" + rmtouch "$MODPATH/zygisk/placeholder" + ui_print "[*] Exiting FoxMMM MMT-Reborn compatibility mode" + sleep 0.5 else print_modname @@ -198,13 +245,20 @@ if $BOOTMODE; then fi # Copy over custom sepolicy rules -if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then - ui_print "- Installing custom sepolicy patch" - # Remove old recovery logs (which may be filling partition) to make room - rm -f $PERSISTDIR/cache/recovery/* - PERSISTMOD=$PERSISTDIR/magisk/$MODID - mkdir -p $PERSISTMOD - cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule || abort "! Insufficient partition size" +if ! type copy_sepolicy_rules &>/dev/null; then + if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then + ui_print "- Installing custom sepolicy patch" + # Remove old recovery logs (which may be filling partition) to make room + rm -f $PERSISTDIR/cache/recovery/* + PERSISTMOD=$PERSISTDIR/magisk/$MODID + mkdir -p $PERSISTMOD + cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule || abort "! Insufficient partition size" + fi +else + if [ -f $MODPATH/sepolicy.rule ]; then + ui_print "- Installing custom sepolicy rules" + copy_sepolicy_rules + fi fi # Remove stuff that doesn't belong to modules and clean up any empty directories diff --git a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java index 0c4465a..54156f9 100644 --- a/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java +++ b/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java @@ -20,14 +20,15 @@ import java.util.HashMap; // See https://docs.github.com/en/rest/reference/repos#releases public class AppUpdateManager { - public static final int FLAG_COMPAT_LOW_QUALITY = 0x01; - public static final int FLAG_COMPAT_NO_EXT = 0x02; - public static final int FLAG_COMPAT_MAGISK_CMD = 0x04; - public static final int FLAG_COMPAT_NEED_32BIT = 0x08; - public static final int FLAG_COMPAT_MALWARE = 0x10; - public static final int FLAG_COMPAT_NO_ANSI = 0x20; - public static final int FLAG_COMPAT_FORCE_ANSI = 0x40; - public static final int FLAG_COMPAT_FORCE_HIDE = 0x80; + public static final int FLAG_COMPAT_LOW_QUALITY = 0x0001; + public static final int FLAG_COMPAT_NO_EXT = 0x0002; + public static final int FLAG_COMPAT_MAGISK_CMD = 0x0004; + public static final int FLAG_COMPAT_NEED_32BIT = 0x0008; + public static final int FLAG_COMPAT_MALWARE = 0x0010; + public static final int FLAG_COMPAT_NO_ANSI = 0x0020; + public static final int FLAG_COMPAT_FORCE_ANSI = 0x0040; + public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080; + public static final int FLAG_COMPAT_MMT_REBORN = 0x0100; private static final String TAG = "AppUpdateManager"; private static final AppUpdateManager INSTANCE = new AppUpdateManager(); private static final String RELEASES_API_URL = @@ -213,6 +214,9 @@ public class AppUpdateManager { case "forceHide": value |= FLAG_COMPAT_FORCE_HIDE; break; + case "mmtReborn": + value |= FLAG_COMPAT_MMT_REBORN; + break; } } compatDataId.put(line.substring(0, i), value); diff --git a/app/src/main/java/com/fox2code/mmm/Constants.java b/app/src/main/java/com/fox2code/mmm/Constants.java index 4696471..3351c74 100644 --- a/app/src/main/java/com/fox2code/mmm/Constants.java +++ b/app/src/main/java/com/fox2code/mmm/Constants.java @@ -14,6 +14,7 @@ public class Constants { public static final String EXTRA_INSTALL_NAME = "extra_install_name"; public static final String EXTRA_INSTALL_CONFIG = "extra_install_config"; public static final String EXTRA_INSTALL_CHECKSUM = "extra_install_checksum"; + public static final String EXTRA_INSTALL_MMT_REBORN = "extra_install_mmt_reborn"; public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions"; public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless"; public static final String EXTRA_ANDROIDACY_COMPAT_LEVEL = "extra_androidacy_compat_level"; diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 8084b52..ccc8b97 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -37,7 +37,6 @@ import com.google.android.material.progressindicator.LinearProgressIndicator; import eightbitlab.com.blurview.BlurView; import eightbitlab.com.blurview.RenderScriptBlur; -import io.sentry.android.core.SentryAndroid; public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, @@ -153,14 +152,16 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); ModuleManager.getINSTANCE().scan(); - moduleViewListBuilder.appendInstalledModules(); + ModuleManager.getINSTANCE().runAfterScan( + moduleViewListBuilder::appendInstalledModules); this.commonNext(); } @Override public void onFailure(int error) { Log.i(TAG, "Failed to get magisk path!"); - moduleViewListBuilder.addNotification(NotificationType.NO_ROOT); + moduleViewListBuilder.addNotification( + InstallerInitializer.getErrorNotification()); this.commonNext(); } @@ -223,7 +224,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe setActionBarBackground(null); updateScreenInsets(getResources().getConfiguration()); }); - moduleViewListBuilder.appendRemoteModules(); + RepoManager.getINSTANCE().runAfterUpdate( + moduleViewListBuilder::appendRemoteModules); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); Log.i(TAG, "Finished app opening state!"); } @@ -318,13 +320,15 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); ModuleManager.getINSTANCE().scan(); - moduleViewListBuilder.appendInstalledModules(); + ModuleManager.getINSTANCE().runAfterScan( + moduleViewListBuilder::appendInstalledModules); this.commonNext(); } @Override public void onFailure(int error) { - moduleViewListBuilder.addNotification(NotificationType.NO_ROOT); + moduleViewListBuilder.addNotification( + InstallerInitializer.getErrorNotification()); this.commonNext(); } @@ -350,7 +354,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe progressIndicator.setVisibility(View.GONE); }); } - moduleViewListBuilder.appendRemoteModules(); + RepoManager.getINSTANCE().runAfterUpdate( + moduleViewListBuilder::appendRemoteModules); Log.i(TAG, "Common Before applyTo"); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); Log.i(TAG, "Common After"); @@ -393,7 +398,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } RepoManager.getINSTANCE().updateEnabledStates(); - this.moduleViewListBuilder.appendRemoteModules(); + RepoManager.getINSTANCE().runAfterUpdate( + moduleViewListBuilder::appendRemoteModules); this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); },"Repo update thread").start(); } diff --git a/app/src/main/java/com/fox2code/mmm/NotificationType.java b/app/src/main/java/com/fox2code/mmm/NotificationType.java index c794dd4..7b40686 100644 --- a/app/src/main/java/com/fox2code/mmm/NotificationType.java +++ b/app/src/main/java/com/fox2code/mmm/NotificationType.java @@ -37,7 +37,13 @@ public enum NotificationType implements NotificationTypeCst { }) { @Override public boolean shouldRemove() { - return InstallerInitializer.peekMagiskPath() != null; + return InstallerInitializer.getErrorNotification() != this; + } + }, + ROOT_DENIED(R.string.fail_root_denied, R.drawable.ic_baseline_numbers_24) { + @Override + public boolean shouldRemove() { + return InstallerInitializer.getErrorNotification() != this; } }, MAGISK_OUTDATED(R.string.magisk_outdated, R.drawable.ic_baseline_update_24, v -> { @@ -102,7 +108,7 @@ public enum NotificationType implements NotificationTypeCst { } else { IntentHelper.openInstaller(compatActivity, d.getAbsolutePath(), compatActivity.getString( - R.string.local_install_title), null, null, + R.string.local_install_title), null, null, false, BuildConfig.DEBUG && // Use debug mode if no root InstallerInitializer.peekMagiskPath() == null); } @@ -115,7 +121,7 @@ public enum NotificationType implements NotificationTypeCst { } else if (s == IntentHelper.RESPONSE_URL) { IntentHelper.openInstaller(compatActivity, u.toString(), compatActivity.getString( - R.string.remote_install_title), null, null, + R.string.remote_install_title), null, null, false, BuildConfig.DEBUG && // Use debug mode if no root InstallerInitializer.peekMagiskPath() == null); } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index 0806db7..6b3477c 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -213,6 +213,7 @@ public final class AndroidacyRepoData extends RepoData { } moduleInfo.needRamdisk = jsonObject.optBoolean("needRamdisk", false); moduleInfo.changeBoot = jsonObject.optBoolean("changeBoot", false); + moduleInfo.mmtReborn = jsonObject.optBoolean("mmtReborn", false); moduleInfo.support = filterURL(jsonObject.optString("support")); moduleInfo.donate = filterURL(jsonObject.optString("donate")); String config = jsonObject.optString("config", ""); @@ -226,6 +227,8 @@ public final class AndroidacyRepoData extends RepoData { RepoModule repoModule = moduleInfoIterator.next(); if (!repoModule.processed) { moduleInfoIterator.remove(); + } else { + repoModule.moduleInfo.verify(); } } this.lastUpdate = lastLastUpdate; diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java index 268a2c5..08e8bee 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -23,7 +23,6 @@ import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.manager.LocalModuleInfo; import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.manager.ModuleManager; -import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoModule; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Hashes; @@ -67,9 +66,11 @@ public class AndroidacyWebAPI { RepoModule repoModule = AndroidacyRepoData .getInstance().moduleHashMap.get(installTitle); String title, description; + boolean mmtReborn = false; if (repoModule != null) { title = repoModule.moduleInfo.name; description = repoModule.moduleInfo.description; + mmtReborn = repoModule.moduleInfo.mmtReborn; if (description == null || description.length() == 0) { description = this.activity.getString(R.string.no_desc_found); } @@ -103,10 +104,11 @@ public class AndroidacyWebAPI { } final String fModuleUrl = moduleUrl, fTitle = title, fConfig = config, fChecksum = checksum; + final boolean fMMTReborn = mmtReborn; builder.setPositiveButton(hasUpdate ? R.string.update_module : R.string.install_module, (x, y) -> { IntentHelper.openInstaller(this.activity, - fModuleUrl, fTitle, fConfig, fChecksum); + fModuleUrl, fTitle, fConfig, fChecksum, fMMTReborn); }); } builder.setOnCancelListener(dialogInterface -> { @@ -247,13 +249,15 @@ public class AndroidacyWebAPI { RepoModule repoModule = AndroidacyRepoData .getInstance().moduleHashMap.get(installTitle); String config = null; + boolean mmtReborn = false; if (repoModule != null && repoModule.moduleInfo.name.length() >= 3) { installTitle = repoModule.moduleInfo.name; // Set title to module name config = repoModule.moduleInfo.config; + mmtReborn = repoModule.moduleInfo.mmtReborn; } this.activity.backOnResume = true; IntentHelper.openInstaller(this.activity, - moduleUrl, installTitle, config, checksum); + moduleUrl, installTitle, config, checksum, mmtReborn); } } diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java index 1e440e7..d45197a 100644 --- a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java @@ -5,6 +5,8 @@ import android.content.Context; import android.content.Intent; import com.fox2code.mmm.MainApplication; +import com.fox2code.mmm.installer.InstallerInitializer; +import com.fox2code.mmm.manager.ModuleManager; public class BackgroundBootListener extends BroadcastReceiver { private static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; @@ -14,7 +16,21 @@ public class BackgroundBootListener extends BroadcastReceiver { if (!BOOT_COMPLETED.equals(intent.getAction())) return; synchronized (BackgroundUpdateChecker.lock) { BackgroundUpdateChecker.onMainActivityCreate(context); - BackgroundUpdateChecker.doCheck(context); + if (MainApplication.isBackgroundUpdateCheckEnabled()) { + BackgroundUpdateChecker.doCheck(context); + } + } + if (ModuleManager.FORCE_NEED_FALLBACK && + MainApplication.hasGottenRootAccess()) { + InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() { + @Override + public void onPathReceived(String path) { + ModuleManager.getINSTANCE().scan(); + } + + @Override + public void onFailure(int error) {} + }); } } } 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 a30d48e..33ea0a3 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -45,6 +45,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; +import java.util.HashSet; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; @@ -83,6 +84,7 @@ public class InstallerActivity extends FoxActivity { final String checksum; final boolean noExtensions; final boolean rootless; + final boolean mmtReborn; // Should we allow 3rd part app to install modules? if (Constants.INTENT_INSTALL_INTERNAL.equals(intent.getAction())) { if (!MainApplication.checkSecret(intent)) { @@ -97,6 +99,8 @@ public class InstallerActivity extends FoxActivity { Constants.EXTRA_INSTALL_NO_EXTENSIONS, false); rootless = intent.getBooleanExtra(// For debug only Constants.EXTRA_INSTALL_TEST_ROOTLESS, false); + mmtReborn = intent.getBooleanExtra(// For debug only + Constants.EXTRA_INSTALL_MMT_REBORN, false); } else { Toast.makeText(this, "Unknown intent!", Toast.LENGTH_SHORT).show(); this.forceBackPressed(); @@ -136,9 +140,10 @@ public class InstallerActivity extends FoxActivity { this.rebootFloatingButton = findViewById(R.id.install_terminal_reboot_fab); this.installerTerminal = new InstallerTerminal( installTerminal = findViewById(R.id.install_terminal), - this.isLightTheme(), foreground); + this.isLightTheme(), foreground, mmtReborn); (horizontalScroller != null ? horizontalScroller : installTerminal) .setBackground(new ColorDrawable(background)); + installTerminal.setItemAnimator(null); this.progressIndicator.setVisibility(View.GONE); this.progressIndicator.setIndeterminate(true); this.getWindow().setFlags( // Note: Doesn't require WAKELOCK permission @@ -305,6 +310,7 @@ public class InstallerActivity extends FoxActivity { String moduleId = null; boolean anyKernel3 = false; boolean magiskModule = false; + boolean mmtReborn = false; String MAGISK_PATH = InstallerInitializer.peekMagiskPath(); if (MAGISK_PATH == null) { this.setInstallStateFinished(false, "! Unable to resolve magisk path", ""); @@ -337,6 +343,11 @@ public class InstallerActivity extends FoxActivity { } ZipEntry moduleProp = zipFile.getEntry("module.prop"); magiskModule = moduleProp != null; + if (zipFile.getEntry("install.sh") == null && + zipFile.getEntry("customize.sh") == null && + zipFile.getEntry("setup.sh") != null && magiskModule) { + mmtReborn = true; // MMT-Reborn require a separate runtime + } moduleId = PropUtils.readModuleId(zipFile .getInputStream(zipFile.getEntry("module.prop"))); } catch (IOException ignored) { @@ -378,6 +389,7 @@ public class InstallerActivity extends FoxActivity { } String installCommand; File installExecutable; + boolean magiskCmdLine = false; if (anyKernel3 && moduleId == null) { // AnyKernel zip don't have a moduleId this.warnReboot = true; // We should probably re-flash magisk... installExecutable = this.extractInstallScript("anykernel3_installer.sh"); @@ -398,6 +410,7 @@ public class InstallerActivity extends FoxActivity { installCommand = "magisk --install-module \"" + file.getAbsolutePath() + "\""; installExecutable = new File(MAGISK_PATH.equals("/sbin") ? "/sbin/magisk" : "/system/bin/magisk"); + magiskCmdLine = true; } else if (moduleId != null) { installExecutable = this.extractInstallScript("module_installer_compat.sh"); if (installExecutable == null) { @@ -435,6 +448,7 @@ public class InstallerActivity extends FoxActivity { "export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"), this.installerTerminal.isAnsiEnabled() ? AnsiConstants.ANSI_CMD_SUPPORT : "true", + mmtReborn ? "export MMM_MMT_REBORN=1" : "true", "export BOOTMODE=true", anyKernel3 ? "export AK3TMPFS=" + InstallerInitializer.peekMagiskPath() + "/ak3tmpfs" : "cd \"" + this.moduleCache.getAbsolutePath() + "\"", @@ -445,14 +459,19 @@ public class InstallerActivity extends FoxActivity { Breadcrumb breadcrumb = new Breadcrumb(); breadcrumb.setType("install"); breadcrumb.setData("moduleId", moduleId == null ? "" : moduleId); + breadcrumb.setData("mmtReborn", mmtReborn ? "true" : "false"); breadcrumb.setData("isAnyKernel3", anyKernel3 ? "true" : "false"); breadcrumb.setData("noExtensions", noExtensions ? "true" : "false"); + breadcrumb.setData("magiskCmdLine", magiskCmdLine ? "true" : "false"); breadcrumb.setData("ansi", this.installerTerminal .isAnsiEnabled() ? "enabled" : "disabled"); breadcrumb.setCategory("app.action.install"); breadcrumb.setLevel(SentryLevel.INFO); Sentry.addBreadcrumb(breadcrumb); } + if (mmtReborn && magiskCmdLine) { + Log.w(TAG, "mmtReborn and magiskCmdLine may not work well together"); + } } boolean success = installJob.exec().isSuccess(); // Wait one UI cycle before disabling controller or processing results @@ -678,14 +697,18 @@ public class InstallerActivity extends FoxActivity { } } + private static final HashSet extracted = new HashSet<>(); private File extractInstallScript(String script) { File compatInstallScript = new File(this.moduleCache, script); - if (!compatInstallScript.exists() || compatInstallScript.length() == 0) { + if (!compatInstallScript.exists() || compatInstallScript.length() == 0 || + !extracted.contains(script)) { try { Files.write(compatInstallScript, Files.readAllBytes( this.getAssets().open(script))); + extracted.add(script); } catch (IOException e) { - compatInstallScript.delete(); + if (compatInstallScript.delete()) + extracted.remove(script); Log.e(TAG, "Failed to extract " + script, e); return null; } diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerInitializer.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerInitializer.java index 533a4c2..14ad586 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerInitializer.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerInitializer.java @@ -4,9 +4,11 @@ import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.fox2code.mmm.Constants; import com.fox2code.mmm.MainApplication; +import com.fox2code.mmm.NotificationType; import com.fox2code.mmm.utils.Files; import com.topjohnwu.superuser.NoShellException; import com.topjohnwu.superuser.Shell; @@ -16,8 +18,14 @@ import java.util.ArrayList; public class InstallerInitializer extends Shell.Initializer { private static final String TAG = "InstallerInitializer"; + private static final File MAGISK_SBIN = + new File("/sbin/magisk"); private static final File MAGISK_SYSTEM = new File("/system/bin/magisk"); + private static final File MAGISK_SYSTEM_EX = + new File("/system/xbin/magisk"); + private static final boolean HAS_MAGISK = MAGISK_SBIN.exists() || + MAGISK_SYSTEM.exists() || MAGISK_SYSTEM_EX.exists(); private static String MAGISK_PATH; private static int MAGISK_VERSION_CODE; private static boolean HAS_RAMDISK; @@ -33,6 +41,19 @@ public class InstallerInitializer extends Shell.Initializer { void onFailure(int error); } + @Nullable + public static NotificationType getErrorNotification() { + Boolean hasRoot = Shell.isAppGrantedRoot(); + if (MAGISK_PATH != null && + hasRoot != Boolean.FALSE) { + return null; + } + if (hasRoot != Boolean.TRUE && HAS_MAGISK) { + return NotificationType.ROOT_DENIED; + } + return NotificationType.NO_ROOT; + } + public static String peekMagiskPath() { return InstallerInitializer.MAGISK_PATH; } diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java index fa22bde..9ab6256 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerTerminal.java @@ -19,13 +19,16 @@ public class InstallerTerminal extends RecyclerView.Adapter(); this.ansiContext = (isLightTheme ? AnsiContext.LIGHT : AnsiContext.DARK).copy(); this.recyclerView.setAdapter(this); @@ -125,6 +128,13 @@ public class InstallerTerminal extends RecyclerView.Adapter moduleInfos; private final SharedPreferences bootPrefs; @@ -46,12 +47,11 @@ public final class ModuleManager extends SyncManager { } protected void scanInternal(@NonNull UpdateListener updateListener) { - boolean firstBoot = MainApplication.isFirstBoot(); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; for (ModuleInfo v : this.moduleInfos.values()) { v.flags |= FLAG_MM_UNPROCESSED; - v.flags &= ~FLAGS_RESET_INIT; + v.flags &= FLAGS_KEEP_INIT; v.name = v.id; v.version = null; v.versionCode = 0; @@ -62,9 +62,9 @@ public final class ModuleManager extends SyncManager { } String modulesPath = InstallerInitializer.peekModulesPath(); String[] modules = new SuFile("/data/adb/modules").list(); - boolean needFallback = modulesPath == null || - !new SuFile(modulesPath).exists(); - if (needFallback) { + boolean needFallback = FORCE_NEED_FALLBACK || + modulesPath == null || !new SuFile(modulesPath).exists(); + if (!FORCE_NEED_FALLBACK && needFallback) { Log.e(TAG, "Failed to detect modules folder, using fallback instead."); } if (modules != null) { @@ -75,25 +75,34 @@ public final class ModuleManager extends SyncManager { if (moduleInfo == null) { moduleInfo = new LocalModuleInfo(module); moduleInfos.put(module, moduleInfo); - // Shis should not really happen, but let's handles theses cases anyway + // This should not really happen, but let's handles theses cases anyway moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING_ONLY; } moduleInfo.flags &= ~FLAGS_RESET_UPDATE; if (new SuFile("/data/adb/modules/" + module + "/disable").exists()) { moduleInfo.flags |= ModuleInfo.FLAG_MODULE_DISABLED; - } else if (needFallback && firstScan) { + } else if (firstScan && needFallback) { moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE; editor.putBoolean("module_" + moduleInfo.id + "_active", true); } if (new SuFile("/data/adb/modules/" + module + "/remove").exists()) { moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UNINSTALLING; } - if ((!needFallback && new SuFile(modulesPath, module).exists()) || (!firstBoot - && bootPrefs.getBoolean("module_" + moduleInfo.id + "_active", false))) { + if ((firstScan && !needFallback && new SuFile(modulesPath, module).exists()) || + bootPrefs.getBoolean("module_" + moduleInfo.id + "_active", false)) { moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE; if (firstScan) { editor.putBoolean("module_" + moduleInfo.id + "_active", true); } + } else if (!needFallback) { + moduleInfo.flags &= ~ModuleInfo.FLAG_MODULE_ACTIVE; + } + if ((moduleInfo.flags & ModuleInfo.FLAGS_MODULE_ACTIVE) != 0 + && (new SuFile("/data/adb/modules/" + module + "/system").exists() || + new SuFile("/data/adb/modules/" + module + "/vendor").exists() || + new SuFile("/data/adb/modules/" + module + "/zygisk").exists() || + new SuFile("/data/adb/modules/" + module + "/riru").exists())) { + moduleInfo.flags |= ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT; } try { PropUtils.readProperties(moduleInfo, @@ -147,6 +156,7 @@ public final class ModuleManager extends SyncManager { if (moduleInfo.version == null || moduleInfo.version.trim().isEmpty()) { moduleInfo.version = "v" + moduleInfo.versionCode; } + moduleInfo.verify(); } if (firstScan) { editor.putBoolean("mm_first_scan", false); @@ -201,7 +211,7 @@ public final class ModuleManager extends SyncManager { } public boolean masterClear(ModuleInfo moduleInfo) { - if (moduleInfo.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)) return false; + if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT)) return false; String escapedId = moduleInfo.id.replace("\\", "\\\\") .replace("\"", "\\\"").replace(" ", "\\ "); try { // Check for module that declare having file outside their own folder. diff --git a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java index e9a2ebd..797b79b 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java +++ b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java @@ -118,7 +118,8 @@ public enum ActionButtonType { R.string.update_module : R.string.install_module, (x, y) -> { String updateZipChecksum = moduleHolder.getUpdateZipChecksum(); IntentHelper.openInstaller(button.getContext(), updateZipUrl, - moduleInfo.name, moduleInfo.config, updateZipChecksum); + moduleInfo.name, moduleInfo.config, updateZipChecksum, + moduleInfo.mmtReborn); }); } int dim5dp = FoxDisplay.dpToPixel(5); @@ -158,6 +159,7 @@ public enum ActionButtonType { doActionLong(button, moduleHolder); return; } + Log.d("ActionButtonType", Integer.toHexString(moduleHolder.moduleInfo.flags)); if (!ModuleManager.getINSTANCE().setUninstallState(moduleHolder.moduleInfo, !moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING))) { Log.e("ActionButtonType", "Failed to switch uninstalled state!"); @@ -167,16 +169,19 @@ public enum ActionButtonType { @Override public boolean doActionLong(Chip button, ModuleHolder moduleHolder) { - // We can't trust active flag on first boot - if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAGS_MODULE_ACTIVE)) return false; + // Actually a module having mount is the only issue when deleting module + if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT)) + return false; // We can't trust active flag on first boot new AlertDialog.Builder(button.getContext()).setTitle(R.string.master_delete) .setPositiveButton(R.string.master_delete_yes, (v, i) -> { + String moduleId = moduleHolder.moduleInfo.id; if (!ModuleManager.getINSTANCE().masterClear(moduleHolder.moduleInfo)) { Toast.makeText(button.getContext(), R.string.master_delete_fail, Toast.LENGTH_SHORT).show(); } else { moduleHolder.moduleInfo = null; FoxActivity.getFoxActivity(button).refreshUI(); + Log.e("ActionButtonType", "Cleared: " + moduleId); } }).setNegativeButton(R.string.master_delete_no, (v, i) -> { }).create().show(); @@ -205,13 +210,15 @@ public enum ActionButtonType { @Override public void update(Chip button, ModuleHolder moduleHolder) { ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo(); - button.setChipIcon(button.getContext().getDrawable(supportIconForUrl(moduleInfo.support))); + button.setChipIcon(button.getContext().getDrawable( + supportIconForUrl(moduleInfo.support))); button.setText(R.string.support); } @Override public void doAction(Chip button, ModuleHolder moduleHolder) { - IntentHelper.openUrl(button.getContext(), moduleHolder.getMainModuleInfo().support); + IntentHelper.openUrl(button.getContext(), + moduleHolder.getMainModuleInfo().support); } }, DONATE() { @@ -232,7 +239,9 @@ public enum ActionButtonType { @DrawableRes public static int supportIconForUrl(String url) { int icon = R.drawable.ic_baseline_support_24; - if (url.startsWith("https://t.me/")) { + if (url == null) { + return icon; + } else if (url.startsWith("https://t.me/")) { icon = R.drawable.ic_baseline_telegram_24; } else if (url.startsWith("https://discord.gg/") || url.startsWith("https://discord.com/invite/")) { @@ -250,7 +259,9 @@ public enum ActionButtonType { @DrawableRes public static int donateIconForUrl(String url) { int icon = R.drawable.ic_baseline_monetization_on_24; - if (url.startsWith("https://www.paypal.me/") || + if (url == null) { + return icon; + } else if (url.startsWith("https://www.paypal.me/") || url.startsWith("https://www.paypal.com/paypalme/")) { icon = R.drawable.ic_baseline_paypal_24; } else if (url.startsWith("https://patreon.com/") || diff --git a/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java b/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java index 28bd62c..3ca5119 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java +++ b/app/src/main/java/com/fox2code/mmm/module/ModuleViewAdapter.java @@ -186,6 +186,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter localModuleInfo.updateVersionCode) { diff --git a/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.java b/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.java index 44948f3..bb163ed 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.java +++ b/app/src/main/java/com/fox2code/mmm/module/ModuleViewListBuilder.java @@ -14,9 +14,6 @@ import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.manager.LocalModuleInfo; import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.manager.ModuleManager; -import com.fox2code.mmm.module.ModuleHolder; -import com.fox2code.mmm.module.ModuleSorter; -import com.fox2code.mmm.module.ModuleViewAdapter; import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoModule; @@ -48,6 +45,10 @@ public class ModuleViewListBuilder { } public void addNotification(NotificationType notificationType) { + if (notificationType == null) { + Log.w(TAG, "addNotification(null) called!"); + return; + } synchronized (this.updateLock) { this.notifications.add(notificationType); } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index a4a07a4..fd0e673 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -142,6 +142,8 @@ public class RepoData extends XRepo { if (!repoModule.processed) { new File(this.cacheRoot, repoModule.id + ".prop").delete(); moduleInfoIterator.remove(); + } else { + repoModule.moduleInfo.verify(); } } // Update final metadata 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 da227b7..3ae6844 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java +++ b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java @@ -192,7 +192,12 @@ public class IntentHelper { } public static void openInstaller(Context context, String url, String title, String config, - String checksum,boolean testDebug) { + String checksum, boolean mmtReborn) { + openInstaller(context, url, title, config, checksum, mmtReborn, false); + } + + public static void openInstaller(Context context, String url, String title, String config, + String checksum, boolean mmtReborn, boolean testDebug) { try { Intent intent = new Intent(context, InstallerActivity.class); intent.setAction(Constants.INTENT_INSTALL_INTERNAL); @@ -203,6 +208,8 @@ public class IntentHelper { intent.putExtra(Constants.EXTRA_INSTALL_CONFIG, config); if (checksum != null && !checksum.isEmpty()) intent.putExtra(Constants.EXTRA_INSTALL_CHECKSUM, checksum); + if (mmtReborn) // Allow early styling of install process + intent.putExtra(Constants.EXTRA_INSTALL_MMT_REBORN, true); if (testDebug && BuildConfig.DEBUG) intent.putExtra(Constants.EXTRA_INSTALL_TEST_ROOTLESS, true); startActivity(context, intent, true); 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 246307c..f4efdde 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java +++ b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java @@ -7,6 +7,7 @@ import android.os.Build; import android.text.TextUtils; import android.util.Log; +import com.fox2code.mmm.AppUpdateManager; import com.fox2code.mmm.manager.ModuleInfo; import com.topjohnwu.superuser.io.SuFileInputStream; @@ -25,6 +26,7 @@ public class PropUtils { private static final HashMap moduleConfigsFallbacks = new HashMap<>(); private static final HashMap moduleMinApiFallbacks = new HashMap<>(); private static final HashMap moduleUpdateJsonFallbacks = new HashMap<>(); + private static final HashSet moduleMTTRebornFallback = new HashSet<>(); private static final HashSet moduleImportantProp = new HashSet<>(Arrays.asList( "id", "name", "version", "versionCode" )); @@ -100,7 +102,8 @@ public class PropUtils { String name, boolean local) throws IOException { boolean readId = false, readIdSec = false, readName = false, readVersionCode = false, readVersion = false, readDescription = false, - readUpdateJson = false, invalid = false, readMinApi = false, readMaxApi = false; + readUpdateJson = false, invalid = false, readMinApi = false, readMaxApi = false, + readMMTReborn = false; try (BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; @@ -203,6 +206,10 @@ public class PropUtils { case "changeBoot": moduleInfo.changeBoot = Boolean.parseBoolean(value); break; + case "mmtReborn": + moduleInfo.mmtReborn = Boolean.parseBoolean(value); + readMMTReborn = true; + break; case "support": // Do not accept invalid or too broad support links if (isInvalidURL(value) || @@ -304,6 +311,11 @@ public class PropUtils { if (moduleInfo.config == null) { moduleInfo.config = moduleConfigsFallbacks.get(moduleInfo.id); } + if (!readMMTReborn) { + moduleInfo.mmtReborn = moduleMTTRebornFallback.contains(moduleInfo.id) || + (AppUpdateManager.getFlagsForModule(moduleInfo.id) & + AppUpdateManager.FLAG_COMPAT_MMT_REBORN) != 0; + } // All local modules should have an author // set to "Unknown" if author is missing. if (local && moduleInfo.author == null) { diff --git a/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java b/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java index 718286b..0f0d327 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java +++ b/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java @@ -13,6 +13,7 @@ public abstract class SyncManager { private static final UpdateListener NO_OP = value -> {}; protected final Object syncLock = new Object(); private boolean syncing; + private long lastSync; public final void scan() { this.update(null); @@ -24,10 +25,13 @@ public abstract class SyncManager { if (!this.syncing) { // Do scan synchronized (this.syncLock) { + if (System.currentTimeMillis() < this.lastSync + 50L) + return; // Skip sync if it was synced too recently this.syncing = true; try { this.scanInternal(updateListener); } finally { + this.lastSync = System.currentTimeMillis(); this.syncing = false; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 30de2c1..23c30d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ Fox\'s Magisk Module Manager Fox\'s Mmm Could not access either Root or Magisk + Root has been denied via the Magisk app Loading… Upgradable Installed