From ff1afd04165c5f41472d56a8805eeb2b884dc26b Mon Sep 17 00:00:00 2001 From: androidacy-user Date: Tue, 11 Oct 2022 21:15:13 -0400 Subject: [PATCH] Overall optimizations - Clarify some failures - Optimize build times - Update dependencies - Make absolute sure sentry doesn't send PII Signed-off-by: androidacy-user --- app/build.gradle | 26 +-- app/src/default/AndroidManifest.xml | 12 +- .../mmm/androidacy/AndroidacyActivity.java | 4 + .../mmm/androidacy/AndroidacyRepoData.java | 136 ++++++------ .../mmm/installer/InstallerActivity.java | 10 +- .../java/com/fox2code/mmm/utils/Http.java | 208 +++++++++--------- app/src/main/res/values/strings.xml | 3 + gradle.properties | 3 +- 8 files changed, 210 insertions(+), 192 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f32216f..6a70dc5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,7 +80,7 @@ aboutLibraries { sentry { // Disable sentry on F-Droid flavor - ignoredFlavors = [ "fdroid" ] + ignoredFlavors = ["fdroid"] // Disables or enables the handling of Proguard mapping for Sentry. // If enabled the plugin will generate a UUID and will take care of @@ -116,7 +116,7 @@ sentry { // Does auto instrumentation for specified features through bytecode manipulation. // Default is enabled. tracingInstrumentation { - enabled = false + enabled = true } // Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations). @@ -134,7 +134,7 @@ sentry { // as Gradle will resolve it to the latest version. // // Defaults to the latest published sentry version. - sentryVersion = '6.4.1' + sentryVersion = '6.5.0' } } @@ -153,7 +153,7 @@ dependencies { implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.webkit:webkit:1.5.0' implementation 'com.google.android.material:material:1.6.1' - implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" + implementation "com.mikepenz:aboutlibraries:10.5.0" implementation "dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0" implementation "dev.rikka.rikkax.insets:insets:1.3.0" implementation 'com.github.Dimezis:BlurView:version-2.0.2' @@ -162,18 +162,18 @@ dependencies { // Utils implementation 'androidx.work:work-runtime:2.7.1' - implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' - implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3' + implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10' + implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.10' implementation 'com.github.topjohnwu.libsu:io:5.0.1' implementation 'com.github.Fox2Code:RosettaX:1.0.9' implementation 'com.github.Fox2Code:AndroidANSI:1.0.1' // Error reporting - defaultImplementation 'io.sentry:sentry-android:6.4.1' - defaultImplementation 'io.sentry:sentry-android-fragment:6.4.1' - defaultImplementation 'io.sentry:sentry-android-okhttp:6.4.1' - defaultImplementation 'io.sentry:sentry-android-core:6.4.1' - defaultImplementation 'io.sentry:sentry-android-ndk:6.4.1' + defaultImplementation 'io.sentry:sentry-android:6.5.0' + defaultImplementation 'io.sentry:sentry-android-fragment:6.5.0' + defaultImplementation 'io.sentry:sentry-android-okhttp:6.5.0' + defaultImplementation 'io.sentry:sentry-android-core:6.5.0' + defaultImplementation 'io.sentry:sentry-android-ndk:6.5.0' // Markdown implementation "io.noties.markwon:core:4.6.2" @@ -184,13 +184,13 @@ dependencies { implementation "com.caverock:androidsvg:1.4" // Test - testImplementation 'junit:junit:4.+' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } if (hasSentryConfig) { - Properties properties = new Properties(); + Properties properties = new Properties() try (FileInputStream fis = new FileInputStream(sentryConfigFile)) { properties.load(fis) } diff --git a/app/src/default/AndroidManifest.xml b/app/src/default/AndroidManifest.xml index e764f3f..ec3e11f 100644 --- a/app/src/default/AndroidManifest.xml +++ b/app/src/default/AndroidManifest.xml @@ -4,7 +4,9 @@ tools:ignore="QueryAllPackagesPermission"> - + @@ -20,5 +22,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index e3bf669..cca9451 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -135,7 +135,10 @@ public final class AndroidacyActivity extends FoxActivity { webSettings.setUserAgentString(Http.getAndroidacyUA()); webSettings.setDomStorageEnabled(true); webSettings.setJavaScriptEnabled(true); + // Disable cache + webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); webSettings.setAllowFileAccess(false); + webSettings.setAllowContentAccess(false); // Attempt at fixing CloudFlare captcha. if (WebViewFeature.isFeatureSupported(WebViewFeature.REQUESTED_WITH_HEADER_CONTROL)) { WebSettingsCompat.setRequestedWithHeaderMode( @@ -448,6 +451,7 @@ public final class AndroidacyActivity extends FoxActivity { fileOutputStream.write(module); } } finally { + //noinspection UnusedAssignment module = null; this.runOnUiThread(() -> progressIndicator.setVisibility(View.INVISIBLE)); diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index 6b3477c..83b1e29 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -3,9 +3,11 @@ package com.fox2code.mmm.androidacy; import android.content.SharedPreferences; import android.util.Log; import android.webkit.CookieManager; +import android.widget.Toast; import androidx.annotation.NonNull; +import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.repo.RepoData; @@ -31,27 +33,24 @@ public final class AndroidacyRepoData extends RepoData { private static final String TAG = "AndroidacyRepoData"; static { - HttpUrl.Builder OK_HTTP_URL_BUILDER = - new HttpUrl.Builder().scheme("https"); + HttpUrl.Builder OK_HTTP_URL_BUILDER = new HttpUrl.Builder().scheme("https"); // Using HttpUrl.Builder.host(String) crash the app OK_HTTP_URL_BUILDER.setHost$okhttp(".androidacy.com"); OK_HTTP_URL_BUILDER.build(); } - // Avoid spamming requests to Androidacy - private long androidacyBlockade = 0; - private String token = null; private final boolean testMode; private final String host; + // Avoid spamming requests to Androidacy + private long androidacyBlockade = 0; + private String token = this.cachedPreferences.getString("pref_androidacy_api_token", null); - public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences, - boolean testMode) { - super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT : - RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences); + public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences, boolean testMode) { + super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT : RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences); if (this.metaDataCache.exists() && !testMode) { this.androidacyBlockade = this.metaDataCache.lastModified() + 30_000L; if (this.androidacyBlockade - 60_000L > System.currentTimeMillis()) { - this.androidacyBlockade = 0; // Don't allow time travel. Well why not??? + this.androidacyBlockade = 0; // Don't allow time travel. // Well why not??? } } this.defaultName = "Androidacy Modules Repo"; @@ -67,12 +66,18 @@ public final class AndroidacyRepoData extends RepoData { return RepoManager.getINSTANCE().getAndroidacyRepoData(); } + private static String filterURL(String url) { + if (url == null || url.isEmpty() || PropUtils.isInvalidURL(url)) { + return null; + } + return url; + } + public boolean isValidToken(string token) { try { Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token, false); } catch (Exception e) { - if ("Received error code: 419".equals(e.getMessage()) || - "Received error code: 429".equals(e.getMessage())) { + if ("Received error code: 419".equals(e.getMessage()) || "Received error code: 429".equals(e.getMessage())) { Log.e(TAG, "We are being rate limited!", e); long time = System.currentTimeMillis(); this.androidacyBlockade = time + 3_600_000L; @@ -92,37 +97,52 @@ public final class AndroidacyRepoData extends RepoData { @Override protected boolean prepare() { // Implementation details discussed on telegram - // DEPRECATED. Please switch to new implementation before v7 hits production + // First, ping the server to check if it's alive + try { + Http.doHttpGet("https://" + this.host + "/ping", false); + } catch (Exception e) { + Log.e(TAG, "Failed to ping server", e); + // Inform user + Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_server_down, Toast.LENGTH_LONG).show(); + return false; + } long time = System.currentTimeMillis(); if (this.androidacyBlockade > time) return false; this.androidacyBlockade = time + 30_000L; - // Get token from androidacy_api_token shared preference - String token = this.cachedPreferences.getString("androidacy_api_token", null); - if (token != null) { - this.token = token; - if (!isValidToken(token)) { - token = null; + if (this.token == null) { + this.token = this.cachedPreferences.getString("pref_androidacy_api_token", null); + if (this.token != null && !this.isValidToken(this.token)) { + this.token = null; } + } else if (!this.isValidToken(this.token)) { + this.token = null; } if (token == null) { try { Log.i(TAG, "Refreshing token..."); // POST request to https://production-api.androidacy.com/auth/register - token = new String(Http.doHttpPost( - "https://" + this.host + "/auth/register", - "foxmmm=true", true), StandardCharsets.UTF_8); + token = new String(Http.doHttpPost("https://" + this.host + "/auth/register", "foxmmm=true", false), StandardCharsets.UTF_8); // Parse token - JSONObject jsonObject = new JSONObject(token); - token = jsonObject.getString("token"); + try { + JSONObject jsonObject = new JSONObject(token); + token = jsonObject.getString("token"); + } catch (JSONException e) { + Log.e(TAG, "Failed to parse token", e); + // Show a toast + Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_parse_token, Toast.LENGTH_LONG).show(); + return false; + } + // Ensure token is valid + if (!isValidToken(token)) { + Log.e(TAG, "Failed to validate token"); + // Show a toast + Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_validate_token, Toast.LENGTH_LONG).show(); + return false; + } // Save token to shared preference - SharedPreferences.Editor editor = this.cachedPreferences.edit(); - editor.putString("androidacy_api_token", token); - editor.apply(); + MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", token).apply(); } catch (Exception e) { - if ("Received error code: 419".equals(e.getMessage()) || - "Received error code: 429".equals(e.getMessage()) || - "Received error code: 503".equals(e.getMessage()) - ) { + if ("Received error code: 419".equals(e.getMessage()) || "Received error code: 429".equals(e.getMessage()) || "Received error code: 503".equals(e.getMessage())) { Log.e(TAG, "We are being rate limited!", e); this.androidacyBlockade = time + 3_600_000L; } @@ -130,6 +150,7 @@ public final class AndroidacyRepoData extends RepoData { return false; } } + //noinspection SillyAssignment // who are you calling silly? this.token = token; return true; } @@ -138,10 +159,8 @@ public final class AndroidacyRepoData extends RepoData { protected List 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; + 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; @@ -153,8 +172,8 @@ public final class AndroidacyRepoData extends RepoData { jsonObject = jsonArray.getJSONObject(i); String moduleId = jsonObject.getString("codename"); // Deny remote modules ids shorter than 3 chars or containing null char or space - if (moduleId.length() < 3 || moduleId.indexOf('\0') != -1 || - moduleId.indexOf(' ') != -1 || "ak3-helper".equals(moduleId)) continue; + if (moduleId.length() < 3 || moduleId.indexOf('\0') != -1 || moduleId.indexOf(' ') != -1 || "ak3-helper".equals(moduleId)) + continue; long lastUpdate = jsonObject.getLong("updated_at") * 1000; lastLastUpdate = Math.max(lastLastUpdate, lastUpdate); RepoModule repoModule = this.moduleHashMap.get(moduleId); @@ -171,10 +190,8 @@ public final class AndroidacyRepoData extends RepoData { repoModule.processed = true; repoModule.lastUpdated = lastUpdate; repoModule.repoName = nameForModules; - repoModule.zipUrl = filterURL( - jsonObject.optString("zipUrl", "")); - repoModule.notesUrl = filterURL( - jsonObject.optString("notesUrl", "")); + repoModule.zipUrl = filterURL(jsonObject.optString("zipUrl", "")); + repoModule.notesUrl = filterURL(jsonObject.optString("notesUrl", "")); if (repoModule.zipUrl == null) { repoModule.zipUrl = // Fallback url in case the API doesn't have zipUrl "https://" + this.host + "/magisk/info/" + moduleId; @@ -192,8 +209,7 @@ public final class AndroidacyRepoData extends RepoData { ModuleInfo moduleInfo = repoModule.moduleInfo; moduleInfo.name = jsonObject.getString("name"); moduleInfo.versionCode = jsonObject.getLong("versionCode"); - moduleInfo.version = jsonObject.optString( - "version", "v" + moduleInfo.versionCode); + moduleInfo.version = jsonObject.optString("version", "v" + moduleInfo.versionCode); moduleInfo.author = jsonObject.optString("author", "Unknown"); moduleInfo.description = jsonObject.optString("description", ""); moduleInfo.minApi = jsonObject.getInt("minApi"); @@ -205,8 +221,7 @@ public final class AndroidacyRepoData extends RepoData { 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); + (Integer.parseInt(minMagisk.substring(0, c)) * 1000) + (Integer.parseInt(minMagisk.substring(c + 1)) * 100); } } catch (Exception e) { moduleInfo.minMagisk = 0; @@ -219,8 +234,7 @@ public final class AndroidacyRepoData extends RepoData { 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); + Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " + moduleInfo.version + " " + moduleInfo.versionCode); } Iterator moduleInfoIterator = this.moduleHashMap.values().iterator(); while (moduleInfoIterator.hasNext()) { @@ -240,13 +254,6 @@ public final class AndroidacyRepoData extends RepoData { 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) { } @@ -254,34 +261,30 @@ public final class AndroidacyRepoData extends RepoData { @Override public boolean tryLoadMetadata(RepoModule repoModule) { if (this.moduleHashMap.containsKey(repoModule.id)) { - repoModule.moduleInfo.flags &= - ~ModuleInfo.FLAG_METADATA_INVALID; + repoModule.moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID; return true; } - repoModule.moduleInfo.flags |= - ModuleInfo.FLAG_METADATA_INVALID; + repoModule.moduleInfo.flags |= ModuleInfo.FLAG_METADATA_INVALID; return false; } @Override public String getUrl() { - return this.token == null ? this.url : - this.url + "?token=" + this.token; + return this.token == null ? this.url : this.url + "?token=" + this.token; } private String injectToken(String url) { // Do not inject token for non Androidacy urls - if (!AndroidacyUtil.isAndroidacyLink(url)) - return url; + if (!AndroidacyUtil.isAndroidacyLink(url)) return url; if (this.testMode) { - if (url.startsWith("https://api.androidacy.com/")) { + if (url.startsWith("https://production-api.androidacy.com/")) { Log.e(TAG, "Got non test mode url: " + AndroidacyUtil.hideToken(url)); url = "https://staging-api.androidacy.com/" + url.substring(27); } } else { if (url.startsWith("https://staging-api.androidacy.com/")) { Log.e(TAG, "Got test mode url: " + AndroidacyUtil.hideToken(url)); - url = "https://api.androidacy.com/" + url.substring(35); + url = "https://production-api.androidacy.com/" + url.substring(35); } } String token = "token=" + this.token; @@ -307,9 +310,8 @@ public final class AndroidacyRepoData extends RepoData { void setToken(String token) { if (Http.hasWebView()) { - CookieManager.getInstance().setCookie("https://.androidacy.com/", - "USER=" + token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" + - " path=/; secure; domain=.androidacy.com"); + // TODO: Figure out why this is needed + CookieManager.getInstance().setCookie("https://.androidacy.com/", "USER=" + token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" + " path=/; secure; domain=.androidacy.com"); this.token = token; } } diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java index 6846921..cff88e1 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -53,6 +53,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; +@SuppressWarnings("IOStreamConstructor") public class InstallerActivity extends FoxActivity { private static final String TAG = "InstallerActivity"; public LinearProgressIndicator progressIndicator; @@ -156,8 +157,9 @@ public class InstallerActivity extends FoxActivity { !new SuFile(moduleCache.getAbsolutePath()).delete()) Log.e(TAG, "Failed to delete module cache"); String errMessage = "Failed to download module zip"; + // Set this to the error message if it's a HTTP error byte[] rawModule; - boolean androidacyBlame = false; // In case Androidacy mess-up again... + boolean androidacyBlame = false; // In case Androidacy mess-up again... yeah screw you too jk jk try { Log.i(TAG, (urlMode ? "Downloading: " : "Loading: ") + target); rawModule = urlMode ? Http.doHttpGet(target, (progress, max, done) -> { @@ -254,11 +256,9 @@ public class InstallerActivity extends FoxActivity { } catch (IOException e) { Log.e(TAG, errMessage, e); if (androidacyBlame) { - this.installerTerminal.addLine( - "! Note: The following error is probably an Androidacy backend error"); + errMessage += " (" + e.getLocalizedMessage() + ")"; } - this.setInstallStateFinished(false, - "! " + errMessage, ""); + this.setInstallStateFinished(false, errMessage, null); } catch (OutOfMemoryError e) { //noinspection UnusedAssignment (Important to avoid OutOfMemoryError) rawModule = null; // Because reference is kept when calling setInstallStateFinished diff --git a/app/src/main/java/com/fox2code/mmm/utils/Http.java b/app/src/main/java/com/fox2code/mmm/utils/Http.java index acc2976..6ecc265 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Http.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Http.java @@ -36,7 +36,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import io.sentry.android.okhttp.SentryOkHttpInterceptor; import okhttp3.Cache; import okhttp3.Cookie; import okhttp3.CookieJar; @@ -100,7 +99,7 @@ public class Http { httpclientBuilder.proxy(Proxy.NO_PROXY); // Do not use system proxy Dns dns = Dns.SYSTEM; try { - InetAddress[] cloudflareBootstrap = new InetAddress[] { + InetAddress[] cloudflareBootstrap = new InetAddress[]{ InetAddress.getByName("162.159.36.1"), InetAddress.getByName("162.159.46.1"), InetAddress.getByName("1.1.1.1"), @@ -120,18 +119,18 @@ public class Http { httpclientBuilder.dns(dns); httpclientBuilder.cookieJar(new CDNCookieJar()); dns = new DnsOverHttps.Builder().client(httpclientBuilder.build()).url( - Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query"))) + Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query"))) .bootstrapDnsHosts(cloudflareBootstrap).resolvePrivateAddresses(true).build(); - } catch (UnknownHostException|RuntimeException e) { + } catch (UnknownHostException | RuntimeException e) { Log.e(TAG, "Failed to init DoH", e); } httpclientBuilder.cookieJar(CookieJar.NO_COOKIES); // User-Agent format was agreed on telegram if (hasWebView) { - androidacyUA = WebSettings.getDefaultUserAgent(mainApplication).replace("; wv", "") - + " FoxMmm/" + BuildConfig.VERSION_CODE; + androidacyUA = WebSettings.getDefaultUserAgent(mainApplication).replace("wv", "FoxMmm" + + "/" + BuildConfig.VERSION_CODE); } else { - androidacyUA = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE +")" + + androidacyUA = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE + ")" + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" + " FoxMmm/" + BuildConfig.VERSION_CODE; } @@ -197,7 +196,7 @@ public class Http { } @SuppressWarnings("resource") - public static byte[] doHttpGet(String url,boolean allowCache) throws IOException { + public static byte[] doHttpGet(String url, boolean allowCache) throws IOException { if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) { throw new IOException("Androidacy repo is disabled, blocking url: " + url); @@ -208,19 +207,19 @@ public class Http { // 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()); + 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) { + if (response.code() == 304) { response = response.cacheResponse(); if (response != null) responseBody = response.body(); } - return responseBody == null ? new byte[0] : responseBody.bytes(); + return responseBody.bytes(); } - public static byte[] doHttpPost(String url,String data,boolean allowCache) throws IOException { + public static byte[] doHttpPost(String url, String data, boolean allowCache) throws IOException { return (byte[]) doHttpPostRaw(url, data, allowCache, false); } @@ -229,8 +228,8 @@ public class Http { } @SuppressWarnings("resource") - private static Object doHttpPostRaw(String url,String data, boolean allowCache, - boolean isRedirect) throws IOException { + private static Object doHttpPostRaw(String url, String data, boolean allowCache, + boolean isRedirect) throws IOException { if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) { throw new IOException("Androidacy repo is disabled, blocking url: " + url); @@ -246,20 +245,20 @@ public class Http { // 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()); + throw new IOException("Received error code: " + response.code()); } if (isRedirect) return url; ResponseBody responseBody = response.body(); // Use cache api if used cached response - if (responseBody == null && response.code() == 304) { + if (response.code() == 304) { response = response.cacheResponse(); if (response != null) responseBody = response.body(); } - return responseBody == null ? new byte[0] : responseBody.bytes(); + return responseBody.bytes(); } - public static byte[] doHttpGet(String url,ProgressListener progressListener) throws IOException { + public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException { Log.d("Http", "Progress URL: " + url); if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) { @@ -268,7 +267,7 @@ public class Http { Response response = getHttpClient().newCall( new Request.Builder().url(url).get().build()).execute(); if (response.code() != 200 && response.code() != 204) { - throw new IOException("Received error code: "+ response.code()); + throw new IOException("Received error code: " + response.code()); } ResponseBody responseBody = Objects.requireNonNull(response.body()); InputStream inputStream = responseBody.byteStream(); @@ -287,7 +286,7 @@ public class Http { progressListener.onUpdate(0, (int) (target / divider), false); while (true) { int read = inputStream.read(buff); - if(read == -1) break; + if (read == -1) break; byteArrayOutputStream.write(buff, 0, read); downloaded += read; currentUpdate = System.currentTimeMillis(); @@ -325,15 +324,65 @@ public class Http { return cookieJar.getAndroidacyCookies(url); } + public static boolean hasWebView() { + return hasWebView; + } + + /** + * Change URL to appropriate url and force Magisk link to use latest version. + */ + public static String updateLink(String string) { + if (string.startsWith("https://cdn.jsdelivr.net/gh/Magisk-Modules-Repo/")) { + String tmp = string.substring(48); + int start = tmp.lastIndexOf('@'), + end = tmp.lastIndexOf('/'); + if ((end - 8) <= start) return string; // Skip if not a commit id + return "https://raw.githubusercontent.com/" + + tmp.substring(0, start) + "/master" + string.substring(end); + } + if (string.startsWith("https://github.com/Magisk-Modules-Repo/")) { + int i = string.lastIndexOf("/archive/"); + if (i != -1 && string.indexOf('/', i + 9) == -1) + return string.substring(0, i + 9) + "master.zip"; + } + return string; + } + + /** + * Change GitHub user-content url to jsdelivr url + * (Unused but kept as a documentation) + */ + public static String cdnIfyLink(String string) { + if (string.startsWith("https://raw.githubusercontent.com/")) { + String[] tokens = string.substring(34).split("/", 4); + if (tokens.length != 4) return string; + return "https://cdn.jsdelivr.net/gh/" + + tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3]; + } + if (string.startsWith("https://github.com/")) { + int i = string.lastIndexOf("/archive/"); + if (i == -1 || string.indexOf('/', i + 9) != -1) + return string; // Not an archive link + String[] tokens = string.substring(19).split("/", 4); + return "https://cdn.jsdelivr.net/gh/" + + tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3]; + } + return string; + } + + public interface ProgressListener { + void onUpdate(int downloaded, int total, boolean done); + } + /** * Cookie jar that allow CDN cookies, reset on app relaunch * Note: An argument can be made that it allow tracking but * caching is a better attack vector for tracking, this system * only exist to improve CDN response time, any other cookies * that are not CDN related are just simply ignored. - * + *

* Note: CDNCookies are only stored in RAM unlike https cache - * */ + */ private static class CDNCookieJar implements CookieJar { private final HashMap cookieMap = new HashMap<>(); private final boolean androidacySupport; @@ -425,17 +474,13 @@ public class Http { } } - public interface ProgressListener { - void onUpdate(int downloaded,int total, boolean done); - } - /** * FallBackDNS store successful DNS request to return them later * can help make the app to work later when the current DNS system * isn't functional or available. - * + *

* Note: DNS Cache is stored in user data. - * */ + */ private static class FallBackDNS implements Dns { private final Dns parent; private final SharedPreferences sharedPreferences; @@ -450,6 +495,31 @@ public class Http { this.fallbackCache = new HashMap<>(); } + @NonNull + private static String toString(@NonNull List inetAddresses) { + if (inetAddresses.isEmpty()) return ""; + Iterator inetAddressIterator = inetAddresses.iterator(); + StringBuilder stringBuilder = new StringBuilder(); + while (true) { + stringBuilder.append(inetAddressIterator.next().getHostAddress()); + if (!inetAddressIterator.hasNext()) + return stringBuilder.toString(); + stringBuilder.append("|"); + } + } + + @NonNull + private static List fromString(@NonNull String string) + throws UnknownHostException { + if (string.isEmpty()) return Collections.emptyList(); + String[] strings = string.split("\\|"); + ArrayList inetAddresses = new ArrayList<>(strings.length); + for (String address : strings) { + inetAddresses.add(InetAddress.getByName(address)); + } + return inetAddresses; + } + @NonNull @Override public List lookup(@NonNull String s) throws UnknownHostException { @@ -491,36 +561,16 @@ public class Http { this.fallbackCache.clear(); } } - - @NonNull - private static String toString(@NonNull List inetAddresses) { - if (inetAddresses.isEmpty()) return ""; - Iterator inetAddressIterator = inetAddresses.iterator(); - StringBuilder stringBuilder = new StringBuilder(); - while (true) { - stringBuilder.append(inetAddressIterator.next().getHostAddress()); - if (!inetAddressIterator.hasNext()) - return stringBuilder.toString(); - stringBuilder.append("|"); - } - } - - @NonNull - private static List fromString(@NonNull String string) - throws UnknownHostException { - if (string.isEmpty()) return Collections.emptyList(); - String[] strings = string.split("\\|"); - ArrayList inetAddresses = new ArrayList<>(strings.length); - for (String address : strings) { - inetAddresses.add(InetAddress.getByName(address)); - } - return inetAddresses; - } } 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]); + final byte[] data; + + private JsonRequestBody(byte[] data) { + this.data = data; + } static JsonRequestBody from(String data) { if (data == null || data.length() == 0) { @@ -529,12 +579,6 @@ public class Http { return new JsonRequestBody(data.getBytes(StandardCharsets.UTF_8)); } - final byte[] data; - - private JsonRequestBody(byte[] data) { - this.data = data; - } - @Nullable @Override public MediaType contentType() { @@ -551,50 +595,4 @@ public class Http { bufferedSink.write(this.data); } } - - public static boolean hasWebView() { - return hasWebView; - } - - /** - * Change URL to appropriate url and force Magisk link to use latest version. - */ - public static String updateLink(String string) { - if (string.startsWith("https://cdn.jsdelivr.net/gh/Magisk-Modules-Repo/")) { - String tmp = string.substring(48); - int start = tmp.lastIndexOf('@'), - end = tmp.lastIndexOf('/'); - if ((end - 8) <= start) return string; // Skip if not a commit id - return "https://raw.githubusercontent.com/" + - tmp.substring(0, start) + "/master" + string.substring(end); - } - if (string.startsWith("https://github.com/Magisk-Modules-Repo/")) { - int i = string.lastIndexOf("/archive/"); - if (i != -1 && string.indexOf('/', i + 9) == -1) - return string.substring(0, i + 9) + "master.zip"; - } - return string; - } - - /** - * Change GitHub user-content url to jsdelivr url - * (Unused but kept as a documentation) - */ - public static String cdnIfyLink(String string) { - if (string.startsWith("https://raw.githubusercontent.com/")) { - String[] tokens = string.substring(34).split("/", 4); - if (tokens.length != 4) return string; - return "https://cdn.jsdelivr.net/gh/" + - tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3]; - } - if (string.startsWith("https://github.com/")) { - int i = string.lastIndexOf("/archive/"); - if (i == -1 || string.indexOf('/', i + 9) != -1) - return string; // Not an archive link - String[] tokens = string.substring(19).split("/", 4); - return "https://cdn.jsdelivr.net/gh/" + - tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3]; - } - return string; - } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1358a5d..194914b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -176,4 +176,7 @@ The app needs to restart to apply this setting Restart App will be restarted to disable staging endpoint + Could not retrieve token from Androidacy. Please try again later. + Could not validate token for Androidacy. Please try again later. + Unable to contact Androidacy server. Check your connection and try again. diff --git a/gradle.properties b/gradle.properties index 2649381..5f80030 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=512m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -21,3 +21,4 @@ android.enableJetifier=true # Fox builds props mods org.gradle.parallel=true android.enableR8.fullMode=true +org.gradle.unsafe.configuration-cache=true