Update with upstream

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/27/head
androidacy-user 3 years ago
commit 154e5d715b

@ -211,6 +211,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
(int) (value * PRECISION), true) :() ->
progressIndicator.setProgressCompat(
(int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else {
@ -372,6 +373,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug();
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove())
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
@ -433,6 +435,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
(int) (value * PRECISION), true) :() ->
progressIndicator.setProgressCompat(
(int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else {
@ -470,6 +473,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false);
});
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
}

@ -204,10 +204,6 @@ public class MainApplication extends FoxApplication
private FoxThemeWrapper markwonThemeContext;
private Markwon markwon;
public static MainApplication getInstance() {
return INSTANCE;
}
public Markwon getMarkwon() {
if (this.markwon != null)
return this.markwon;

@ -10,6 +10,7 @@ import androidx.annotation.StringRes;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.module.ModuleViewListBuilder;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Http;
@ -69,6 +70,15 @@ public enum NotificationType implements NotificationTypeCst {
RepoManager.getINSTANCE().hasConnectivity();
}
},
NEED_CAPTCHA_ANDROIDACY(R.string.androidacy_need_captcha, R.drawable.ic_baseline_refresh_24, v ->
IntentHelper.openUrlAndroidacy(v.getContext(),
"https://" + Http.needCaptchaAndroidacyHost() + "/", false)) {
@Override
public boolean shouldRemove() {
return !RepoManager.isAndroidacyRepoEnabled()
|| !Http.needCaptchaAndroidacy();
}
},
NO_WEB_VIEW(R.string.no_web_view, R.drawable.ic_baseline_android_24) {
@Override
public boolean shouldRemove() {
@ -181,4 +191,8 @@ public enum NotificationType implements NotificationTypeCst {
public boolean shouldRemove() {
return false;
}
public final void autoAdd(ModuleViewListBuilder moduleViewListBuilder) {
if (!shouldRemove()) moduleViewListBuilder.addNotification(this);
}
}

@ -91,6 +91,7 @@ public final class AndroidacyActivity extends FoxActivity {
this.forceBackPressed();
return;
}
Http.markCaptchaAndroidacySolved();
if (!url.contains(AndroidacyUtil.REFERRER)) {
if (url.lastIndexOf('/') < url.lastIndexOf('?')) {
url = url + '&' + AndroidacyUtil.REFERRER;

@ -2,7 +2,6 @@ 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;
@ -14,13 +13,16 @@ 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.HttpException;
import com.fox2code.mmm.utils.PropUtils;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
@ -73,22 +75,19 @@ public final class AndroidacyRepoData extends RepoData {
return url;
}
public <string> boolean isValidToken(string token) {
public boolean isValidToken(String token) throws IOException {
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())) {
Log.e(TAG, "We are being rate limited!", e);
long time = System.currentTimeMillis();
this.androidacyBlockade = time + 3_600_000L;
} catch (HttpException e) {
if (e.getErrorCode() == 401) {
Log.w(TAG, "Invalid token, resetting...");
// Remove saved preference
SharedPreferences.Editor editor = this.cachedPreferences.edit();
editor.remove("androidacy_api_token");
editor.apply();
return false;
}
Log.w(TAG, "Invalid token, resetting...");
// Remove saved preference
SharedPreferences.Editor editor = this.cachedPreferences.edit();
editor.remove("androidacy_api_token");
editor.apply();
return false;
throw e;
}
// If status code is 200, we are good
return true;
@ -96,6 +95,7 @@ public final class AndroidacyRepoData extends RepoData {
@Override
protected boolean prepare() {
if (Http.needCaptchaAndroidacy()) return false;
// Implementation details discussed on telegram
// First, ping the server to check if it's alive
try {
@ -103,19 +103,30 @@ public final class AndroidacyRepoData extends RepoData {
} catch (Exception e) {
Log.e(TAG, "Failed to ping server", e);
// Inform user
new Thread(() -> Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_server_down, Toast.LENGTH_LONG).show()).start();
if (!HttpException.shouldTimeout(e)) {
UiThreadHandler.run(() -> Toast.makeText(MainApplication.getINSTANCE(),
R.string.androidacy_server_down, Toast.LENGTH_SHORT).show());
}
return false;
}
long time = System.currentTimeMillis();
if (this.androidacyBlockade > time) return false;
this.androidacyBlockade = time + 30_000L;
if (this.token == null) {
this.token = this.cachedPreferences.getString("pref_androidacy_api_token", null);
if (this.token != null && !this.isValidToken(this.token)) {
try {
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;
}
} else if (!this.isValidToken(this.token)) {
this.token = null;
} catch (IOException e) {
if (HttpException.shouldTimeout(e)) {
Log.e(TAG, "We are being rate limited!", e);
this.androidacyBlockade = time + 3_600_000L;
}
return false;
}
if (token == null) {
try {
@ -142,7 +153,7 @@ public final class AndroidacyRepoData extends RepoData {
// Save token to shared preference
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 (HttpException.shouldTimeout(e)) {
Log.e(TAG, "We are being rate limited!", e);
this.androidacyBlockade = time + 3_600_000L;
}
@ -308,10 +319,8 @@ public final class AndroidacyRepoData extends RepoData {
return this.token;
}
void setToken(String token) {
public void setToken(String token) {
if (Http.hasWebView()) {
// 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;
}
}

@ -464,7 +464,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
}
String originalApiKey = MainApplication.getSharedPreferences().getString("pref_androidacy_api_token", "");
String[] originalApiKeyRef = new String[]{
MainApplication.getSharedPreferences().getString("pref_androidacy_api_token", "")};
// Create the pref_androidacy_repo_api_key text input with validation
EditTextPreference prefAndroidacyRepoApiKey = findPreference("pref_androidacy_repo_api_key");
assert prefAndroidacyRepoApiKey != null;
@ -479,44 +480,57 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
prefAndroidacyRepoApiKey.setPositiveButtonText(R.string.save_api_key);
prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> {
if (originalApiKeyRef[0].equals(newValue)) return true; // Skip if nothing changed.
// Curious if this actually works - so crash the app on purpose
// throw new RuntimeException("This is a test crash");
// get original api key
String apiKey = String.valueOf(newValue);
// Show snack bar with indeterminate progress
Snackbar.make(requireView(), R.string.checking_api_key, Snackbar.LENGTH_INDEFINITE).setAction(R.string.cancel, v -> {
Snackbar.make(requireView(), R.string.checking_api_key, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.cancel, v -> {
// Restore the original api key
prefAndroidacyRepoApiKey.setText(originalApiKey);
prefAndroidacyRepoApiKey.setText(originalApiKeyRef[0]);
}).show();
// Check the API key on a background thread
new Thread(() -> {
// If key is empty, just remove it and change the text of the snack bar
if (apiKey.isEmpty()) {
MainApplication.getSharedPreferences().edit().remove("pref_androidacy_repo_api_key").apply();
new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), R.string.api_key_removed, Snackbar.LENGTH_SHORT).show());
MainApplication.getSharedPreferences().edit().remove(
"pref_androidacy_repo_api_key").apply();
new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(),
R.string.api_key_removed, Snackbar.LENGTH_SHORT).show());
} else {
// If key < 64 chars, it's not valid
if (apiKey.length() < 64) {
new Handler(Looper.getMainLooper()).post(() -> {
Snackbar.make(requireView(), R.string.api_key_invalid, Snackbar.LENGTH_SHORT).show();
// Save the original key
MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", originalApiKey).apply();
MainApplication.getSharedPreferences().edit().putString(
"pref_androidacy_api_token", originalApiKeyRef[0]).apply();
// Re-show the dialog with an error
prefAndroidacyRepoApiKey.performClick();
// Show error
prefAndroidacyRepoApiKey.setDialogMessage(getString(R.string.api_key_invalid));
});
} else {
boolean valid = AndroidacyRepoData.getInstance().isValidToken(apiKey);
boolean valid = false;
try {
valid = AndroidacyRepoData.getInstance().isValidToken(apiKey);
} catch (IOException ignored) {}
// If the key is valid, save it
if (valid) {
MainApplication.getSharedPreferences().edit().putString("pref_androidacy_repo_api_key", apiKey).apply();
new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), R.string.api_key_valid, Snackbar.LENGTH_SHORT).show());
originalApiKeyRef[0] = apiKey;
RepoManager.getINSTANCE().getAndroidacyRepoData().setToken(apiKey);
MainApplication.getSharedPreferences().edit().putString(
"pref_androidacy_repo_api_key", apiKey).apply();
new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(),
R.string.api_key_valid, Snackbar.LENGTH_SHORT).show());
} else {
new Handler(Looper.getMainLooper()).post(() -> {
Snackbar.make(requireView(), R.string.api_key_invalid, Snackbar.LENGTH_SHORT).show();
// Save the original key
MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", originalApiKey).apply();
MainApplication.getSharedPreferences().edit().putString(
"pref_androidacy_api_token", originalApiKeyRef[0]).apply();
// Re-show the dialog with an error
prefAndroidacyRepoApiKey.performClick();
// Show error

@ -2,22 +2,19 @@ package com.fox2code.mmm.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.WebSettings;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.repo.RepoManager;
@ -67,6 +64,7 @@ public class Http {
private static final CDNCookieJar cookieJar;
private static final String androidacyUA;
private static final boolean hasWebView;
private static String needCaptchaAndroidacyHost;
private static boolean doh;
static {
@ -103,7 +101,16 @@ public class Http {
httpclientBuilder.proxy(Proxy.NO_PROXY); // Do not use system proxy
Dns dns = Dns.SYSTEM;
try {
InetAddress[] cloudflareBootstrap = new InetAddress[]{InetAddress.getByName("162.159.36.1"), InetAddress.getByName("162.159.46.1"), InetAddress.getByName("1.1.1.1"), InetAddress.getByName("1.0.0.1"), InetAddress.getByName("162.159.132.53"), InetAddress.getByName("2606:4700:4700::1111"), InetAddress.getByName("2606:4700:4700::1001"), InetAddress.getByName("2606:4700:4700::0064"), InetAddress.getByName("2606:4700:4700::6400")};
InetAddress[] cloudflareBootstrap = new InetAddress[]{
InetAddress.getByName("162.159.36.1"),
InetAddress.getByName("162.159.46.1"),
InetAddress.getByName("1.1.1.1"),
InetAddress.getByName("1.0.0.1"),
InetAddress.getByName("162.159.132.53"),
InetAddress.getByName("2606:4700:4700::1111"),
InetAddress.getByName("2606:4700:4700::1001"),
InetAddress.getByName("2606:4700:4700::0064"),
InetAddress.getByName("2606:4700:4700::6400")};
dns = s -> {
if ("cloudflare-dns.com".equals(s)) {
return Arrays.asList(cloudflareBootstrap);
@ -112,16 +119,21 @@ 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"))).bootstrapDnsHosts(cloudflareBootstrap).resolvePrivateAddresses(true).build();
dns = new DnsOverHttps.Builder().client(httpclientBuilder.build()).url(
Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query")))
.bootstrapDnsHosts(cloudflareBootstrap).resolvePrivateAddresses(true).build();
} 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 + ")" + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" + " FoxMmm/" + BuildConfig.VERSION_CODE;
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;
}
httpclientBuilder.addInterceptor(chain -> {
Request.Builder request = chain.request().newBuilder();
@ -129,7 +141,8 @@ public class Http {
String host = chain.request().url().host();
if (host.endsWith(".androidacy.com")) {
request.header("User-Agent", androidacyUA);
} else if (!(host.equals("github.com") || host.endsWith(".github.com") || host.endsWith(".jsdelivr.net") || host.endsWith(".githubusercontent.com"))) {
} else if (!(host.equals("github.com") || host.endsWith(".github.com") ||
host.endsWith(".jsdelivr.net") || host.endsWith(".githubusercontent.com"))) {
if (InstallerInitializer.peekMagiskPath() != null) {
request.header("User-Agent", // Declare Magisk version to the server
"Magisk/" + InstallerInitializer.peekMagiskVersion());
@ -142,7 +155,12 @@ public class Http {
return chain.proceed(request.build());
});
// Fallback DNS cache responses in case request fail but already succeeded once in the past
fallbackDNS = new FallBackDNS(mainApplication, dns, "github.com", "api.github.com", "raw.githubusercontent.com", "camo.githubusercontent.com", "user-images.githubusercontent.com", "cdn.jsdelivr.net", "img.shields.io", "magisk-modules-repo.github.io", "www.androidacy.com", "api.androidacy.com");
fallbackDNS = new FallBackDNS(mainApplication, dns, "github.com", "api.github.com",
"raw.githubusercontent.com", "camo.githubusercontent.com",
"user-images.githubusercontent.com", "cdn.jsdelivr.net",
"img.shields.io", "magisk-modules-repo.github.io",
"www.androidacy.com", "api.androidacy.com",
"production-api.androidacy.com");
httpclientBuilder.cookieJar(cookieJar = new CDNCookieJar(cookieManager));
httpclientBuilder.dns(Dns.SYSTEM);
httpClient = followRedirects(httpclientBuilder, true).build();
@ -175,20 +193,44 @@ public class Http {
return doh ? httpClientWithCacheDoH : httpClientWithCache;
}
private static void checkNeedCaptchaAndroidacy(String url, int errorCode) {
if (errorCode == 403 && AndroidacyUtil.isAndroidacyLink(url)) {
needCaptchaAndroidacyHost = Uri.parse(url).getHost();
}
}
private static void checkNeedBlockAndroidacyRequest(String url) throws IOException {
if (!RepoManager.isAndroidacyRepoEnabled()) {
if (AndroidacyUtil.isAndroidacyLink(url)) {
throw new IOException("Androidacy repo is disabled, blocking url: " + url);
}
} else if (needCaptchaAndroidacy() && AndroidacyUtil.isAndroidacyLink(url)) {
throw new HttpException("Androidacy require the user to solve a captcha", 403);
}
}
public static boolean needCaptchaAndroidacy() {
return needCaptchaAndroidacyHost != null;
}
public static String needCaptchaAndroidacyHost() {
return needCaptchaAndroidacyHost;
}
public static void markCaptchaAndroidacySolved() {
needCaptchaAndroidacyHost = null;
}
@SuppressWarnings("resource")
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);
}
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
checkNeedBlockAndroidacyRequest(url);
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient())
.newCall(new Request.Builder().url(url).get().build()).execute();
// 200/204 == success, 304 == cache valid
if (response.code() == 403 && AndroidacyUtil.isAndroidacyLink(url)) {
// Open webview to solve captcha
Log.e(TAG, "Received 403 error code, opening webview to solve captcha");
// Show toast on main thread
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(MainApplication.getInstance(), R.string.androidacy_server_down, Toast.LENGTH_LONG).show());
} else if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
throw new IOException("Received error code: " + response.code());
if (response.code() != 200 && response.code() != 204 &&
(response.code() != 304 || !allowCache)) {
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
ResponseBody responseBody = response.body();
// Use cache api if used cached response
@ -209,21 +251,16 @@ public class Http {
@SuppressWarnings("resource")
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);
}
checkNeedBlockAndroidacyRequest(url);
Response response = (isRedirect ? getHttpClientNoRedirect() : allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute();
if (isRedirect && response.isRedirect()) {
return response.request().url().uri().toString();
}
// 200/204 == success, 304 == cache valid
if (response.code() == 403 && AndroidacyUtil.isAndroidacyLink(url)) {
// Open webview to solve captcha
Log.e(TAG, "Received 403 error code, opening webview to solve captcha");
// Show toast on main thread
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(MainApplication.getInstance(), R.string.androidacy_server_down, Toast.LENGTH_LONG).show());
} else if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
throw new IOException("Received error code: " + response.code());
if (response.code() != 200 && response.code() != 204 &&
(response.code() != 304 || !allowCache)) {
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
ResponseBody responseBody = response.body();
// Use cache api if used cached response
@ -236,12 +273,11 @@ public class Http {
public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException {
Log.d("Http", "Progress URL: " + url);
if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) {
throw new IOException("Androidacy repo is disabled, blocking url: " + url);
}
checkNeedBlockAndroidacyRequest(url);
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());
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
ResponseBody responseBody = Objects.requireNonNull(response.body());
InputStream inputStream = responseBody.byteStream();

@ -0,0 +1,40 @@
package com.fox2code.mmm.utils;
import androidx.annotation.Keep;
import java.io.IOException;
public final class HttpException extends IOException {
private final int errorCode;
HttpException(String text, int errorCode) {
super(text);
this.errorCode = errorCode;
}
@Keep
public HttpException(int errorCode) {
super("Received error code: " + errorCode);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
public boolean shouldTimeout() {
switch (errorCode) {
case 419:
case 429:
case 503:
return true;
default:
return false;
}
}
public static boolean shouldTimeout(Exception exception) {
return exception instanceof HttpException &&
((HttpException) exception).shouldTimeout();
}
}

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

@ -179,5 +179,5 @@
<string name="androidacy_failed_to_parse_token">Could not retrieve token from Androidacy. Please try again later.</string>
<string name="androidacy_failed_to_validate_token">Could not validate token for Androidacy. Please try again later.</string>
<string name="androidacy_server_down">Unable to contact Androidacy server. Check your connection and try again.</string>
<string name="show_captcha">Test the captcha webview implementation</string>
<string name="androidacy_need_captcha">Androidacy update blocked by Captcha</string>
</resources>

Loading…
Cancel
Save