Initial Andoridacy repo support, still work in progress.

pull/55/head
Fox2Code 2 years ago
parent 39e015a56d
commit f49234d293

@ -11,17 +11,23 @@ import androidx.annotation.DrawableRes;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.IntentHelper;
public enum ActionButtonType {
INFO(R.drawable.ic_baseline_info_24) {
@Override
public void doAction(ImageButton button, ModuleHolder moduleHolder) {
IntentHelper.openMarkdown(button.getContext(),
moduleHolder.repoModule.notesUrl,
moduleHolder.repoModule.moduleInfo.name,
moduleHolder.getMainModuleConfig());
String notesUrl = moduleHolder.repoModule.notesUrl;
if (notesUrl.startsWith("https://api.androidacy.com/magisk/readme/?module=") ||
notesUrl.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(button.getContext(), notesUrl, false,
moduleHolder.repoModule.moduleInfo.name,
moduleHolder.getMainModuleConfig());
} else {
IntentHelper.openMarkdown(button.getContext(), notesUrl,
moduleHolder.repoModule.moduleInfo.name,
moduleHolder.getMainModuleConfig());
}
}
@Override
@ -47,6 +53,11 @@ public enum ActionButtonType {
if (moduleInfo == null) return;
String updateZipUrl = moduleHolder.getUpdateZipUrl();
if (updateZipUrl == null) return;
if (updateZipUrl.startsWith("https://www.androidacy.com/")) {
IntentHelper.openUrlAndroidacy(
button.getContext(), updateZipUrl, true);
return;
}
String updateZipChecksum = moduleHolder.getUpdateZipChecksum();
IntentHelper.openInstaller(button.getContext(), updateZipUrl,
moduleInfo.name, moduleInfo.config, updateZipChecksum);

@ -18,6 +18,8 @@ public class Constants {
public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions";
public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless";
public static final String EXTRA_ANDROIDACY_ALLOW_INSTALL = "extra_androidacy_allow_install";
public static final String EXTRA_ANDROIDACY_ACTIONBAR_TITLE = "extra_androidacy_actionbar_title";
public static final String EXTRA_ANDROIDACY_ACTIONBAR_CONFIG = "extra_androidacy_actionbar_config";
public static final String EXTRA_MARKDOWN_URL = "extra_markdown_url";
public static final String EXTRA_MARKDOWN_TITLE = "extra_markdown_title";
public static final String EXTRA_MARKDOWN_CONFIG = "extra_markdown_config";

@ -32,6 +32,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
public LinearProgressIndicator progressIndicator;
private ModuleViewAdapter moduleViewAdapter;
private SwipeRefreshLayout swipeRefreshLayout;
private long swipeRefreshBlocker = 0;
private RecyclerView moduleList;
private CardView searchCard;
private SearchView searchView;
@ -54,6 +55,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
this.setTitle(R.string.app_name);
this.progressIndicator = findViewById(R.id.progress_bar);
this.swipeRefreshLayout = findViewById(R.id.swipe_refresh);
this.swipeRefreshBlocker = Long.MAX_VALUE;
this.moduleList = findViewById(R.id.module_list);
this.searchCard = findViewById(R.id.search_card);
this.searchView = findViewById(R.id.search_bar);
@ -106,6 +108,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
}
public void commonNext() {
swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
moduleViewListBuilder.setFooterPx(searchCard.getHeight()); // Fix an edge case
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
@ -222,12 +225,15 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
@Override
public void onRefresh() {
if (this.initMode || this.progressIndicator == null ||
if (this.swipeRefreshBlocker > System.currentTimeMillis() ||
this.initMode || this.progressIndicator == null ||
this.progressIndicator.getVisibility() == View.VISIBLE) {
this.swipeRefreshLayout.setRefreshing(false);
return; // Do not double scan
}
this.progressIndicator.setVisibility(View.VISIBLE);
this.progressIndicator.setProgressCompat(0, false);
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
// this.swipeRefreshLayout.setRefreshing(true); ??
new Thread(() -> {
Http.cleanDnsCache(); // Allow DNS reload from network

@ -153,7 +153,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
if (this.moduleInfo != null && !showcaseMode) {
buttonTypeList.add(ActionButtonType.UNINSTALL);
}
if (this.repoModule != null) {
if (this.repoModule != null && this.repoModule.notesUrl != null) {
buttonTypeList.add(ActionButtonType.INFO);
}
if ((this.repoModule != null || (this.moduleInfo != null &&

@ -13,7 +13,6 @@ import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.cardview.widget.CardView;
@ -22,7 +21,6 @@ import androidx.recyclerview.widget.RecyclerView;
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.google.android.material.switchmaterial.SwitchMaterial;
import com.topjohnwu.superuser.internal.UiThreadHandler;

@ -207,7 +207,8 @@ public class ModuleViewListBuilder {
String query = this.query;
String idLw = moduleInfo.id.toLowerCase(Locale.ROOT);
String nameLw = moduleInfo.name.toLowerCase(Locale.ROOT);
String authorLw = moduleInfo.author.toLowerCase(Locale.ROOT);
String authorLw = moduleInfo.author == null ? "" :
moduleInfo.author.toLowerCase(Locale.ROOT);
if (query.isEmpty() || query.equals(idLw) ||
query.equals(nameLw) || query.equals(authorLw)) {
moduleHolder.filterLevel = 0; // Lower = better

@ -2,9 +2,12 @@ package com.fox2code.mmm.androidacy;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
@ -46,8 +49,26 @@ public class AndroidacyActivity extends CompatActivity {
}
boolean allowInstall = intent.getBooleanExtra(
Constants.EXTRA_ANDROIDACY_ALLOW_INSTALL, false);
String title = intent.getStringExtra(Constants.EXTRA_ANDROIDACY_ACTIONBAR_TITLE);
String config = intent.getStringExtra(Constants.EXTRA_ANDROIDACY_ACTIONBAR_CONFIG);
this.setContentView(R.layout.webview);
this.hideActionBar();
if (title == null || title.isEmpty()) {
this.hideActionBar();
} else { // Only used for note section
this.setTitle(title);
this.setDisplayHomeAsUpEnabled(true);
if (config != null && !config.isEmpty()) {
String configPkg = IntentHelper.getPackageOfConfig(config);
try {
this.getPackageManager().getPackageInfo(configPkg, 0);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_app_settings_alt_24,
menu -> {
IntentHelper.openConfig(this, config);
return true;
});
} catch (PackageManager.NameNotFoundException ignored) {}
}
}
this.webView = this.findViewById(R.id.webView);
WebSettings webSettings = this.webView.getSettings();
webSettings.setUserAgentString(Http.getAndroidacyUA());
@ -69,6 +90,17 @@ public class AndroidacyActivity extends CompatActivity {
return false;
}
});
this.webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
CompatActivity.getCompatActivity(webView).startActivityForResult(
fileChooserParams.createIntent(), (code, data) ->
filePathCallback.onReceiveValue(
FileChooserParams.parseResult(code, data)));
return true;
}
});
this.webView.addJavascriptInterface(
new AndroidacyWebAPI(this, allowInstall), "mmm");
this.webView.loadUrl(uri.toString());

@ -0,0 +1,189 @@
package com.fox2code.mmm.androidacy;
import android.content.SharedPreferences;
import android.util.Log;
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.RepoModule;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.PropUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class AndroidacyRepoData extends RepoData {
private static final String TAG = "AndroidacyRepoData";
private long androidacyBlockade = 0;
public AndroidacyRepoData(String url, File cacheRoot,
SharedPreferences cachedPreferences) {
super(url, cacheRoot, cachedPreferences);
}
@Override
protected boolean prepare() {
// Implementation details discussed on telegram
long time = System.currentTimeMillis();
if (this.androidacyBlockade > time) return false;
this.androidacyBlockade = time + 5_000L;
String cookies = CookieManager.getInstance().getCookie("https://.androidacy.com/");
int start = cookies.indexOf("USER=");
String token = null;
if (start != -1) {
int end = cookies.indexOf(";", start);
if (end != -1)
token = cookies.substring(start, end);
}
if (token != null) {
try {
Http.doHttpGet("https://api.androidacy.com/auth/me",true);
} catch (Exception e) {
if ("Received error code: 419".equals(e.getMessage()) ||
"Received error code: 429".equals(e.getMessage())) {
Log.e(TAG, "We are being rate limited!", e);
this.androidacyBlockade = time + 3_600_000L;
return false;
}
Log.w(TAG, "Invalid token, resetting...");
CookieManager.getInstance().setCookie("https://.androidacy.com/",
"USER=; expires=Thu, 01 Jan 1970 00:00:00 GMT;" +
" path=/; secure; domain=.androidacy.com");
token = null;
}
}
if (token == null) {
try {
Log.i(TAG, "Refreshing token...");
token = new String(Http.doHttpPost(
"https://api.androidacy.com/auth/register",
"",true), StandardCharsets.UTF_8);
CookieManager.getInstance().setCookie(".androidacy.com",
"USER="+ token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" +
" path=/; secure; domain=.androidacy.com");
} catch (Exception e) {
if ("Received error code: 419".equals(e.getMessage()) ||
"Received error code: 429".equals(e.getMessage())) {
Log.e(TAG, "We are being rate limited!", e);
this.androidacyBlockade = time + 3_600_000L;
}
Log.e(TAG, "Failed to get a new token", e);
return false;
}
}
return true;
}
@Override
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
if (!jsonObject.getString("status").equals("success"))
throw new JSONException("Response is not a success!");
String name = jsonObject.optString(
"name", "Androidacy Modules Repo");
String nameForModules = name.endsWith(" (Official)") ?
name.substring(0, name.length() - 11) : name;
JSONArray jsonArray = jsonObject.getJSONArray("data");
for (RepoModule repoModule : this.moduleHashMap.values()) {
repoModule.processed = false;
}
ArrayList<RepoModule> newModules = new ArrayList<>();
int len = jsonArray.length();
long lastLastUpdate = 0;
for (int i = 0; i < len; i++) {
jsonObject = jsonArray.getJSONObject(i);
String moduleId = jsonObject.getString("codename");
long lastUpdate = jsonObject.getLong("updated_at") * 1000;
lastLastUpdate = Math.max(lastLastUpdate, lastUpdate);
RepoModule repoModule = this.moduleHashMap.get(moduleId);
if (repoModule == null) {
repoModule = new RepoModule(moduleId);
repoModule.moduleInfo.flags = 0;
this.moduleHashMap.put(moduleId, repoModule);
newModules.add(repoModule);
} else {
if (repoModule.lastUpdated < lastUpdate) {
newModules.add(repoModule);
}
}
repoModule.processed = true;
repoModule.lastUpdated = lastUpdate;
repoModule.repoName = nameForModules;
repoModule.zipUrl = filterURL(
jsonObject.optString("zipUrl", ""));
repoModule.notesUrl = filterURL(
jsonObject.optString("notesUrl", ""));
if (repoModule.zipUrl == null)
repoModule.zipUrl = jsonObject.getString("url");
if (repoModule.notesUrl == null) {
repoModule.notesUrl = // Fallback url in case the API doesn't return one
"https://api.androidacy.com/magisk/readme/?module=" + moduleId;
}
repoModule.qualityText = R.string.module_downloads;
repoModule.qualityValue = jsonObject.optInt("downloads", 0);
String checksum = jsonObject.optString("checksum", "");
repoModule.checksum = checksum.isEmpty() ? null : checksum;
ModuleInfo moduleInfo = repoModule.moduleInfo;
moduleInfo.name = jsonObject.getString("name");
moduleInfo.versionCode = jsonObject.getLong("versionCode");
moduleInfo.version = jsonObject.optString(
"version", "v" + moduleInfo.versionCode);
moduleInfo.author = jsonObject.optString("author", "Unknown");
moduleInfo.minApi = jsonObject.getInt("minApi");
moduleInfo.maxApi = jsonObject.getInt("maxApi");
String minMagisk = jsonObject.getString("minMagisk");
try {
int c = minMagisk.indexOf('.');
if (c == -1) {
moduleInfo.minMagisk = Integer.parseInt(minMagisk);
} else {
moduleInfo.minMagisk = // Allow 24.1 to mean 24100
(Integer.parseInt(minMagisk.substring(0, c)) * 1000) +
(Integer.parseInt(minMagisk.substring(c + 1)) * 100);
}
} catch (Exception e) {
moduleInfo.minMagisk = 0;
}
moduleInfo.support = filterURL(jsonObject.optString("support"));
moduleInfo.donate = filterURL(jsonObject.optString("donate"));
String config = jsonObject.optString("config", "");
moduleInfo.config = config.isEmpty() ? null : config;
PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks
Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " +
moduleInfo.version + " " + moduleInfo.versionCode);
}
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
while (moduleInfoIterator.hasNext()) {
RepoModule repoModule = moduleInfoIterator.next();
if (!repoModule.processed) {
moduleInfoIterator.remove();
}
}
this.lastUpdate = lastLastUpdate;
this.name = name;
return newModules;
}
private static String filterURL(String url) {
if (url == null || url.isEmpty() || PropUtils.isInvalidURL(url)) {
return null;
}
return url;
}
@Override
public void storeMetadata(RepoModule repoModule, byte[] data) {}
@Override
public boolean tryLoadMetadata(RepoModule repoModule) {
return true;
}
}

@ -122,4 +122,12 @@ public class AndroidacyWebAPI {
LocalModuleInfo localModuleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId);
return localModuleInfo != null ? localModuleInfo.updateVersionCode : -1L;
}
/**
* Hide action bar if visible, the action bar is only visible by default on notes.
*/
@JavascriptInterface
public void hideActionBar() {
this.activity.hideActionBar();
}
}

@ -4,17 +4,13 @@ import android.content.SharedPreferences;
import android.util.Log;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.PropUtils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

@ -28,9 +28,9 @@ public class MarkdownActivity extends CompatActivity {
super.onCreate(savedInstanceState);
this.setDisplayHomeAsUpEnabled(true);
Intent intent = this.getIntent();
if (intent == null || !MainApplication.checkSecret(intent)) {
if (!MainApplication.checkSecret(intent)) {
Log.e(TAG, "Impersonation detected!");
this.onBackPressed();
this.forceBackPressed();
return;
}
String url = intent.getExtras()

@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@ -40,7 +39,7 @@ public class RepoData {
private final Map<String, Long> specialTimes;
private long specialLastUpdate;
RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
protected RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) {
this(url, cacheRoot, cachedPreferences, false);
}
@ -95,7 +94,11 @@ public class RepoData {
}
}
List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
protected boolean prepare() {
return true;
}
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
List<RepoModule> newModules = new ArrayList<>();
synchronized (this.populateLock) {
String name = jsonObject.getString("name").trim();

@ -5,6 +5,7 @@ import android.content.SharedPreferences;
import android.util.Log;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.androidacy.AndroidacyRepoData;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Hashes;
@ -29,6 +30,8 @@ public final class RepoManager {
"https://raw.githubusercontent.com/Magisk-Modules-Alt-Repo/json/main/modules.json";
public static final String MAGISK_ALT_REPO_JSDELIVR =
"https://cdn.jsdelivr.net/gh/Magisk-Modules-Alt-Repo/json@main/modules.json";
public static final String ANDROIDACY_MAGISK_REPO_ENDPOINT =
"https://api.androidacy.com/magisk/repo";
public static final String MAGISK_REPO_HOMEPAGE =
"https://github.com/Magisk-Modules-Repo";
@ -67,6 +70,7 @@ public final class RepoManager {
// We do not have repo list config yet.
this.addRepoData(MAGISK_REPO_JSDELIVR);
this.addRepoData(MAGISK_ALT_REPO_JSDELIVR);
this.addAndroidacyRepoData();
// Populate default cache
for (RepoData repoData:this.repoData.values()) {
for (RepoModule repoModule:repoData.moduleHashMap.values()) {
@ -79,7 +83,8 @@ public final class RepoManager {
this.modules.put(repoModule.id, repoModule);
}
} else {
Log.e(TAG, "Detected module with invalid metadata: " + repoModule.id);
Log.e(TAG, "Detected module with invalid metadata: " +
repoModule.repoName + "/" + repoModule.id);
}
}
}
@ -103,7 +108,11 @@ public final class RepoManager {
synchronized (this.repoUpdateLock) {
repoData = this.repoData.get(url);
if (repoData == null) {
return this.addRepoData(url);
if (ANDROIDACY_MAGISK_REPO_ENDPOINT.equals(url)) {
return this.addAndroidacyRepoData();
} else {
return this.addRepoData(url);
}
}
}
return repoData;
@ -170,13 +179,16 @@ public final class RepoManager {
for (int i = 0; i < repoUpdaters.length; i++) {
List<RepoModule> repoModules = repoUpdaters[i].toUpdate();
RepoData repoData = repoDatas[i];
Log.d(TAG, "Registering " + repoData.name);
for (RepoModule repoModule:repoModules) {
try {
repoData.storeMetadata(repoModule,
Http.doHttpGet(repoModule.propUrl, false));
Files.write(new File(repoData.cacheRoot, repoModule.id + ".prop"),
Http.doHttpGet(repoModule.propUrl, false));
if (repoDatas[i].tryLoadMetadata(repoModule) && (allowLowQualityModules ||
if (repoModule.propUrl != null) {
repoData.storeMetadata(repoModule,
Http.doHttpGet(repoModule.propUrl, false));
Files.write(new File(repoData.cacheRoot, repoModule.id + ".prop"),
Http.doHttpGet(repoModule.propUrl, false));
}
if (repoData.tryLoadMetadata(repoModule) && (allowLowQualityModules ||
!PropUtils.isLowQualityModule(repoModule.moduleInfo))) {
// Note: registeredRepoModule may not be null if registered by multiple repos
RepoModule registeredRepoModule = this.modules.get(repoModule.id);
@ -233,6 +245,8 @@ public final class RepoManager {
case MAGISK_ALT_REPO:
case MAGISK_ALT_REPO_JSDELIVR:
return "magisk_alt_repo";
case ANDROIDACY_MAGISK_REPO_ENDPOINT:
return "androidacy_repo";
default:
return "repo_" + Hashes.hashSha1(
url.getBytes(StandardCharsets.UTF_8));
@ -249,4 +263,14 @@ public final class RepoManager {
this.repoData.put(url, repoData);
return repoData;
}
private RepoData addAndroidacyRepoData() {
File cacheRoot = new File(this.mainApplication.getCacheDir(), "androidacy_repo");
SharedPreferences sharedPreferences = this.mainApplication
.getSharedPreferences("mmm_androidacy_repo", Context.MODE_PRIVATE);
RepoData repoData = new AndroidacyRepoData(
ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, sharedPreferences);
this.repoData.put(ANDROIDACY_MAGISK_REPO_ENDPOINT, repoData);
return repoData;
}
}

@ -1,5 +1,7 @@
package com.fox2code.mmm.repo;
import androidx.annotation.StringRes;
import com.fox2code.mmm.manager.ModuleInfo;
public class RepoModule {
@ -11,7 +13,10 @@ public class RepoModule {
public String zipUrl;
public String notesUrl;
public String checksum;
boolean processed;
public boolean processed;
@StringRes
public int qualityText;
public int qualityValue;
public RepoModule(String id) {
this.moduleInfo = new ModuleInfo(id);

@ -27,6 +27,9 @@ public class RepoUpdater {
public int fetchIndex() {
try {
if (!this.repoData.prepare()) {
return 0;
}
this.indexRaw = Http.doHttpGet(this.repoData.url, false);
if (this.repoData.special) this.repoData.updateSpecialTimes(true);
this.toUpdate = this.repoData.populate(new JSONObject(

@ -160,22 +160,25 @@ public class SettingsActivity extends CompatActivity {
setPreferencesFromResource(R.xml.repo_preferences, rootKey);
setRepoData("pref_repo_main", RepoManager.MAGISK_REPO,
"Magisk Modules Repo (Official)", RepoManager.MAGISK_REPO_HOMEPAGE,
null, null);
null, null,null);
setRepoData("pref_repo_alt", RepoManager.MAGISK_ALT_REPO,
"Magisk Modules Alt Repo", RepoManager.MAGISK_ALT_REPO_HOMEPAGE,
null,
null, null,
"https://github.com/Magisk-Modules-Alt-Repo/submission/issues");
// Androidacy backend not yet implemented!
setRepoData("pref_repo_androidacy", null,
"Androidacy Magisk Modules Repo",
setRepoData("pref_repo_androidacy",
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/");
}
private void setRepoData(String preferenceName, String url,
String fallbackTitle, String homepage,
String supportUrl, String submissionUrl) {
String supportUrl, String donateUrl,
String submissionUrl) {
Preference preference = findPreference(preferenceName);
if (preference == null) return;
RepoData repoData = RepoManager.getINSTANCE().get(url);
@ -200,6 +203,13 @@ public class SettingsActivity extends CompatActivity {
return true;
});
}
preference = findPreference(preferenceName + "_donate");
if (preference != null && donateUrl != null) {
preference.setOnPreferenceClickListener(p -> {
IntentHelper.openUrl(getCompatActivity(this), donateUrl);
return true;
});
}
preference = findPreference(preferenceName + "_submit");
if (preference != null && submissionUrl != null) {
preference.setOnPreferenceClickListener(p -> {

@ -10,6 +10,7 @@ import android.util.Log;
import android.webkit.CookieManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
@ -22,6 +23,7 @@ import java.io.InputStream;
import java.net.InetAddress;
import java.net.Proxy;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -29,7 +31,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@ -38,12 +39,15 @@ import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Dns;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.brotli.BrotliInterceptor;
import okhttp3.dnsoverhttps.DnsOverHttps;
import okio.BufferedSink;
public class Http {
private static final String TAG = "Http";
@ -152,8 +156,29 @@ public class Http {
Response response = (allowCache ? httpClientWithCache : httpClient).newCall(
makeRequestBuilder().url(url).get().build()
).execute();
// 200 == success, 304 == cache valid
if (response.code() != 200 && (response.code() != 304 || !allowCache)) {
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 &&
(response.code() != 304 || !allowCache)) {
throw new IOException("Received error code: "+ response.code());
}
ResponseBody responseBody = response.body();
// Use cache api if used cached response
if (responseBody == null && response.code() == 304) {
response = response.cacheResponse();
if (response != null)
responseBody = response.body();
}
return responseBody == null ? new byte[0] : responseBody.bytes();
}
public static byte[] doHttpPost(String url,String data,boolean allowCache) throws IOException {
Response response = (allowCache ? httpClientWithCache : httpClient).newCall(
makeRequestBuilder().url(url).post(JsonRequestBody.from(data))
.header("Content-Type", "application/json").build()
).execute();
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 &&
(response.code() != 304 || !allowCache)) {
throw new IOException("Received error code: "+ response.code());
}
ResponseBody responseBody = response.body();
@ -169,7 +194,7 @@ public class Http {
public static byte[] doHttpGet(String url,ProgressListener progressListener) throws IOException {
Log.d("Http", "Progress URL: " + url);
Response response = httpClient.newCall(makeRequestBuilder().url(url).get().build()).execute();
if (response.code() != 200) {
if (response.code() != 200 && response.code() != 204) {
throw new IOException("Received error code: "+ response.code());
}
ResponseBody responseBody = Objects.requireNonNull(response.body());
@ -378,6 +403,40 @@ public class Http {
}
}
private static class JsonRequestBody extends RequestBody {
private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json");
private static final JsonRequestBody EMPTY = new JsonRequestBody(new byte[0]);
static JsonRequestBody from(String data) {
if (data == null || data.length() == 0) {
return EMPTY;
}
return new JsonRequestBody(data.getBytes(StandardCharsets.UTF_8));
}
final byte[] data;
private JsonRequestBody(byte[] data) {
this.data = data;
}
@Nullable
@Override
public MediaType contentType() {
return JSON_MEDIA_TYPE;
}
@Override
public long contentLength() {
return this.data.length;
}
@Override
public void writeTo(@NonNull BufferedSink bufferedSink) throws IOException {
bufferedSink.write(this.data);
}
}
/**
* Change URL to appropriate url and force Magisk link to use latest version.
*/

@ -27,9 +27,7 @@ import com.fox2code.mmm.markdown.MarkdownActivity;
import com.topjohnwu.superuser.io.SuFileInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -46,12 +44,21 @@ public class IntentHelper {
}
public static void openUrlAndroidacy(Context context, String url,boolean allowInstall) {
openUrlAndroidacy(context, url, allowInstall, null, null);
}
public static void openUrlAndroidacy(Context context, String url, boolean allowInstall,
String title,String config) {
Uri uri = Uri.parse(url);
try {
Intent myIntent = new Intent(
Constants.INTENT_ANDROIDACY_INTERNAL,
uri, context, AndroidacyActivity.class);
myIntent.putExtra(Constants.EXTRA_ANDROIDACY_ALLOW_INSTALL, allowInstall);
if (title != null)
myIntent.putExtra(Constants.EXTRA_ANDROIDACY_ACTIONBAR_TITLE, title);
if (config != null)
myIntent.putExtra(Constants.EXTRA_ANDROIDACY_ACTIONBAR_CONFIG, config);
MainApplication.addSecret(myIntent);
context.startActivity(myIntent);
} catch (ActivityNotFoundException e) {

@ -15,7 +15,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
public class PropUtils {
private static final HashMap<String, String> moduleSupportsFallbacks = new HashMap<>();
@ -275,6 +274,23 @@ public class PropUtils {
}
}
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);
}
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;
}
}
// Some module are really so low quality that it has become very annoying.
public static boolean isLowQualityModule(ModuleInfo moduleInfo) {
final String description;
@ -290,7 +306,7 @@ public class PropUtils {
return !TextUtils.isGraphic(name) || name.indexOf('\0') != -1;
}
private static boolean isInvalidURL(String url) {
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://")

@ -36,6 +36,11 @@
app:icon="@drawable/ic_baseline_telegram_24"
app:title="@string/support"
app:singleLineTitle="false" />
<Preference
app:key="pref_repo_androidacy_donate"
app:icon="@drawable/ic_patreon"
app:title="@string/donate"
app:singleLineTitle="false" />
<Preference
app:key="pref_repo_androidacy_submit"
app:icon="@drawable/ic_baseline_upload_file_24"

Loading…
Cancel
Save