Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit f49234d

Browse files
committed
Initial Andoridacy repo support, still work in progress.
1 parent 39e015a commit f49234d

20 files changed

+416
-41
lines changed

app/src/main/java/com/fox2code/mmm/ActionButtonType.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@
1111
import com.fox2code.mmm.compat.CompatActivity;
1212
import com.fox2code.mmm.manager.ModuleInfo;
1313
import com.fox2code.mmm.manager.ModuleManager;
14-
import com.fox2code.mmm.repo.RepoModule;
1514
import com.fox2code.mmm.utils.IntentHelper;
1615

1716
public enum ActionButtonType {
1817
INFO(R.drawable.ic_baseline_info_24) {
1918
@Override
2019
public void doAction(ImageButton button, ModuleHolder moduleHolder) {
21-
IntentHelper.openMarkdown(button.getContext(),
22-
moduleHolder.repoModule.notesUrl,
23-
moduleHolder.repoModule.moduleInfo.name,
24-
moduleHolder.getMainModuleConfig());
20+
String notesUrl = moduleHolder.repoModule.notesUrl;
21+
if (notesUrl.startsWith("https://api.androidacy.com/magisk/readme/?module=") ||
22+
notesUrl.startsWith("https://www.androidacy.com/")) {
23+
IntentHelper.openUrlAndroidacy(button.getContext(), notesUrl, false,
24+
moduleHolder.repoModule.moduleInfo.name,
25+
moduleHolder.getMainModuleConfig());
26+
} else {
27+
IntentHelper.openMarkdown(button.getContext(), notesUrl,
28+
moduleHolder.repoModule.moduleInfo.name,
29+
moduleHolder.getMainModuleConfig());
30+
}
2531
}
2632

2733
@Override
@@ -47,6 +53,11 @@ public void doAction(ImageButton button, ModuleHolder moduleHolder) {
4753
if (moduleInfo == null) return;
4854
String updateZipUrl = moduleHolder.getUpdateZipUrl();
4955
if (updateZipUrl == null) return;
56+
if (updateZipUrl.startsWith("https://www.androidacy.com/")) {
57+
IntentHelper.openUrlAndroidacy(
58+
button.getContext(), updateZipUrl, true);
59+
return;
60+
}
5061
String updateZipChecksum = moduleHolder.getUpdateZipChecksum();
5162
IntentHelper.openInstaller(button.getContext(), updateZipUrl,
5263
moduleInfo.name, moduleInfo.config, updateZipChecksum);

app/src/main/java/com/fox2code/mmm/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class Constants {
1818
public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions";
1919
public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless";
2020
public static final String EXTRA_ANDROIDACY_ALLOW_INSTALL = "extra_androidacy_allow_install";
21+
public static final String EXTRA_ANDROIDACY_ACTIONBAR_TITLE = "extra_androidacy_actionbar_title";
22+
public static final String EXTRA_ANDROIDACY_ACTIONBAR_CONFIG = "extra_androidacy_actionbar_config";
2123
public static final String EXTRA_MARKDOWN_URL = "extra_markdown_url";
2224
public static final String EXTRA_MARKDOWN_TITLE = "extra_markdown_title";
2325
public static final String EXTRA_MARKDOWN_CONFIG = "extra_markdown_config";

app/src/main/java/com/fox2code/mmm/MainActivity.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O
3232
public LinearProgressIndicator progressIndicator;
3333
private ModuleViewAdapter moduleViewAdapter;
3434
private SwipeRefreshLayout swipeRefreshLayout;
35+
private long swipeRefreshBlocker = 0;
3536
private RecyclerView moduleList;
3637
private CardView searchCard;
3738
private SearchView searchView;
@@ -54,6 +55,7 @@ protected void onCreate(Bundle savedInstanceState) {
5455
this.setTitle(R.string.app_name);
5556
this.progressIndicator = findViewById(R.id.progress_bar);
5657
this.swipeRefreshLayout = findViewById(R.id.swipe_refresh);
58+
this.swipeRefreshBlocker = Long.MAX_VALUE;
5759
this.moduleList = findViewById(R.id.module_list);
5860
this.searchCard = findViewById(R.id.search_card);
5961
this.searchView = findViewById(R.id.search_bar);
@@ -106,6 +108,7 @@ public void onFailure(int error) {
106108
}
107109

108110
public void commonNext() {
111+
swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
109112
moduleViewListBuilder.setFooterPx(searchCard.getHeight()); // Fix an edge case
110113
if (MainApplication.isShowcaseMode())
111114
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
@@ -222,12 +225,15 @@ else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
222225

223226
@Override
224227
public void onRefresh() {
225-
if (this.initMode || this.progressIndicator == null ||
228+
if (this.swipeRefreshBlocker > System.currentTimeMillis() ||
229+
this.initMode || this.progressIndicator == null ||
226230
this.progressIndicator.getVisibility() == View.VISIBLE) {
231+
this.swipeRefreshLayout.setRefreshing(false);
227232
return; // Do not double scan
228233
}
229234
this.progressIndicator.setVisibility(View.VISIBLE);
230235
this.progressIndicator.setProgressCompat(0, false);
236+
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
231237
// this.swipeRefreshLayout.setRefreshing(true); ??
232238
new Thread(() -> {
233239
Http.cleanDnsCache(); // Allow DNS reload from network

app/src/main/java/com/fox2code/mmm/ModuleHolder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public void getButtons(Context context, List<ActionButtonType> buttonTypeList, b
153153
if (this.moduleInfo != null && !showcaseMode) {
154154
buttonTypeList.add(ActionButtonType.UNINSTALL);
155155
}
156-
if (this.repoModule != null) {
156+
if (this.repoModule != null && this.repoModule.notesUrl != null) {
157157
buttonTypeList.add(ActionButtonType.INFO);
158158
}
159159
if ((this.repoModule != null || (this.moduleInfo != null &&

app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import android.widget.TextView;
1414

1515
import androidx.annotation.ColorInt;
16-
import androidx.annotation.ColorRes;
1716
import androidx.annotation.NonNull;
1817
import androidx.annotation.StringRes;
1918
import androidx.cardview.widget.CardView;
@@ -22,7 +21,6 @@
2221
import com.fox2code.mmm.manager.LocalModuleInfo;
2322
import com.fox2code.mmm.manager.ModuleInfo;
2423
import com.fox2code.mmm.manager.ModuleManager;
25-
import com.fox2code.mmm.repo.RepoModule;
2624
import com.google.android.material.switchmaterial.SwitchMaterial;
2725
import com.topjohnwu.superuser.internal.UiThreadHandler;
2826

app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ private boolean matchFilter(ModuleHolder moduleHolder) {
207207
String query = this.query;
208208
String idLw = moduleInfo.id.toLowerCase(Locale.ROOT);
209209
String nameLw = moduleInfo.name.toLowerCase(Locale.ROOT);
210-
String authorLw = moduleInfo.author.toLowerCase(Locale.ROOT);
210+
String authorLw = moduleInfo.author == null ? "" :
211+
moduleInfo.author.toLowerCase(Locale.ROOT);
211212
if (query.isEmpty() || query.equals(idLw) ||
212213
query.equals(nameLw) || query.equals(authorLw)) {
213214
moduleHolder.filterLevel = 0; // Lower = better

app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import android.annotation.SuppressLint;
44
import android.content.Intent;
5+
import android.content.pm.PackageManager;
56
import android.net.Uri;
67
import android.os.Build;
78
import android.os.Bundle;
9+
import android.webkit.ValueCallback;
10+
import android.webkit.WebChromeClient;
811
import android.webkit.WebResourceRequest;
912
import android.webkit.WebSettings;
1013
import android.webkit.WebView;
@@ -46,8 +49,26 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
4649
}
4750
boolean allowInstall = intent.getBooleanExtra(
4851
Constants.EXTRA_ANDROIDACY_ALLOW_INSTALL, false);
52+
String title = intent.getStringExtra(Constants.EXTRA_ANDROIDACY_ACTIONBAR_TITLE);
53+
String config = intent.getStringExtra(Constants.EXTRA_ANDROIDACY_ACTIONBAR_CONFIG);
4954
this.setContentView(R.layout.webview);
50-
this.hideActionBar();
55+
if (title == null || title.isEmpty()) {
56+
this.hideActionBar();
57+
} else { // Only used for note section
58+
this.setTitle(title);
59+
this.setDisplayHomeAsUpEnabled(true);
60+
if (config != null && !config.isEmpty()) {
61+
String configPkg = IntentHelper.getPackageOfConfig(config);
62+
try {
63+
this.getPackageManager().getPackageInfo(configPkg, 0);
64+
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_app_settings_alt_24,
65+
menu -> {
66+
IntentHelper.openConfig(this, config);
67+
return true;
68+
});
69+
} catch (PackageManager.NameNotFoundException ignored) {}
70+
}
71+
}
5172
this.webView = this.findViewById(R.id.webView);
5273
WebSettings webSettings = this.webView.getSettings();
5374
webSettings.setUserAgentString(Http.getAndroidacyUA());
@@ -69,6 +90,17 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request
6990
return false;
7091
}
7192
});
93+
this.webView.setWebChromeClient(new WebChromeClient() {
94+
@Override
95+
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
96+
FileChooserParams fileChooserParams) {
97+
CompatActivity.getCompatActivity(webView).startActivityForResult(
98+
fileChooserParams.createIntent(), (code, data) ->
99+
filePathCallback.onReceiveValue(
100+
FileChooserParams.parseResult(code, data)));
101+
return true;
102+
}
103+
});
72104
this.webView.addJavascriptInterface(
73105
new AndroidacyWebAPI(this, allowInstall), "mmm");
74106
this.webView.loadUrl(uri.toString());
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package com.fox2code.mmm.androidacy;
2+
3+
import android.content.SharedPreferences;
4+
import android.util.Log;
5+
import android.webkit.CookieManager;
6+
7+
import com.fox2code.mmm.R;
8+
import com.fox2code.mmm.manager.ModuleInfo;
9+
import com.fox2code.mmm.repo.RepoData;
10+
import com.fox2code.mmm.repo.RepoModule;
11+
import com.fox2code.mmm.utils.Http;
12+
import com.fox2code.mmm.utils.PropUtils;
13+
14+
import org.json.JSONArray;
15+
import org.json.JSONException;
16+
import org.json.JSONObject;
17+
18+
import java.io.File;
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.ArrayList;
21+
import java.util.Iterator;
22+
import java.util.List;
23+
24+
public class AndroidacyRepoData extends RepoData {
25+
private static final String TAG = "AndroidacyRepoData";
26+
private long androidacyBlockade = 0;
27+
28+
public AndroidacyRepoData(String url, File cacheRoot,
29+
SharedPreferences cachedPreferences) {
30+
super(url, cacheRoot, cachedPreferences);
31+
}
32+
33+
@Override
34+
protected boolean prepare() {
35+
// Implementation details discussed on telegram
36+
long time = System.currentTimeMillis();
37+
if (this.androidacyBlockade > time) return false;
38+
this.androidacyBlockade = time + 5_000L;
39+
String cookies = CookieManager.getInstance().getCookie("https://.androidacy.com/");
40+
int start = cookies.indexOf("USER=");
41+
String token = null;
42+
if (start != -1) {
43+
int end = cookies.indexOf(";", start);
44+
if (end != -1)
45+
token = cookies.substring(start, end);
46+
}
47+
if (token != null) {
48+
try {
49+
Http.doHttpGet("https://api.androidacy.com/auth/me",true);
50+
} catch (Exception e) {
51+
if ("Received error code: 419".equals(e.getMessage()) ||
52+
"Received error code: 429".equals(e.getMessage())) {
53+
Log.e(TAG, "We are being rate limited!", e);
54+
this.androidacyBlockade = time + 3_600_000L;
55+
return false;
56+
}
57+
Log.w(TAG, "Invalid token, resetting...");
58+
CookieManager.getInstance().setCookie("https://.androidacy.com/",
59+
"USER=; expires=Thu, 01 Jan 1970 00:00:00 GMT;" +
60+
" path=/; secure; domain=.androidacy.com");
61+
token = null;
62+
}
63+
}
64+
if (token == null) {
65+
try {
66+
Log.i(TAG, "Refreshing token...");
67+
token = new String(Http.doHttpPost(
68+
"https://api.androidacy.com/auth/register",
69+
"",true), StandardCharsets.UTF_8);
70+
CookieManager.getInstance().setCookie(".androidacy.com",
71+
"USER="+ token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" +
72+
" path=/; secure; domain=.androidacy.com");
73+
} catch (Exception e) {
74+
if ("Received error code: 419".equals(e.getMessage()) ||
75+
"Received error code: 429".equals(e.getMessage())) {
76+
Log.e(TAG, "We are being rate limited!", e);
77+
this.androidacyBlockade = time + 3_600_000L;
78+
}
79+
Log.e(TAG, "Failed to get a new token", e);
80+
return false;
81+
}
82+
}
83+
return true;
84+
}
85+
86+
@Override
87+
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
88+
if (!jsonObject.getString("status").equals("success"))
89+
throw new JSONException("Response is not a success!");
90+
String name = jsonObject.optString(
91+
"name", "Androidacy Modules Repo");
92+
String nameForModules = name.endsWith(" (Official)") ?
93+
name.substring(0, name.length() - 11) : name;
94+
JSONArray jsonArray = jsonObject.getJSONArray("data");
95+
for (RepoModule repoModule : this.moduleHashMap.values()) {
96+
repoModule.processed = false;
97+
}
98+
ArrayList<RepoModule> newModules = new ArrayList<>();
99+
int len = jsonArray.length();
100+
long lastLastUpdate = 0;
101+
for (int i = 0; i < len; i++) {
102+
jsonObject = jsonArray.getJSONObject(i);
103+
String moduleId = jsonObject.getString("codename");
104+
long lastUpdate = jsonObject.getLong("updated_at") * 1000;
105+
lastLastUpdate = Math.max(lastLastUpdate, lastUpdate);
106+
RepoModule repoModule = this.moduleHashMap.get(moduleId);
107+
if (repoModule == null) {
108+
repoModule = new RepoModule(moduleId);
109+
repoModule.moduleInfo.flags = 0;
110+
this.moduleHashMap.put(moduleId, repoModule);
111+
newModules.add(repoModule);
112+
} else {
113+
if (repoModule.lastUpdated < lastUpdate) {
114+
newModules.add(repoModule);
115+
}
116+
}
117+
repoModule.processed = true;
118+
repoModule.lastUpdated = lastUpdate;
119+
repoModule.repoName = nameForModules;
120+
repoModule.zipUrl = filterURL(
121+
jsonObject.optString("zipUrl", ""));
122+
repoModule.notesUrl = filterURL(
123+
jsonObject.optString("notesUrl", ""));
124+
if (repoModule.zipUrl == null)
125+
repoModule.zipUrl = jsonObject.getString("url");
126+
if (repoModule.notesUrl == null) {
127+
repoModule.notesUrl = // Fallback url in case the API doesn't return one
128+
"https://api.androidacy.com/magisk/readme/?module=" + moduleId;
129+
}
130+
repoModule.qualityText = R.string.module_downloads;
131+
repoModule.qualityValue = jsonObject.optInt("downloads", 0);
132+
String checksum = jsonObject.optString("checksum", "");
133+
repoModule.checksum = checksum.isEmpty() ? null : checksum;
134+
ModuleInfo moduleInfo = repoModule.moduleInfo;
135+
moduleInfo.name = jsonObject.getString("name");
136+
moduleInfo.versionCode = jsonObject.getLong("versionCode");
137+
moduleInfo.version = jsonObject.optString(
138+
"version", "v" + moduleInfo.versionCode);
139+
moduleInfo.author = jsonObject.optString("author", "Unknown");
140+
moduleInfo.minApi = jsonObject.getInt("minApi");
141+
moduleInfo.maxApi = jsonObject.getInt("maxApi");
142+
String minMagisk = jsonObject.getString("minMagisk");
143+
try {
144+
int c = minMagisk.indexOf('.');
145+
if (c == -1) {
146+
moduleInfo.minMagisk = Integer.parseInt(minMagisk);
147+
} else {
148+
moduleInfo.minMagisk = // Allow 24.1 to mean 24100
149+
(Integer.parseInt(minMagisk.substring(0, c)) * 1000) +
150+
(Integer.parseInt(minMagisk.substring(c + 1)) * 100);
151+
}
152+
} catch (Exception e) {
153+
moduleInfo.minMagisk = 0;
154+
}
155+
moduleInfo.support = filterURL(jsonObject.optString("support"));
156+
moduleInfo.donate = filterURL(jsonObject.optString("donate"));
157+
String config = jsonObject.optString("config", "");
158+
moduleInfo.config = config.isEmpty() ? null : config;
159+
PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks
160+
Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " +
161+
moduleInfo.version + " " + moduleInfo.versionCode);
162+
}
163+
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
164+
while (moduleInfoIterator.hasNext()) {
165+
RepoModule repoModule = moduleInfoIterator.next();
166+
if (!repoModule.processed) {
167+
moduleInfoIterator.remove();
168+
}
169+
}
170+
this.lastUpdate = lastLastUpdate;
171+
this.name = name;
172+
return newModules;
173+
}
174+
175+
private static String filterURL(String url) {
176+
if (url == null || url.isEmpty() || PropUtils.isInvalidURL(url)) {
177+
return null;
178+
}
179+
return url;
180+
}
181+
182+
@Override
183+
public void storeMetadata(RepoModule repoModule, byte[] data) {}
184+
185+
@Override
186+
public boolean tryLoadMetadata(RepoModule repoModule) {
187+
return true;
188+
}
189+
}

app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,12 @@ public long getModuleVersionCode(String moduleId) {
122122
LocalModuleInfo localModuleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId);
123123
return localModuleInfo != null ? localModuleInfo.updateVersionCode : -1L;
124124
}
125+
126+
/**
127+
* Hide action bar if visible, the action bar is only visible by default on notes.
128+
*/
129+
@JavascriptInterface
130+
public void hideActionBar() {
131+
this.activity.hideActionBar();
132+
}
125133
}

0 commit comments

Comments
 (0)