Introduce new `modules.json` format extensions.

pull/178/head
Fox2Code 2 years ago
parent 06a3e1fc6b
commit 1488f13e95

@ -27,6 +27,33 @@ it is a check that verify that the module is declaring the minimum required to
allow the app to show your module to the user without hurting his experience.
Filling all basic Magisk properties is often enough to not get filtered out by it.
## Custom Repo format
Note: This feature is for `0.6.0` version that is not released yet.
`last_update` fields uses unix millis.
Json format is
```json
{
"name": "Repo name",
"website": "repo website",
"support": "optional support url",
"donate": "optional support url",
"submitModule": "optional submit module URL",
"last_update": 0,
"modules": [
{
"id": "module id",
"last_update": 0,
"notes_url": "notes url",
"prop_url": "module.prop url",
"zip_url": "module.zip url"
}
]
}
```
## Properties
In addition to the following required magisk properties

@ -36,8 +36,8 @@ android {
buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true"
buildConfigField(
"java.util.List<String>",
"DISABLED_REPOS",
"java.util.Arrays.asList()",
"ENABLED_REPOS",
"java.util.Arrays.asList(\"magisk_alt_repo\", \"androidacy_repo\")",
)
}
@ -54,8 +54,8 @@ android {
// F-Droid flavor.
buildConfigField(
"java.util.List<String>",
"DISABLED_REPOS",
"java.util.Arrays.asList(\"androidacy_repo\")",
"ENABLED_REPOS",
"java.util.Arrays.asList(\"magisk_alt_repo\")",
)
}
}

@ -8,11 +8,14 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -46,6 +49,7 @@ public class AndroidacyActivity extends FoxActivity {
}
WebView webView;
TextView webViewNote;
AndroidacyWebAPI androidacyWebAPI;
boolean backOnResume;
@ -108,6 +112,7 @@ public class AndroidacyActivity extends FoxActivity {
}
}
this.webView = this.findViewById(R.id.webView);
this.webViewNote = this.findViewById(R.id.webViewNote);
WebSettings webSettings = this.webView.getSettings();
webSettings.setUserAgentString(Http.getAndroidacyUA());
webSettings.setDomStorageEnabled(true);
@ -140,12 +145,20 @@ public class AndroidacyActivity extends FoxActivity {
this.pageUrl = url;
}
private void onReceivedError(String url,int errorCode) {
@Override
public void onPageFinished(WebView view, String url) {
webViewNote.setVisibility(View.GONE);
}
private void onReceivedError(String url, int errorCode) {
if ((url.startsWith("https://api.androidacy.com/magisk/") ||
url.equals(pageUrl)) && (errorCode == 419 || errorCode == 429 || errorCode == 503)) {
Toast.makeText(AndroidacyActivity.this,
"Too many requests!", Toast.LENGTH_LONG).show();
AndroidacyActivity.this.runOnUiThread(AndroidacyActivity.this::onBackPressed);
} else if (url.equals(this.pageUrl)) {
postOnUiThread(() ->
webViewNote.setVisibility(View.VISIBLE));
}
}
@ -172,6 +185,28 @@ public class AndroidacyActivity extends FoxActivity {
FileChooserParams.parseResult(code, data)));
return true;
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
switch (consoleMessage.messageLevel()) {
case TIP:
Log.v(TAG, consoleMessage.message());
break;
case LOG:
Log.i(TAG, consoleMessage.message());
break;
case WARNING:
Log.w(TAG, consoleMessage.message());
break;
case ERROR:
Log.e(TAG, consoleMessage.message());
break;
case DEBUG:
Log.d(TAG, consoleMessage.message());
break;
}
return super.onConsoleMessage(consoleMessage);
}
});
this.webView.setDownloadListener((
downloadUrl, userAgent, contentDisposition, mimetype, contentLength) -> {

@ -7,6 +7,7 @@ import android.webkit.CookieManager;
import com.fox2code.mmm.R;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.PropUtils;
@ -49,6 +50,11 @@ public class AndroidacyRepoData extends RepoData {
this.androidacyBlockade = 0; // Don't allow time travel. Well why not???
}
}
this.defaultName = "Androidacy Modules Repo";
this.defaultWebsite = RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE;
this.defaultSupport = "https://t.me/androidacy_discussions";
this.defaultDonate = "https://patreon.com/androidacy";
this.defaultSubmitModule = "https://www.androidacy.com/module-repository-applications/";
}
private static String getCookies() {
@ -231,6 +237,10 @@ public class AndroidacyRepoData extends RepoData {
}
this.lastUpdate = lastLastUpdate;
this.name = name;
this.website = jsonObject.optString("website");
this.support = jsonObject.optString("support");
this.donate = jsonObject.optString("donate");
this.submitModule = jsonObject.optString("submitModule");
return newModules;
}

@ -6,15 +6,14 @@ import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Keep;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.BuildConfig;
@ -42,6 +41,7 @@ public class AndroidacyWebAPI {
private static final int MAX_COMPAT_MODE = 1;
private final AndroidacyActivity activity;
private final boolean allowInstall;
private boolean allowHideNote = true;
boolean consumedAction;
boolean downloadMode;
int effectiveCompatMode;
@ -322,6 +322,11 @@ public class AndroidacyWebAPI {
this.activity.runOnUiThread(() -> {
this.activity.hideActionBar();
this.consumedAction = false;
if (this.allowHideNote) {
this.allowHideNote = false;
this.activity.webViewNote
.setVisibility(View.GONE);
}
});
}

@ -218,13 +218,8 @@ public enum ActionButtonType {
@Override
public void update(Chip button, ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
int icon = R.drawable.ic_baseline_monetization_on_24;
if (moduleInfo.donate.startsWith("https://www.paypal.me/")) {
icon = R.drawable.ic_baseline_paypal_24;
} else if (moduleInfo.donate.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
button.setChipIcon(button.getContext().getDrawable(icon));
button.setChipIcon(button.getContext().getDrawable(
donateIconForUrl(moduleInfo.donate)));
button.setText(R.string.donate);
}
@ -252,6 +247,18 @@ public enum ActionButtonType {
return icon;
}
@DrawableRes
public static int donateIconForUrl(String url) {
int icon = R.drawable.ic_baseline_monetization_on_24;
if (url.startsWith("https://www.paypal.me/") ||
url.startsWith("https://www.paypal.com/paypalme/")) {
icon = R.drawable.ic_baseline_paypal_24;
} else if (url.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
return icon;
}
@DrawableRes
private final int iconId;

@ -0,0 +1,21 @@
package com.fox2code.mmm.repo;
import android.content.SharedPreferences;
import java.io.File;
public class LimitedRepoData extends RepoData {
LimitedRepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
super(url, cacheRoot, cachedPreferences);
}
@Override
public final boolean isEnabledByDefault() {
return false;
}
@Override
public final boolean isLimited() {
return true;
}
}

@ -1,6 +1,9 @@
package com.fox2code.mmm.repo;
import android.content.SharedPreferences;
import android.net.Uri;
import androidx.annotation.NonNull;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
@ -32,7 +35,9 @@ public class RepoData extends XRepo {
public final File metaDataCache;
public final HashMap<String, RepoModule> moduleHashMap;
public long lastUpdate;
public String name;
protected String defaultName, defaultWebsite,
defaultSupport, defaultDonate, defaultSubmitModule;
public String name, website, support, donate, submitModule;
private boolean enabled; // Cache for speed
protected RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
@ -45,6 +50,8 @@ public class RepoData extends XRepo {
this.name = this.url; // Set url as default name
this.enabled = MainApplication.getSharedPreferences()
.getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault());
this.defaultName = url;
this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/";
if (!this.cacheRoot.isDirectory()) {
this.cacheRoot.mkdirs();
} else {
@ -114,7 +121,7 @@ public class RepoData extends XRepo {
repoModule.propUrl = modulePropsUrl;
repoModule.zipUrl = moduleZipUrl;
repoModule.checksum = moduleChecksum;
if (!moduleStars.isEmpty()) {
if (!moduleStars.isEmpty() && !this.isLimited()) {
try {
repoModule.qualityValue = Integer.parseInt(moduleStars);
repoModule.qualityText = R.string.module_stars;
@ -133,13 +140,17 @@ public class RepoData extends XRepo {
// Update final metadata
this.name = name;
this.lastUpdate = lastUpdate;
this.website = jsonObject.optString("website");
this.support = jsonObject.optString("support");
this.donate = jsonObject.optString("donate");
this.submitModule = jsonObject.optString("submitModule");
}
return newModules;
}
@Override
public boolean isEnabledByDefault() {
return !BuildConfig.DISABLED_REPOS.contains(this.id);
return BuildConfig.ENABLED_REPOS.contains(this.id);
}
public void storeMetadata(RepoModule repoModule,byte[] data) throws IOException {
@ -166,12 +177,6 @@ public class RepoData extends XRepo {
return false;
}
public String getNameOrFallback(String fallback) {
return this.name == null ||
this.name.equals(this.url) ?
fallback : this.name;
}
@Override
public boolean isEnabled() {
return this.enabled;
@ -181,15 +186,65 @@ public class RepoData extends XRepo {
public void setEnabled(boolean enabled) {
this.enabled = enabled;
MainApplication.getSharedPreferences().edit()
.putBoolean("pref_" + this.id + "_enabled", enabled).apply();
.putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply();
}
public void updateEnabledState() {
this.enabled = MainApplication.getSharedPreferences()
.getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault());
.getBoolean("pref_" + this.getPreferenceId() + "_enabled", this.isEnabledByDefault());
}
public String getUrl() {
return this.url;
}
public boolean isLimited() {
return false;
}
public String getPreferenceId() {
return this.id;
}
// Repo data info getters
@NonNull
public String getName() {
if (this.name != null &&
!this.name.isEmpty())
return this.name;
if (this.defaultName != null)
return this.defaultName;
return this.url;
}
@NonNull
public String getWebsite() {
if (this.website != null &&
!this.website.isEmpty())
return this.website;
if (this.defaultWebsite != null)
return this.defaultWebsite;
return this.url;
}
public String getSupport() {
if (this.support != null &&
!this.support.isEmpty())
return this.support;
return this.defaultSupport;
}
public String getDonate() {
if (this.donate != null &&
!this.donate.isEmpty())
return this.donate;
return this.defaultDonate;
}
public String getSubmitModule() {
if (this.submitModule != null &&
!this.submitModule.isEmpty())
return this.submitModule;
return this.defaultSubmitModule;
}
}

@ -68,7 +68,14 @@ public final class RepoManager {
this.repoData = new LinkedHashMap<>();
this.modules = new HashMap<>();
// We do not have repo list config yet.
this.addRepoData(MAGISK_ALT_REPO);
RepoData altRepo = this.addRepoData(MAGISK_ALT_REPO);
altRepo.defaultName = "Magisk Modules Alt Repo";
altRepo.defaultWebsite = RepoManager.MAGISK_ALT_REPO_HOMEPAGE;
altRepo.defaultSubmitModule =
"https://github.com/Magisk-Modules-Alt-Repo/submission/issues";
/*RepoData dgRepo = this.addRepoData(DG_MAGISK_REPO);
dgRepo.defaultName = "DerGoogler Magisk Repo";
dgRepo.defaultWebsite = "https://repo.dergoogler.com/";*/
this.androidacyRepoData =
this.addAndroidacyRepoData();
// Populate default cache
@ -258,11 +265,15 @@ public final class RepoManager {
}
private RepoData addRepoData(String url) {
if (MAGISK_ALT_REPO_JSDELIVR.equals(url))
url = MAGISK_ALT_REPO;
String id = internalIdOfUrl(url);
File cacheRoot = new File(this.mainApplication.getCacheDir(), id);
SharedPreferences sharedPreferences = this.mainApplication
.getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE);
RepoData repoData = new RepoData(url, cacheRoot, sharedPreferences);
RepoData repoData = id.startsWith("repo_") ?
new LimitedRepoData(url, cacheRoot, sharedPreferences) :
new RepoData(url, cacheRoot, sharedPreferences);
this.repoData.put(url, repoData);
if (this.initialized) {
this.populateDefaultCache(repoData);

@ -8,6 +8,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.StringRes;
@ -26,6 +27,7 @@ import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.module.ActionButtonType;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.Http;
@ -239,29 +241,17 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
getPreferenceManager().setSharedPreferencesName("mmm");
setPreferencesFromResource(R.xml.repo_preferences, rootKey);
setRepoData(RepoManager.MAGISK_ALT_REPO,
"Magisk Modules Alt Repo", RepoManager.MAGISK_ALT_REPO_HOMEPAGE,
null, null,
"https://github.com/Magisk-Modules-Alt-Repo/submission/issues");
setRepoData(RepoManager.MAGISK_ALT_REPO);
// Androidacy backend not yet implemented!
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT,
"Androidacy Modules Repo",
RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE,
"https://t.me/androidacy_discussions",
"https://patreon.com/androidacy",
"https://www.androidacy.com/module-repository-applications/");
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
}
private void setRepoData(String url,
String fallbackTitle, String homepage,
String supportUrl, String donateUrl,
String submissionUrl) {
private void setRepoData(String url) {
String preferenceName = "pref_" + RepoManager.internalIdOfUrl(url);
Preference preference = findPreference(preferenceName);
if (preference == null) return;
final RepoData repoData = RepoManager.getINSTANCE().get(url);
preference.setTitle(repoData == null ? fallbackTitle :
repoData.getNameOrFallback(fallbackTitle));
preference.setTitle(repoData == null ? url : repoData.getName());
preference = findPreference(preferenceName + "_enabled");
if (preference != null) {
if (repoData == null) {
@ -279,42 +269,68 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
}
}
preference = findPreference(preferenceName + "_website");
if (preference != null && homepage != null) {
preference.setOnPreferenceClickListener(p -> {
if (homepage.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getFoxActivity(this), homepage, true);
} else {
IntentHelper.openUrl(getFoxActivity(this), homepage);
}
return true;
});
String homepage = repoData == null ? null : repoData.getWebsite();
if (preference != null) {
if (homepage != null && !homepage.isEmpty()) {
preference.setVisible(true);
preference.setOnPreferenceClickListener(p -> {
if (homepage.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getFoxActivity(this), homepage, true);
} else {
IntentHelper.openUrl(getFoxActivity(this), homepage);
}
return true;
});
} else {
preference.setVisible(false);
}
}
preference = findPreference(preferenceName + "_support");
if (preference != null && supportUrl != null) {
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getFoxActivity(this), supportUrl);
return true;
});
String supportUrl = repoData == null ? null : repoData.getSupport();
if (preference != null) {
if (supportUrl != null && !supportUrl.isEmpty()) {
preference.setVisible(true);
preference.setIcon(ActionButtonType.supportIconForUrl(supportUrl));
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getFoxActivity(this), supportUrl);
return true;
});
} else {
preference.setVisible(false);
}
}
preference = findPreference(preferenceName + "_donate");
if (preference != null && donateUrl != null) {
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getFoxActivity(this), donateUrl);
return true;
});
String donateUrl = repoData == null ? null : repoData.getDonate();
if (preference != null) {
if (donateUrl != null) {
preference.setVisible(true);
preference.setIcon(ActionButtonType.donateIconForUrl(donateUrl));
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getFoxActivity(this), donateUrl);
return true;
});
} else {
preference.setVisible(false);
}
}
preference = findPreference(preferenceName + "_submit");
if (preference != null && submissionUrl != null) {
preference.setOnPreferenceClickListener(p -> {
if (submissionUrl.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getFoxActivity(this), submissionUrl, true);
} else {
IntentHelper.openUrl(getFoxActivity(this), submissionUrl);
}
return true;
});
String submissionUrl = repoData == null ? null : repoData.getSubmitModule();
if (preference != null) {
if (submissionUrl != null && !submissionUrl.isEmpty()) {
preference.setVisible(true);
preference.setOnPreferenceClickListener(p -> {
if (submissionUrl.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
getFoxActivity(this), submissionUrl, true);
} else {
IntentHelper.openUrl(getFoxActivity(this), submissionUrl);
}
return true;
});
} else {
preference.setVisible(false);
}
}
}
}

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fitsSystemWindowsInsets="left|right">
@ -9,4 +10,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webView" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Androidacy Web Interface"
android:textColor="#ff808080"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/webViewNote"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -14,6 +14,16 @@
app:icon="@drawable/ic_baseline_language_24"
app:title="@string/website"
app:singleLineTitle="false" />
<Preference
app:key="pref_magisk_alt_repo_support"
app:icon="@drawable/ic_baseline_support_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_magisk_alt_repo_donate"
app:icon="@drawable/ic_baseline_monetization_on_24"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_magisk_alt_repo_submit"
app:icon="@drawable/ic_baseline_upload_file_24"

Loading…
Cancel
Save