diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt index 13db196..a51bc45 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.kt @@ -202,7 +202,7 @@ class AndroidacyActivity : FoxActivity() { if (downloadMode || backOnResume) return true // sanitize url @Suppress("NAME_SHADOWING") var url = request.url.toString() - url = AndroidacyUtil.hideToken(url).toString() + url = AndroidacyUtil.hideToken(url) Timber.i("Exiting WebView %s", url) IntentHelper.openUri(view.context, request.url.toString()) return true 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 8494299..55fe44a 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -417,7 +417,7 @@ public final class AndroidacyRepoData extends RepoData { private String injectToken(String url) { // Do not inject token for non Androidacy urls - if (!AndroidacyUtil.isAndroidacyLink(url)) return url; + if (!AndroidacyUtil.Companion.isAndroidacyLink(url)) return url; if (this.testMode) { if (url.startsWith("https://production-api.androidacy.com/")) { Timber.e("Got non test mode url: %s", AndroidacyUtil.hideToken(url)); diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.kt b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.kt index 55e9682..ae2b895 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.kt +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.kt @@ -1,138 +1,139 @@ -package com.fox2code.mmm.androidacy; +@file:Suppress("unused") -import android.net.Uri; +package com.fox2code.mmm.androidacy -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.net.Uri +import com.fox2code.mmm.BuildConfig +import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet +import java.io.IOException -import com.fox2code.mmm.BuildConfig; -import com.fox2code.mmm.utils.io.net.Http; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Objects; - -public enum AndroidacyUtil { +enum class AndroidacyUtil { ; - public static final String REFERRER = "utm_source=FoxMMM&utm_medium=app"; - - public static boolean isAndroidacyLink(@Nullable Uri uri) { - return uri != null && isAndroidacyLink(uri.toString(), uri); - } - - public static boolean isAndroidacyLink(@Nullable String url) { - return url != null && isAndroidacyLink(url, Uri.parse(url)); - } - static boolean isAndroidacyLink(@NonNull String url, @NonNull Uri uri) { - int i; // Check both string and Uri to mitigate parse exploit - return url.startsWith("https://") && (i = url.indexOf("/", 8)) != -1 && url.substring(8, i).endsWith("api.androidacy.com") && Objects.requireNonNull(uri.getHost()).endsWith("api.androidacy.com"); - } + companion object { + const val REFERRER = "utm_source=FoxMMM&utm_medium=app" + fun isAndroidacyLink(uri: Uri?): Boolean { + return uri != null && isAndroidacyLink(uri.toString(), uri) + } - public static boolean isAndroidacyFileUrl(@Nullable String url) { - if (url == null) - return false; - for (String prefix : new String[]{"https://production-api.androidacy.com/downloads/", "https://production-api.androidacy.com/magisk/file/", "https://staging-api.androidacy.com/magisk/file/"}) { // Make both staging and non staging act the same - if (url.startsWith(prefix)) - return true; + fun isAndroidacyLink(url: String?): Boolean { + return url != null && isAndroidacyLink(url, Uri.parse(url)) } - return false; - } - // Avoid logging token - public static String hideToken(@NonNull String url) { - // for token, device_id, and client_id, replace with by using replaceAll to match until the next non-alphanumeric character or end - // Also, URL decode - url = Uri.decode(url); - url = url + "&"; - url = url.replaceAll("token=[^&]*", "token="); - url = url.replaceAll("device_id=[^&]*", "device_id="); - url = url.replaceAll("client_id=[^&]*", "client_id="); - // remove last & added at the end - url = url.substring(0, url.length() - 1); - return url; - } + fun isAndroidacyLink(url: String, uri: Uri): Boolean { + var i = 0// Check both string and Uri to mitigate parse exploit + return url.startsWith("https://") && url.indexOf("/", 8) + .also { i = it } != -1 && url.substring(8, i) + .endsWith("api.androidacy.com") && uri.host?.endsWith("api.androidacy.com") ?: false + } - public static String getModuleId(String moduleUrl) { - // Get the &module= part - int i = moduleUrl.indexOf("&module="); - String moduleId; - // Match until next & or end - if (i != -1) { - int j = moduleUrl.indexOf('&', i + 1); - if (j == -1) { - moduleId = moduleUrl.substring(i + 8); - } else { - moduleId = moduleUrl.substring(i + 8, j); + @JvmStatic + fun isAndroidacyFileUrl(url: String?): Boolean { + if (url == null) return false + for (prefix in arrayOf( + "https://production-api.androidacy.com/downloads/", + "https://production-api.androidacy.com/magisk/file/", + "https://staging-api.androidacy.com/magisk/file/" + )) { // Make both staging and non staging act the same + if (url.startsWith(prefix)) return true } - // URL decode - moduleId = Uri.decode(moduleId); - // Strip non alphanumeric - moduleId = moduleId.replaceAll("[^a-zA-Z\\d]", ""); - return moduleId; + return false } - if (BuildConfig.DEBUG) { - throw new IllegalArgumentException("Invalid module url: " + moduleUrl); + + // Avoid logging token + @Suppress("NAME_SHADOWING") + @JvmStatic + fun hideToken(url: String): String { + // for token, device_id, and client_id, replace with by using replaceAll to match until the next non-alphanumeric character or end + // Also, URL decode + var url = url + url = Uri.decode(url) + url = "$url&" + url = url.replace("token=[^&]*".toRegex(), "token=") + url = url.replace("device_id=[^&]*".toRegex(), "device_id=") + url = url.replace("client_id=[^&]*".toRegex(), "client_id=") + // remove last & added at the end + url = url.substring(0, url.length - 1) + return url } - return null; - } - public static String getModuleTitle(String moduleUrl) { - // Get the &title= part - int i = moduleUrl.indexOf("&moduleTitle="); - // Match until next & or end - if (i != -1) { - int j = moduleUrl.indexOf('&', i + 1); - if (j == -1) { - return Uri.decode(moduleUrl.substring(i + 13)); - } else { - return Uri.decode(moduleUrl.substring(i + 13, j)); + @JvmStatic + fun getModuleId(moduleUrl: String): String? { + // Get the &module= part + val i = moduleUrl.indexOf("&module=") + var moduleId: String + // Match until next & or end + if (i != -1) { + val j = moduleUrl.indexOf('&', i + 1) + moduleId = if (j == -1) { + moduleUrl.substring(i + 8) + } else { + moduleUrl.substring(i + 8, j) + } + // URL decode + moduleId = Uri.decode(moduleId) + // Strip non alphanumeric + moduleId = moduleId.replace("[^a-zA-Z\\d]".toRegex(), "") + return moduleId } + require(!BuildConfig.DEBUG) { "Invalid module url: $moduleUrl" } + return null } - return null; - } - public static String getChecksumFromURL(String moduleUrl) { - // Get the &version= part - int i = moduleUrl.indexOf("&checksum="); - // Match until next & or end - if (i != -1) { - int j = moduleUrl.indexOf('&', i + 1); - if (j == -1) { - return moduleUrl.substring(i + 10); - } else { - return moduleUrl.substring(i + 10, j); + @JvmStatic + fun getModuleTitle(moduleUrl: String): String? { + // Get the &title= part + val i = moduleUrl.indexOf("&moduleTitle=") + // Match until next & or end + if (i != -1) { + val j = moduleUrl.indexOf('&', i + 1) + return if (j == -1) { + Uri.decode(moduleUrl.substring(i + 13)) + } else { + Uri.decode(moduleUrl.substring(i + 13, j)) + } } + return null } - return null; - } - /** - * Check if the url is a premium direct download link - * @param url url to check - * @return true if it is a premium direct download link - * @noinspection unused - */ - public static boolean isPremiumDirectDownloadLink(String url) { - return url.contains("/magisk/ddl/"); - } + fun getChecksumFromURL(moduleUrl: String): String? { + // Get the &version= part + val i = moduleUrl.indexOf("&checksum=") + // Match until next & or end + if (i != -1) { + val j = moduleUrl.indexOf('&', i + 1) + return if (j == -1) { + moduleUrl.substring(i + 10) + } else { + moduleUrl.substring(i + 10, j) + } + } + return null + } - /** - * Returns the markdown directly from the API for rendering. Premium only, and internal testing only currently. - * @param url URL to get markdown from - * @return String of markdown - * @noinspection unused - */ - public static String getMarkdownFromAPI(String url) { - byte[] md; - try { - md = Http.doHttpGet(url, false); - } catch (IOException ignored) { - return null; + /** + * Check if the url is a premium direct download link + * @param url url to check + * @return true if it is a premium direct download link + * @noinspection unused + */ + fun isPremiumDirectDownloadLink(url: String): Boolean { + return url.contains("/magisk/ddl/") } - if (md == null) { - return null; + + /** + * Returns the markdown directly from the API for rendering. Premium only, and internal testing only currently. + * @param url URL to get markdown from + * @return String of markdown + * @noinspection unused + */ + fun getMarkdownFromAPI(url: String?): String? { + val md: ByteArray = try { + doHttpGet(url!!, false) + } catch (ignored: IOException) { + return null + } + return String(md) } - return new String(md); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt index 08ba98d..d461e1b 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.kt @@ -1,191 +1,212 @@ -package com.fox2code.mmm.androidacy; - -import android.annotation.SuppressLint; -import android.content.res.Resources; -import android.graphics.Color; -import android.net.Uri; -import android.os.Build; -import android.util.TypedValue; -import android.webkit.JavascriptInterface; -import android.widget.Button; -import android.widget.Toast; - -import androidx.annotation.Keep; -import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; - -import com.fox2code.foxcompat.view.FoxDisplay; -import com.fox2code.mmm.BuildConfig; -import com.fox2code.mmm.MainApplication; -import com.fox2code.mmm.R; -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.RepoModule; -import com.fox2code.mmm.utils.ExternalHelper; -import com.fox2code.mmm.utils.IntentHelper; -import com.fox2code.mmm.utils.io.Files; -import com.fox2code.mmm.utils.io.Hashes; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -import timber.log.Timber; - -@SuppressWarnings({"unused", "SameReturnValue"}) -@Keep -public class AndroidacyWebAPI { - public static final int COMPAT_UNSUPPORTED = 0; - public static final int COMPAT_DOWNLOAD = 1; - private static final int MAX_COMPAT_MODE = 1; - private final AndroidacyActivity activity; - private final boolean allowInstall; - boolean consumedAction; - boolean downloadMode; - int effectiveCompatMode; - int notifiedCompatMode; - - public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) { - this.activity = activity; - this.allowInstall = allowInstall; - } +@file:Suppress("NAME_SHADOWING", "MemberVisibilityCanBePrivate") + +package com.fox2code.mmm.androidacy + +import android.annotation.SuppressLint +import android.content.DialogInterface +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.util.TypedValue +import android.webkit.JavascriptInterface +import android.widget.Toast +import androidx.annotation.Keep +import androidx.core.content.ContextCompat +import com.fox2code.foxcompat.view.FoxDisplay +import com.fox2code.mmm.BuildConfig +import com.fox2code.mmm.MainApplication +import com.fox2code.mmm.R +import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.getModuleId +import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.getModuleTitle +import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.hideToken +import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.isAndroidacyFileUrl +import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.isAndroidacyLink +import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskPath +import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskVersion +import com.fox2code.mmm.manager.ModuleInfo +import com.fox2code.mmm.manager.ModuleManager.Companion.instance +import com.fox2code.mmm.utils.ExternalHelper +import com.fox2code.mmm.utils.IntentHelper.Companion.openCustomTab +import com.fox2code.mmm.utils.IntentHelper.Companion.openInstaller +import com.fox2code.mmm.utils.IntentHelper.Companion.openUrl +import com.fox2code.mmm.utils.io.Files.Companion.readSU +import com.fox2code.mmm.utils.io.Files.Companion.writeSU +import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumFormat +import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumName +import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumValid +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import timber.log.Timber +import java.io.File +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.util.Objects - void forceQuitRaw(String error) { - Toast.makeText(this.activity, error, Toast.LENGTH_LONG).show(); - this.activity.runOnUiThread(this.activity::forceBackPressed); - this.activity.backOnResume = true; // Set backOnResume just in case - this.downloadMode = false; - } +@Keep +class AndroidacyWebAPI( + private val activity: AndroidacyActivity, + private val allowInstall: Boolean +) { + var consumedAction = false + var downloadMode = false - void openNativeModuleDialogRaw(String moduleUrl, String moduleId, String installTitle, String checksum, boolean canInstall) { - if (BuildConfig.DEBUG) - Timber.d("ModuleDialog, downloadUrl: " + AndroidacyUtil.hideToken(moduleUrl) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall); + /** + * Allow Androidacy backend to notify compat mode + * return current effective compat mode + */ + @get:JavascriptInterface + var effectiveCompatMode = 0 + var notifiedCompatMode = 0 + fun forceQuitRaw(error: String?) { + Toast.makeText(activity, error, Toast.LENGTH_LONG).show() + activity.runOnUiThread { activity.forceBackPressed() } + activity.backOnResume = true // Set backOnResume just in case + downloadMode = false + } + + fun openNativeModuleDialogRaw( + moduleUrl: String?, + moduleId: String?, + installTitle: String?, + checksum: String?, + canInstall: Boolean + ) { + if (BuildConfig.DEBUG) Timber.d( + "ModuleDialog, downloadUrl: " + hideToken( + moduleUrl!! + ) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall + ) // moduleUrl should be a valid URL, i.e. in the androidacy.com domain // if it is not, do not proceed - if (!AndroidacyUtil.isAndroidacyFileUrl(moduleUrl)) { - Timber.e("ModuleDialog, invalid URL: %s", moduleUrl); - return; + if (!isAndroidacyFileUrl(moduleUrl)) { + Timber.e("ModuleDialog, invalid URL: %s", moduleUrl) + return } - this.downloadMode = false; - RepoModule repoModule = AndroidacyRepoData.getInstance().moduleHashMap.get(installTitle); - String title, description; - boolean mmtReborn = false; + downloadMode = false + val repoModule = AndroidacyRepoData.getInstance().moduleHashMap[installTitle] + val title: String? + var description: String? + var 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); + title = repoModule.moduleInfo.name + description = repoModule.moduleInfo.description + mmtReborn = repoModule.moduleInfo.mmtReborn + if (description.isNullOrEmpty()) { + description = activity.getString(R.string.no_desc_found) } } else { // URL Decode installTitle - title = installTitle; - String checkSumType = Hashes.checkSumName(checksum); - if (checkSumType == null) { - description = "Checksum: " + ((checksum == null || checksum.isEmpty()) ? "null" : checksum); + title = installTitle + val checkSumType = checkSumName(checksum) + description = if (checkSumType == null) { + "Checksum: ${if (checksum.isNullOrEmpty()) "null" else checksum}" } else { - description = checkSumType + ": " + checksum; + "$checkSumType: $checksum" } } - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this.activity); - builder.setTitle(title).setMessage(description).setCancelable(true).setIcon(R.drawable.ic_baseline_extension_24); - builder.setNegativeButton(R.string.download_module, (x, y) -> { - this.downloadMode = true; - IntentHelper.openCustomTab(this.activity, moduleUrl); - }); + val builder = MaterialAlertDialogBuilder(activity) + builder.setTitle(title).setMessage(description).setCancelable(true) + .setIcon(R.drawable.ic_baseline_extension_24) + builder.setNegativeButton(R.string.download_module) { _: DialogInterface?, _: Int -> + downloadMode = true + openCustomTab(activity, moduleUrl) + } if (canInstall) { - boolean hasUpdate = false; - String config = null; + var hasUpdate = false + var config: String? = null if (repoModule != null) { - config = repoModule.moduleInfo.config; - LocalModuleInfo localModuleInfo = Objects.requireNonNull(ModuleManager.getInstance()).getModules().get(repoModule.id); - hasUpdate = localModuleInfo != null && repoModule.moduleInfo.versionCode > localModuleInfo.versionCode; + config = repoModule.moduleInfo.config + val localModuleInfo = instance?.modules?.get(repoModule.id) + hasUpdate = + localModuleInfo != null && repoModule.moduleInfo.versionCode > localModuleInfo.versionCode } - 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, fMMTReborn); + val fConfig = config + val fMMTReborn = mmtReborn + builder.setPositiveButton(if (hasUpdate) R.string.update_module else R.string.install_module) { _: DialogInterface?, _: Int -> + openInstaller( + activity, + moduleUrl, + title, + fConfig, + checksum, + fMMTReborn + ) // close activity - this.activity.runOnUiThread(this.activity::finishAndRemoveTask); - }); + activity.runOnUiThread { activity.finishAndRemoveTask() } + } + } + builder.setOnCancelListener { _: DialogInterface? -> + if (!activity.backOnResume) consumedAction = false } - builder.setOnCancelListener(dialogInterface -> { - if (!this.activity.backOnResume) - this.consumedAction = false; - }); - ExternalHelper.INSTANCE.injectButton(builder, () -> { - this.downloadMode = true; + ExternalHelper.INSTANCE.injectButton(builder, { + downloadMode = true try { - return this.activity.downloadFileAsync(moduleUrl); - } catch ( - IOException e) { - Timber.e(e, "Failed to download module"); - AndroidacyWebAPI.this.activity.runOnUiThread(() -> Toast.makeText(AndroidacyWebAPI.this.activity, R.string.failed_download, Toast.LENGTH_SHORT).show()); - return null; + return@injectButton activity.downloadFileAsync(moduleUrl) + } catch (e: IOException) { + Timber.e(e, "Failed to download module") + activity.runOnUiThread { + Toast.makeText( + activity, + R.string.failed_download, + Toast.LENGTH_SHORT + ).show() + } + return@injectButton null } - }, "androidacy_repo"); - final int dim5dp = FoxDisplay.dpToPixel(5); - builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp); - this.activity.runOnUiThread(() -> { - AlertDialog alertDialog = builder.show(); - for (int i = -3; i < 0; i++) { - Button alertButton = alertDialog.getButton(i); - if (alertButton != null && alertButton.getPaddingStart() > dim5dp) { - alertButton.setPadding(dim5dp, dim5dp, dim5dp, dim5dp); + }, "androidacy_repo") + val dim5dp = FoxDisplay.dpToPixel(5f) + builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp) + activity.runOnUiThread { + val alertDialog = builder.show() + for (i in -3..-1) { + val alertButton = alertDialog.getButton(i) + if (alertButton != null && alertButton.paddingStart > dim5dp) { + alertButton.setPadding(dim5dp, dim5dp, dim5dp, dim5dp) } } - }); + } } - void notifyCompatModeRaw(int value) { - if (this.consumedAction) - return; - if (BuildConfig.DEBUG) - Timber.d("Androidacy Compat mode: %s", value); - this.notifiedCompatMode = value; + @Suppress("NAME_SHADOWING") + fun notifyCompatModeRaw(value: Int) { + var value = value + if (consumedAction) return + if (BuildConfig.DEBUG) Timber.d("Androidacy Compat mode: %s", value) + notifiedCompatMode = value if (value < 0) { - value = 0; + value = 0 } else if (value > MAX_COMPAT_MODE) { - value = MAX_COMPAT_MODE; + value = MAX_COMPAT_MODE } - this.effectiveCompatMode = value; + effectiveCompatMode = value } @JavascriptInterface - public void forceQuit(String error) { + fun forceQuit(error: String?) { // Allow forceQuit and cancel in downloadMode - if (this.consumedAction && !this.downloadMode) - return; - this.consumedAction = true; - this.forceQuitRaw(error); + if (consumedAction && !downloadMode) return + consumedAction = true + forceQuitRaw(error) } @JavascriptInterface - public void cancel() { + fun cancel() { // Allow forceQuit and cancel in downloadMode - if (this.consumedAction && !this.downloadMode) - return; - this.consumedAction = true; - this.activity.runOnUiThread(this.activity::forceBackPressed); + if (consumedAction && !downloadMode) return + consumedAction = true + activity.runOnUiThread { activity.forceBackPressed() } } /** * Open an url always in an external page or browser. */ @JavascriptInterface - public void openUrl(String url) { - if (this.consumedAction) - return; - this.consumedAction = true; - this.downloadMode = false; - if (BuildConfig.DEBUG) - Timber.d("Received openUrl request: %s", url); - if (Objects.equals(Uri.parse(url).getScheme(), "https")) { - IntentHelper.Companion.openUrl(this.activity, url); + fun openUrl(url: String?) { + if (consumedAction) return + consumedAction = true + downloadMode = false + if (BuildConfig.DEBUG) Timber.d("Received openUrl request: %s", url) + if (Uri.parse(url).scheme == "https") { + openUrl(activity, url) } } @@ -193,93 +214,91 @@ public class AndroidacyWebAPI { * Open an url in a custom tab if possible. */ @JavascriptInterface - public void openCustomTab(String url) { - if (this.consumedAction) - return; - this.consumedAction = true; - this.downloadMode = false; - if (BuildConfig.DEBUG) - Timber.d("Received openCustomTab request: %s", url); - if (Objects.equals(Uri.parse(url).getScheme(), "https")) { - IntentHelper.openCustomTab(this.activity, url); + fun openCustomTab(url: String?) { + if (consumedAction) return + consumedAction = true + downloadMode = false + if (BuildConfig.DEBUG) Timber.d("Received openCustomTab request: %s", url) + if (Uri.parse(url).scheme == "https") { + openCustomTab(activity, url) } } /** * Return if current theme is a light theme. */ - @JavascriptInterface - public boolean isLightTheme() { - return MainApplication.getINSTANCE().isLightTheme(); - } + @get:JavascriptInterface + val isLightTheme: Boolean + get() = MainApplication.getINSTANCE().isLightTheme /** * Check if the manager has received root access * (Note: hasRoot only return true on Magisk rooted phones) */ @JavascriptInterface - public boolean hasRoot() { - return InstallerInitializer.peekMagiskPath() != null; + fun hasRoot(): Boolean { + return peekMagiskPath() != null } /** * Check if the install API can be used */ @JavascriptInterface - public boolean canInstall() { + fun canInstall(): Boolean { // With lockdown mode enabled or lack of root, install should not have any effect - return this.allowInstall && this.hasRoot() && !MainApplication.isShowcaseMode(); + return allowInstall && hasRoot() && !MainApplication.isShowcaseMode() } /** * install a module via url, with the file checked with the md5 checksum value. */ @JavascriptInterface - public void install(String moduleUrl, String installTitle, String checksum) { + fun install(moduleUrl: String, installTitle: String?, checksum: String?) { // If compat mode is 0, this means Androidacy didn't implemented a download mode yet - if (this.consumedAction || (this.effectiveCompatMode >= 1 && !this.canInstall())) { - return; + var installTitle = installTitle + var checksum = checksum + if (consumedAction || effectiveCompatMode >= 1 && !canInstall()) { + return } - this.consumedAction = true; - this.downloadMode = false; - if (BuildConfig.DEBUG) - Timber.d("Received install request: " + moduleUrl + " " + installTitle + " " + checksum); - if (!AndroidacyUtil.isAndroidacyLink(moduleUrl)) { - this.forceQuitRaw("Non Androidacy module link used on Androidacy"); - return; + consumedAction = true + downloadMode = false + if (BuildConfig.DEBUG) Timber.d("Received install request: $moduleUrl $installTitle $checksum") + if (!isAndroidacyLink(moduleUrl)) { + forceQuitRaw("Non Androidacy module link used on Androidacy") + return } - checksum = Hashes.checkSumFormat(checksum); - if (checksum == null || checksum.isEmpty()) { - Timber.w("Androidacy didn't provided a checksum!"); - } else if (!Hashes.checkSumValid(checksum)) { - this.forceQuitRaw("Androidacy didn't provided a valid checksum"); - return; + checksum = checkSumFormat(checksum) + if (checksum.isNullOrEmpty()) { + Timber.w("Androidacy didn't provided a checksum!") + } else if (!checkSumValid(checksum)) { + forceQuitRaw("Androidacy didn't provided a valid checksum") + return } // moduleId is the module parameter in the url - String moduleId = AndroidacyUtil.getModuleId(moduleUrl); + val moduleId = getModuleId(moduleUrl) // Let's handle download mode ourself if not implemented - if (this.effectiveCompatMode < 1) { - if (!this.canInstall()) { - this.downloadMode = true; - this.activity.runOnUiThread(() -> { - if (this.activity.webView != null) { - this.activity.webView.loadUrl(moduleUrl); + if (effectiveCompatMode < 1) { + if (!canInstall()) { + downloadMode = true + activity.runOnUiThread { + if (activity.webView != null) { + activity.webView!!.loadUrl(moduleUrl) } - }); + } } else { - this.openNativeModuleDialogRaw(moduleUrl, moduleId, installTitle, checksum, true); + openNativeModuleDialogRaw(moduleUrl, moduleId, installTitle, checksum, true) } } else { - RepoModule repoModule = AndroidacyRepoData.getInstance().moduleHashMap.get(installTitle); - String config = null; - boolean mmtReborn = false; - if (repoModule != null && Objects.requireNonNull(repoModule.moduleInfo.name).length() >= 3) { - installTitle = repoModule.moduleInfo.name; // Set title to module name - config = repoModule.moduleInfo.config; - mmtReborn = repoModule.moduleInfo.mmtReborn; + val repoModule = AndroidacyRepoData.getInstance().moduleHashMap[installTitle] + var config: String? = null + var mmtReborn = false + if (repoModule != null && Objects.requireNonNull(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, mmtReborn); + activity.backOnResume = true + openInstaller(activity, moduleUrl, installTitle, config, checksum, mmtReborn) } } @@ -287,74 +306,73 @@ public class AndroidacyWebAPI { * install a module via url, with the file checked with the md5 checksum value. */ @JavascriptInterface - public void openNativeModuleDialog(String moduleUrl, String moduleId, String checksum) { - if (this.consumedAction) - return; - this.consumedAction = true; - this.downloadMode = false; - if (!AndroidacyUtil.isAndroidacyLink(moduleUrl)) { - this.forceQuitRaw("Non Androidacy module link used on Androidacy"); - return; + fun openNativeModuleDialog(moduleUrl: String?, moduleId: String?, checksum: String?) { + var checksum = checksum + if (consumedAction) return + consumedAction = true + downloadMode = false + if (!isAndroidacyLink(moduleUrl)) { + forceQuitRaw("Non Androidacy module link used on Androidacy") + return } - checksum = Hashes.checkSumFormat(checksum); - if (checksum == null || checksum.isEmpty()) { - Timber.w("Androidacy WebView didn't provided a checksum!"); - } else if (!Hashes.checkSumValid(checksum)) { - this.forceQuitRaw("Androidacy didn't provided a valid checksum"); - return; + checksum = checkSumFormat(checksum) + if (checksum.isNullOrEmpty()) { + Timber.w("Androidacy WebView didn't provided a checksum!") + } else if (!checkSumValid(checksum)) { + forceQuitRaw("Androidacy didn't provided a valid checksum") + return } // Get moduleTitle from url - String moduleTitle = AndroidacyUtil.getModuleTitle(moduleUrl); - this.openNativeModuleDialogRaw(moduleUrl, moduleId, moduleTitle, checksum, this.canInstall()); + val moduleTitle = getModuleTitle(moduleUrl!!) + openNativeModuleDialogRaw(moduleUrl, moduleId, moduleTitle, checksum, canInstall()) } /** * Tell if the moduleId is installed on the device */ @JavascriptInterface - public boolean isModuleInstalled(String moduleId) { - return Objects.requireNonNull(ModuleManager.getInstance()).getModules().get(moduleId) != null; + fun isModuleInstalled(moduleId: String?): Boolean { + return instance?.modules?.get(moduleId) != null } /** * Tell if the moduleId is updating and waiting a reboot to update */ @JavascriptInterface - public boolean isModuleUpdating(String moduleId) { - LocalModuleInfo localModuleInfo = Objects.requireNonNull(ModuleManager.getInstance()).getModules().get(moduleId); - return localModuleInfo != null && localModuleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING); + fun isModuleUpdating(moduleId: String?): Boolean { + val localModuleInfo = instance?.modules?.get(moduleId) + return localModuleInfo != null && localModuleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) } /** * Return the module version name or null if not installed. */ @JavascriptInterface - public String getModuleVersion(String moduleId) { - LocalModuleInfo localModuleInfo = Objects.requireNonNull(ModuleManager.getInstance()).getModules().get(moduleId); - return localModuleInfo != null ? localModuleInfo.version : null; + fun getModuleVersion(moduleId: String?): String? { + val localModuleInfo = instance?.modules?.get(moduleId) + return localModuleInfo?.version } /** * Return the module version code or -1 if not installed. */ @JavascriptInterface - public long getModuleVersionCode(String moduleId) { - LocalModuleInfo localModuleInfo = Objects.requireNonNull(ModuleManager.getInstance()).getModules().get(moduleId); - return localModuleInfo != null ? localModuleInfo.versionCode : -1L; + fun getModuleVersionCode(moduleId: String?): Long { + val localModuleInfo = instance?.modules?.get(moduleId) + return localModuleInfo?.versionCode ?: -1L } /** * Hide action bar if visible, the action bar is only visible by default on notes. */ @JavascriptInterface - public void hideActionBar() { - if (this.consumedAction) - return; - this.consumedAction = true; - this.activity.runOnUiThread(() -> { - this.activity.hideActionBar(); - this.consumedAction = false; - }); + fun hideActionBar() { + if (consumedAction) return + consumedAction = true + activity.runOnUiThread { + activity.hideActionBar() + consumedAction = false + } } /** @@ -362,27 +380,27 @@ public class AndroidacyWebAPI { * Optional title param to set action bar title. */ @JavascriptInterface - public void showActionBar(final String title) { - if (this.consumedAction) - return; - this.consumedAction = true; - this.activity.runOnUiThread(() -> { - this.activity.showActionBar(); - if (title != null && !title.isEmpty()) { - this.activity.setTitle(title); + fun showActionBar(title: String?) { + if (consumedAction) return + consumedAction = true + activity.runOnUiThread { + activity.showActionBar() + if (!title.isNullOrEmpty()) { + activity.title = title } - this.consumedAction = false; - }); + consumedAction = false + } } /** * Return true if the module is an Androidacy module. */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") @JavascriptInterface - public boolean isAndroidacyModule(String moduleId) { - LocalModuleInfo localModuleInfo = Objects.requireNonNull(ModuleManager.getInstance()).getModules().get(moduleId); - return localModuleInfo != null && ("Androidacy".equals(localModuleInfo.author) || AndroidacyUtil.isAndroidacyLink(localModuleInfo.config)); + fun isAndroidacyModule(moduleId: String?): Boolean { + val localModuleInfo = instance?.modules?.get(moduleId) + return localModuleInfo != null && ("Androidacy" == localModuleInfo.author || isAndroidacyLink( + localModuleInfo.config + )) } /** @@ -390,20 +408,18 @@ public class AndroidacyWebAPI { * an Androidacy module or if file doesn't exists. */ @JavascriptInterface - public String getAndroidacyModuleFile(String moduleId, String moduleFile) { - moduleId = moduleId.replaceAll("\\.", "").replaceAll("/", ""); - if (moduleFile == null || this.consumedAction || !this.isAndroidacyModule(moduleId)) - return ""; - moduleFile = moduleFile.replaceAll("\\.", "").replaceAll("/", ""); - File moduleFolder = new File("/data/adb/modules/" + moduleId); - File absModuleFile = new File(moduleFolder, moduleFile).getAbsoluteFile(); - if (!absModuleFile.getPath().startsWith(moduleFolder.getPath())) - return ""; - try { - return new String(Files.readSU(absModuleFile.getAbsoluteFile()), StandardCharsets.UTF_8); - } catch ( - IOException e) { - return ""; + fun getAndroidacyModuleFile(moduleId: String, moduleFile: String?): String { + var moduleId = moduleId + var moduleFile = moduleFile + moduleId = moduleId.replace("\\.".toRegex(), "").replace("/".toRegex(), "") + if (moduleFile == null || consumedAction || !isAndroidacyModule(moduleId)) return "" + moduleFile = moduleFile.replace("\\.".toRegex(), "").replace("/".toRegex(), "") + val moduleFolder = File("/data/adb/modules/$moduleId") + val absModuleFile = File(moduleFolder, moduleFile).absoluteFile + return if (!absModuleFile.path.startsWith(moduleFolder.path)) "" else try { + String(readSU(absModuleFile.absoluteFile), StandardCharsets.UTF_8) + } catch (e: IOException) { + "" } } @@ -412,139 +428,136 @@ public class AndroidacyWebAPI { * Return true if action succeeded */ @JavascriptInterface - public boolean setAndroidacyModuleMeta(String moduleId, String content) { - moduleId = moduleId.replaceAll("\\.", "").replaceAll("/", ""); - if (content == null || this.consumedAction || !this.isAndroidacyModule(moduleId)) - return false; - File androidacyMetaFile = new File("/data/adb/modules/" + moduleId + "/.androidacy"); - try { - Files.writeSU(androidacyMetaFile, content.getBytes(StandardCharsets.UTF_8)); - return true; - } catch ( - IOException e) { - return false; + fun setAndroidacyModuleMeta(moduleId: String, content: String?): Boolean { + var moduleId = moduleId + moduleId = moduleId.replace("\\.".toRegex(), "").replace("/".toRegex(), "") + if (content == null || consumedAction || !isAndroidacyModule(moduleId)) return false + val androidacyMetaFile = File("/data/adb/modules/$moduleId/.androidacy") + return try { + writeSU(androidacyMetaFile, content.toByteArray(StandardCharsets.UTF_8)) + true + } catch (e: IOException) { + false } } /** * Return current app version code */ - @JavascriptInterface - public int getAppVersionCode() { - return BuildConfig.VERSION_CODE; - } + @get:JavascriptInterface + val appVersionCode: Int + get() = BuildConfig.VERSION_CODE /** * Return current app version name */ - @JavascriptInterface - public String getAppVersionName() { - return BuildConfig.VERSION_NAME; - } + @get:JavascriptInterface + val appVersionName: String + get() = BuildConfig.VERSION_NAME /** * Return current magisk version code or 0 if not applicable */ - @JavascriptInterface - public int getMagiskVersionCode() { - return InstallerInitializer.peekMagiskPath() == null ? 0 : InstallerInitializer.peekMagiskVersion(); - } + @get:JavascriptInterface + val magiskVersionCode: Int + get() = if (peekMagiskPath() == null) 0 else peekMagiskVersion() /** * Return current android sdk-int version code, see: - * right here + * [right here](https://source.android.com/setup/start/build-numbers) */ - @JavascriptInterface - public int getAndroidVersionCode() { - return Build.VERSION.SDK_INT; - } + @get:JavascriptInterface + val androidVersionCode: Int + get() = Build.VERSION.SDK_INT /** * Return current navigation bar height or 0 if not visible */ - @JavascriptInterface - public int getNavigationBarHeight() { - return this.activity.getNavigationBarHeight(); - } - - /** - * Allow Androidacy backend to notify compat mode - * return current effective compat mode - */ - @JavascriptInterface - public int getEffectiveCompatMode() { - return this.effectiveCompatMode; - } + @get:JavascriptInterface + val navigationBarHeight: Int + get() = activity.navigationBarHeight /** * Return current theme accent color */ - @JavascriptInterface - public int getAccentColor() { - Resources.Theme theme = this.activity.getTheme(); - TypedValue typedValue = new TypedValue(); - theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, typedValue, true); - if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { - return typedValue.data; + @get:JavascriptInterface + val accentColor: Int + get() { + val theme = activity.theme + val typedValue = TypedValue() + theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, typedValue, true) + if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { + return typedValue.data + } + theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true) + return typedValue.data } - theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true); - return typedValue.data; - } /** * Return current theme foreground color */ - @JavascriptInterface - public int getForegroundColor() { - return this.activity.isLightTheme() ? Color.BLACK : Color.WHITE; - } + @get:JavascriptInterface + val foregroundColor: Int + get() = if (activity.isLightTheme) Color.BLACK else Color.WHITE /** * Return current theme background color */ - @JavascriptInterface - public int getBackgroundColor() { - Resources.Theme theme = this.activity.getTheme(); - TypedValue typedValue = new TypedValue(); - theme.resolveAttribute(com.google.android.material.R.attr.backgroundColor, typedValue, true); - if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { - return typedValue.data; + @get:JavascriptInterface + val backgroundColor: Int + get() { + val theme = activity.theme + val typedValue = TypedValue() + theme.resolveAttribute( + com.google.android.material.R.attr.backgroundColor, + typedValue, + true + ) + if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { + return typedValue.data + } + theme.resolveAttribute(android.R.attr.background, typedValue, true) + return typedValue.data } - theme.resolveAttribute(android.R.attr.background, typedValue, true); - return typedValue.data; - } /** * Return current hex string of monet theme */ @JavascriptInterface - public String getMonetColor(String id) { - @SuppressLint("DiscouragedApi") int nameResourceID = this.activity.getResources().getIdentifier("@android:color/" + id, "color", this.activity.getApplicationInfo().packageName); - if (nameResourceID == 0) { - throw new IllegalArgumentException("No resource string found with name " + id); + fun getMonetColor(id: String): String { + @SuppressLint("DiscouragedApi") val nameResourceID = activity.resources.getIdentifier( + "@android:color/$id", "color", activity.applicationInfo.packageName + ) + return if (nameResourceID == 0) { + throw IllegalArgumentException("No resource string found with name $id") } else { - int color = ContextCompat.getColor(this.activity, nameResourceID); - int red = Color.red(color); - int blue = Color.blue(color); - int green = Color.green(color); - return String.format("#%02x%02x%02x", red, green, blue); + val color = ContextCompat.getColor(activity, nameResourceID) + val red = Color.red(color) + val blue = Color.blue(color) + val green = Color.green(color) + String.format("#%02x%02x%02x", red, green, blue) } } @JavascriptInterface - public void setAndroidacyToken(String token) { - AndroidacyRepoData.getInstance().setToken(token); + fun setAndroidacyToken(token: String?) { + AndroidacyRepoData.getInstance().setToken(token) } // Androidacy feature level declaration method - @JavascriptInterface - public void notifyCompatUnsupported() { - this.notifyCompatModeRaw(COMPAT_UNSUPPORTED); + fun notifyCompatUnsupported() { + notifyCompatModeRaw(COMPAT_UNSUPPORTED) } @JavascriptInterface - public void notifyCompatDownloadButton() { - this.notifyCompatModeRaw(COMPAT_DOWNLOAD); + fun notifyCompatDownloadButton() { + notifyCompatModeRaw(COMPAT_DOWNLOAD) + } + + companion object { + const val COMPAT_UNSUPPORTED = 0 + const val COMPAT_DOWNLOAD = 1 + private const val MAX_COMPAT_MODE = 1 } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt index 38e6e27..7eabf3d 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt +++ b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.kt @@ -64,7 +64,7 @@ class ModuleManager private constructor() : SyncManager() { // get all dirs under the realms/repos/ dir under app's data dir val cacheRoot = File(MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/").toURI()) - var moduleListCache: ModuleListCache + var moduleListCache: ModuleListCache? for (dir in Objects.requireNonNull>(cacheRoot.listFiles())) { if (dir.isDirectory) { // if the dir name matches the module name, use it as the cache dir @@ -83,25 +83,27 @@ class ModuleManager private constructor() : SyncManager() { ) moduleListCache = realm.where(ModuleListCache::class.java).equalTo("codename", module) - .findFirst()!! + .findFirst() Timber.d("Found cache for %s", module) // get module info from cache if (moduleInfo == null) { moduleInfo = LocalModuleInfo(module) } - moduleInfo.name = - if (moduleListCache.name != "") moduleListCache.name else module - moduleInfo.description = - if (moduleListCache.description != "") moduleListCache.description else null - moduleInfo.author = - if (moduleListCache.author != "") moduleListCache.author else null - moduleInfo.safe = moduleListCache.isSafe == true - moduleInfo.support = - if (moduleListCache.support != "") moduleListCache.support else null - moduleInfo.donate = - if (moduleListCache.donate != "") moduleListCache.donate else null - moduleInfo.flags = moduleInfo.flags or FLAG_MM_REMOTE_MODULE - moduleInfos[module] = moduleInfo + if (moduleListCache != null) { + moduleInfo.name = + if (moduleListCache.name != "") moduleListCache.name else module + moduleInfo.description = + if (moduleListCache.description != "") moduleListCache.description else moduleInfo.description + moduleInfo.author = + if (moduleListCache.author != "") moduleListCache.author else moduleInfo.author + moduleInfo.safe = moduleListCache.isSafe == true + moduleInfo.support = + if (moduleListCache.support != "") moduleListCache.support else null + moduleInfo.donate = + if (moduleListCache.donate != "") moduleListCache.donate else null + moduleInfo.flags = moduleInfo.flags or FLAG_MM_REMOTE_MODULE + moduleInfos[module] = moduleInfo + } realm.close() break } 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 21b5912..39beb74 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java +++ b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java @@ -2,6 +2,7 @@ package com.fox2code.mmm.module; import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.Spanned; import android.widget.Button; @@ -52,7 +53,7 @@ public enum ActionButtonType { } TrackHelper.track().event("view_notes", name).with(MainApplication.getINSTANCE().getTracker()); String notesUrl = moduleHolder.repoModule.notesUrl; - if (AndroidacyUtil.isAndroidacyLink(notesUrl)) { + if (AndroidacyUtil.Companion.isAndroidacyLink(notesUrl)) { IntentHelper.openUrlAndroidacy(button.getContext(), notesUrl, false, moduleHolder.repoModule.moduleInfo.name, moduleHolder.getMainModuleConfig()); } else { IntentHelper.openMarkdown(button.getContext(), notesUrl, moduleHolder.repoModule.moduleInfo.name, moduleHolder.getMainModuleConfig(), moduleHolder.repoModule.moduleInfo.changeBoot, moduleHolder.repoModule.moduleInfo.needRamdisk, moduleHolder.repoModule.moduleInfo.minMagisk, moduleHolder.repoModule.moduleInfo.minApi, moduleHolder.repoModule.moduleInfo.maxApi); @@ -94,10 +95,28 @@ public enum ActionButtonType { name = moduleHolder.repoModule.moduleInfo.name; } TrackHelper.track().event("view_update_install", name).with(MainApplication.getINSTANCE().getTracker()); + // if icon is reinstall, we need to uninstall first - warn the user but don't proceed + if (moduleHolder.moduleInfo != null) { + // get icon of the button + Drawable icon = button.getChipIcon(); + if (icon != null && icon.getConstantState() != null) { + Drawable reinstallIcon = button.getContext().getDrawable(R.drawable.ic_baseline_refresh_24); + if (reinstallIcon != null && reinstallIcon.getConstantState() != null) { + if (icon.getConstantState().equals(reinstallIcon.getConstantState())) { + new MaterialAlertDialogBuilder(button.getContext()) + .setTitle(R.string.reinstall) + .setMessage(R.string.reinstall_warning) + .setPositiveButton(R.string.reinstall, null) + .show(); + return; + } + } + } + } String updateZipUrl = moduleHolder.getUpdateZipUrl(); if (updateZipUrl == null) return; // Androidacy manage the selection between download and install - if (AndroidacyUtil.isAndroidacyLink(updateZipUrl)) { + if (AndroidacyUtil.Companion.isAndroidacyLink(updateZipUrl)) { IntentHelper.openUrlAndroidacy(button.getContext(), updateZipUrl, true, moduleInfo.name, moduleInfo.config); return; } @@ -211,7 +230,7 @@ public enum ActionButtonType { name = moduleHolder.repoModule.moduleInfo.name; } TrackHelper.track().event("config_module", name).with(MainApplication.getINSTANCE().getTracker()); - if (AndroidacyUtil.isAndroidacyLink(config)) { + if (AndroidacyUtil.Companion.isAndroidacyLink(config)) { IntentHelper.openUrlAndroidacy(button.getContext(), config, true); } else { IntentHelper.openConfig(button.getContext(), config); 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 a7f1ca7..74020b2 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -268,7 +268,7 @@ public class RepoData extends XRepo { if (file.exists()) { try { ModuleInfo moduleInfo = repoModule.moduleInfo; - PropUtils.readProperties(moduleInfo, file.getAbsolutePath(), repoModule.repoName + "/" + moduleInfo.name, false); + PropUtils.Companion.readProperties(moduleInfo, file.getAbsolutePath(), repoModule.repoName + "/" + moduleInfo.name, false); moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID; if (moduleInfo.version == null) { moduleInfo.version = "v" + moduleInfo.versionCode; diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoModule.kt b/app/src/main/java/com/fox2code/mmm/repo/RepoModule.kt index a0d6784..f31e2b8 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoModule.kt +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoModule.kt @@ -1,49 +1,70 @@ -package com.fox2code.mmm.repo; +package com.fox2code.mmm.repo -import androidx.annotation.StringRes; +import androidx.annotation.StringRes +import com.fox2code.mmm.manager.ModuleInfo -import com.fox2code.mmm.manager.ModuleInfo; +class RepoModule { + @JvmField + val repoData: RepoData + @JvmField + val moduleInfo: ModuleInfo + @JvmField + val id: String + @JvmField + var repoName: String? = null + @JvmField + var lastUpdated: Long = 0 + @JvmField + var propUrl: String? = null + @JvmField + var zipUrl: String? = null + @JvmField + var notesUrl: String? = null + @JvmField + var checksum: String? = null + @JvmField + var processed = false -public class RepoModule { - public final RepoData repoData; - public final ModuleInfo moduleInfo; - public final String id; - public String repoName; - public long lastUpdated; - public String propUrl; - public String zipUrl; - public String notesUrl; - public String checksum; - public boolean processed; + @JvmField @StringRes - public int qualityText; - public int qualityValue; - public boolean safe; + var qualityText = 0 + @JvmField + var qualityValue = 0 + var safe: Boolean - public RepoModule(RepoData repoData, String id) { - this.repoData = repoData; - this.moduleInfo = new ModuleInfo(id); - this.id = id; - this.moduleInfo.flags |= - ModuleInfo.FLAG_METADATA_INVALID; - this.safe = this.moduleInfo.safe; + constructor(repoData: RepoData, id: String) { + this.repoData = repoData + moduleInfo = ModuleInfo(id) + this.id = id + moduleInfo.flags = moduleInfo.flags or ModuleInfo.FLAG_METADATA_INVALID + safe = moduleInfo.safe } // allows all fields to be set- - public RepoModule(RepoData repoData, String id, String name, String description, String author, String donate, String config, String support, String version, int versionCode) { - this.repoData = repoData; - this.moduleInfo = new ModuleInfo(id); - this.id = id; - this.moduleInfo.name = name; - this.moduleInfo.description = description; - this.moduleInfo.author = author; - this.moduleInfo.donate = donate; - this.moduleInfo.config = config; - this.moduleInfo.support = support; - this.moduleInfo.version = version; - this.moduleInfo.versionCode = versionCode; - this.moduleInfo.flags |= - ModuleInfo.FLAG_METADATA_INVALID; - this.safe = this.moduleInfo.safe; + constructor( + repoData: RepoData, + id: String, + name: String?, + description: String?, + author: String?, + donate: String?, + config: String?, + support: String?, + version: String?, + versionCode: Int + ) { + this.repoData = repoData + moduleInfo = ModuleInfo(id) + this.id = id + moduleInfo.name = name + moduleInfo.description = description + moduleInfo.author = author + moduleInfo.donate = donate + moduleInfo.config = config + moduleInfo.support = support + moduleInfo.version = version + moduleInfo.versionCode = versionCode.toLong() + moduleInfo.flags = moduleInfo.flags or ModuleInfo.FLAG_METADATA_INVALID + safe = moduleInfo.safe } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/PropUtils.kt b/app/src/main/java/com/fox2code/mmm/utils/io/PropUtils.kt index 660b3f2..52ba7cb 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/PropUtils.kt +++ b/app/src/main/java/com/fox2code/mmm/utils/io/PropUtils.kt @@ -1,423 +1,460 @@ -package com.fox2code.mmm.utils.io; +@file:Suppress("unused") -import static com.fox2code.mmm.AppUpdateManager.FLAG_COMPAT_LOW_QUALITY; -import static com.fox2code.mmm.AppUpdateManager.getFlagsForModule; +package com.fox2code.mmm.utils.io -import android.os.Build; -import android.text.TextUtils; +import android.os.Build +import android.text.TextUtils +import com.fox2code.mmm.AppUpdateManager +import com.fox2code.mmm.manager.ModuleInfo +import com.topjohnwu.superuser.io.SuFileInputStream +import timber.log.Timber +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets -import com.fox2code.mmm.AppUpdateManager; -import com.fox2code.mmm.manager.ModuleInfo; -import com.topjohnwu.superuser.io.SuFileInputStream; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; +enum class PropUtils { + ; -import timber.log.Timber; + @Suppress("SpellCheckingInspection") + companion object { + private val moduleSupportsFallbacks = HashMap() + private val moduleConfigsFallbacks = HashMap() + private val moduleMinApiFallbacks = HashMap() + private val moduleUpdateJsonFallbacks = HashMap() + private val moduleMTTRebornFallback = HashSet() + private val moduleImportantProp = HashSet( + mutableListOf( + "id", "name", "version", "versionCode" + ) + ) + private var RIRU_MIN_API = 0 -@SuppressWarnings("SpellCheckingInspection") -public enum PropUtils { - ; - private static final HashMap moduleSupportsFallbacks = new HashMap<>(); - private static final HashMap moduleConfigsFallbacks = new HashMap<>(); - private static final HashMap moduleMinApiFallbacks = new HashMap<>(); - private static final HashMap moduleUpdateJsonFallbacks = new HashMap<>(); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private static final HashSet moduleMTTRebornFallback = new HashSet<>(); - private static final HashSet moduleImportantProp = new HashSet<>(Arrays.asList( - "id", "name", "version", "versionCode" - )); - private static final int RIRU_MIN_API; - - // Note: These fallback values may not be up-to-date - // They are only used if modules don't define the metadata - static { - // Support are pages or groups where the user can get support for the module - moduleSupportsFallbacks.put("aospill", "https://t.me/PannekoX"); - moduleSupportsFallbacks.put("bromitewebview", "https://t.me/androidacy_discussions"); - moduleSupportsFallbacks.put("fontrevival", "https://t.me/androidacy_discussions"); - moduleSupportsFallbacks.put("MagiskHidePropsConf", "https://forum.xda-developers.com/t" + - "/module-magiskhide-props-config-safetynet-prop-edits-and-more-v6-1-1.3789228/"); - moduleSupportsFallbacks.put("quickstepswitcher", "https://t.me/QuickstepSwitcherSupport"); - moduleSupportsFallbacks.put("riru_edxposed", "https://t.me/EdXposed"); - moduleSupportsFallbacks.put("riru_lsposed", "https://github.com/LSPosed/LSPosed/issues"); - moduleSupportsFallbacks.put("substratum", "https://github.com/substratum/substratum/issues"); - // Config are application installed by modules that allow them to be configured - moduleConfigsFallbacks.put("quickstepswitcher", "xyz.paphonb.quickstepswitcher"); - moduleConfigsFallbacks.put("hex_installer_module", "project.vivid.hex.bodhi"); - moduleConfigsFallbacks.put("riru_edxposed", "org.meowcat.edxposed.manager"); - moduleConfigsFallbacks.put("riru_lsposed", "org.lsposed.manager"); - moduleConfigsFallbacks.put("zygisk_lsposed", "org.lsposed.manager"); - moduleConfigsFallbacks.put("xposed_dalvik", "de.robv.android.xposed.installer"); - moduleConfigsFallbacks.put("xposed", "de.robv.android.xposed.installer"); - moduleConfigsFallbacks.put("substratum", "projekt.substratum"); - // minApi is the minimum android version required to use the module - moduleMinApiFallbacks.put("HideNavBar", Build.VERSION_CODES.Q); - moduleMinApiFallbacks.put("riru_ifw_enhance", Build.VERSION_CODES.O); - moduleMinApiFallbacks.put("zygisk_ifw_enhance", Build.VERSION_CODES.O); - moduleMinApiFallbacks.put("riru_edxposed", Build.VERSION_CODES.O); - moduleMinApiFallbacks.put("zygisk_edxposed", Build.VERSION_CODES.O); - moduleMinApiFallbacks.put("riru_lsposed", Build.VERSION_CODES.O_MR1); - moduleMinApiFallbacks.put("zygisk_lsposed", Build.VERSION_CODES.O_MR1); - moduleMinApiFallbacks.put("noneDisplayCutout", Build.VERSION_CODES.P); - moduleMinApiFallbacks.put("quickstepswitcher", Build.VERSION_CODES.P); - moduleMinApiFallbacks.put("riru_clipboard_whitelist", Build.VERSION_CODES.Q); - // minApi for riru core include submodules - moduleMinApiFallbacks.put("riru-core", RIRU_MIN_API = Build.VERSION_CODES.M); - // Fallbacks in case updateJson is missing - final String GH_UC = "https://raw.githubusercontent.com/"; - moduleUpdateJsonFallbacks.put("BluetoothLibraryPatcher", - GH_UC + "3arthur6/BluetoothLibraryPatcher/master/update.json"); - moduleUpdateJsonFallbacks.put("Detach", - GH_UC + "xerta555/Detach-Files/blob/master/Updater.json"); - for (String module : new String[]{"busybox-ndk", "adb-ndk", "twrp-keep", - "adreno-dev", "nano-ndk", "zipsigner", "nexusmedia", "mtd-ndk"}) { - moduleUpdateJsonFallbacks.put(module, - GH_UC + "Magisk-Modules-Repo/" + module + "/master/update.json"); + // Note: These fallback values may not be up-to-date + // They are only used if modules don't define the metadata + init { + // Support are pages or groups where the user can get support for the module + moduleSupportsFallbacks["aospill"] = "https://t.me/PannekoX" + moduleSupportsFallbacks["bromitewebview"] = "https://t.me/androidacy_discussions" + moduleSupportsFallbacks["fontrevival"] = "https://t.me/androidacy_discussions" + moduleSupportsFallbacks["MagiskHidePropsConf"] = "https://forum.xda-developers.com/t" + + "/module-magiskhide-props-config-safetynet-prop-edits-and-more-v6-1-1.3789228/" + moduleSupportsFallbacks["quickstepswitcher"] = "https://t.me/QuickstepSwitcherSupport" + moduleSupportsFallbacks["riru_edxposed"] = "https://t.me/EdXposed" + moduleSupportsFallbacks["riru_lsposed"] = "https://github.com/LSPosed/LSPosed/issues" + moduleSupportsFallbacks["substratum"] = + "https://github.com/substratum/substratum/issues" + // Config are application installed by modules that allow them to be configured + moduleConfigsFallbacks["quickstepswitcher"] = "xyz.paphonb.quickstepswitcher" + moduleConfigsFallbacks["hex_installer_module"] = "project.vivid.hex.bodhi" + moduleConfigsFallbacks["riru_edxposed"] = "org.meowcat.edxposed.manager" + moduleConfigsFallbacks["riru_lsposed"] = "org.lsposed.manager" + moduleConfigsFallbacks["zygisk_lsposed"] = "org.lsposed.manager" + moduleConfigsFallbacks["xposed_dalvik"] = "de.robv.android.xposed.installer" + moduleConfigsFallbacks["xposed"] = "de.robv.android.xposed.installer" + moduleConfigsFallbacks["substratum"] = "projekt.substratum" + // minApi is the minimum android version required to use the module + moduleMinApiFallbacks["HideNavBar"] = Build.VERSION_CODES.Q + moduleMinApiFallbacks["riru_ifw_enhance"] = Build.VERSION_CODES.O + moduleMinApiFallbacks["zygisk_ifw_enhance"] = Build.VERSION_CODES.O + moduleMinApiFallbacks["riru_edxposed"] = Build.VERSION_CODES.O + moduleMinApiFallbacks["zygisk_edxposed"] = Build.VERSION_CODES.O + moduleMinApiFallbacks["riru_lsposed"] = Build.VERSION_CODES.O_MR1 + moduleMinApiFallbacks["zygisk_lsposed"] = Build.VERSION_CODES.O_MR1 + moduleMinApiFallbacks["noneDisplayCutout"] = Build.VERSION_CODES.P + moduleMinApiFallbacks["quickstepswitcher"] = Build.VERSION_CODES.P + moduleMinApiFallbacks["riru_clipboard_whitelist"] = Build.VERSION_CODES.Q + // minApi for riru core include submodules + moduleMinApiFallbacks["riru-core"] = Build.VERSION_CODES.M.also { RIRU_MIN_API = it } + // Fallbacks in case updateJson is missing + val ghUC = "https://raw.githubusercontent.com/" + moduleUpdateJsonFallbacks["BluetoothLibraryPatcher"] = + ghUC + "3arthur6/BluetoothLibraryPatcher/master/update.json" + moduleUpdateJsonFallbacks["Detach"] = + ghUC + "xerta555/Detach-Files/blob/master/Updater.json" + for (module in arrayOf( + "busybox-ndk", "adb-ndk", "twrp-keep", + "adreno-dev", "nano-ndk", "zipsigner", "nexusmedia", "mtd-ndk" + )) { + moduleUpdateJsonFallbacks[module] = + ghUC + "Magisk-Modules-Repo/" + module + "/master/update.json" + } + moduleUpdateJsonFallbacks["riru_ifw_enhance"] = "https://github.com/" + + "Kr328/Riru-IFWEnhance/releases/latest/download/riru-ifw-enhance.json" + moduleUpdateJsonFallbacks["zygisk_ifw_enhance"] = "https://github.com/" + + "Kr328/Riru-IFWEnhance/releases/latest/download/zygisk-ifw-enhance.json" + moduleUpdateJsonFallbacks["riru_lsposed"] = + "https://lsposed.github.io/LSPosed/release/riru.json" + moduleUpdateJsonFallbacks["zygisk_lsposed"] = + "https://lsposed.github.io/LSPosed/release/zygisk.json" } - moduleUpdateJsonFallbacks.put("riru_ifw_enhance", "https://github.com/" + - "Kr328/Riru-IFWEnhance/releases/latest/download/riru-ifw-enhance.json"); - moduleUpdateJsonFallbacks.put("zygisk_ifw_enhance", "https://github.com/" + - "Kr328/Riru-IFWEnhance/releases/latest/download/zygisk-ifw-enhance.json"); - moduleUpdateJsonFallbacks.put("riru_lsposed", - "https://lsposed.github.io/LSPosed/release/riru.json"); - moduleUpdateJsonFallbacks.put("zygisk_lsposed", - "https://lsposed.github.io/LSPosed/release/zygisk.json"); - } - public static void readProperties(ModuleInfo moduleInfo, String file, - boolean local) throws IOException { - readProperties(moduleInfo, SuFileInputStream.open(file), file, local); - } + @Throws(IOException::class) + fun readProperties( + moduleInfo: ModuleInfo, file: String, + local: Boolean + ) { + readProperties(moduleInfo, SuFileInputStream.open(file), file, local) + } - public static void readProperties(ModuleInfo moduleInfo, String file, - String name, boolean local) throws IOException { - readProperties(moduleInfo, SuFileInputStream.open(file), name, local); - } + @Throws(IOException::class) + fun readProperties( + moduleInfo: ModuleInfo, file: String?, + name: String, local: Boolean + ) { + readProperties(moduleInfo, SuFileInputStream.open(file!!), name, local) + } - public static void readProperties(ModuleInfo moduleInfo, InputStream inputStream, - 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, - readMMTReborn = false; - try (BufferedReader bufferedReader = new BufferedReader( - new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - String line; - int lineNum = 0; - while ((line = bufferedReader.readLine()) != null) { - if (lineNum == 0 && line.startsWith("\u0000")) { - while (line.startsWith("\u0000")) - line = line.substring(1); - } - lineNum++; - int index = line.indexOf('='); - if (index == -1 || line.startsWith("#")) - continue; - String key = line.substring(0, index); - String value = line.substring(index + 1).trim(); - // check if field is defined on the moduleInfo object we are reading - if (moduleInfo.toString().contains(key)) { - continue; - } - // name and id have their own implementation - if (isInvalidValue(key)) { - if (local) { - invalid = true; - continue; - } else throw new IOException("Invalid key at line " + lineNum); - } else { - if (value.isEmpty() && !moduleImportantProp.contains(key)) - continue; // allow empty values to pass. - if (isInvalidValue(value)) { - if (local) { - invalid = true; - continue; - } else throw new IOException("Invalid value for key " + key); + @Throws(IOException::class) + fun readProperties( + moduleInfo: ModuleInfo, inputStream: InputStream?, + name: String, local: Boolean + ) { + var readId = false + var readIdSec = false + var readName = false + var readVersionCode = false + var readVersion = false + var readDescription = false + var readUpdateJson = false + var invalid = false + var readMinApi = false + var readMaxApi = false + var readMMTReborn = false + BufferedReader( + InputStreamReader(inputStream, StandardCharsets.UTF_8) + ).use { bufferedReader -> + var line: String + var lineNum = 0 + while (bufferedReader.readLine().also { line = it } != null) { + if (lineNum == 0 && line.startsWith("\u0000")) { + while (line.startsWith("\u0000")) line = line.substring(1) } - } - switch (key) { - case "id": + lineNum++ + val index = line.indexOf('=') + if (index == -1 || line.startsWith("#")) continue + val key = line.substring(0, index) + val value = line.substring(index + 1).trim { it <= ' ' } + // check if field is defined on the moduleInfo object we are reading + if (moduleInfo.toString().contains(key)) { + continue + } + // name and id have their own implementation + if (isInvalidValue(key)) { + if (local) { + invalid = true + continue + } else throw IOException("Invalid key at line $lineNum") + } else { + if (value.isEmpty() && !moduleImportantProp.contains(key)) continue // allow empty values to pass. if (isInvalidValue(value)) { if (local) { - invalid = true; - break; + invalid = true + continue + } else throw IOException("Invalid value for key $key") + } + } + when (key) { + "id" -> { + if (isInvalidValue(value)) { + if (local) { + invalid = true + break + } + throw IOException("Invalid module id!") + } + readId = true + if (moduleInfo.id != value) { + invalid = if (local) { + true + } else { + throw IOException( + name + " has an non matching module id! " + + "(Expected \"" + moduleInfo.id + "\" got \"" + value + "\"" + ) + } } - throw new IOException("Invalid module id!"); } - readId = true; - if (!moduleInfo.id.equals(value)) { - if (local) { - invalid = true; - } else { - throw new IOException(name + " has an non matching module id! " + - "(Expected \"" + moduleInfo.id + "\" got \"" + value + "\""); + + "name" -> { + if (readName) { + if (local) { + invalid = true + break + } else throw IOException("Duplicate module name!") + } + if (isInvalidValue(value)) { + if (local) { + invalid = true + break + } + throw IOException("Invalid module name!") + } + readName = true + moduleInfo.name = value + if (moduleInfo.id == value) { + readIdSec = true } } - break; - case "name": - if (readName) { - if (local) { - invalid = true; - break; - } else throw new IOException("Duplicate module name!"); + + "version" -> { + readVersion = true + moduleInfo.version = value } - if (isInvalidValue(value)) { - if (local) { - invalid = true; - break; + + "versionCode" -> { + readVersionCode = true + try { + moduleInfo.versionCode = value.toLong() + } catch (e: RuntimeException) { + if (local) { + invalid = true + moduleInfo.versionCode = 0 + } else throw e } - throw new IOException("Invalid module name!"); } - readName = true; - moduleInfo.name = value; - if (moduleInfo.id.equals(value)) { - readIdSec = true; + + "author" -> moduleInfo.author = + if (value.endsWith(" development team")) value.substring( + 0, + value.length - 17 + ) else value + + "description" -> { + moduleInfo.description = value + readDescription = true + } + + "updateJsonAk3" -> { + // Only allow AnyKernel3 helper to use "updateJsonAk3" + if ("ak3-helper" != moduleInfo.id) break + if (isInvalidURL(value)) break + moduleInfo.updateJson = value + readUpdateJson = true } - break; - case "version": - readVersion = true; - moduleInfo.version = value; - break; - case "versionCode": - readVersionCode = true; - try { - moduleInfo.versionCode = Long.parseLong(value); - } catch (RuntimeException e) { - if (local) { - invalid = true; - moduleInfo.versionCode = 0; - } else throw e; + + "updateJson" -> { + if (isInvalidURL(value)) break + moduleInfo.updateJson = value + readUpdateJson = true + } + + "changeBoot" -> moduleInfo.changeBoot = + java.lang.Boolean.parseBoolean(value) + + "mmtReborn" -> { + moduleInfo.mmtReborn = java.lang.Boolean.parseBoolean(value) + readMMTReborn = true + } + + "support" -> { + // Do not accept invalid or too broad support links + if (isInvalidURL(value) || "https://forum.xda-developers.com/" == value) break + moduleInfo.support = value } - break; - case "author": - moduleInfo.author = value.endsWith(" development team") ? - value.substring(0, value.length() - 17) : value; - break; - case "description": - moduleInfo.description = value; - readDescription = true; - break; - case "updateJsonAk3": - // Only allow AnyKernel3 helper to use "updateJsonAk3" - if (!"ak3-helper".equals(moduleInfo.id)) break; - case "updateJson": - if (isInvalidURL(value)) break; - moduleInfo.updateJson = value; - readUpdateJson = true; - break; - 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) || - "https://forum.xda-developers.com/".equals(value)) - break; - moduleInfo.support = value; - break; - case "donate": - // Do not accept invalid donate links - if (isInvalidURL(value)) break; - moduleInfo.donate = value; - break; - case "config": - moduleInfo.config = value; - break; - case "needRamdisk": - moduleInfo.needRamdisk = Boolean.parseBoolean(value); - break; - case "minMagisk": - try { - int i = value.indexOf('.'); + + "donate" -> { + // Do not accept invalid donate links + if (isInvalidURL(value)) break + moduleInfo.donate = value + } + + "config" -> moduleInfo.config = value + "needRamdisk" -> moduleInfo.needRamdisk = + java.lang.Boolean.parseBoolean(value) + + "minMagisk" -> try { + val i = value.indexOf('.') if (i == -1) { - moduleInfo.minMagisk = Integer.parseInt(value); + moduleInfo.minMagisk = value.toInt() } else { - moduleInfo.minMagisk = // Allow 24.1 to mean 24100 - (Integer.parseInt(value.substring(0, i)) * 1000) + - (Integer.parseInt(value.substring(i + 1)) * 100); + moduleInfo.minMagisk = // Allow 24.1 to mean 24100 + value.substring(0, i).toInt() * 1000 + value.substring(i + 1) + .toInt() * 100 + } + } catch (e: Exception) { + moduleInfo.minMagisk = 0 + } + + "minApi" -> { + // Special case for Riru EdXposed because + // minApi don't mean the same thing for them + if ("10" == value) break + try { + moduleInfo.minApi = value.toInt() + readMinApi = true + } catch (e: Exception) { + if (!readMinApi) moduleInfo.minApi = 0 } - } catch (Exception e) { - moduleInfo.minMagisk = 0; } - break; - case "minApi": - // Special case for Riru EdXposed because - // minApi don't mean the same thing for them - if ("10".equals(value)) break; - case "minSdkVersion": // Improve compatibility - try { - moduleInfo.minApi = Integer.parseInt(value); - readMinApi = true; - } catch (Exception e) { - if (!readMinApi) moduleInfo.minApi = 0; + + "minSdkVersion" -> try { + moduleInfo.minApi = value.toInt() + readMinApi = true + } catch (e: Exception) { + if (!readMinApi) moduleInfo.minApi = 0 } - break; - case "maxSdkVersion": // Improve compatibility - case "maxApi": - try { - moduleInfo.maxApi = Integer.parseInt(value); - readMaxApi = true; - } catch (Exception e) { - if (!readMaxApi) moduleInfo.maxApi = 0; + + "maxSdkVersion", "maxApi" -> try { + moduleInfo.maxApi = value.toInt() + readMaxApi = true + } catch (e: Exception) { + if (!readMaxApi) moduleInfo.maxApi = 0 } - break; + } } } - } - if (!readId) { - if (readIdSec && local) { - // Using the name for module id is not really appropriate, so beautify it a bit - moduleInfo.name = makeNameFromId(moduleInfo.id); - } else if (!local) { // Allow local modules to not declare ids - throw new IOException("Didn't read module id at least once!"); + if (!readId) { + if (readIdSec && local) { + // Using the name for module id is not really appropriate, so beautify it a bit + moduleInfo.name = makeNameFromId(moduleInfo.id) + } else if (!local) { // Allow local modules to not declare ids + throw IOException("Didn't read module id at least once!") + } } - } - if (!readVersionCode) { - if (local) { - invalid = true; - moduleInfo.versionCode = 0; + if (!readVersionCode) { + if (local) { + invalid = true + moduleInfo.versionCode = 0 + } else { + throw IOException("Didn't read module versionCode at least once!") + } + } + if (!readName || isInvalidValue(moduleInfo.name)) { + moduleInfo.name = makeNameFromId(moduleInfo.id) + } + if (!readVersion) { + moduleInfo.version = "v" + moduleInfo.versionCode } else { - throw new IOException("Didn't read module versionCode at least once!"); + moduleInfo.version = shortenVersionName( + moduleInfo.version, moduleInfo.versionCode + ) + } + if (!readDescription || isInvalidValue(moduleInfo.description)) { + moduleInfo.description = "" + } + if (!readUpdateJson) { + moduleInfo.updateJson = moduleUpdateJsonFallbacks[moduleInfo.id] + } + if (moduleInfo.minApi == 0 || !readMinApi) { + val minApiFallback = moduleMinApiFallbacks[moduleInfo.id] + if (minApiFallback != null) moduleInfo.minApi = + minApiFallback else if (moduleInfo.id.startsWith("riru_") + || moduleInfo.id.startsWith("riru-") + ) moduleInfo.minApi = RIRU_MIN_API + } + if (moduleInfo.support == null) { + moduleInfo.support = moduleSupportsFallbacks[moduleInfo.id] + } + if (moduleInfo.config == null) { + moduleInfo.config = moduleConfigsFallbacks[moduleInfo.id] + } + if (!readMMTReborn) { + moduleInfo.mmtReborn = moduleMTTRebornFallback.contains(moduleInfo.id) || + AppUpdateManager.getFlagsForModule(moduleInfo.id) and + 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) { + moduleInfo.author = "Unknown" + } + if (invalid) { + moduleInfo.flags = moduleInfo.flags or ModuleInfo.FLAG_METADATA_INVALID + // This shouldn't happen but just in case + if (!local) throw IOException("Invalid properties!") } } - if (!readName || isInvalidValue(moduleInfo.name)) { - moduleInfo.name = makeNameFromId(moduleInfo.id); - } - if (!readVersion) { - moduleInfo.version = "v" + moduleInfo.versionCode; - } else { - moduleInfo.version = shortenVersionName( - moduleInfo.version, moduleInfo.versionCode); - } - if (!readDescription || isInvalidValue(moduleInfo.description)) { - moduleInfo.description = ""; - } - if (!readUpdateJson) { - moduleInfo.updateJson = moduleUpdateJsonFallbacks.get(moduleInfo.id); - } - if (moduleInfo.minApi == 0 || !readMinApi) { - Integer minApiFallback = moduleMinApiFallbacks.get(moduleInfo.id); - if (minApiFallback != null) - moduleInfo.minApi = minApiFallback; - else if (moduleInfo.id.startsWith("riru_") - || moduleInfo.id.startsWith("riru-")) - moduleInfo.minApi = RIRU_MIN_API; - } - if (moduleInfo.support == null) { - moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id); - } - 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) { - moduleInfo.author = "Unknown"; - } - if (invalid) { - moduleInfo.flags |= ModuleInfo.FLAG_METADATA_INVALID; - // This shouldn't happen but just in case - if (!local) throw new IOException("Invalid properties!"); - } - } - public static String readModulePropSimple(InputStream inputStream, String what) { - if (inputStream == null) return null; - String moduleId = null; - try (BufferedReader bufferedReader = new BufferedReader( - new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - while (line.startsWith("\u0000")) - line = line.substring(1); - if (line.startsWith(what + "=")) { - moduleId = line.substring(what.length() + 1).trim(); + @JvmStatic + fun readModulePropSimple(inputStream: InputStream?, what: String): String? { + if (inputStream == null) return null + var moduleId: String? = null + try { + BufferedReader( + InputStreamReader(inputStream, StandardCharsets.UTF_8) + ).use { bufferedReader -> + var line: String + while (bufferedReader.readLine().also { line = it } != null) { + while (line.startsWith("\u0000")) line = line.substring(1) + if (line.startsWith("$what=")) { + moduleId = line.substring(what.length + 1).trim { it <= ' ' } + } + } } + } catch (e: IOException) { + Timber.i(e) } - } catch (IOException e) { - Timber.i(e); + return moduleId } - return moduleId; - } - - public static String readModuleId(InputStream inputStream) { - return readModulePropSimple(inputStream, "id"); - } - public static void applyFallbacks(ModuleInfo moduleInfo) { - if (moduleInfo.support == null || moduleInfo.support.isEmpty()) { - moduleInfo.support = moduleSupportsFallbacks.get(moduleInfo.id); - } - if (moduleInfo.config == null || moduleInfo.config.isEmpty()) { - moduleInfo.config = moduleConfigsFallbacks.get(moduleInfo.id); + fun readModuleId(inputStream: InputStream?): String? { + return readModulePropSimple(inputStream, "id") } - if (moduleInfo.minApi == 0) { - Integer minApiFallback = moduleMinApiFallbacks.get(moduleInfo.id); - if (minApiFallback != null) - moduleInfo.minApi = minApiFallback; - else if (moduleInfo.id.startsWith("riru_") - || moduleInfo.id.startsWith("riru-")) - moduleInfo.minApi = RIRU_MIN_API; + + @JvmStatic + fun applyFallbacks(moduleInfo: ModuleInfo) { + if (moduleInfo.support == null || moduleInfo.support!!.isEmpty()) { + moduleInfo.support = moduleSupportsFallbacks[moduleInfo.id] + } + if (moduleInfo.config == null || moduleInfo.config!!.isEmpty()) { + moduleInfo.config = moduleConfigsFallbacks[moduleInfo.id] + } + if (moduleInfo.minApi == 0) { + val minApiFallback = moduleMinApiFallbacks[moduleInfo.id] + if (minApiFallback != null) moduleInfo.minApi = + minApiFallback else if (moduleInfo.id.startsWith("riru_") + || moduleInfo.id.startsWith("riru-") + ) moduleInfo.minApi = RIRU_MIN_API + } } - } - // Some module are really so low quality that it has become very annoying. - public static boolean isLowQualityModule(ModuleInfo moduleInfo) { - final String description; - return moduleInfo == null || moduleInfo.hasFlag(ModuleInfo.FLAG_METADATA_INVALID) - || moduleInfo.name.length() < 3 || moduleInfo.versionCode < 0 - || moduleInfo.author == null || !TextUtils.isGraphic(moduleInfo.author) - || isNullString(description = moduleInfo.description) || !TextUtils.isGraphic(description) - || description.toLowerCase(Locale.ROOT).equals(moduleInfo.name.toLowerCase(Locale.ROOT)) - || (getFlagsForModule(moduleInfo.id) & FLAG_COMPAT_LOW_QUALITY) != 0 - || (moduleInfo.id.startsWith(".")); - } + // Some module are really so low quality that it has become very annoying. + @JvmStatic + fun isLowQualityModule(moduleInfo: ModuleInfo?): Boolean { + var description: String = moduleInfo?.description ?: return true + return (moduleInfo.hasFlag(ModuleInfo.FLAG_METADATA_INVALID) || moduleInfo.name!!.length < 3 || moduleInfo.versionCode < 0 || moduleInfo.author == null || !TextUtils.isGraphic( + moduleInfo.author + ) || isNullString(moduleInfo.description.also { + description = it!! + }) || !TextUtils.isGraphic(description)) || description.lowercase() == moduleInfo.name!!.lowercase() || AppUpdateManager.getFlagsForModule( + moduleInfo.id + ) and AppUpdateManager.FLAG_COMPAT_LOW_QUALITY != 0 || moduleInfo.id.startsWith(".") + } - private static boolean isInvalidValue(String name) { - return !TextUtils.isGraphic(name) || name.indexOf('\0') != -1; - } + private fun isInvalidValue(name: String?): Boolean { + return !TextUtils.isGraphic(name) || name!!.indexOf('\u0000') != -1 + } - public static boolean isInvalidURL(String url) { - int i = url.indexOf('/', 8); - int e = url.indexOf('.', 8); - return i == -1 || e == -1 || e >= i || !url.startsWith("https://") - || url.length() <= 12 || url.indexOf('\0') != -1; - } + @JvmStatic + fun isInvalidURL(url: String): Boolean { + val i = url.indexOf('/', 8) + val e = url.indexOf('.', 8) + return i == -1 || e == -1 || e >= i || !url.startsWith("https://") || url.length <= 12 || url.indexOf( + '\u0000' + ) != -1 + } - public static String makeNameFromId(String moduleId) { - return moduleId.substring(0, 1).toUpperCase(Locale.ROOT) + - moduleId.substring(1).replace('_', ' '); - } + private fun makeNameFromId(moduleId: String): String { + return moduleId.substring(0, 1).uppercase() + + moduleId.substring(1).replace('_', ' ') + } - public static boolean isNullString(String string) { - return string == null || string.isEmpty() || "null".equals(string); - } + @JvmStatic + fun isNullString(string: String?): Boolean { + return string.isNullOrEmpty() || "null" == string + } - // Make versionName no longer than 16 charters to avoid UI overflow. - public static String shortenVersionName(String versionName, long versionCode) { - if (isNullString(versionName)) return "v" + versionCode; - if (versionName.length() <= 16) return versionName; - int i = versionName.lastIndexOf('.'); - if (i != -1 && i <= 16 && versionName.indexOf('.') != i - && versionName.indexOf(' ') == -1) - return versionName.substring(0, i); - return "v" + versionCode; + // Make versionName no longer than 16 charters to avoid UI overflow. + fun shortenVersionName(versionName: String?, versionCode: Long): String { + if (isNullString(versionName)) return "v$versionCode" + if (versionName!!.length <= 16) return versionName + val i = versionName.lastIndexOf('.') + return if (i != -1 && i <= 16 && versionName.indexOf('.') != i && versionName.indexOf( + ' ' + ) == -1 + ) versionName.substring(0, i) else "v$versionCode" + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java b/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java index c7b4785..1ddc697 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java +++ b/app/src/main/java/com/fox2code/mmm/utils/sentry/SentryMain.java @@ -121,7 +121,7 @@ public class SentryMain { String url = (String) breadcrumb.getData("url"); if (url == null || url.isEmpty()) return breadcrumb; if ("cloudflare-dns.com".equals(Uri.parse(url).getHost())) return null; - if (AndroidacyUtil.isAndroidacyLink(url)) { + if (AndroidacyUtil.Companion.isAndroidacyLink(url)) { breadcrumb.setData("url", AndroidacyUtil.hideToken(url)); } return breadcrumb; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b73c741..8a32a5f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -428,4 +428,5 @@ Androidacy Premium offers faster downloads, an ad-free experience, and more! Upgrade The stacktrace may be found below. However, we strongly recommend you to use the feedback form below to submit feedback instead. This way, instead of manually copying the stacktrace, it will send it to us automatically. It also is deobfuscated that way and additional details are reported automatically. + Most modules should be uninstalled, then reinstalled cleanly. For this reason, FoxMMM does not support reinstalling modules while they are installed on the device.