revert encryption

we'll wait for a stable jetpack security 1.1 release, this one's too slow and buggy

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/287/head
androidacy-user 1 year ago
parent b00d69cf91
commit cf671df86b

@ -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) {

@ -36,10 +36,14 @@
<uses-permission-sdk-23 android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- Open and read zips -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Write to external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Post background notifications -->
<uses-permission-sdk-23 android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Install updates -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".MainApplication"
@ -62,7 +66,8 @@
tools:targetApi="tiramisu">
<activity
android:name=".UpdateActivity"
android:exported="false" />
android:exported="false"
android:process=":update" />
<activity
android:name=".CrashHandler"
android:exported="false"
@ -70,6 +75,7 @@
<activity
android:name=".SetupActivity"
android:exported="false"
android:process=":setup"
android:label="@string/title_activity_setup"
android:theme="@style/Theme.MagiskModuleManager.NoActionBar" />

@ -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() {

@ -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);
}
}
}
}
}

@ -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()
}
}

@ -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<String> 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);
}

@ -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<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException {
protected List<RepoModule> 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;

@ -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

@ -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();

@ -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<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException {
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
List<RepoModule> 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> moduleListCache = realm.where(ModuleListCache.class).equalTo("repoId", this.id).findAll();
if (repo != null) {

@ -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);

@ -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<ModuleListCache> 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=<string>
@ -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();

@ -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<ReposList> 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);
}

@ -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);
}
}
}

@ -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;

@ -11,27 +11,26 @@
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- FrameLayout is the best way to fix blurring -->
<FrameLayout
android:id="@+id/blur_frame"
android:paddingBottom="56dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list"
android:paddingVertical="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:edgeToEdge="true" />
<!-- online modules -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_list_online"
android:paddingVertical="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
@ -108,7 +107,6 @@
android:visibility="visible"
app:compatShadowEnabled="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/swipe_refresh"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_nav_menu" />

@ -7,10 +7,9 @@
<FrameLayout
android:id="@+id/settings"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="56dp"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
android:paddingBottom="84dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -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" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fitsSystemWindowsInsets="left|right">
@ -20,15 +18,4 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Androidacy Web Interface"
android:textColor="#ff808080"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/webViewNote"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/bottom_nav_menu">
<!-- two options: "Installed" and "Online" -->
<item
android:id="@+id/back"
android:icon="@drawable/ic_baseline_extension_24"
android:title="@string/modules"
app:showAsAction="ifRoom" />
<item
android:id="@+id/settings_menu_item"
android:icon="@drawable/ic_baseline_settings_24"
android:title="@string/action_settings"
app:showAsAction="ifRoom" />
</menu>

@ -365,5 +365,12 @@
<string name="safe_module">Verified safe module</string>
<string name="safe_message">This module has been verified by the repository as safe, meaning it passes certain quality and safety thresholds, and is checked for malware.</string>
<string name="notification_update_app_desc">Enable automatically checking for app updates. Updates will not download automatically.</string>
<string name="notification_update_app_pref">Check for app updates</string><string name="notification_channel_category_app_update">App updates</string><string name="notification_channel_category_app_update_description">Notifies when an app update is available</string><string name="notification_channel_background_update_app">App update available!</string><string name="notification_channel_background_update_app_description">An update is available for FoxMMM. Tap here to update.</string>
<string name="notification_update_app_pref">Check for app updates</string>
<string name="notification_channel_category_app_update">App updates</string>
<string name="notification_channel_category_app_update_description">Notifies when an app update is available</string>
<string name="notification_channel_background_update_app">App update available!</string>
<string name="notification_channel_background_update_app_description">An update is available for FoxMMM. Tap here to update.</string>
<string name="yer_a_wizard_harry">Yer a wizard, Harry!</string>
<string name="keep_tapping_to_enter_hogwarts">Keep tapping to be admitted into Hogwarts!</string>
<string name="modules">Modules</string>
</resources>

@ -244,7 +244,7 @@
app:title="@string/contributors" />
<!-- And the translators -->
<Preference
app:enabled="false"
app:enabled="true"
app:iconSpaceReserved="false"
app:key="pref_pkg_info"
app:singleLineTitle="false"

Loading…
Cancel
Save