diff --git a/app/build.gradle b/app/build.gradle index aadd35e..5fba460 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -347,7 +347,7 @@ dependencies { // ksp implementation "com.google.devtools.ksp:symbol-processing-api:1.8.10-1.0.9" - implementation "androidx.security:security-crypto:1.1.0-alpha04" + implementation "androidx.security:security-crypto:1.1.0-alpha05" } if (hasSentryConfig) { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ba23fd9..8cd0fd6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,10 +36,14 @@ + + - + + + + android:exported="false" + android:process=":update" /> diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 46270bc..18e4ca6 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -219,6 +219,10 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe } }); } + // update the padding of blur_frame to match the new bottom nav height + View blurFrame = findViewById(R.id.blur_frame); + blurFrame.setPadding(blurFrame.getPaddingLeft(), blurFrame.getPaddingTop(), + blurFrame.getPaddingRight(), bottomNavigationView.getHeight()); return true; }); InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() { diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index 851be53..1822928 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -22,8 +22,6 @@ import androidx.core.app.NotificationManagerCompat; import androidx.emoji2.text.DefaultEmojiCompatConfig; import androidx.emoji2.text.EmojiCompat; import androidx.emoji2.text.FontRequestEmojiCompatConfig; -import androidx.security.crypto.EncryptedSharedPreferences; -import androidx.security.crypto.MasterKey; import com.fox2code.foxcompat.app.FoxActivity; import com.fox2code.foxcompat.app.FoxApplication; @@ -41,7 +39,6 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; @@ -139,17 +136,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con } public static SharedPreferences getSharedPreferences(String store) { - MasterKey mainKeyAlias; - try { - mainKeyAlias = new MasterKey.Builder(INSTANCE.getApplicationContext()).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } - try { - return EncryptedSharedPreferences.create(INSTANCE.getApplicationContext(), store, mainKeyAlias, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } + return FoxApplication.getInitialApplication().getSharedPreferences(store, Context.MODE_PRIVATE); } public static boolean isShowcaseMode() { @@ -304,6 +291,8 @@ public class MainApplication extends FoxApplication implements androidx.work.Con true; case R.style.Theme_MagiskModuleManager_Monet_Dark, R.style.Theme_MagiskModuleManager_Dark -> false; + case R.style.Theme_MagiskModuleManager_Monet_Black, R.style.Theme_MagiskModuleManager_Black -> + false; default -> super.isLightTheme(); }; } @@ -517,24 +506,11 @@ public class MainApplication extends FoxApplication implements androidx.work.Con } } - private static class ReleaseTree extends Timber.Tree { - @Override - protected void log(int priority, String tag, @NonNull String message, Throwable t) { - // basically silently drop all logs below error, and write the rest to logcat - if (priority >= Log.ERROR) { - if (t != null) { - Log.println(priority, tag, message); - t.printStackTrace(); - } else { - Log.println(priority, tag, message); - } - } - } - } - // Access the encrypted key in the keystore, decrypt it with the secret, // and use it to open and read from the realm again public byte[] getExistingKey() { + Timber.d("Getting existing key..."); + long startTime = System.currentTimeMillis(); // open a connection to the android keystore KeyStore keyStore; try { @@ -582,6 +558,23 @@ public class MainApplication extends FoxApplication implements androidx.work.Con InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } + long endTime = System.currentTimeMillis(); + Timber.d("Got existing key in %d ms", endTime - startTime); return decryptedKey; // pass to a realm configuration via encryptionKey() } + + private static class ReleaseTree extends Timber.Tree { + @Override + protected void log(int priority, String tag, @NonNull String message, Throwable t) { + // basically silently drop all logs below error, and write the rest to logcat + if (priority >= Log.ERROR) { + if (t != null) { + Log.println(priority, tag, message); + t.printStackTrace(); + } else { + Log.println(priority, tag, message); + } + } + } + } } diff --git a/app/src/main/java/com/fox2code/mmm/SetupActivity.java b/app/src/main/java/com/fox2code/mmm/SetupActivity.java index 77a3c7e..fc04611 100644 --- a/app/src/main/java/com/fox2code/mmm/SetupActivity.java +++ b/app/src/main/java/com/fox2code/mmm/SetupActivity.java @@ -4,7 +4,6 @@ import static com.fox2code.mmm.utils.IntentHelper.getActivity; import android.annotation.SuppressLint; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -19,8 +18,6 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.FragmentActivity; -import androidx.security.crypto.EncryptedFile; -import androidx.security.crypto.MasterKey; import com.fox2code.foxcompat.app.FoxActivity; import com.fox2code.mmm.androidacy.AndroidacyRepoData; @@ -35,13 +32,10 @@ import com.google.android.material.materialswitch.MaterialSwitch; import com.topjohnwu.superuser.internal.UiThreadHandler; import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; @@ -65,9 +59,6 @@ import io.realm.RealmConfiguration; import timber.log.Timber; public class SetupActivity extends FoxActivity implements LanguageActivity { - - MasterKey mainKeyAlias; - @SuppressLint({"ApplySharedPref", "RestrictedApi"}) @Override protected void onCreate(Bundle savedInstanceState) { @@ -193,10 +184,11 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { // Set the crash reporting pref editor.putBoolean("pref_crash_reporting", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).isChecked()); // Set the repos in the ReposList realm db - RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).build(); + RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).build(); boolean androidacyRepo = ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).isChecked(); boolean magiskAltRepo = ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).isChecked(); Realm realm = Realm.getInstance(realmConfig); + realm.beginTransaction(); Objects.requireNonNull(realm.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst()).setEnabled(androidacyRepo); Objects.requireNonNull(realm.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst()).setEnabled(magiskAltRepo); // commit the changes @@ -265,13 +257,12 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { // creates the realm database private void createRealmDatabase() { Timber.d("Creating Realm databases"); + long startTime = System.currentTimeMillis(); // create encryption key Timber.d("Creating encryption key"); - // generate the encryption key and store it in the prefs - byte[] encryptionKey = getNewKey(); // create the realm database for ReposList // next, create the realm database for ReposList - RealmConfiguration config2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).encryptionKey(encryptionKey).build(); + RealmConfiguration config2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); // get the instance Realm.getInstanceAsync(config2, new Realm.Callback() { @Override @@ -311,47 +302,34 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { realm1.commitTransaction(); } }); + long endTime = System.currentTimeMillis(); + Timber.d("Realm databases created in %d ms", endTime - startTime); } public void createFiles() { try { String cookieFileName = "cookies"; - // initial set of cookies, only really used to create the keypair and encrypted file + // initial set of cookies, only really used to create the file String initialCookie = "is_foxmmm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=production-api.androidacy.com; SameSite=None; Secure;|foxmmm_version=" + BuildConfig.VERSION_CODE + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; domain=production-api.androidacy.com; SameSite=None; Secure;"; - Context context = getApplicationContext(); - mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); - EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - InputStream inputStream; - try { - inputStream = encryptedFile.openFileInput(); - } catch (FileNotFoundException e) { - Timber.d("Cookie file not found, creating new file"); - OutputStream outputStream = encryptedFile.openFileOutput(); - outputStream.write(initialCookie.getBytes()); - outputStream.close(); - outputStream.flush(); - inputStream = encryptedFile.openFileInput(); + File cookieFile = new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName); + // if the file exists, delete it + if (cookieFile.exists()) { + cookieFile.delete(); } - byte[] buffer = new byte[1024]; - int bytesRead; - StringBuilder outputString = new StringBuilder(); - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputString.append(new String(buffer, 0, bytesRead)); - } - inputStream.close(); - if (outputString.toString().isEmpty()) { - Timber.d("Cookie file is empty, writing initial cookie"); - OutputStream outputStream = encryptedFile.openFileOutput(); - outputStream.write(initialCookie.getBytes()); - outputStream.close(); - outputStream.flush(); - } - } catch (GeneralSecurityException | IOException e) { + // create the file + cookieFile.createNewFile(); + // create the file output stream + FileOutputStream fileOutputStream = new FileOutputStream(cookieFile); + // write the initial cookie to the file + fileOutputStream.write(Base64.encode(initialCookie.getBytes(), Base64.DEFAULT)); + // close the file output stream + fileOutputStream.close(); + } catch (IOException e) { Timber.e(e); } // we literally only use these to create the http cache folders - File httpCacheDir = MainApplication.getINSTANCE().getDataDirWithPath("cache/WebView/Default/HTTP Cache/Code Cache/js"); - File httpCacheDir2 = MainApplication.getINSTANCE().getDataDirWithPath("cache/WebView/Default/HTTP Cache/Code Cache/wasm"); + File httpCacheDir = MainApplication.getINSTANCE().getDataDirWithPath("cache/WebView/Default/HTTP Cache/Code Cache/wasm"); + File httpCacheDir2 = MainApplication.getINSTANCE().getDataDirWithPath("cache/WebView/Default/HTTP Cache/Code Cache/js"); if (!httpCacheDir.exists()) { if (httpCacheDir.mkdirs()) { Timber.d("Created http cache dir"); @@ -368,19 +346,27 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { @SuppressWarnings("ConstantConditions") public void disableUpdateActivityForFdroidFlavor() { if (BuildConfig.FLAVOR.equals("fdroid")) { - Timber.d("Disabling update activity for fdroid flavor"); - // disable update activity through package manager + // check if the update activity is enabled PackageManager pm = getPackageManager(); ComponentName componentName = new ComponentName(this, UpdateActivity.class); - pm.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + int componentEnabledSetting = pm.getComponentEnabledSetting(componentName); + if (componentEnabledSetting == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + Timber.d("Disabling update activity for fdroid flavor"); + // disable update activity through package manager + pm.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + } } } @SuppressLint("NewApi") public byte[] getNewKey() { + long startTime; if (MainApplication.getSharedPreferences("mmm").getBoolean("keygen", false)) { Timber.d("Key already generated, returning"); return MainApplication.getINSTANCE().getExistingKey(); + } else { + startTime = System.currentTimeMillis(); + Timber.d("Generating new key for realm"); } // open a connection to the android keystore KeyStore keyStore; @@ -438,6 +424,8 @@ public class SetupActivity extends FoxActivity implements LanguageActivity { buffer.put(encryptedKeyForRealm); MainApplication.getSharedPreferences("realm_key").edit().putString("iv_and_encrypted_key", Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP)).apply(); MainApplication.getSharedPreferences("mmm").edit().putBoolean("keygen", true).apply(); + long endTime = System.currentTimeMillis(); + Timber.d("Key generation took %s ms", endTime - startTime); return realmKey; // pass to a realm configuration via encryptionKey() } } \ 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 61102fc..0b9c436 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -39,7 +39,6 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -107,11 +106,7 @@ public final class AndroidacyActivity extends FoxActivity { String device_id = uri.getQueryParameter("device_id"); if (device_id == null) { // get from shared preferences - try { - device_id = AndroidacyRepoData.generateDeviceId(); - } catch ( - NoSuchAlgorithmException ignored) { - } + device_id = AndroidacyRepoData.generateDeviceId(); url = url + "&device_id=" + device_id; } boolean allowInstall = intent.getBooleanExtra(Constants.EXTRA_ANDROIDACY_ALLOW_INSTALL, false); @@ -145,7 +140,6 @@ public final class AndroidacyActivity extends FoxActivity { this.progressIndicator = this.findViewById(R.id.progress_bar); this.progressIndicator.setMax(100); this.webView = this.findViewById(R.id.webView); - this.webViewNote = this.findViewById(R.id.webViewNote); WebSettings webSettings = this.webView.getSettings(); webSettings.setUserAgentString(Http.getAndroidacyUA()); webSettings.setDomStorageEnabled(true); @@ -156,6 +150,17 @@ public final class AndroidacyActivity extends FoxActivity { webSettings.setAllowFileAccessFromFileURLs(false); webSettings.setAllowUniversalAccessFromFileURLs(false); webSettings.setMediaPlaybackRequiresUserGesture(false); + // if app is in dark mode, force dark mode on webview + if (MainApplication.getINSTANCE().isDarkTheme()) { + // for api 33, use setAlgorithmicDarkeningAllowed, for api 29-32 use setForceDark, for api 28 and below use setForceDarkStrategy + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + WebSettingsCompat.setAlgorithmicDarkeningAllowed(webSettings, true); + } else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + WebSettingsCompat.setForceDark(webSettings, WebSettingsCompat.FORCE_DARK_ON); + } else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { + WebSettingsCompat.setForceDarkStrategy(webSettings, WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY); + } + } // Attempt at fixing CloudFlare captcha. if (WebViewFeature.isFeatureSupported(WebViewFeature.REQUESTED_WITH_HEADER_ALLOW_LIST)) { Set allowList = new HashSet<>(); @@ -200,7 +205,6 @@ public final class AndroidacyActivity extends FoxActivity { @Override public void onPageFinished(WebView view, String url) { - webViewNote.setVisibility(View.GONE); progressIndicator.setVisibility(View.INVISIBLE); progressIndicator.setProgressCompat(0, false); } 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 a514f4f..74d40eb 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -86,7 +86,7 @@ public final class AndroidacyRepoData extends RepoData { // Generates a unique device ID. This is used to identify the device in the API for rate // limiting and fraud detection. - public static String generateDeviceId() throws NoSuchAlgorithmException { + public static String generateDeviceId() { // Try to get the device ID from the shared preferences SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("androidacy"); String deviceIdPref = sharedPreferences.getString("device_id", null); @@ -115,7 +115,13 @@ public final class AndroidacyRepoData extends RepoData { // Append it all together deviceId += deviceModel + deviceManufacturer + androidVersion; // Hash it - MessageDigest digest = MessageDigest.getInstance("SHA-256"); + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException ignored) { + // This should never happen so we can just return the original device ID + return deviceId; + } byte[] hash = digest.digest(deviceId.getBytes()); // Convert it to a hex string StringBuilder hexString = new StringBuilder(); @@ -135,7 +141,7 @@ public final class AndroidacyRepoData extends RepoData { } } - public boolean isValidToken(String token) throws IOException, NoSuchAlgorithmException { + public boolean isValidToken(String token) throws IOException { String deviceId = generateDeviceId(); try { byte[] resp = Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token + "&device_id=" + deviceId, false); @@ -172,7 +178,7 @@ public final class AndroidacyRepoData extends RepoData { @SuppressLint("RestrictedApi") @Override - protected boolean prepare() throws NoSuchAlgorithmException { + protected boolean prepare() { // If ANDROIDACY_CLIENT_ID is not set or is empty, disable this repo and return if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) { SharedPreferences.Editor editor = MainApplication.getSharedPreferences("mmm").edit(); @@ -283,7 +289,7 @@ public final class AndroidacyRepoData extends RepoData { } @Override - protected List populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException { + protected List populate(JSONObject jsonObject) throws JSONException { Timber.d("AndroidacyRepoData populate start"); String name = jsonObject.optString("name", "Androidacy Modules Repo"); String nameForModules = name.endsWith(" (Official)") ? name.substring(0, name.length() - 11) : name; @@ -420,11 +426,11 @@ public final class AndroidacyRepoData extends RepoData { } @Override - public String getUrl() throws NoSuchAlgorithmException { + public String getUrl() { return token == null ? this.url : this.url + "?token=" + token + "&v=" + BuildConfig.VERSION_CODE + "&c=" + BuildConfig.VERSION_NAME + "&device_id=" + generateDeviceId() + "&client_id=" + BuildConfig.ANDROIDACY_CLIENT_ID; } - private String injectToken(String url) throws NoSuchAlgorithmException { + private String injectToken(String url) { // Do not inject token for non Androidacy urls if (!AndroidacyUtil.isAndroidacyLink(url)) return url; diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java index 1495f30..5d05b61 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -6,7 +6,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.util.TypedValue; -import android.view.View; import android.webkit.JavascriptInterface; import android.widget.Button; import android.widget.Toast; @@ -48,7 +47,6 @@ public class AndroidacyWebAPI { boolean downloadMode; int effectiveCompatMode; int notifiedCompatMode; - private boolean allowHideNote = true; public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) { this.activity = activity; @@ -341,10 +339,6 @@ public class AndroidacyWebAPI { this.activity.runOnUiThread(() -> { this.activity.hideActionBar(); this.consumedAction = false; - if (this.allowHideNote) { - this.allowHideNote = false; - this.activity.webViewNote.setVisibility(View.GONE); - } }); } @@ -367,7 +361,7 @@ public class AndroidacyWebAPI { } /** - * Return true if the module is an Andoridacy module. + * Return true if the module is an Androidacy module. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") @JavascriptInterface diff --git a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java index 0da695a..32d6db4 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java +++ b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java @@ -94,7 +94,7 @@ public final class ModuleManager extends SyncManager { // if the dir name matches the module name, use it as the cache dir File tempCacheRoot = new File(dir.toString()); Timber.d("Looking for cache in %s", tempCacheRoot); - realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(tempCacheRoot).build(); + realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(tempCacheRoot).build(); Realm realm = Realm.getInstance(realmConfiguration); Timber.d("Looking for cache for %s out of %d", module, realm.where(ModuleListCache.class).count()); moduleListCache = realm.where(ModuleListCache.class).equalTo("codename", module).findFirst(); diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index dabea3d..e7a5892 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -23,7 +23,6 @@ import org.json.JSONObject; import java.io.File; import java.io.IOException; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -72,10 +71,6 @@ public class RepoData extends XRepo { private boolean forceHide, enabled; // Cache for speed public RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) { - // if last_shown_setup is not "v1", them=n refuse to continue - if (!cachedPreferences.getString("last_shown_setup", "").equals("v1")) { - return; - } // setup supportedProperties try { supportedProperties.put("id", ""); @@ -110,7 +105,7 @@ public class RepoData extends XRepo { this.defaultName = url; // Set url as default name this.forceHide = AppUpdateManager.shouldForceHide(this.id); // this.enable is set from the database - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm = Realm.getInstance(realmConfiguration); ReposList reposList = realm.where(ReposList.class).equalTo("id", this.id).findFirst(); if (BuildConfig.DEBUG) { @@ -156,11 +151,11 @@ public class RepoData extends XRepo { return str != null && !str.isEmpty() && !"null".equals(str); } - protected boolean prepare() throws NoSuchAlgorithmException { + protected boolean prepare() { return true; } - protected List populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException { + protected List populate(JSONObject jsonObject) throws JSONException { List newModules = new ArrayList<>(); synchronized (this.populateLock) { String name = jsonObject.getString("name").trim(); @@ -296,7 +291,7 @@ public class RepoData extends XRepo { public void setEnabled(boolean enabled) { this.enabled = enabled && !this.forceHide; // reposlist realm - RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm2 = Realm.getInstance(realmConfiguration2); realm2.executeTransaction(realm -> { ReposList reposList = realm.where(ReposList.class).equalTo("id", this.id).findFirst(); @@ -312,9 +307,13 @@ public class RepoData extends XRepo { if (MainActivity.doSetupNowRunning) { return; } + if (this.id == null) { + Timber.e("Repo ID is null"); + return; + } this.forceHide = AppUpdateManager.shouldForceHide(this.id); // reposlist realm - RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm2 = Realm.getInstance(realmConfiguration2); boolean dbEnabled; try { @@ -327,7 +326,7 @@ public class RepoData extends XRepo { this.enabled = (!this.forceHide) && dbEnabled; } - public String getUrl() throws NoSuchAlgorithmException { + public String getUrl() { return this.url; } @@ -373,12 +372,12 @@ public class RepoData extends XRepo { // should update (lastUpdate > 15 minutes) public boolean shouldUpdate() { Timber.d("Repo " + this.id + " should update check called"); - RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm2 = Realm.getInstance(realmConfiguration2); ReposList repo = realm2.where(ReposList.class).equalTo("id", this.id).findFirst(); // Make sure ModuleListCache for repoId is not null File cacheRoot = MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + this.id); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build(); Realm realm = Realm.getInstance(realmConfiguration); RealmResults moduleListCache = realm.where(ModuleListCache.class).equalTo("repoId", this.id).findAll(); if (repo != null) { diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index 4027604..55ff6a9 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -151,6 +151,10 @@ public final class RepoManager extends SyncManager { if (!MainApplication.getSharedPreferences("mmm").getString("last_shown_setup", "").equals("v1")) { return; } + // make sure repodata is not null + if (repoData == null || repoData.moduleHashMap == null) { + return; + } for (RepoModule repoModule : repoData.moduleHashMap.values()) { if (!repoModule.moduleInfo.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) { RepoModule registeredRepoModule = this.modules.get(repoModule.id); @@ -335,7 +339,7 @@ public final class RepoManager extends SyncManager { try { resp = Http.doHttpGet("https://production-api.androidacy.com/cdn-cgi/trace", false); } catch (Exception e) { - Timber.e("Failed to check internet connection. Assuming no internet connection."); + Timber.e(e, "Failed to check internet connection. Assuming no internet connection."); } // get the response body String response = new String(resp, StandardCharsets.UTF_8); diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java index 36ba289..877fb77 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java @@ -49,11 +49,11 @@ public class RepoUpdater { if (!this.repoData.shouldUpdate()) { Timber.d("Fetching index from cache for %s", this.repoData.id); File cacheRoot = MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + this.repoData.id); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build(); Realm realm = Realm.getInstance(realmConfiguration); RealmResults results = realm.where(ModuleListCache.class).equalTo("repoId", this.repoData.id).findAll(); // reposlist realm - RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm2 = Realm.getInstance(realmConfiguration2); this.toUpdate = Collections.emptyList(); this.toApply = new HashSet<>(); @@ -122,7 +122,7 @@ public class RepoUpdater { // use realm to insert to // props avail: File cacheRoot = MainApplication.getINSTANCE().getDataDirWithPath("realms/repos/" + this.repoData.id); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build(); // array with module info default values // supported properties for a module //id= @@ -333,7 +333,7 @@ public class RepoUpdater { Timber.w("Failed to get module info from %s with error %s", this.repoData.id, e.getMessage()); } this.indexRaw = null; - RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm2 = Realm.getInstance(realmConfiguration2); if (realm2.isInTransaction()) { realm2.cancelTransaction(); diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index 967b619..a359367 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -87,7 +87,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.RandomAccessFile; -import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -109,6 +108,20 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { private static final int LANGUAGE_SUPPORT_LEVEL = 1; private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper(); private static int devModeStep = 0; + @SuppressLint("RestrictedApi") + private final NavigationBarView.OnItemSelectedListener onItemSelectedListener = item -> { + int itemId = item.getItemId(); + if (itemId == R.id.back) { + startActivity(new Intent(this, MainActivity.class)); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + finish(); + return true; + } else //noinspection RedundantIfStatement + if (itemId == R.id.settings_menu_item) { + return true; + } + return false; + }; @PerformanceClass public static int getDevicePerformanceClass() { @@ -125,8 +138,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { totalCpuFreq += parseInt(line) / 1000; freqResolved++; } - } catch ( - Exception ignore) { + } catch (Exception ignore) { } } int maxCpuFreq = freqResolved == 0 ? -1 : (int) Math.ceil(totalCpuFreq / (float) freqResolved); @@ -144,21 +156,6 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return devicePerformanceClass; } - @SuppressLint("RestrictedApi") - private final NavigationBarView.OnItemSelectedListener onItemSelectedListener = item -> { - int itemId = item.getItemId(); - if (itemId == R.id.installed_menu_item || itemId == R.id.online_menu_item) { - startActivity(new Intent(this, MainActivity.class)); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - finish(); - return true; - } else //noinspection RedundantIfStatement - if (itemId == R.id.settings_menu_item) { - return true; - } - return false; - }; - @SuppressLint("RestrictedApi") @Override protected void onCreate(Bundle savedInstanceState) { @@ -171,17 +168,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { bottomNavigationView.setOnItemSelectedListener(onItemSelectedListener); if (savedInstanceState == null) { SettingsFragment settingsFragment = new SettingsFragment(); - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.settings, settingsFragment) - .commit(); + getSupportFragmentManager().beginTransaction().replace(R.id.settings, settingsFragment).commit(); } - // get height of bottomnavigationview and adjust padding of settings fragment - bottomNavigationView.post(() -> { - int bottomNavigationHeight = bottomNavigationView.getHeight(); - View settingsFragment = findViewById(R.id.settings); - settingsFragment.setPadding(0, 0, 0, bottomNavigationHeight); - }); } @Override @@ -240,8 +228,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { themePreference.setSummaryProvider(p -> themePreference.getEntry()); themePreference.setOnPreferenceChangeListener((preference, newValue) -> { // You need to reboot your device at least once to be able to access dev-mode - if (devModeStepFirstBootIgnore || !MainApplication.isFirstBoot()) - devModeStep = 1; + if (devModeStepFirstBootIgnore || !MainApplication.isFirstBoot()) devModeStep = 1; Timber.d("refreshing activity. New value: %s", newValue); // Immediately save SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit(); @@ -295,15 +282,13 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { }); // Crash reporting TwoStatePreference crashReportingPreference = findPreference("pref_crash_reporting"); - if (!SentryMain.IS_SENTRY_INSTALLED) - crashReportingPreference.setVisible(false); + if (!SentryMain.IS_SENTRY_INSTALLED) crashReportingPreference.setVisible(false); crashReportingPreference.setChecked(MainApplication.isCrashReportingEnabled()); final Object initialValue = MainApplication.isCrashReportingEnabled(); crashReportingPreference.setOnPreferenceChangeListener((preference, newValue) -> { devModeStepFirstBootIgnore = true; devModeStep = 0; - if (initialValue == newValue) - return true; + if (initialValue == newValue) return true; // Show a dialog to restart the app MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(requireContext()); materialAlertDialogBuilder.setTitle(R.string.crash_reporting_restart_title); @@ -596,16 +581,15 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { // Set summary to the last commit this build was built from @ User/Repo // Build userRepo by removing all parts of REMOTE_URL that are not the user/repo String userRepo = BuildConfig.REMOTE_URL; - // Get the index of the first slash after the protocol (https://) - int firstSlash = userRepo.indexOf('/', 8); - // Check if it ends with .git - if (userRepo.endsWith(".git")) { - // Remove the .git - userRepo = userRepo.substring(0, userRepo.length() - 4); - } - // Remove everything before the first slash - userRepo = userRepo.substring(firstSlash + 1); - linkClickable.setSummary(String.format(getString(R.string.source_code_summary), BuildConfig.COMMIT_HASH, userRepo)); + // remove .git + userRepo = userRepo.replaceAll("\\.git$", ""); + Timber.d("userRepo: %s", userRepo); + + // finalUserRepo is the user/repo part of REMOTE_URL + // get everything after .com/ or .org/ or .io/ or .me/ or .net/ or .xyz/ or .tk/ or .co/ minus .git + String finalUserRepo = userRepo.replaceAll("^(https?://)?(www\\.)?(github\\.com|gitlab\\.com|bitbucket\\.org|git\\.io|git\\.me|git\\.net|git\\.xyz|git\\.tk|git\\.co)/", ""); + linkClickable.setSummary(String.format(getString(R.string.source_code_summary), BuildConfig.COMMIT_HASH, finalUserRepo)); + Timber.d("finalUserRepo: %s", finalUserRepo); linkClickable.setOnPreferenceClickListener(p -> { if (devModeStep == 2) { devModeStep = 0; @@ -622,7 +606,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return true; } // build url from BuildConfig.REMOTE_URL and BuildConfig.COMMIT_HASH. May have to remove the .git at the end - IntentHelper.openUrl(p.getContext(), BuildConfig.REMOTE_URL + "/tree/" + BuildConfig.COMMIT_HASH); + IntentHelper.openUrl(p.getContext(), finalUserRepo + "/tree/" + BuildConfig.COMMIT_HASH); return true; }); linkClickable.setOnPreferenceLongClickListener(p -> { @@ -678,8 +662,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { while ((line = bufferedReader.readLine()) != null) { fileOutputStream.write((line + "\n").getBytes()); } - } catch ( - IOException e) { + } catch (IOException e) { e.printStackTrace(); Toast.makeText(requireContext(), R.string.error_saving_logs, Toast.LENGTH_SHORT).show(); return true; @@ -749,6 +732,25 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { // Set the summary of pref_pkg_info to something like default-debug v1.0 (123) (Official) String pkgInfo = getString(R.string.pref_pkg_info_summary, flavor + "-" + type, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, MainApplication.isOfficial ? getString(R.string.official) : getString(R.string.unofficial)); findPreference("pref_pkg_info").setSummary(pkgInfo); + // special easter egg :) + var ref = new Object() { + int versionClicks = 0; + }; + findPreference("pref_pkg_info").setOnPreferenceClickListener(p -> { + ref.versionClicks++; + Timber.d("Version clicks: %d", ref.versionClicks); + // if it's been 3 clicks, toast "yer a wizard, harry" or "keep tapping to enter hogwarts" + if (ref.versionClicks == 3) { + // pick 1 or 2 + int random = new Random().nextInt(2) + 1; + Toast.makeText(p.getContext(), random == 1 ? R.string.yer_a_wizard_harry : R.string.keep_tapping_to_enter_hogwarts, Toast.LENGTH_SHORT).show(); + } + if (ref.versionClicks == 7) { + ref.versionClicks = 0; + IntentHelper.openUrl(p.getContext(), "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + } + return true; + }); } private void openFragment(Fragment fragment, @StringRes int title) { @@ -767,8 +769,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { private int currentLanguageLevel() { int declaredLanguageLevel = this.getResources().getInteger(R.integer.language_support_level); - if (declaredLanguageLevel != LANGUAGE_SUPPORT_LEVEL) - return declaredLanguageLevel; + if (declaredLanguageLevel != LANGUAGE_SUPPORT_LEVEL) return declaredLanguageLevel; if (!this.getResources().getConfiguration().getLocales().get(0).getLanguage().equals("en") && this.getResources().getString(R.string.notification_update_pref).equals("Background modules update check") && this.getResources().getString(R.string.notification_update_desc).equals("May increase battery usage")) { return 0; } @@ -850,7 +851,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { }); } // Get magisk_alt_repo enabled state from realm db - RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm1 = Realm.getInstance(realmConfig); ReposList reposList = realm1.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst(); if (reposList != null) { @@ -885,7 +886,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { SwitchPreferenceCompat switchPreferenceCompat = (SwitchPreferenceCompat) androidacyRepoEnabled; switchPreferenceCompat.setChecked(false); // Disable in realm db - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm = Realm.getInstance(realmConfiguration); realm.executeTransaction(realm2 -> { ReposList repoRealmResults = realm2.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst(); @@ -898,7 +899,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { }); } // get if androidacy repo is enabled from realm db - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm = Realm.getInstance(realmConfiguration); ReposList repoRealmResults = realm.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst(); if (repoRealmResults == null) { @@ -958,8 +959,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return false; } // Make sure originalApiKeyRef is not null - if (originalApiKeyRef[0].equals(newValue)) - return true; + if (originalApiKeyRef[0].equals(newValue)) return true; // get original api key String apiKey = String.valueOf(newValue); // Show snack bar with indeterminate progress @@ -1010,9 +1010,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { boolean valid = false; try { valid = AndroidacyRepoData.getInstance().isValidToken(apiKey); - } catch ( - IOException | - NoSuchAlgorithmException ignored) { + } catch (IOException ignored) { } // If the key is valid, save it if (valid) { @@ -1058,7 +1056,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { @SuppressLint("RestrictedApi") public void updateCustomRepoList(boolean initial) { - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm = Realm.getInstance(realmConfiguration); // get all repos that are not built-in int CUSTOM_REPO_ENTRIES = 0; @@ -1074,8 +1072,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { setRepoData(repoData, "pref_custom_repo_" + i); if (initial) { Preference preference = findPreference("pref_custom_repo_" + i + "_delete"); - if (preference == null) - continue; + if (preference == null) continue; final int index = i; preference.setOnPreferenceClickListener(preference1 -> { realm.beginTransaction(); @@ -1088,13 +1085,11 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { } } Preference preference = findPreference("pref_custom_add_repo"); - if (preference == null) - return; + if (preference == null) return; preference.setVisible(customRepoManager.canAddRepo() && customRepoManager.getRepoCount() < CUSTOM_REPO_ENTRIES); if (initial) { // Custom repo add button part. preference = findPreference("pref_custom_add_repo_button"); - if (preference == null) - return; + if (preference == null) return; int finalCUSTOM_REPO_ENTRIES = CUSTOM_REPO_ENTRIES; preference.setOnPreferenceClickListener(preference1 -> { final Context context = this.requireContext(); @@ -1119,8 +1114,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { try { customRepoData.quickPrePopulate(); UiThreadHandler.handler.post(() -> updateCustomRepoList(false)); - } catch ( - Exception e) { + } catch (Exception e) { Timber.e(e); // show new dialog new Handler(Looper.getMainLooper()).post(() -> new MaterialAlertDialogBuilder(context).setTitle(R.string.error_adding).setMessage(e.getMessage()).setPositiveButton(android.R.string.ok, (dialog1, which1) -> { @@ -1175,16 +1169,14 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { } private void setRepoData(final RepoData repoData, String preferenceName) { - if (repoData == null) - return; + if (repoData == null) return; Timber.d("Setting preference " + preferenceName + " to " + repoData); ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE); Preference preference = findPreference(preferenceName); - if (preference == null) - return; + if (preference == null) return; if (!preferenceName.contains("androidacy") && !preferenceName.contains("magisk_alt_repo")) { if (repoData != null) { - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(MainApplication.getINSTANCE().getExistingKey()).build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); Realm realm = Realm.getInstance(realmConfiguration); RealmResults repoDataRealmResults = realm.where(ReposList.class).equalTo("id", repoData.id).findAll(); Timber.d("Setting preference " + preferenceName + " because it is not the Androidacy repo or the Magisk Alt Repo"); @@ -1305,8 +1297,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { private void hideRepoData(String preferenceName) { Preference preference = findPreference(preferenceName); - if (preference == null) - return; + if (preference == null) return; preference.setVisible(false); } diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java b/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java index 3dab63f..04691ad 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java +++ b/app/src/main/java/com/fox2code/mmm/utils/io/AddCookiesInterceptor.java @@ -1,16 +1,16 @@ package com.fox2code.mmm.utils.io; import android.content.Context; +import android.util.Base64; import androidx.annotation.NonNull; -import androidx.security.crypto.EncryptedFile; -import androidx.security.crypto.MasterKey; - -import com.fox2code.mmm.MainApplication; +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; import java.util.regex.Pattern; import okhttp3.Interceptor; @@ -31,31 +31,27 @@ public class AddCookiesInterceptor implements Interceptor { public Response intercept(Interceptor.Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); - // Cookies are stored in an encrypted file in the files directory in our app data - // so we need to decrypt the file before using it - // first, get our decryption key from MasterKey using the AES_256_GCM encryption scheme - // then, create an EncryptedFile object using the key and the file name - // finally, open the file and read the contents into a string - // the string is then split into an array of cookies - // the cookies are then added to the request builder - String cookieFileName = "cookies"; - String[] cookies = new String[0]; - MasterKey mainKeyAlias; - try { - mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); - EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - InputStream inputStream = encryptedFile.openFileInput(); - byte[] buffer = new byte[1024]; - int bytesRead; - StringBuilder outputString = new StringBuilder(); - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputString.append(new String(buffer, 0, bytesRead)); + String[] cookies; + // cookies are split by | so we can split the string into an array of cookies + File cookieFile = new File(context.getFilesDir(), cookieFileName); + if (cookieFile.exists()) { + // read the file into a string + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(cookieFile))); + char[] buf = new char[1024]; + int read; + StringBuilder stringBuilder = new StringBuilder(); + while ((read = bufferedReader.read(buf)) != -1) { + stringBuilder.append(buf, 0, read); } - cookies = outputString.toString().split("\\|"); - inputStream.close(); - } catch (Exception e) { - Timber.e(e, "Error reading cookies from file"); + bufferedReader.close(); + String cookieString = stringBuilder.toString(); + // base64 decode the string + cookieString = Arrays.toString(Base64.decode(cookieString, Base64.DEFAULT)); + // split the string into an array of cookies + cookies = cookieString.split("\\|"); + } else { + cookies = new String[0]; } for (String cookie : cookies) { @@ -77,9 +73,11 @@ public class AddCookiesInterceptor implements Interceptor { Exception ignored) { } } else { - // yeet any newlines from the cookie - cookie = cookie.replaceAll("[\\r\\n]", ""); - builder.addHeader("Cookie", cookie); + try { + builder.addHeader("Cookie", cookie); + } catch (Exception e) { + Timber.e(e, "Error adding cookie to request: %s", cookie); + } } } diff --git a/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java b/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java index 6083e41..fd24463 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java +++ b/app/src/main/java/com/fox2code/mmm/utils/io/ReceivedCookiesInterceptor.java @@ -2,16 +2,16 @@ package com.fox2code.mmm.utils.io; import android.annotation.SuppressLint; import android.content.Context; +import android.util.Base64; import androidx.annotation.NonNull; -import androidx.security.crypto.EncryptedFile; -import androidx.security.crypto.MasterKey; - -import com.fox2code.mmm.MainApplication; +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Arrays; import java.util.HashSet; @@ -43,28 +43,23 @@ public class ReceivedCookiesInterceptor implements Interceptor { cookieBuffer.deleteCharAt(lastPipe); } - // Cookies are stored in an encrypted file in the files directory in our app data - // so we need to decrypt the file before using it - // first, get our decryption key from MasterKey using the AES_256_GCM encryption scheme - // then, create an EncryptedFile object using the key and the file name - // finally, open the file and read the contents into a string - // the string is then split into an array of cookies - String cookieFileName = "cookies"; String[] cookies = new String[0]; - MasterKey mainKeyAlias; try { - mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); - EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - InputStream inputStream = encryptedFile.openFileInput(); - byte[] buffer = new byte[1024]; - int bytesRead; - StringBuilder outputString = new StringBuilder(); - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputString.append(new String(buffer, 0, bytesRead)); + File cookieFile = new File(context.getFilesDir(), cookieFileName); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(cookieFile))); + char[] buf = new char[1024]; + int read; + StringBuilder stringBuilder = new StringBuilder(); + while ((read = bufferedReader.read(buf)) != -1) { + stringBuilder.append(buf, 0, read); } - cookies = outputString.toString().split("\\|"); - inputStream.close(); + bufferedReader.close(); + String cookieString = stringBuilder.toString(); + // base64 decode the string + cookieString = Arrays.toString(Base64.decode(cookieString, Base64.DEFAULT)); + // split the string into an array of cookies + cookies = cookieString.split("\\|"); } catch (Exception e) { Timber.e(e, "Error reading cookies from file"); } @@ -93,14 +88,10 @@ public class ReceivedCookiesInterceptor implements Interceptor { } // write the new cookies to the file - try { - mainKeyAlias = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(); - EncryptedFile encryptedFile = new EncryptedFile.Builder(context, new File(MainApplication.getINSTANCE().getFilesDir(), cookieFileName), mainKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build(); - encryptedFile.openFileOutput().write(newCookieBuffer.toString().getBytes()); - } catch (Exception e) { - Timber.e(e, "Error writing cookies to file"); - } - + File cookieFile = new File(context.getFilesDir(), cookieFileName); + FileOutputStream fileOutputStream = new FileOutputStream(cookieFile); + fileOutputStream.write(newCookieBuffer.toString().getBytes()); + fileOutputStream.close(); } return originalResponse; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 065aa7c..5f11073 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,27 +11,26 @@ diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml index 625e7f8..c8fd443 100644 --- a/app/src/main/res/layout/settings_activity.xml +++ b/app/src/main/res/layout/settings_activity.xml @@ -7,10 +7,9 @@ @@ -25,5 +24,5 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:menu="@menu/bottom_nav_menu" /> + app:menu="@menu/bottom_nav_menu_settings" /> \ No newline at end of file diff --git a/app/src/main/res/layout/webview.xml b/app/src/main/res/layout/webview.xml index 40ce75c..b99abae 100644 --- a/app/src/main/res/layout/webview.xml +++ b/app/src/main/res/layout/webview.xml @@ -1,8 +1,6 @@ - @@ -20,15 +18,4 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu_settings.xml b/app/src/main/res/menu/bottom_nav_menu_settings.xml new file mode 100644 index 0000000..19d538b --- /dev/null +++ b/app/src/main/res/menu/bottom_nav_menu_settings.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5af50e0..d0edc1d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -365,5 +365,12 @@ Verified safe module This module has been verified by the repository as safe, meaning it passes certain quality and safety thresholds, and is checked for malware. Enable automatically checking for app updates. Updates will not download automatically. - Check for app updatesApp updatesNotifies when an app update is availableApp update available!An update is available for FoxMMM. Tap here to update. + Check for app updates + App updates + Notifies when an app update is available + App update available! + An update is available for FoxMMM. Tap here to update. + Yer a wizard, Harry! + Keep tapping to be admitted into Hogwarts! + Modules diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index ed7b4f0..d9cca61 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -244,7 +244,7 @@ app:title="@string/contributors" />