rework a bunch fof stuff

work around alt repo crap (incomplete)

update workflow - fixes #296

etc etc

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/299/head
androidacy-user 1 year ago
parent 1369043f8b
commit 92a0d50443

@ -12,7 +12,7 @@ on:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
build:
@ -33,16 +33,20 @@ jobs:
java-version: 17
distribution: 'adopt'
cache: gradle
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Change wrapper permissions
run: chmod +x ./gradlew
- name: Run tests
run: ./gradlew test
- name: Build apk debug
run: ./gradlew app:assembleDefaultDebug
# will not upload, just build to check if it builds
- name: Build apk fdroid-debug
run: ./gradlew app:assembleFdroidDebug
@ -54,58 +58,27 @@ jobs:
with:
name: FoxMMM-default-arm64-v8a-debug
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-arm64-v8a-debug.apk
- name: Upload FoxMMM-default-armeabi-v7a-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-default-armeabi-v7a-debug
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-armeabi-v7a-debug.apk
- name: Upload FoxMMM-default-universal-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-default-universal-debug
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-universal-debug.apk
- name: Upload FoxMMM-default-x86-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-default-x86-debug
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-x86-debug.apk
- name: Upload FoxMMM-default-x86_64-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-default-x86_64-debug
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-x86_64-debug.apk
# FoxMMM-fdroid-debug
- name: Upload FoxMMM-fdroid-arm64-v8a-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-fdroid-arm64-v8a-debug
path: app/build/outputs/apk/fdroid/debug/FoxMMM-v*-fdroid-arm64-v8a-debug.apk
- name: Upload FoxMMM-fdroid-armeabi-v7a-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-fdroid-armeabi-v7a-debug
path: app/build/outputs/apk/fdroid/debug/FoxMMM-v*-fdroid-armeabi-v7a-debug.apk
- name: Upload FoxMMM-fdroid-universal-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-fdroid-universal-debug
path: app/build/outputs/apk/fdroid/debug/FoxMMM-v*-fdroid-universal-debug.apk
- name: Upload FoxMMM-fdroid-x86-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-fdroid-x86-debug
path: app/build/outputs/apk/fdroid/debug/FoxMMM-v*-fdroid-x86-debug.apk
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-x86-debug.apk
- name: Upload FoxMMM-fdroid-x86_64-debug
- name: Upload FoxMMM-default-x86_64-debug
uses: actions/upload-artifact@v3
with:
name: FoxMMM-fdroid-x86_64-debug
path: app/build/outputs/apk/fdroid/debug/FoxMMM-v*-fdroid-x86_64-debug.apk
name: FoxMMM-default-x86_64-debug
path: app/build/outputs/apk/default/debug/FoxMMM-v*-default-x86_64-debug.apk

@ -295,13 +295,13 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
//noinspection GradleDependency
implementation "androidx.activity:activity-ktx:1.7.0-beta01"
implementation 'androidx.emoji2:emoji2:1.2.0'
implementation 'androidx.emoji2:emoji2-views-helper:1.2.0'
implementation 'androidx.emoji2:emoji2:1.3.0'
implementation 'androidx.emoji2:emoji2-views-helper:1.3.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.webkit:webkit:1.6.0'
implementation 'androidx.webkit:webkit:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0'
implementation "dev.rikka.rikkax.insets:insets:1.3.0"
@ -312,7 +312,7 @@ dependencies {
implementation 'com.mikepenz:aboutlibraries:10.6.1'
// Utils
implementation 'androidx.work:work-runtime:2.8.0'
implementation 'androidx.work:work-runtime:2.8.1'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10'
// logging interceptor
@ -357,6 +357,9 @@ dependencies {
implementation "com.google.devtools.ksp:symbol-processing-api:1.8.10-1.0.9"
implementation "androidx.security:security-crypto:1.1.0-alpha05"
// some utils
implementation 'commons-io:commons-io:2.11.0'
}
if (hasSentryConfig) {

@ -44,6 +44,8 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- wake lock -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".MainApplication"

@ -12,6 +12,9 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.SystemClock;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
@ -36,6 +39,18 @@ import com.google.common.hash.Hashing;
import com.topjohnwu.superuser.Shell;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
@ -45,6 +60,14 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import io.noties.markwon.Markwon;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.image.ImagesPlugin;
@ -86,6 +109,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
private int managerThemeResId = R.style.Theme_MagiskModuleManager;
private FoxThemeWrapper markwonThemeContext;
private Markwon markwon;
private byte[] existingKey;
public MainApplication() {
if (INSTANCE != null && INSTANCE != this)
@ -491,6 +515,172 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
}
}
// Create a key to encrypt a realm and save it securely in the keystore
public byte[] getNewKey() {
Timber.d("Creating a new key.");
// check if we have a key already
SharedPreferences sharedPreferences = MainApplication.getPreferences("realm_key");
if (sharedPreferences.contains("iv_and_encrypted_key")) {
Timber.v("Found a key in the keystore.");
return getExistingKey();
}
// open a connection to the android keystore
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException | NoSuchAlgorithmException
| CertificateException | IOException e) {
Timber.v("Failed to open the keystore.");
throw new RuntimeException(e);
}
// create a securely generated random asymmetric RSA key
byte[] realmKey = new byte[Realm.ENCRYPTION_KEY_LENGTH];
new SecureRandom().nextBytes(realmKey);
// create a cipher that uses AES encryption -- we'll use this to encrypt our key
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
+ "/" + KeyProperties.BLOCK_MODE_CBC
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Timber.e("Failed to create a cipher.");
throw new RuntimeException(e);
}
Timber.v("Cipher created.");
// generate secret key
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
Timber.e("Failed to access the key generator.");
throw new RuntimeException(e);
}
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
"realm_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build();
try {
keyGenerator.init(keySpec);
} catch (InvalidAlgorithmParameterException e) {
Timber.e("Failed to generate a secret key.");
throw new RuntimeException(e);
}
Timber.v("Secret key generated.");
keyGenerator.generateKey();
Timber.v("Secret key stored in the keystore.");
// access the generated key in the android keystore, then
// use the cipher to create an encrypted version of the key
byte[] initializationVector;
byte[] encryptedKeyForRealm;
try {
SecretKey secretKey =
(SecretKey) keyStore.getKey("realm_key", null);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
encryptedKeyForRealm = cipher.doFinal(realmKey);
initializationVector = cipher.getIV();
} catch (InvalidKeyException | UnrecoverableKeyException
| NoSuchAlgorithmException | KeyStoreException
| BadPaddingException | IllegalBlockSizeException e) {
Timber.e("Failed encrypting the key with the secret key.");
throw new RuntimeException(e);
}
// keep the encrypted key in shared preferences
// to persist it across application runs
byte[] initializationVectorAndEncryptedKey =
new byte[Integer.BYTES +
initializationVector.length +
encryptedKeyForRealm.length];
ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putInt(initializationVector.length);
buffer.put(initializationVector);
buffer.put(encryptedKeyForRealm);
Timber.d("Created all keys successfully.");
MainApplication.getPreferences("realm_key").edit()
.putString("iv_and_encrypted_key",
Base64.encodeToString(initializationVectorAndEncryptedKey, Base64.NO_WRAP))
.apply();
Timber.d("Saved the encrypted key in shared preferences.");
return realmKey; // pass to a realm configuration via encryptionKey()
}
// 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("Accessing the existing key.");
// attempt to read the existingKey property
if (existingKey != null) {
Timber.v("Found an existing key in memory.");
return existingKey;
}
// open a connection to the android keystore
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException | NoSuchAlgorithmException
| CertificateException | IOException e) {
Timber.e("Failed to open the keystore.");
throw new RuntimeException(e);
}
Timber.v("Keystore opened.");
// access the encrypted key that's stored in shared preferences
byte[] initializationVectorAndEncryptedKey = Base64.decode(MainApplication
.getPreferences("realm_key")
.getString("iv_and_encrypted_key", null), Base64.DEFAULT);
Timber.d("Retrieved the encrypted key from shared preferences. Key length: %d",
initializationVectorAndEncryptedKey.length);
ByteBuffer buffer = ByteBuffer.wrap(initializationVectorAndEncryptedKey);
buffer.order(ByteOrder.BIG_ENDIAN);
// extract the length of the initialization vector from the buffer
int initializationVectorLength = buffer.getInt();
// extract the initialization vector based on that length
byte[] initializationVector = new byte[initializationVectorLength];
buffer.get(initializationVector);
// extract the encrypted key
byte[] encryptedKey = new byte[initializationVectorAndEncryptedKey.length
- Integer.BYTES
- initializationVectorLength];
buffer.get(encryptedKey);
Timber.d("Got key from shared preferences.");
// create a cipher that uses AES encryption to decrypt our key
Cipher cipher;
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES
+ "/" + KeyProperties.BLOCK_MODE_CBC
+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Timber.e("Failed to create cipher.");
throw new RuntimeException(e);
}
// decrypt the encrypted key with the secret key stored in the keystore
byte[] decryptedKey;
try {
final SecretKey secretKey =
(SecretKey) keyStore.getKey("realm_key", null);
final IvParameterSpec initializationVectorSpec =
new IvParameterSpec(initializationVector);
cipher.init(Cipher.DECRYPT_MODE, secretKey, initializationVectorSpec);
decryptedKey = cipher.doFinal(encryptedKey);
} catch (InvalidKeyException e) {
Timber.e("Failed to decrypt. Invalid key.");
throw new RuntimeException(e);
} catch (UnrecoverableKeyException | NoSuchAlgorithmException
| BadPaddingException | KeyStoreException
| IllegalBlockSizeException | InvalidAlgorithmParameterException e) {
Timber.e("Failed to decrypt the encrypted realm key with the secret key.");
throw new RuntimeException(e);
}
// set property on MainApplication to indicate that the key has been accessed
existingKey = decryptedKey;
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) {

@ -3,6 +3,7 @@ package com.fox2code.mmm;
import static com.fox2code.mmm.utils.IntentHelper.getActivity;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
@ -12,11 +13,11 @@ import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.FragmentActivity;
import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.mmm.androidacy.AndroidacyRepoData;
import com.fox2code.mmm.databinding.ActivitySetupBinding;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.realm.ReposList;
@ -39,6 +40,7 @@ import timber.log.Timber;
public class SetupActivity extends FoxActivity implements LanguageActivity {
private int cachedTheme;
private boolean realmDatabasesCreated;
@SuppressLint({"ApplySharedPref", "RestrictedApi"})
@Override
@ -154,25 +156,48 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
setupButton.setOnClickListener(v -> {
Timber.i("Setup button clicked");
// get instance of editor
Timber.d("Saving preferences");
SharedPreferences.Editor editor = prefs.edit();
Timber.d("Got editor: %s", editor);
// Set the Automatic update check pref
editor.putBoolean("pref_background_update_check", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).isChecked());
// Set the crash reporting pref
editor.putBoolean("pref_crash_reporting", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).isChecked());
Timber.d("Saving preferences");
// Set the repos in the ReposList realm db
RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).build();
RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).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
realm.commitTransaction();
realm.close();
Timber.d("Realm instance: %s", realm);
if (realm.isInTransaction()) {
realm.commitTransaction();
Timber.d("Committed last unfinished transaction");
}
// check if instance has been closed
if (realm.isClosed()) {
Timber.d("Realm instance was closed, reopening");
realm = Realm.getInstance(realmConfig);
}
realm.executeTransactionAsync(r -> {
Timber.d("Realm transaction started");
Objects.requireNonNull(r.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst()).setEnabled(androidacyRepo);
Objects.requireNonNull(r.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst()).setEnabled(magiskAltRepo);
Timber.d("Realm transaction committing");
// commit the changes
r.commitTransaction();
r.close();
Timber.d("Realm transaction committed");
});
editor.putString("last_shown_setup", "v1");
// Commit the changes
editor.commit();
// sleep to allow the realm transaction to finish
try {
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Log the changes
Timber.d("Setup finished. Preferences: %s", prefs.getAll());
Timber.d("Androidacy repo: %s", androidacyRepo);
@ -181,23 +206,19 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
Timber.d("Last shown setup: %s", prefs.getString("last_shown_setup", "v0"));
// Restart the activity
MainActivity.doSetupRestarting = true;
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("doSetupRestarting", true);
startActivity(intent);
finish();
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_IMMUTABLE);
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
android.os.Process.killProcess(android.os.Process.myPid());
});
// Cancel button
BottomNavigationItemView cancelButton = view.findViewById(R.id.cancel_setup);
cancelButton.setOnClickListener(v -> {
Timber.i("Cancel button clicked");
// Set first launch to false and restart the activity
prefs.edit().putString("last_shown_setup", "v1").commit();
MainActivity.doSetupRestarting = true;
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("doSetupRestarting", true);
startActivity(intent);
// close the app
finish();
});
}
@ -252,55 +273,78 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
// creates the realm database
private void createRealmDatabase() {
if (realmDatabasesCreated) {
Timber.d("Realm databases already created");
return;
}
Timber.d("Creating Realm databases");
long startTime = System.currentTimeMillis();
// create encryption key
// Timber.d("Creating encryption key");
Timber.d("Creating encryption key");
byte[] key = MainApplication.getINSTANCE().getNewKey();
// create the realm database for ReposList
// next, create the realm database for ReposList
new Thread(() -> {
// create the realm database for ReposList
// create the realm configuration
RealmConfiguration config2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
// get the instance
Realm realm1 = Realm.getInstance(config2);
// create androidacy_repo and magisk_alt_repo if they don't exist under ReposList
// each has id, name, donate, website, support, enabled, and lastUpdate and name
// create androidacy_repo
realm1.beginTransaction();
if (realm1.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst() == null) {
// cant use createObject because it crashes because reasons. use copyToRealm instead
ReposList androidacy_repo = realm1.createObject(ReposList.class, "androidacy_repo");
androidacy_repo.setName("Androidacy Repo");
androidacy_repo.setDonate(AndroidacyRepoData.getInstance().getDonate());
androidacy_repo.setSupport(AndroidacyRepoData.getInstance().getSupport());
androidacy_repo.setSubmitModule(AndroidacyRepoData.getInstance().getSubmitModule());
androidacy_repo.setUrl(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
androidacy_repo.setEnabled(true);
androidacy_repo.setLastUpdate(0);
androidacy_repo.setWebsite(RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE);
// now copy the data from the data class to the realm object using copyToRealmOrUpdate
realm1.insertOrUpdate(androidacy_repo);
// create the realm configuration
RealmConfiguration config = new RealmConfiguration.Builder().name("ReposList.realm").directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).encryptionKey(key).build();
// get the instance
Realm.getInstanceAsync(config, new Realm.Callback() {
@Override
public void onSuccess(@NonNull Realm realm) {
Timber.d("Realm instance: %s", realm);
realm.beginTransaction();
// create the ReposList realm database
Timber.d("Creating ReposList realm database");
if (realm.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst() == null) {
Timber.d("Creating androidacy_repo");
// create the androidacy_repo row
// cant use createObject because it crashes because reasons. use copyToRealm instead
ReposList androidacy_repo = realm.createObject(ReposList.class, "androidacy_repo");
Timber.d("Created androidacy_repo object");
androidacy_repo.setName("Androidacy Repo");
Timber.d("Set androidacy_repo name");
androidacy_repo.setDonate("https://www.androidacy.com/membership-account/membership-join/?utm_source=fox-app&utm_medium=app&utm_campaign=app");
Timber.d("Set androidacy_repo donate");
androidacy_repo.setSupport("https://t.me/androidacy_discussions");
Timber.d("Set androidacy_repo support");
androidacy_repo.setSubmitModule("https://www.androidacy.com/module-repository-applications/?utm_source=fox-app&utm_medium=app&utm_campaign=app");
Timber.d("Set androidacy_repo submit module");
androidacy_repo.setUrl(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
Timber.d("Set androidacy_repo url");
androidacy_repo.setEnabled(true);
Timber.d("Set androidacy_repo enabled");
androidacy_repo.setLastUpdate(0);
Timber.d("Set androidacy_repo last update");
androidacy_repo.setWebsite(RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE);
Timber.d("Set androidacy_repo website");
// now copy the data from the data class to the realm object using copyToRealmOrUpdate
Timber.d("Copying data to realm object");
realm.copyToRealmOrUpdate(androidacy_repo);
Timber.d("Created androidacy_repo");
}
// create magisk_alt_repo
if (realm.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst() == null) {
Timber.d("Creating magisk_alt_repo");
ReposList magisk_alt_repo = realm.createObject(ReposList.class, "magisk_alt_repo");
Timber.d("Created magisk_alt_repo object");
magisk_alt_repo.setName("Magisk Alt Repo");
magisk_alt_repo.setDonate(null);
magisk_alt_repo.setWebsite(RepoManager.MAGISK_ALT_REPO_HOMEPAGE);
magisk_alt_repo.setSupport(null);
magisk_alt_repo.setEnabled(true);
magisk_alt_repo.setUrl(RepoManager.MAGISK_ALT_REPO);
magisk_alt_repo.setSubmitModule(RepoManager.MAGISK_ALT_REPO_HOMEPAGE + "/submission");
magisk_alt_repo.setLastUpdate(0);
// commit the changes
Timber.d("Copying data to realm object");
realm.copyToRealmOrUpdate(magisk_alt_repo);
Timber.d("Created magisk_alt_repo");
}
realm.commitTransaction();
realmDatabasesCreated = true;
Timber.d("Realm transaction finished");
long endTime = System.currentTimeMillis();
Timber.d("Realm databases created in %d ms", endTime - startTime);
}
// create magisk_alt_repo
if (realm1.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst() == null) {
ReposList magisk_alt_repo = realm1.createObject(ReposList.class, "magisk_alt_repo");
magisk_alt_repo.setName("Magisk Alt Repo");
magisk_alt_repo.setDonate(null);
magisk_alt_repo.setWebsite(RepoManager.MAGISK_ALT_REPO_HOMEPAGE);
magisk_alt_repo.setSupport(null);
magisk_alt_repo.setEnabled(true);
magisk_alt_repo.setUrl(RepoManager.MAGISK_ALT_REPO);
magisk_alt_repo.setSubmitModule(RepoManager.MAGISK_ALT_REPO_HOMEPAGE + "/submission");
magisk_alt_repo.setLastUpdate(0);
// commit the changes
realm1.insertOrUpdate(magisk_alt_repo);
}
realm1.commitTransaction();
realm1.close();
long endTime = System.currentTimeMillis();
Timber.d("Realm databases created in %d ms", endTime - startTime);
}).start();
});
}
public void createFiles() {

@ -1,12 +1,14 @@
package com.fox2code.mmm.installer;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
@ -31,8 +33,8 @@ import com.fox2code.mmm.utils.FastException;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.Files;
import com.fox2code.mmm.utils.io.Hashes;
import com.fox2code.mmm.utils.io.net.Http;
import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.io.net.Http;
import com.fox2code.mmm.utils.sentry.SentryBreadcrumb;
import com.fox2code.mmm.utils.sentry.SentryMain;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -58,6 +60,7 @@ import java.util.zip.ZipInputStream;
import timber.log.Timber;
public class InstallerActivity extends FoxActivity {
private static final HashSet<String> extracted = new HashSet<>();
public LinearProgressIndicator progressIndicator;
public ExtendedFloatingActionButton rebootFloatingButton;
public InstallerTerminal installerTerminal;
@ -66,8 +69,7 @@ public class InstallerActivity extends FoxActivity {
private boolean textWrap;
private boolean canceled;
private boolean warnReboot;
private static final HashSet<String> extracted = new HashSet<>();
private PowerManager.WakeLock wakeLock;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -153,9 +155,12 @@ public class InstallerActivity extends FoxActivity {
installTerminal.setItemAnimator(null);
this.progressIndicator.setVisibility(View.GONE);
this.progressIndicator.setIndeterminate(true);
this.getWindow().setFlags( // Note: Doesn't require WAKELOCK permission
this.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// acquire wakelock
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Fox:Installer");
this.progressIndicator.setVisibility(View.VISIBLE);
if (urlMode) this.installerTerminal.addLine("- Downloading " + name);
String finalTarget = target;
@ -201,6 +206,8 @@ public class InstallerActivity extends FoxActivity {
}
if (this.canceled) return;
Files.fixJavaZipHax(rawModule);
// checks to make sure zip is not a source archive, and if it is, unzips the folder within, switches to it, and zips up the contents of it
Files.fixSourceArchiveShit(rawModule);
boolean noPatch = false;
boolean isModule = false;
boolean isAnyKernel3 = false;
@ -219,7 +226,8 @@ public class InstallerActivity extends FoxActivity {
noPatch = true;
isModule = true;
break;
} if (entryName.equals("META-INF/com/google/android/magisk/module.prop")) {
}
if (entryName.equals("META-INF/com/google/android/magisk/module.prop")) {
noPatch = true;
isInstallZipModule = true;
break;
@ -318,14 +326,14 @@ public class InstallerActivity extends FoxActivity {
}
installerMonitor = new InstallerMonitor(installScript);
installJob = Shell.cmd("export MMM_EXT_SUPPORT=1",
"export MMM_USER_LANGUAGE=" + this.getResources()
.getConfiguration().getLocales().get(0).toLanguageTag(),
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
AnsiConstants.ANSI_CMD_SUPPORT,
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
"sh \"" + installScript.getAbsolutePath() + "\"" +
" 3 0 \"" + file.getAbsolutePath() + "\"")
"export MMM_USER_LANGUAGE=" + this.getResources()
.getConfiguration().getLocales().get(0).toLanguageTag(),
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
AnsiConstants.ANSI_CMD_SUPPORT,
"cd \"" + this.moduleCache.getAbsolutePath() + "\"",
"sh \"" + installScript.getAbsolutePath() + "\"" +
" 3 0 \"" + file.getAbsolutePath() + "\"")
.to(installerController, installerMonitor);
} else {
String arch32 = "true"; // Do nothing by default
@ -508,7 +516,8 @@ public class InstallerActivity extends FoxActivity {
}
boolean success = installJob.exec().isSuccess();
// Wait one UI cycle before disabling controller or processing results
UiThreadHandler.runAndWait(() -> {}); // to avoid race conditions
UiThreadHandler.runAndWait(() -> {
}); // to avoid race conditions
installerController.disable();
String message = "- Install successful";
if (!success) {
@ -561,6 +570,11 @@ public class InstallerActivity extends FoxActivity {
} else toDelete = null;
this.runOnUiThread(() -> {
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0);
// release wakelock
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
wakeLock = null;
}
// Set the back press to finish the activity and return to the main activity
this.setOnBackPressedCallback(a -> {
this.finishAndRemoveTask();
@ -589,7 +603,6 @@ public class InstallerActivity extends FoxActivity {
}
});
this.rebootFloatingButton.setVisibility(View.VISIBLE);
if (message != null && !message.isEmpty())
this.installerTerminal.addLine(message);
if (optionalLink != null && !optionalLink.isEmpty()) {
@ -670,7 +683,8 @@ public class InstallerActivity extends FoxActivity {
this.processCommand("showLoading 256");
this.processCommand("setLoading " + progressInt);
this.isRecoveryBar = true;
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
} else {
this.terminal.addLine(s.replace(
this.moduleFile.getAbsolutePath(),

@ -159,7 +159,7 @@ public class InstallerInitializer extends Shell.Initializer {
public boolean onInit(@NonNull Context context, @NonNull Shell shell) {
if (!shell.isRoot())
return true;
// switch to global namespace using the setns syscall
// switch to global namespace
return shell.newJob().add("export ASH_STANDALONE=1; nsenter -t 1 -m -u /data/adb/magisk/busybox ash").exec().isSuccess();
}
}

@ -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).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(tempCacheRoot).build();
realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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();

@ -8,7 +8,6 @@ import com.fox2code.mmm.utils.realm.ReposList;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import timber.log.Timber;
public class CustomRepoManager {
public static final int MAX_CUSTOM_REPOS = 5;
@ -23,16 +22,19 @@ public class CustomRepoManager {
this.repoManager = repoManager;
this.customRepos = new String[MAX_CUSTOM_REPOS];
this.customReposCount = 0;
// refuse to load if setup is not complete
if (MainApplication.getPreferences("mmm").getString("last_shown_setup", "").equals("")) {
return;
}
SharedPreferences sharedPreferences = this.getSharedPreferences();
int lastFilled = 0;
for (int i = 0; i < MAX_CUSTOM_REPOS; i++) {
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm = Realm.getInstance(realmConfiguration);
try {
realm.beginTransaction();
} catch (IllegalStateException e) {
Timber.w(e, "Failed to begin transaction");
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
// find the matching entry for repo_0, repo_1, etc.
ReposList reposList = realm.where(ReposList.class).equalTo("id", "repo_" + i).findFirst();
if (reposList == null) {
@ -41,22 +43,19 @@ public class CustomRepoManager {
String repo = reposList.getUrl();
if (!PropUtils.isNullString(repo) && !RepoManager.isBuiltInRepo(repo)) {
lastFilled = i;
int index = AUTO_RECOMPILE ?
this.customReposCount : i;
int index = AUTO_RECOMPILE ? this.customReposCount : i;
this.customRepos[index] = repo;
this.customReposCount++;
((CustomRepoData) this.repoManager.addOrGet(repo))
.override = "custom_repo_" + index;
((CustomRepoData) this.repoManager.addOrGet(repo)).override = "custom_repo_" + index;
}
}
if (AUTO_RECOMPILE && (lastFilled + 1) != this.customReposCount) {
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm = Realm.getInstance(realmConfiguration);
try {
realm.beginTransaction();
} catch (IllegalStateException e) {
Timber.w(e, "Failed to begin transaction");
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
for (int i = 0; i < MAX_CUSTOM_REPOS; i++) {
if (this.customRepos[i] != null) {
// find the matching entry for repo_0, repo_1, etc.
@ -71,22 +70,23 @@ public class CustomRepoManager {
}
private SharedPreferences getSharedPreferences() {
return MainApplication.getPreferences(
"mmm_custom_repos");
return MainApplication.getPreferences("mmm_custom_repos");
}
public CustomRepoData addRepo(String repo) {
if (RepoManager.isBuiltInRepo(repo))
throw new IllegalArgumentException("Can't add built-in repo to custom repos");
for (String repoEntry : this.customRepos) {
if (repo.equals(repoEntry))
return (CustomRepoData) this.repoManager.get(repoEntry);
if (repo.equals(repoEntry)) return (CustomRepoData) this.repoManager.get(repoEntry);
}
int i = 0;
while (customRepos[i] != null) i++;
customRepos[i] = repo;
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm = Realm.getInstance(realmConfiguration);
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
// find the matching entry for repo_0, repo_1, etc.
ReposList reposList = realm.where(ReposList.class).equalTo("id", "repo_" + i).findFirst();
@ -97,8 +97,7 @@ public class CustomRepoManager {
realm.commitTransaction();
customReposCount++;
this.dirty = true;
CustomRepoData customRepoData = (CustomRepoData)
this.repoManager.addOrGet(repo);
CustomRepoData customRepoData = (CustomRepoData) this.repoManager.addOrGet(repo);
customRepoData.override = "custom_repo_" + i;
// Set the enabled state to true
customRepoData.setEnabled(true);
@ -109,8 +108,7 @@ public class CustomRepoManager {
public CustomRepoData getRepo(int index) {
if (index >= MAX_CUSTOM_REPOS) return null;
String repo = customRepos[index];
return repo == null ? null :
(CustomRepoData) this.repoManager.get(repo);
return repo == null ? null : (CustomRepoData) this.repoManager.get(repo);
}
public void removeRepo(int index) {
@ -118,22 +116,19 @@ public class CustomRepoManager {
if (oldRepo != null) {
customRepos[index] = null;
customReposCount--;
CustomRepoData customRepoData =
(CustomRepoData) this.repoManager.get(oldRepo);
CustomRepoData customRepoData = (CustomRepoData) this.repoManager.get(oldRepo);
if (customRepoData != null) {
customRepoData.setEnabled(false);
customRepoData.override = null;
}
this.getSharedPreferences().edit()
.remove("repo_" + index).apply();
this.getSharedPreferences().edit().remove("repo_" + index).apply();
this.dirty = true;
}
}
public boolean hasRepo(String repo) {
for (String repoEntry : this.customRepos) {
if (repo.equals(repoEntry))
return true;
if (repo.equals(repoEntry)) return true;
}
return false;
}
@ -143,11 +138,9 @@ public class CustomRepoManager {
}
public boolean canAddRepo(String repo) {
if (RepoManager.isBuiltInRepo(repo) ||
this.hasRepo(repo) || !this.canAddRepo())
if (RepoManager.isBuiltInRepo(repo) || this.hasRepo(repo) || !this.canAddRepo())
return false;
return repo.startsWith("https://") &&
repo.indexOf('/', 9) != -1;
return repo.startsWith("https://") && repo.indexOf('/', 9) != -1;
}
public int getRepoCount() {

@ -105,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).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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) {
@ -291,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).build();
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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();
@ -313,7 +313,7 @@ public class RepoData extends XRepo {
}
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).build();
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm2 = Realm.getInstance(realmConfiguration2);
boolean dbEnabled;
try {
@ -372,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).build();
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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) {

@ -52,8 +52,8 @@ public final class RepoManager extends SyncManager {
private final MainApplication mainApplication;
private final LinkedHashMap<String, RepoData> repoData;
private final HashMap<String, RepoModule> modules;
private final AndroidacyRepoData androidacyRepoData;
private final CustomRepoManager customRepoManager;
private AndroidacyRepoData androidacyRepoData;
private CustomRepoManager customRepoManager;
public String repoLastErrorName = null;
private boolean hasInternet;
private boolean initialized;
@ -65,6 +65,10 @@ public final class RepoManager extends SyncManager {
this.mainApplication = mainApplication;
this.repoData = new LinkedHashMap<>();
this.modules = new HashMap<>();
// refuse to load if setup is not complete
if (MainApplication.getPreferences("mmm").getString("last_shown_setup", "").equals("")) {
return;
}
// We do not have repo list config yet.
this.androidacyRepoData = this.addAndroidacyRepoData();
RepoData altRepo = this.addRepoData(MAGISK_ALT_REPO, "Magisk Modules Alt Repo");

@ -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).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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).build();
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).schemaVersion(1).deleteRealmIfMigrationNeeded().allowWritesOnUiThread(true).allowQueriesOnUiThread(true).directory(cacheRoot).build();
// array with module info default values
// supported properties for a module
//id=<string>
@ -159,6 +159,9 @@ public class RepoUpdater {
}
Realm realm = Realm.getInstance(realmConfiguration);
// drop old data
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
realm.where(ModuleListCache.class).equalTo("repoId", this.repoData.id).findAll().deleteAllFromRealm();
realm.commitTransaction();
@ -297,6 +300,9 @@ public class RepoUpdater {
}
// create a realm object and insert or update it
// add everything to the realm object
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
ModuleListCache moduleListCache = realm.createObject(ModuleListCache.class, id);
moduleListCache.setName(name);
@ -333,7 +339,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).build();
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm2 = Realm.getInstance(realmConfiguration2);
if (realm2.isInTransaction()) {
realm2.cancelTransaction();

@ -393,13 +393,42 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (!MainApplication.isDeveloper()) {
findPreference("pref_disable_low_quality_module_filter").setVisible(false);
// Find pref_clear_data and set it invisible
Objects.requireNonNull((Preference) findPreference("pref_clear_data")).setVisible(false);
}
// hande clear cache
findPreference("pref_clear_cache").setOnPreferenceClickListener(preference -> {
// Clear cache
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.clear_cache_dialogue_title).setMessage(R.string.clear_cache_dialogue_message).setPositiveButton(R.string.yes, (dialog, which) -> {
// Clear app cache
try {
File cacheDir = requireContext().getCacheDir();
for (File file : cacheDir.listFiles()) {
if (file.isDirectory()) {
for (File file2 : file.listFiles()) {
if (!file2.delete()) {
Timber.e("Failed to delete %s", file2.getAbsolutePath());
}
}
}
if (!file.delete()) {
Timber.e("Failed to delete %s", file.getAbsolutePath());
}
}
Toast.makeText(requireContext(), R.string.cache_cleared, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Timber.e(e);
Toast.makeText(requireContext(), R.string.cache_clear_failed, Toast.LENGTH_SHORT).show();
}
}).setNegativeButton(R.string.no, (dialog, which) -> {
// Do nothing
}).show();
return true;
});
if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG || InstallerInitializer.peekMagiskPath() == null) {
// Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app
Timber.i(InstallerInitializer.peekMagiskPath());
Objects.requireNonNull((Preference) findPreference("pref_test_crash")).setVisible(false);
// Find pref_clear_data and set it invisible
Objects.requireNonNull((Preference) findPreference("pref_clear_data")).setVisible(false);
} else {
if (findPreference("pref_test_crash") != null && findPreference("pref_clear_data") != null) {
findPreference("pref_test_crash").setOnPreferenceClickListener(preference -> {
@ -850,7 +879,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).build();
RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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 +914,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).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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 +927,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).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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) {
@ -1055,7 +1084,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).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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,6 +1103,9 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (preference == null) continue;
final int index = i;
preference.setOnPreferenceClickListener(preference1 -> {
if (realm.isInTransaction()) {
realm.commitTransaction();
}
realm.beginTransaction();
Objects.requireNonNull(realm.where(ReposList.class).equalTo("id", repoData.id).findFirst()).deleteFromRealm();
realm.commitTransaction();
@ -1175,7 +1207,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
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).build();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getExistingKey()).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");

@ -10,10 +10,13 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fox2code.mmm.MainApplication;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import com.topjohnwu.superuser.io.SuFileOutputStream;
import org.apache.commons.io.FileUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@ -23,6 +26,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@ -188,4 +193,55 @@ public enum Files {
zipOutputStream.close();
zipInputStream.close();
}
public static void fixSourceArchiveShit(byte[] rawModule) {
// unzip the module, check if it has just one folder within. if so, switch to the folder and zip up contents, and replace the original file with that
try {
File tempDir = new File(MainApplication.getINSTANCE().getCacheDir(), "temp");
if (tempDir.exists()) {
FileUtils.deleteDirectory(tempDir);
}
tempDir.mkdirs();
File tempFile = new File(tempDir, "module.zip");
Files.write(tempFile, rawModule);
File tempUnzipDir = new File(tempDir, "unzip");
tempUnzipDir.mkdirs();
// unzip
try (ZipInputStream zipInputStream = new ZipInputStream(
new ByteArrayInputStream(rawModule))) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
String name = zipEntry.getName();
File file = new File(tempUnzipDir, name);
if (zipEntry.isDirectory()) {
file.mkdirs();
} else {
file.getParentFile().mkdirs();
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
int nRead;
byte[] data = new byte[16384];
while ((nRead = zipInputStream.read(data, 0, data.length)) != -1) {
fileOutputStream.write(data, 0, nRead);
}
fileOutputStream.flush();
}
}
}
} catch (Exception e) {
Timber.e(Log.getStackTraceString(e));
}
File[] files = tempUnzipDir.listFiles();
if (files != null && files.length == 1 && files[0].isDirectory()) {
File[] files2 = files[0].listFiles();
if (files2 != null && files2.length > 0) {
File tempZipFile = new File(tempDir, "module2.zip");
// TODO: zip the contents of the folder
Files.write(tempFile, Files.read(tempZipFile));
}
}
rawModule = Files.read(tempFile);
} catch (Exception e) {
Timber.e(Log.getStackTraceString(e));
}
}
}

@ -4,6 +4,7 @@
<!-- cancel and finish setup -->
<item
android:id="@+id/cancel_setup"
android:checked="false"
android:icon="@drawable/baseline_close_24"
android:title="@string/cancel"
app:showAsAction="ifRoom" />
@ -11,6 +12,7 @@
<!-- save and finish setup -->
<item
android:id="@+id/setup_finish"
android:checked="false"
android:icon="@drawable/baseline_check_24"
android:title="@string/finish"
app:showAsAction="ifRoom" />

@ -330,7 +330,7 @@
<string name="title_activity_update">In-app Updater</string>
<string name="update_title">Update app</string>
<string name="update_message">Please wait while we check for and install updates to FoxMMM. This may take a few minutes</string>
<string name="update_button">Please wait...</string>
<string name="update_button">Please wait</string>
<string name="error_no_extras">ERROR: Invalid data received on launch</string>
<string name="update_debug_warning">You appear to be running a debug build. Debug builds must be updated manually, and do not support in-app updates</string>
<string name="error_no_action">ERROR: Invalid action specified. Refusing to continue.</string><string name="update_available">Update found</string>
@ -350,7 +350,7 @@
<string name="add_repo_message">Repos must be served over HTTPS, and must follow the spec outlined in the documentation.</string>
<string name="notification_update_summary">The following modules can be updated:</string>
<string name="notification_update_module_template">%1$s to version %2$s</string>
<string name="notification_channel_background_update">Checking for updates...</string>
<string name="notification_channel_background_update">Checking for updates</string>
<string name="notification_channel_background_update_description">FoxMMM is checking for updates in the background.</string>
<string name="notification_channel_category_background_update">Background update status</string>
<string name="notification_channel_category_background_update_description">Shows a notification while checking for updates so the system doesn\'t kill it</string>
@ -380,4 +380,10 @@
<string name="setup_background_update_check_require_wifi">Require wifi for update checks</string>
<string name="setup_background_update_check_require_wifi_summary">Require wifi or an otherwise unmetered connection to do update checks</string>
<string name="setup_app_analytics">Allow app analytics</string>
<string name="clear_app_cache">Clear app cache</string>
<string name="clear_app_cache_desc">This shouldn\'t be necessary normally but may help fix some issues.</string>
<string name="cache_cleared">Successfully cleared cache</string>
<string name="cache_clear_failed">Failed to clear cache</string>
<string name="clear_cache_dialogue_title">Clear app cache?</string>
<string name="clear_cache_dialogue_message">This will clear app cache. Your preferences will be saved, but the app may take longer to do some operations temporarily.</string>
</resources>

@ -190,6 +190,13 @@
app:key="pref_clear_data"
app:singleLineTitle="false"
app:title="@string/clear_app_data" />
<!-- clear app cache -->
<Preference
app:icon="@drawable/ic_baseline_delete_24"
app:key="pref_clear_cache"
app:singleLineTitle="false"
app:title="@string/clear_app_cache"
app:summary="@string/clear_app_cache_desc" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/pref_category_info">

@ -19,7 +19,7 @@ buildscript {
project.ext.sentry_version = "6.16.0"
dependencies {
//noinspection AndroidGradlePluginVersion
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.android.tools.build:gradle:8.0.0-rc01'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10"
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${latestAboutLibsRelease}"

@ -22,3 +22,6 @@ android.enableJetifier=true
org.gradle.parallel=true
android.enableR8.fullMode=true
org.gradle.unsafe.configuration-cache=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

Loading…
Cancel
Save