diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index bc14195..f03645d 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -54,7 +54,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001 private static final Shell.Builder shellBuilder; private static final long secret; - @SuppressLint("RestrictedApi") // Use FoxProcess wrapper helper. + @SuppressLint("RestrictedApi") + // Use FoxProcess wrapper helper. private static final boolean wrapped = !FoxProcessExt.isRootLoader(); private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().locale; private static SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale); @@ -63,7 +64,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con @SuppressLint("StaticFieldLeak") private static MainApplication INSTANCE; private static boolean firstBoot; - private static boolean loadSentryInitialized; static { Shell.setDefaultBuilder(shellBuilder = Shell.Builder.create().setFlags(Shell.FLAG_REDIRECT_STDERR).setTimeout(10).setInitializers(InstallerInitializer.class)); @@ -142,7 +142,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con } public static boolean isDeveloper() { - if (BuildConfig.DEBUG) return true; + if (BuildConfig.DEBUG) + return true; return getSharedPreferences().getBoolean("developer", false); } @@ -171,9 +172,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con } public static boolean isCrashReportingEnabled() { - return SentryMain.IS_SENTRY_INSTALLED && - getSharedPreferences().getBoolean("pref_crash_reporting", - BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING); + return SentryMain.IS_SENTRY_INSTALLED && getSharedPreferences().getBoolean("pref_crash_reporting", BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING); } public static SharedPreferences getBootSharedPreferences() { @@ -194,7 +193,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con } public Markwon getMarkwon() { - if (this.markwon != null) return this.markwon; + if (this.markwon != null) + return this.markwon; FoxThemeWrapper contextThemeWrapper = this.markwonThemeContext; if (contextThemeWrapper == null) { contextThemeWrapper = this.markwonThemeContext = new FoxThemeWrapper(this, this.managerThemeResId); @@ -276,7 +276,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con @Override public void onCreate() { - if (INSTANCE == null) INSTANCE = this; + if (INSTANCE == null) + INSTANCE = this; relPackageName = this.getPackageName(); super.onCreate(); SharedPreferences sharedPreferences = MainApplication.getSharedPreferences(); @@ -362,24 +363,30 @@ public class MainApplication extends FoxApplication implements androidx.work.Con String[] children = cacheDir.list(); if (children != null) { for (String s : children) { - if (BuildConfig.DEBUG) Log.w("MainApplication", "Deleting " + s); + if (BuildConfig.DEBUG) + Log.w("MainApplication", "Deleting " + s); if (!s.equals("lib")) { if (!new File(cacheDir, s).delete()) { - if (BuildConfig.DEBUG) Log.w("MainApplication", "Failed to delete " + s); + if (BuildConfig.DEBUG) + Log.w("MainApplication", "Failed to delete " + s); } } } } } - if (BuildConfig.DEBUG) Log.w("MainApplication", "Deleting cache dir"); + if (BuildConfig.DEBUG) + Log.w("MainApplication", "Deleting cache dir"); this.deleteSharedPreferences("mmm_boot"); this.deleteSharedPreferences("mmm"); this.deleteSharedPreferences("sentry"); this.deleteSharedPreferences("androidacy"); - if (BuildConfig.DEBUG) Log.w("MainApplication", "Deleting shared prefs"); + if (BuildConfig.DEBUG) + Log.w("MainApplication", "Deleting shared prefs"); this.getPackageManager().clearPackagePreferredActivities(this.getPackageName()); - if (BuildConfig.DEBUG) Log.w("MainApplication", "Done clearing app data"); - } catch (Exception e) { + if (BuildConfig.DEBUG) + Log.w("MainApplication", "Done clearing app data"); + } catch ( + Exception e) { Log.e("MainApplication", "Failed to clear app data", e); } } 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 9963be6..406b36a 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -1,7 +1,9 @@ package com.fox2code.mmm.androidacy; +import android.annotation.SuppressLint; +import android.content.Intent; import android.content.SharedPreferences; -import android.os.Looper; +import android.net.Uri; import android.util.Log; import android.widget.Toast; @@ -26,6 +28,8 @@ import org.json.JSONObject; import java.io.File; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -64,7 +68,8 @@ public final class AndroidacyRepoData extends RepoData { if (!modulesJson.createNewFile()) { throw new IOException("Failed to create modules.json"); } - } catch (IOException e) { + } catch ( + IOException e) { e.printStackTrace(); } } @@ -144,7 +149,8 @@ public final class AndroidacyRepoData extends RepoData { String deviceId = generateDeviceId(); try { Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token + "&device_id=" + deviceId, false); - } catch (HttpException e) { + } catch ( + HttpException e) { if (e.getErrorCode() == 401) { Log.w(TAG, "Invalid token, resetting..."); // Remove saved preference @@ -159,6 +165,7 @@ public final class AndroidacyRepoData extends RepoData { return true; } + @SuppressLint("RestrictedApi") @Override protected boolean prepare() throws NoSuchAlgorithmException { // If ANDROIDACY_CLIENT_ID is not set or is empty, disable this repo and return @@ -168,28 +175,44 @@ public final class AndroidacyRepoData extends RepoData { editor.apply(); return false; } - if (Http.needCaptchaAndroidacy()) return false; + if (Http.needCaptchaAndroidacy()) + return false; // Implementation details discussed on telegram // First, ping the server to check if it's alive try { - Http.doHttpGet("https://" + this.host + "/ping", false); - } catch (Exception e) { + HttpURLConnection connection = (HttpURLConnection) new URL("https://" + this.host + "/ping").openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.connect(); + if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) { + // If it's a 400, the app is probably outdated. Show a snackbar suggesting user update app and webview + if (connection.getResponseCode() == 400) { + // Show a dialog using androidacy_update_needed string + new MaterialAlertDialogBuilder(MainApplication.getINSTANCE()) + .setTitle(R.string.androidacy_update_needed) + .setMessage(R.string.androidacy_update_needed_message) + .setPositiveButton(R.string.update, (dialog, which) -> { + // Open the app's page on the Play Store + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("https://github.com/Fox2Code/FoxMagiskModuleManager/releases/latest")); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApplication.getINSTANCE().startActivity(intent); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + return false; + } + } catch ( + Exception e) { Log.e(TAG, "Failed to ping server", e); - // Inform user - Looper.prepare(); - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(MainApplication.getINSTANCE().getBaseContext()); - builder.setTitle("Androidacy Server Down"); - builder.setMessage("The Androidacy server is down. Unfortunately, this means that you" + - " will not be able to download or view modules from the Androidacy repository" + - ". Please try again later."); - builder.setPositiveButton("OK", (dialog, which) -> dialog.dismiss()); - builder.show(); - Looper.loop(); return false; } String deviceId = generateDeviceId(); long time = System.currentTimeMillis(); - if (this.androidacyBlockade > time) return true; // fake it till you make it. Basically, + if (this.androidacyBlockade > time) + return true; // fake it till you make it. Basically, // don'e fail just becaue we're rate limited. API and web rate limits are different. this.androidacyBlockade = time + 30_000L; try { @@ -206,7 +229,8 @@ public final class AndroidacyRepoData extends RepoData { } this.token = null; } - } catch (IOException e) { + } catch ( + IOException e) { if (HttpException.shouldTimeout(e)) { Log.e(TAG, "We are being rate limited!", e); this.androidacyBlockade = time + 3_600_000L; @@ -222,7 +246,8 @@ public final class AndroidacyRepoData extends RepoData { try { JSONObject jsonObject = new JSONObject(token); token = jsonObject.getString("token"); - } catch (JSONException e) { + } 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(); @@ -239,7 +264,8 @@ public final class AndroidacyRepoData extends RepoData { SharedPreferences.Editor editor = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).edit(); editor.putString("pref_androidacy_api_token", token); editor.apply(); - } catch (Exception e) { + } catch ( + Exception e) { if (HttpException.shouldTimeout(e)) { Log.e(TAG, "We are being rate limited!", e); this.androidacyBlockade = time + 3_600_000L; @@ -324,7 +350,8 @@ public final class AndroidacyRepoData extends RepoData { 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) { + } catch ( + Exception e) { moduleInfo.minMagisk = 0; } moduleInfo.needRamdisk = jsonObject.optBoolean("needRamdisk", false); @@ -370,13 +397,13 @@ public final class AndroidacyRepoData extends RepoData { @Override public String getUrl() throws NoSuchAlgorithmException { - return this.token == null ? this.url : - this.url + "?token=" + this.token + "&v=" + BuildConfig.VERSION_CODE + "&c=" + BuildConfig.VERSION_NAME + "&device_id=" + generateDeviceId(); + return this.token == null ? this.url : this.url + "?token=" + this.token + "&v=" + BuildConfig.VERSION_CODE + "&c=" + BuildConfig.VERSION_NAME + "&device_id=" + generateDeviceId(); } private String injectToken(String url) throws NoSuchAlgorithmException { // 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://production-api.androidacy.com/")) { Log.e(TAG, "Got non test mode url: " + AndroidacyUtil.hideToken(url)); 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 3fd5ee7..bb1f6b0 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Http.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Http.java @@ -19,7 +19,6 @@ import com.fox2code.mmm.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.androidacy.AndroidacyUtil; import com.fox2code.mmm.installer.InstallerInitializer; -import com.fox2code.mmm.repo.RepoManager; import com.google.net.cronet.okhttptransport.CronetInterceptor; import org.chromium.net.CronetEngine; @@ -78,7 +77,8 @@ public class Http { System.err.flush(); try { Os.kill(Os.getpid(), 9); - } catch (ErrnoException e) { + } catch ( + ErrnoException e) { System.exit(9); } throw error; @@ -88,7 +88,8 @@ public class Http { cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); cookieManager.flush(); // Make sure the instance work - } catch (Throwable t) { + } catch ( + Throwable t) { cookieManager = null; Log.e(TAG, "No WebView support!", t); } @@ -111,7 +112,9 @@ 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(); - } catch (UnknownHostException | RuntimeException e) { + } catch ( + UnknownHostException | + RuntimeException e) { Log.e(TAG, "Failed to init DoH", e); } httpclientBuilder.cookieJar(CookieJar.NO_COOKIES); @@ -165,7 +168,8 @@ public class Http { builder.addQuicHint("sentry.io", 443, 443); CronetEngine engine = builder.build(); httpclientBuilder.addInterceptor(CronetInterceptor.newBuilder(engine).build()); - } catch (Exception e) { + } catch ( + Exception e) { Log.e(TAG, "Failed to init cronet", e); // Gracefully fallback to okhttp } @@ -205,16 +209,6 @@ public class Http { } } - 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; } @@ -246,7 +240,8 @@ public class Http { // Use cache api if used cached response if (response.code() == 304) { response = response.cacheResponse(); - if (response != null) responseBody = response.body(); + if (response != null) + responseBody = response.body(); } return responseBody.bytes(); } @@ -257,7 +252,8 @@ public class Http { @SuppressWarnings("resource") private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException { - if (BuildConfig.DEBUG) Log.i(TAG, "POST " + url + " " + data); + if (BuildConfig.DEBUG) + Log.i(TAG, "POST " + url + " " + data); Response response; response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute(); if (response.isRedirect()) { @@ -274,13 +270,15 @@ public class Http { // Use cache api if used cached response if (response.code() == 304) { response = response.cacheResponse(); - if (response != null) responseBody = response.body(); + if (response != null) + responseBody = response.body(); } return responseBody.bytes(); } public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException { - if (BuildConfig.DEBUG) Log.i("Http", "GET " + url.split("\\?")[0]); + if (BuildConfig.DEBUG) + Log.i("Http", "GET " + url.split("\\?")[0]); Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute(); if (response.code() != 200 && response.code() != 204) { Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code()); @@ -304,7 +302,8 @@ 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(); @@ -420,11 +419,14 @@ public class Http { @NonNull @Override public List loadForRequest(@NonNull HttpUrl httpUrl) { - if (!httpUrl.isHttps()) return Collections.emptyList(); + if (!httpUrl.isHttps()) + return Collections.emptyList(); if (this.androidacySupport && httpUrl.host().endsWith(".androidacy.com")) { - if (this.cookieManager == null) return this.androidacyCookies; + if (this.cookieManager == null) + return this.androidacyCookies; String cookies = this.cookieManager.getCookie(httpUrl.uri().toString()); - if (cookies == null || cookies.isEmpty()) return Collections.emptyList(); + if (cookies == null || cookies.isEmpty()) + return Collections.emptyList(); String[] splitCookies = cookies.split(";"); ArrayList cookieList = new ArrayList<>(splitCookies.length); for (String cookie : splitCookies) { @@ -438,7 +440,8 @@ public class Http { @Override public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List cookies) { - if (!httpUrl.isHttps()) return; + if (!httpUrl.isHttps()) + return; if (this.androidacySupport && httpUrl.host().endsWith(".androidacy.com")) { if (this.cookieManager == null) { if (httpUrl.host().equals(".androidacy.com") || !cookies.isEmpty()) @@ -496,19 +499,22 @@ public class Http { @NonNull private static String toString(@NonNull List inetAddresses) { - if (inetAddresses.isEmpty()) return ""; + 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(); + if (!inetAddressIterator.hasNext()) + return stringBuilder.toString(); stringBuilder.append("|"); } } @NonNull private static List fromString(@NonNull String string) throws UnknownHostException { - if (string.isEmpty()) return Collections.emptyList(); + if (string.isEmpty()) + return Collections.emptyList(); String[] strings = string.split("\\|"); ArrayList inetAddresses = new ArrayList<>(strings.length); for (String address : strings) { @@ -524,20 +530,24 @@ public class Http { List addresses; synchronized (this.fallbackCache) { addresses = this.fallbackCache.get(s); - if (addresses != null) return addresses; + if (addresses != null) + return addresses; try { addresses = this.parent.lookup(s); if (addresses.isEmpty() || addresses.get(0).isLoopbackAddress()) throw new UnknownHostException(s); this.fallbackCache.put(s, addresses); this.sharedPreferences.edit().putString(s.replace('.', '_'), toString(addresses)).apply(); - } catch (UnknownHostException e) { + } catch ( + UnknownHostException e) { String key = this.sharedPreferences.getString(s.replace('.', '_'), ""); - if (key.isEmpty()) throw e; + if (key.isEmpty()) + throw e; try { addresses = fromString(key); this.fallbackCache.put(s, addresses); - } catch (UnknownHostException e2) { + } catch ( + UnknownHostException e2) { this.sharedPreferences.edit().remove(s.replace('.', '_')).apply(); throw e; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4359e52..1597001 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -248,5 +248,12 @@ This is a debug build. Expect some bugs and worse performance. Androidacy Repo Magisk Alt Repo - You\'ve enabled or disabled a repo. Please refresh the module list or restart the app.FinishChoose a themeChoose a themeSystem themeLight themeDark themeAMOLED Black themeTransparent light theme - will disable monet and blur!Choose a themeThemeSystemDarkAMOLED BlackLight (transparency)Light + You\'ve enabled or disabled a repo. Please refresh the module list or restart the app.FinishChoose a themeChoose a themeSystem themeLight themeDark themeAMOLED Black themeTransparent light theme - will disable monet and blur!Choose a themeThemeSystem + Dark + AMOLED Black + Light (transparency) + Light + This app is outdated. + Please update the app to the latest version. + Your webview is outdated! Please update it.