0.2.5 Release

pull/27/head 0.2.5
Fox2Code 5 years ago
parent 754e3890cf
commit 5f6e92174d

@ -12,7 +12,7 @@ Index:
## Properties ## Properties
In addition to the following magisk properties In addition to the following required magisk properties
```properties ```properties
# Magisk supported properties # Magisk supported properties
id=<string> id=<string>
@ -22,8 +22,9 @@ versionCode=<int>
author=<string> author=<string>
description=<string> description=<string>
``` ```
(Note: The Fox's mmm will not show the module if theses values are not filled properly)
This the manager support these new properties This the manager support these new optional properties
```properties ```properties
# Fox's Mmm supported properties # Fox's Mmm supported properties
minApi=<int> minApi=<int>
@ -38,7 +39,7 @@ config=<package>
- `minApi` and `maxApi` tell the manager which is the SDK version range the module support - `minApi` and `maxApi` tell the manager which is the SDK version range the module support
(See: [Codenames, Tags, and Build Numbers](https://source.android.com/setup/start/build-numbers)) (See: [Codenames, Tags, and Build Numbers](https://source.android.com/setup/start/build-numbers))
- `minMagisk` tell the manager which is the minimum Magisk version required for the module - `minMagisk` tell the manager which is the minimum Magisk version required for the module
(Often for magisk `xx.y` the version code is `xxy00`) (Often for magisk `xx.y` the version code is `xxyzz`, `zz` being non `00` on canary builds)
- `support` support link to direct users when they need support for you modules - `support` support link to direct users when they need support for you modules
- `donate` donate link to direct users to where they can financially support your project - `donate` donate link to direct users to where they can financially support your project
- `config` package name of the application that configure your module - `config` package name of the application that configure your module

@ -10,10 +10,14 @@ So I made my own app to do that! :3
Minimum: Minimum:
- Android 5.0+ - Android 5.0+
- Magisk 19.0+ - Magisk 19.0+
- An internet connection
Recommended: Recommended:
- Android 6.0+ - Android 6.0+
- Magisk 21.2+ - Magisk 21.2+
- An internet connection
Note: This app may require the use of a VPN in countries with a state wide firewall.
## For users ## For users

@ -10,8 +10,8 @@ android {
applicationId "com.fox2code.mmm" applicationId "com.fox2code.mmm"
minSdk 21 minSdk 21
targetSdk 31 targetSdk 31
versionCode 14 versionCode 15
versionName "0.2.4" versionName "0.2.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

@ -4,6 +4,9 @@
package="com.fox2code.mmm" package="com.fox2code.mmm"
tools:ignore="QueryAllPackagesPermission"> tools:ignore="QueryAllPackagesPermission">
<!-- Wifi is not the only way to get an internet connection -->
<uses-feature android:name="android.hardware.wifi" android:required="false" />
<!-- Retrieve online modules --> <!-- Retrieve online modules -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!-- Make sure of the module active state by checking enabled modules on boot --> <!-- Make sure of the module active state by checking enabled modules on boot -->

@ -37,13 +37,13 @@ public class AppUpdateManager {
// Return true if should show a notification // Return true if should show a notification
public boolean checkUpdate(boolean force) { public boolean checkUpdate(boolean force) {
if (this.peekShouldUpdate()) if (!force && this.peekShouldUpdate())
return true; return true;
long lastChecked = this.lastChecked; long lastChecked = this.lastChecked;
if (!force && lastChecked != 0 && if (lastChecked != 0 &&
// Avoid spam calls by putting a 10 seconds timer // Avoid spam calls by putting a 10 seconds timer
lastChecked < System.currentTimeMillis() - 10000L) lastChecked < System.currentTimeMillis() - 10000L)
return false; return force && this.peekShouldUpdate();
synchronized (this.updateLock) { synchronized (this.updateLock) {
if (lastChecked != this.lastChecked) if (lastChecked != this.lastChecked)
return this.peekShouldUpdate(); return this.peekShouldUpdate();

@ -21,6 +21,7 @@ import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.ModuleManager; import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.settings.SettingsActivity; import com.fox2code.mmm.settings.SettingsActivity;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper; import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.progressindicator.LinearProgressIndicator;
@ -180,7 +181,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
if (!RepoManager.getINSTANCE().hasConnectivity()) if (!RepoManager.getINSTANCE().hasConnectivity())
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(true)) else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
moduleViewListBuilder.appendRemoteModules(); moduleViewListBuilder.appendRemoteModules();
Log.i(TAG, "Common Before applyTo"); Log.i(TAG, "Common Before applyTo");
@ -201,8 +202,14 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.progressIndicator.setProgressCompat(0, false); this.progressIndicator.setProgressCompat(0, false);
// this.swipeRefreshLayout.setRefreshing(true); ?? // this.swipeRefreshLayout.setRefreshing(true); ??
new Thread(() -> { new Thread(() -> {
Http.cleanDnsCache(); // Allow DNS reload from network
RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> RepoManager.getINSTANCE().update(value -> runOnUiThread(() ->
this.progressIndicator.setProgressCompat((int) (value * PRECISION), true))); this.progressIndicator.setProgressCompat(
(int) (value * PRECISION), true)));
if (!RepoManager.getINSTANCE().hasConnectivity())
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
runOnUiThread(() -> { runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE); this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false); this.swipeRefreshLayout.setRefreshing(false);

@ -261,6 +261,9 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
int backgroundAttr = R.attr.colorBackgroundFloating; int backgroundAttr = R.attr.colorBackgroundFloating;
if (type == ModuleHolder.Type.NOTIFICATION) { if (type == ModuleHolder.Type.NOTIFICATION) {
backgroundAttr = moduleHolder.notificationType.backgroundAttr; backgroundAttr = moduleHolder.notificationType.backgroundAttr;
} else if (type == ModuleHolder.Type.INSTALLED &&
moduleHolder.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) {
backgroundAttr = R.attr.colorError;
} }
Resources.Theme theme = this.cardView.getContext().getTheme(); Resources.Theme theme = this.cardView.getContext().getTheme();
TypedValue value = new TypedValue(); TypedValue value = new TypedValue();

@ -112,8 +112,8 @@ public class ModuleViewListBuilder {
} }
} }
newNotificationsLen = this.notifications.size() - special; newNotificationsLen = this.notifications.size() - special;
EnumSet<ModuleHolder.Type> headerTypes = EnumSet.of( EnumSet<ModuleHolder.Type> headerTypes = EnumSet.of(ModuleHolder.Type.SEPARATOR,
ModuleHolder.Type.NOTIFICATION, ModuleHolder.Type.SEPARATOR); ModuleHolder.Type.NOTIFICATION, ModuleHolder.Type.FOOTER);
Iterator<ModuleHolder> moduleHolderIterator = this.mappedModuleHolders.values().iterator(); Iterator<ModuleHolder> moduleHolderIterator = this.mappedModuleHolders.values().iterator();
synchronized (this.queryLock) { synchronized (this.queryLock) {
while (moduleHolderIterator.hasNext()) { while (moduleHolderIterator.hasNext()) {

@ -21,10 +21,9 @@ public final class ModuleManager {
ModuleInfo.FLAG_MODULE_UNINSTALLING | ModuleInfo.FLAG_MODULE_ACTIVE; ModuleInfo.FLAG_MODULE_UNINSTALLING | ModuleInfo.FLAG_MODULE_ACTIVE;
private static final int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED; private static final int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED;
private final HashMap<String, ModuleInfo> moduleInfos; private final HashMap<String, ModuleInfo> moduleInfos;
private final HashMap<String, ModuleInfo> invalidModules;
private final SharedPreferences bootPrefs; private final SharedPreferences bootPrefs;
private final Object scanLock = new Object(); private final Object scanLock = new Object();
private boolean scanning, lastScanResult; private boolean scanning;
private static final ModuleManager INSTANCE = new ModuleManager(); private static final ModuleManager INSTANCE = new ModuleManager();
@ -34,19 +33,17 @@ public final class ModuleManager {
private ModuleManager() { private ModuleManager() {
this.moduleInfos = new HashMap<>(); this.moduleInfos = new HashMap<>();
this.invalidModules = new HashMap<>();
this.bootPrefs = MainApplication.getBootSharedPreferences(); this.bootPrefs = MainApplication.getBootSharedPreferences();
} }
// MultiThread friendly method // MultiThread friendly method
public final boolean scan() { public final void scan() {
if (!this.scanning) { if (!this.scanning) {
// Do scan // Do scan
synchronized (scanLock) { synchronized (scanLock) {
this.scanning = true; this.scanning = true;
try { try {
this.lastScanResult = this.scanInternal();
this.scanInternal();
} finally { } finally {
this.scanning = false; this.scanning = false;
} }
@ -55,7 +52,6 @@ public final class ModuleManager {
// Wait for current scan // Wait for current scan
synchronized (scanLock) {} synchronized (scanLock) {}
} }
return this.lastScanResult;
} }
// Pause execution until the scan is completed if one is currently running // Pause execution until the scan is completed if one is currently running
@ -69,13 +65,9 @@ public final class ModuleManager {
} }
} }
private boolean scanInternal() { private void scanInternal() {
boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true);
boolean changed = false;
SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null;
// Reset existing ModuleInfo
this.moduleInfos.putAll(this.invalidModules);
this.invalidModules.clear();
for (ModuleInfo v : this.moduleInfos.values()) { for (ModuleInfo v : this.moduleInfos.values()) {
v.flags |= FLAG_MM_UNPROCESSED; v.flags |= FLAG_MM_UNPROCESSED;
v.flags &= ~FLAGS_RESET_INIT; v.flags &= ~FLAGS_RESET_INIT;
@ -94,7 +86,6 @@ public final class ModuleManager {
if (moduleInfo == null) { if (moduleInfo == null) {
moduleInfo = new ModuleInfo(module); moduleInfo = new ModuleInfo(module);
moduleInfos.put(module, moduleInfo); moduleInfos.put(module, moduleInfo);
changed = true;
// Shis should not really happen, but let's handles theses cases anyway // Shis should not really happen, but let's handles theses cases anyway
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING_ONLY; moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING_ONLY;
} }
@ -118,7 +109,7 @@ public final class ModuleManager {
} }
try { try {
PropUtils.readProperties(moduleInfo, PropUtils.readProperties(moduleInfo,
"/data/adb/modules/" + module + "/module.prop"); "/data/adb/modules/" + module + "/module.prop", true);
} catch (Exception e) { } catch (Exception e) {
Log.d(TAG, "Failed to parse metadata!", e); Log.d(TAG, "Failed to parse metadata!", e);
moduleInfo.flags |= FLAG_MM_INVALID; moduleInfo.flags |= FLAG_MM_INVALID;
@ -132,13 +123,12 @@ public final class ModuleManager {
if (moduleInfo == null) { if (moduleInfo == null) {
moduleInfo = new ModuleInfo(module); moduleInfo = new ModuleInfo(module);
moduleInfos.put(module, moduleInfo); moduleInfos.put(module, moduleInfo);
changed = true;
} }
moduleInfo.flags &= ~FLAGS_RESET_UPDATE; moduleInfo.flags &= ~FLAGS_RESET_UPDATE;
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING; moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING;
try { try {
PropUtils.readProperties(moduleInfo, PropUtils.readProperties(moduleInfo,
"/data/adb/modules_update/" + module + "/module.prop"); "/data/adb/modules_update/" + module + "/module.prop", true);
} catch (Exception e) { } catch (Exception e) {
Log.d(TAG, "Failed to parse metadata!", e); Log.d(TAG, "Failed to parse metadata!", e);
moduleInfo.flags |= FLAG_MM_INVALID; moduleInfo.flags |= FLAG_MM_INVALID;
@ -152,10 +142,6 @@ public final class ModuleManager {
if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) { if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) {
moduleInfoIterator.remove(); moduleInfoIterator.remove();
continue; // Don't process fallbacks if unreferenced continue; // Don't process fallbacks if unreferenced
} else if ((moduleInfo.flags & FLAG_MM_INVALID) != 0) {
moduleInfo.flags &=~ FLAG_MM_INVALID;
this.invalidModules.put(moduleInfo.id, moduleInfo);
moduleInfoIterator.remove();
} }
if (moduleInfo.name == null || (moduleInfo.name.equals(moduleInfo.id))) { if (moduleInfo.name == null || (moduleInfo.name.equals(moduleInfo.id))) {
moduleInfo.name = Character.toUpperCase(moduleInfo.id.charAt(0)) + moduleInfo.name = Character.toUpperCase(moduleInfo.id.charAt(0)) +
@ -169,7 +155,6 @@ public final class ModuleManager {
editor.putBoolean("mm_first_scan", false); editor.putBoolean("mm_first_scan", false);
editor.apply(); editor.apply();
} }
return changed;
} }
public HashMap<String, ModuleInfo> getModules() { public HashMap<String, ModuleInfo> getModules() {
@ -177,11 +162,6 @@ public final class ModuleManager {
return this.moduleInfos; return this.moduleInfos;
} }
public HashMap<String, ModuleInfo> getInvalidModules() {
this.afterScan();
return invalidModules;
}
public boolean setEnabledState(ModuleInfo moduleInfo, boolean checked) { public boolean setEnabledState(ModuleInfo moduleInfo, boolean checked) {
if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) && !checked) return false; if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) && !checked) return false;
SuFile disable = new SuFile("/data/adb/modules/" + moduleInfo.id + "/disable"); SuFile disable = new SuFile("/data/adb/modules/" + moduleInfo.id + "/disable");

@ -112,7 +112,7 @@ public class RepoData {
if (file.exists()) { if (file.exists()) {
try { try {
ModuleInfo moduleInfo = repoModule.moduleInfo; ModuleInfo moduleInfo = repoModule.moduleInfo;
PropUtils.readProperties(moduleInfo, file.getAbsolutePath()); PropUtils.readProperties(moduleInfo, file.getAbsolutePath(), false);
moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID; moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID;
if (moduleInfo.version == null) { if (moduleInfo.version == null) {
moduleInfo.version = "v" + moduleInfo.versionCode; moduleInfo.version = "v" + moduleInfo.versionCode;

@ -41,6 +41,7 @@ public class Http {
private static final String TAG = "Http"; private static final String TAG = "Http";
private static final OkHttpClient httpClient; private static final OkHttpClient httpClient;
private static final OkHttpClient httpClientWithCache; private static final OkHttpClient httpClientWithCache;
private static final FallBackDNS fallbackDNS;
static { static {
OkHttpClient.Builder httpclientBuilder = new OkHttpClient.Builder(); OkHttpClient.Builder httpclientBuilder = new OkHttpClient.Builder();
@ -80,10 +81,11 @@ public class Http {
httpclientBuilder.cookieJar(CookieJar.NO_COOKIES); httpclientBuilder.cookieJar(CookieJar.NO_COOKIES);
MainApplication mainApplication = MainApplication.getINSTANCE(); MainApplication mainApplication = MainApplication.getINSTANCE();
if (mainApplication != null) { if (mainApplication != null) {
httpclientBuilder.dns(new FallBackDNS(mainApplication, dns, "github.com", httpclientBuilder.dns(fallbackDNS = new FallBackDNS(mainApplication, dns,
"api.github.com", "raw.githubusercontent.com", "camo.githubusercontent.com", "github.com", "api.github.com", "raw.githubusercontent.com",
"user-images.githubusercontent.com", "cdn.jsdelivr.net", "img.shields.io", "camo.githubusercontent.com", "user-images.githubusercontent.com",
"magisk-modules-repo.github.io", "www.androidacy.com")); "cdn.jsdelivr.net", "img.shields.io", "magisk-modules-repo.github.io",
"www.androidacy.com"));
httpClient = httpclientBuilder.build(); httpClient = httpclientBuilder.build();
httpclientBuilder.cache(new Cache( httpclientBuilder.cache(new Cache(
new File(mainApplication.getCacheDir(), "http_cache"), new File(mainApplication.getCacheDir(), "http_cache"),
@ -92,6 +94,7 @@ public class Http {
httpClientWithCache = httpclientBuilder.build(); httpClientWithCache = httpclientBuilder.build();
Log.i(TAG, "Initialized Http successfully!"); Log.i(TAG, "Initialized Http successfully!");
} else { } else {
fallbackDNS = null;
httpclientBuilder.dns(dns); httpclientBuilder.dns(dns);
httpClientWithCache = httpClient = httpclientBuilder.build(); httpClientWithCache = httpClient = httpclientBuilder.build();
Log.e(TAG, "Initialized Http too soon!"); Log.e(TAG, "Initialized Http too soon!");
@ -166,6 +169,12 @@ public class Http {
return byteArrayOutputStream.toByteArray(); return byteArrayOutputStream.toByteArray();
} }
public static void cleanDnsCache() {
if (Http.fallbackDNS != null) {
Http.fallbackDNS.cleanDnsCache();
}
}
/** /**
* Cookie jar that allow CDN cookies, reset on app relaunch * Cookie jar that allow CDN cookies, reset on app relaunch
* Note: An argument can be made that it allow tracking but * Note: An argument can be made that it allow tracking but
@ -244,36 +253,44 @@ public class Http {
@Override @Override
public List<InetAddress> lookup(@NonNull String s) throws UnknownHostException { public List<InetAddress> lookup(@NonNull String s) throws UnknownHostException {
if (this.fallbacks.contains(s)) { if (this.fallbacks.contains(s)) {
List<InetAddress> addresses = this.fallbackCache.get(s); List<InetAddress> addresses;
if (addresses != null) synchronized (this.fallbackCache) {
return addresses; addresses = this.fallbackCache.get(s);
try { if (addresses != null)
addresses = this.parent.lookup(s);
if (addresses.isEmpty() || addresses.get(0).isLoopbackAddress())
throw new UnknownHostException(s);
this.fallbackCache.put(s, addresses);
this.sharedPreferences.edit().putString(
s.replace('.', '_'), toString(addresses)).apply();
} catch (UnknownHostException e) {
String key = this.sharedPreferences.getString(
s.replace('.', '_'), "");
if (!key.isEmpty()) try {
addresses = fromString(key);
this.fallbackCache.put(s, addresses);
return addresses; return addresses;
} catch (UnknownHostException e2) { try {
this.sharedPreferences.edit().remove( addresses = this.parent.lookup(s);
s.replace('.', '_')).apply(); if (addresses.isEmpty() || addresses.get(0).isLoopbackAddress())
throw new UnknownHostException(s);
this.fallbackCache.put(s, addresses);
this.sharedPreferences.edit().putString(
s.replace('.', '_'), toString(addresses)).apply();
} catch (UnknownHostException e) {
String key = this.sharedPreferences.getString(
s.replace('.', '_'), "");
if (key.isEmpty()) throw e;
try {
addresses = fromString(key);
this.fallbackCache.put(s, addresses);
} catch (UnknownHostException e2) {
this.sharedPreferences.edit().remove(
s.replace('.', '_')).apply();
throw e;
}
} }
throw e;
} }
return addresses; return addresses;
} else { } else {
return this.parent.lookup(s); return this.parent.lookup(s);
} }
} }
void cleanDnsCache() {
synchronized (this.fallbackCache) {
this.fallbackCache.clear();
}
}
@NonNull @NonNull
private static String toString(@NonNull List<InetAddress> inetAddresses) { private static String toString(@NonNull List<InetAddress> inetAddresses) {
if (inetAddresses.isEmpty()) return ""; if (inetAddresses.isEmpty()) return "";

@ -38,7 +38,7 @@ public class IntentHelper {
context.startActivity(myIntent); context.startActivity(myIntent);
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
Toast.makeText(context, "No application can handle this request." Toast.makeText(context, "No application can handle this request."
+ " Please install a webbrowser", Toast.LENGTH_SHORT).show(); + " Please install a web-browser", Toast.LENGTH_SHORT).show();
e.printStackTrace(); e.printStackTrace();
} }
} }

@ -50,39 +50,83 @@ public class PropUtils {
moduleMinApiFallbacks.put("riru-core", RIRU_MIN_API = Build.VERSION_CODES.M); moduleMinApiFallbacks.put("riru-core", RIRU_MIN_API = Build.VERSION_CODES.M);
} }
public static void readProperties(ModuleInfo moduleInfo, String file) throws IOException { public static void readProperties(ModuleInfo moduleInfo, String file,boolean local) throws IOException {
boolean readId = false, readIdSec = false, readVersionCode = false; boolean readId = false, readIdSec = false, readName = false,
readVersionCode = false, readVersion = false, invalid = false;
try (BufferedReader bufferedReader = new BufferedReader( try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(SuFileInputStream.open(file), StandardCharsets.UTF_8))) { new InputStreamReader(SuFileInputStream.open(file), StandardCharsets.UTF_8))) {
String line; String line;
int lineNum = 0;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
lineNum++;
int index = line.indexOf('='); int index = line.indexOf('=');
if (index == -1 || line.startsWith("#")) if (index == -1 || line.startsWith("#"))
continue; continue;
String key = line.substring(0, index); String key = line.substring(0, index);
String value = line.substring(index + 1).trim(); String value = line.substring(index + 1).trim();
// 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 (isInvalidValue(value) && !key.equals("id") && !key.equals("name")) {
if (local) {
invalid = true;
continue;
} else throw new IOException("Invalid value for key " + key);
}
switch (key) { switch (key) {
case "id": case "id":
if (isInvalidValue(value)) {
if (local) {
invalid = true;
break;
} throw new IOException("Invalid module id!");
}
readId = true; readId = true;
if (!moduleInfo.id.equals(value)) { if (!moduleInfo.id.equals(value)) {
throw new IOException(file + " has an non matching module id! "+ if (local) {
"(Expected \"" + moduleInfo.id + "\" got \"" + value + "\""); invalid = true;
} else {
throw new IOException(file + " has an non matching module id! " +
"(Expected \"" + moduleInfo.id + "\" got \"" + value + "\"");
}
} }
break; break;
case "name": case "name":
if (readIdSec && !moduleInfo.id.equals(value)) if (readName) {
throw new IOException("Duplicate module name!"); if (local) {
invalid = true;
break;
} else throw new IOException("Duplicate module name!");
}
if (isInvalidValue(value)) {
if (local) {
invalid = true;
break;
} throw new IOException("Invalid module name!");
}
readName = true;
moduleInfo.name = value; moduleInfo.name = value;
if (moduleInfo.id.equals(value)) { if (moduleInfo.id.equals(value)) {
readIdSec = true; readIdSec = true;
} }
break; break;
case "version": case "version":
readVersion = true;
moduleInfo.version = value; moduleInfo.version = value;
break; break;
case "versionCode": case "versionCode":
readVersionCode = true; readVersionCode = true;
moduleInfo.versionCode = Long.parseLong(value); try {
moduleInfo.versionCode = Long.parseLong(value);
} catch (RuntimeException e) {
if (local) {
invalid = true;
moduleInfo.versionCode = 0;
} else throw e;
}
break; break;
case "author": case "author":
moduleInfo.author = value; moduleInfo.author = value;
@ -92,14 +136,14 @@ public class PropUtils {
break; break;
case "support": case "support":
// Do not accept invalid or too broad support links // Do not accept invalid or too broad support links
if (!value.startsWith("https://") || if (isInvalidURL(value) ||
"https://forum.xda-developers.com/".equals(value)) "https://forum.xda-developers.com/".equals(value))
break; break;
moduleInfo.support = value; moduleInfo.support = value;
break; break;
case "donate": case "donate":
// Do not accept invalid donate links // Do not accept invalid donate links
if (!value.startsWith("https://")) break; if (isInvalidURL(value)) break;
moduleInfo.donate = value; moduleInfo.donate = value;
break; break;
case "config": case "config":
@ -136,21 +180,27 @@ public class PropUtils {
} }
} }
if (!readId) { if (!readId) {
if (readIdSec) { if (readIdSec && local) {
// Using the name for module id is not really appropriate, so beautify it a bit // Using the name for module id is not really appropriate, so beautify it a bit
moduleInfo.name = moduleInfo.id.substring(0, 1).toUpperCase(Locale.ROOT) + moduleInfo.name = makeNameFromId(moduleInfo.id);
moduleInfo.id.substring(1).replace('_', ' '); } else if (local) { // Allow local module to not declare ids
invalid = true;
} else { } else {
throw new IOException("Didn't read module id at least once!"); throw new IOException("Didn't read module id at least once!");
} }
} }
if (!readVersionCode) { if (!readVersionCode) {
throw new IOException("Didn't read module versionCode at least once!"); if (local) {
invalid = true;
moduleInfo.versionCode = 0;
} else {
throw new IOException("Didn't read module versionCode at least once!");
}
} }
if (moduleInfo.name == null) { if (moduleInfo.name == null || !readName) {
moduleInfo.name = moduleInfo.id; moduleInfo.name = makeNameFromId(moduleInfo.id);
} }
if (moduleInfo.version == null) { if (moduleInfo.version == null || !readVersion) {
moduleInfo.version = "v" + moduleInfo.versionCode; moduleInfo.version = "v" + moduleInfo.versionCode;
} }
if (moduleInfo.minApi == 0) { if (moduleInfo.minApi == 0) {
@ -167,6 +217,16 @@ public class PropUtils {
if (moduleInfo.config == null) { if (moduleInfo.config == null) {
moduleInfo.config = moduleConfigsFallbacks.get(moduleInfo.id); moduleInfo.config = moduleConfigsFallbacks.get(moduleInfo.id);
} }
// 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!");
}
} }
// Some module are really so low quality that it has become very annoying. // Some module are really so low quality that it has become very annoying.
@ -179,4 +239,20 @@ public class PropUtils {
|| description.toLowerCase(Locale.ROOT).equals(moduleInfo.name.toLowerCase(Locale.ROOT)) || description.toLowerCase(Locale.ROOT).equals(moduleInfo.name.toLowerCase(Locale.ROOT))
|| description.length() < Math.min(Math.max(moduleInfo.name.length() + 4, 16), 24); || description.length() < Math.min(Math.max(moduleInfo.name.length() + 4, 16), 24);
} }
private static boolean isInvalidValue(String name) {
return !TextUtils.isGraphic(name) || name.indexOf('\0') != -1;
}
private 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;
}
private static String makeNameFromId(String moduleId) {
return moduleId.substring(0, 1).toUpperCase(Locale.ROOT) +
moduleId.substring(1).replace('_', ' ');
}
} }

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string-array name="theme_values">
<item>system</item>
<item>dark</item>
<item>light</item>
</string-array>
<string-array name="theme_values_names"> <string-array name="theme_values_names">
<item>Systemvorgabe</item> <item>Systemvorgabe</item>
<item>Dunkel</item> <item>Dunkel</item>

Loading…
Cancel
Save