more work on realm

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/284/head
androidacy-user 1 year ago
parent 5b3bf70f83
commit a54ecbc6d8

@ -12,6 +12,7 @@ apply plugin: "realm-android"
android { android {
namespace "com.fox2code.mmm" namespace "com.fox2code.mmm"
compileSdk 33 compileSdk 33
ndkVersion "25.1.8937393"
signingConfigs { signingConfigs {
release { release {
// Everything comes from local.properties // Everything comes from local.properties
@ -288,7 +289,12 @@ dependencies {
implementation 'com.github.topjohnwu.libsu:io:5.0.1' implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.9' implementation 'com.github.Fox2Code:RosettaX:1.0.9'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1' implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
// sentry
implementation "io.sentry:sentry-android:$sentry_version" implementation "io.sentry:sentry-android:$sentry_version"
implementation "io.sentry:sentry-android-timber:$sentry_version"
implementation "io.sentry:sentry-android-fragment:$sentry_version"
implementation "io.sentry:sentry-android-okhttp:$sentry_version"
// Markdown // Markdown
implementation "io.noties.markwon:core:4.6.2" implementation "io.noties.markwon:core:4.6.2"
@ -307,7 +313,9 @@ dependencies {
// timber // timber
implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'com.jakewharton.timber:timber:5.0.1'
implementation "io.sentry:sentry-android-timber:$sentry_version"
// ksp
implementation "com.google.devtools.ksp:symbol-processing-api:1.8.0-1.0.8"
} }
if (hasSentryConfig) { if (hasSentryConfig) {

@ -150,12 +150,19 @@ public class AppUpdateManager {
// Convert both BuildConfig.VERSION_NAME and latestRelease to int // Convert both BuildConfig.VERSION_NAME and latestRelease to int
int currentVersion = 0, latestVersion = 0; int currentVersion = 0, latestVersion = 0;
try { try {
currentVersion = Integer.parseInt(BuildConfig.VERSION_NAME.replace(".", "")); currentVersion = Integer.parseInt(BuildConfig.VERSION_NAME.replaceAll("\\D", ""));
latestVersion = Integer.parseInt(this.latestRelease.replace(".", "")); latestVersion = Integer.parseInt(this.latestRelease.replace("v", "").replaceAll("\\D", ""));
} catch ( } catch (
NumberFormatException ignored) { NumberFormatException ignored) {
} }
return currentVersion < latestVersion || (this.preReleaseNewer && currentVersion < Integer.parseInt(this.latestPreRelease.replace(".", ""))); int latestPreReleaseVersion = 0;
// replace all non-numeric characters with empty string
try {
latestPreReleaseVersion = Integer.parseInt(this.latestPreRelease.replaceAll("\\D", ""));
} catch (
NumberFormatException ignored) {
}
return currentVersion < latestVersion || (this.preReleaseNewer && currentVersion < latestPreReleaseVersion);
} }
public boolean peekHasUpdate() { public boolean peekHasUpdate() {

@ -11,17 +11,28 @@ import android.widget.Toast;
import com.fox2code.foxcompat.app.FoxActivity; import com.fox2code.foxcompat.app.FoxActivity;
import com.google.android.material.textview.MaterialTextView; import com.google.android.material.textview.MaterialTextView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import io.sentry.Sentry; import timber.log.Timber;
import io.sentry.UserFeedback;
import io.sentry.protocol.SentryId;
public class CrashHandler extends FoxActivity { public class CrashHandler extends FoxActivity {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
Timber.i("CrashHandler.onCreate(%s)", savedInstanceState);
// log intent with extras
Timber.d("CrashHandler.onCreate: intent=%s", getIntent());
// get exception, stacktrace, and lastEventId from intent and log them
Timber.d("CrashHandler.onCreate: exception=%s", getIntent().getSerializableExtra("exception"));
Timber.d("CrashHandler.onCreate: stacktrace=%s", getIntent().getSerializableExtra("stacktrace"));
Timber.d("CrashHandler.onCreate: lastEventId=%s", getIntent().getStringExtra("lastEventId"));
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash_handler); setContentView(R.layout.activity_crash_handler);
// set crash_details MaterialTextView to the exception passed in the intent or unknown if null // set crash_details MaterialTextView to the exception passed in the intent or unknown if null
@ -43,11 +54,12 @@ public class CrashHandler extends FoxActivity {
stacktrace = stacktrace.replace(",", "\n "); stacktrace = stacktrace.replace(",", "\n ");
crashDetails.setText(getString(R.string.crash_full_stacktrace, stacktrace)); crashDetails.setText(getString(R.string.crash_full_stacktrace, stacktrace));
} }
SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE);
// get lastEventId from intent
String lastEventId = getIntent().getStringExtra("lastEventId");
// disable feedback if sentry is disabled // disable feedback if sentry is disabled
if (MainApplication.isCrashReportingEnabled()) { //noinspection ConstantConditions
SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE); if (MainApplication.isCrashReportingEnabled() && !BuildConfig.SENTRY_TOKEN.equals("") && lastEventId != null) {
// get lastEventId from intent
SentryId lastEventId = Sentry.captureException((Throwable) getIntent().getSerializableExtra("exception"));
// get name, email, and message fields // get name, email, and message fields
EditText name = findViewById(R.id.feedback_name); EditText name = findViewById(R.id.feedback_name);
EditText email = findViewById(R.id.feedback_email); EditText email = findViewById(R.id.feedback_email);
@ -60,17 +72,48 @@ public class CrashHandler extends FoxActivity {
return; return;
} }
// if email or name is empty, use "Anonymous" // if email or name is empty, use "Anonymous"
String nameString = name.getText().toString().equals("") ? "Anonymous" : name.getText().toString(); final String[] nameString = {name.getText().toString().equals("") ? "Anonymous" : name.getText().toString()};
String emailString = email.getText().toString().equals("") ? "Anonymous" : email.getText().toString(); final String[] emailString = {email.getText().toString().equals("") ? "Anonymous" : email.getText().toString()};
// Prevent strict mode violation // Prevent strict mode violation
// create sentry userFeedback request
new Thread(() -> { new Thread(() -> {
// create sentry userFeedback request try {
UserFeedback userFeedback = new UserFeedback(lastEventId); HttpURLConnection connection = (HttpURLConnection) new URL("https" + "://sentry.io/api/0/projects/androidacy-i6/foxmmm/user-feedback/").openConnection();
userFeedback.setName(nameString); connection.setRequestMethod("POST");
userFeedback.setEmail(emailString); connection.setRequestProperty("Content-Type", "application/json");
userFeedback.setComments(description.getText().toString()); connection.setRequestProperty("Authorization", "Bearer " + BuildConfig.SENTRY_TOKEN);
// send the request // Setups the JSON body
Sentry.captureUserFeedback(userFeedback); if (nameString[0].equals(""))
nameString[0] = "Anonymous";
if (emailString[0].equals(""))
emailString[0] = "Anonymous";
JSONObject body = new JSONObject();
body.put("event_id", lastEventId);
body.put("name", nameString[0]);
body.put("email", emailString[0]);
body.put("comments", description.getText().toString());
// Send the request
connection.setDoOutput(true);
connection.getOutputStream().write(body.toString().getBytes());
connection.connect();
// For debug builds, log the response code and response body
if (BuildConfig.DEBUG) {
Timber.d("Response Code: %s", connection.getResponseCode());
}
// Check if the request was successful
if (connection.getResponseCode() == 200) {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG).show());
} else {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
// close and disconnect the connection
connection.disconnect();
} catch (
JSONException |
IOException ignored) {
// Show a toast if the user feedback could not be submitted
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
}).start(); }).start();
// Close the activity // Close the activity
finish(); finish();
@ -123,7 +166,8 @@ public class CrashHandler extends FoxActivity {
new Thread(() -> { new Thread(() -> {
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (InterruptedException e) { } catch (
InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
runOnUiThread(() -> view.setBackgroundResource(R.drawable.baseline_copy_all_24)); runOnUiThread(() -> view.setBackgroundResource(R.drawable.baseline_copy_all_24));

@ -47,11 +47,16 @@ import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler; import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import io.realm.Realm; import io.realm.Realm;
import io.sentry.IHub;
import io.sentry.Sentry; import io.sentry.Sentry;
import io.sentry.SentryLevel; import io.sentry.SentryLevel;
import io.sentry.android.timber.SentryTimberTree;
import timber.log.Timber; import timber.log.Timber;
public class MainApplication extends FoxApplication implements androidx.work.Configuration.Provider { public class MainApplication extends FoxApplication implements androidx.work.Configuration.Provider {
// Warning! Locales that are't exist will crash the app
// Anything that is commented out is supported but the translation is not complete to at least 60%
public static final HashSet<String> supportedLocales = new HashSet<>();
private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001 private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
private static final Shell.Builder shellBuilder; private static final Shell.Builder shellBuilder;
private static final long secret; private static final long secret;
@ -59,9 +64,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
// Use FoxProcess wrapper helper. // Use FoxProcess wrapper helper.
private static final boolean wrapped = !FoxProcessExt.isRootLoader(); private static final boolean wrapped = !FoxProcessExt.isRootLoader();
public static boolean isOfficial = false; public static boolean isOfficial = false;
// Warning! Locales that are't exist will crash the app
// Anything that is commented out is supported but the translation is not complete to at least 60%
public static final HashSet<String> supportedLocales = new HashSet<>();
private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().getLocales().get(0); private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().getLocales().get(0);
private static SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale); private static SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale);
private static SharedPreferences bootSharedPreferences; private static SharedPreferences bootSharedPreferences;
@ -282,8 +284,16 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
@Override @Override
public void onCreate() { public void onCreate() {
// init timber // init timber
if (BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree()); if (BuildConfig.DEBUG) {
else Timber.plant(new ReleaseTree()); Timber.plant(new Timber.DebugTree());
} else {
if (isCrashReportingEnabled()) {
@SuppressWarnings("UnstableApiUsage") IHub hub = Sentry.getCurrentHub();
Timber.plant(new SentryTimberTree(hub, SentryLevel.ERROR, SentryLevel.ERROR));
} else {
Timber.plant(new ReleaseTree());
}
}
// supportedLocales.add("ar"); // supportedLocales.add("ar");
// supportedLocales.add("ar_SA"); // supportedLocales.add("ar_SA");
supportedLocales.add("cs"); supportedLocales.add("cs");
@ -479,17 +489,16 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
} }
private static class ReleaseTree extends Timber.Tree { private static class ReleaseTree extends Timber.Tree {
@SuppressWarnings("StatementWithEmptyBody")
@Override @Override
protected void log(int priority, String tag, @NonNull String message, Throwable t) { protected void log(int priority, String tag, @NonNull String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) { // basically silently drop all logs below error, and write the rest to logcat
// silently ignore if (priority >= Log.ERROR) {
} else if (priority == Log.INFO) { if (t != null) {
Sentry.captureMessage(message, SentryLevel.INFO); Log.println(priority, tag, message);
} else if (priority == Log.WARN) { t.printStackTrace();
Sentry.captureMessage(message, SentryLevel.WARNING); } else {
} else if (priority == Log.ERROR) { Log.println(priority, tag, message);
Sentry.captureException(t); }
} }
} }
} }

@ -18,7 +18,6 @@ import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.mmm.androidacy.AndroidacyRepoData; import com.fox2code.mmm.androidacy.AndroidacyRepoData;
import com.fox2code.mmm.databinding.ActivitySetupBinding; import com.fox2code.mmm.databinding.ActivitySetupBinding;
import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.realm.ModuleListCache;
import com.fox2code.mmm.utils.realm.ReposList; import com.fox2code.mmm.utils.realm.ReposList;
import com.fox2code.rosettax.LanguageActivity; import com.fox2code.rosettax.LanguageActivity;
import com.fox2code.rosettax.LanguageSwitcher; import com.fox2code.rosettax.LanguageSwitcher;
@ -275,17 +274,6 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
// creates the realm database // creates the realm database
private void createRealmDatabase() { private void createRealmDatabase() {
Timber.d("Creating Realm databases"); Timber.d("Creating Realm databases");
// create the realm database for ModuleListCache
RealmConfiguration config = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).build();
// do a dummy write to create the database
Realm.getInstanceAsync(config, new Realm.Callback() {
@Override
public void onSuccess(@NonNull Realm realm) {
realm.executeTransactionAsync(realm1 -> {
// do nothing
});
}
});
// create the realm database for ReposList // create the realm database for ReposList
// next, create the realm database for ReposList // next, create the realm database for ReposList
RealmConfiguration config2 = new RealmConfiguration.Builder().name("ReposList.realm").directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build(); RealmConfiguration config2 = new RealmConfiguration.Builder().name("ReposList.realm").directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
@ -332,16 +320,9 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
realm1.close(); realm1.close();
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.d("Realm databases created"); Timber.d("Realm databases created");
// log each database
Realm realm2 = Realm.getInstance(config);
RealmResults<ModuleListCache> moduleListCaches = realm2.where(ModuleListCache.class).findAll();
Timber.d("ModuleListCache.realm");
for (ModuleListCache moduleListCache : moduleListCaches) {
Timber.d(moduleListCache.toString());
}
realm2.close();
Realm realm3 = Realm.getInstance(config2); Realm realm3 = Realm.getInstance(config2);
RealmResults<ReposList> reposLists = realm3.where(ReposList.class).findAll(); RealmResults<ReposList> reposLists = realm3.where(ReposList.class).findAll();
assert reposLists != null;
Timber.d("ReposList.realm"); Timber.d("ReposList.realm");
for (ReposList reposList : reposLists) { for (ReposList reposList : reposLists) {
Timber.d(reposList.toString()); Timber.d(reposList.toString());

@ -36,8 +36,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import timber.log.Timber; import timber.log.Timber;
@ -64,9 +62,6 @@ public final class AndroidacyRepoData extends RepoData {
public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences, boolean testMode) { public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences, boolean testMode) {
super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT : RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences); super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT : RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").allowWritesOnUiThread(true).allowWritesOnUiThread(true).directory(cacheRoot).build();
Realm.setDefaultConfiguration(realmConfiguration);
Realm.getInstance(realmConfiguration);
this.defaultName = "Androidacy Modules Repo"; this.defaultName = "Androidacy Modules Repo";
this.defaultWebsite = RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE; this.defaultWebsite = RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE;
this.defaultSupport = "https://t.me/androidacy_discussions"; this.defaultSupport = "https://t.me/androidacy_discussions";

@ -383,7 +383,7 @@ public final class RepoManager extends SyncManager {
private RepoData addRepoData(String url, String fallBackName) { private RepoData addRepoData(String url, String fallBackName) {
String id = internalIdOfUrl(url); String id = internalIdOfUrl(url);
File cacheRoot = new File(this.mainApplication.getDataDir(), id); File cacheRoot = new File(this.mainApplication.getDataDir(), "repos/" + id);
SharedPreferences sharedPreferences = this.mainApplication.getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE); SharedPreferences sharedPreferences = this.mainApplication.getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE);
RepoData repoData = id.startsWith("repo_") ? new CustomRepoData(url, cacheRoot, sharedPreferences) : new RepoData(url, cacheRoot, sharedPreferences); RepoData repoData = id.startsWith("repo_") ? new CustomRepoData(url, cacheRoot, sharedPreferences) : new RepoData(url, cacheRoot, sharedPreferences);
if (fallBackName != null && !fallBackName.isEmpty()) { if (fallBackName != null && !fallBackName.isEmpty()) {
@ -409,7 +409,7 @@ public final class RepoManager extends SyncManager {
private AndroidacyRepoData addAndroidacyRepoData() { private AndroidacyRepoData addAndroidacyRepoData() {
// cache dir is actually under app data // cache dir is actually under app data
File cacheRoot = new File(this.mainApplication.getDataDir(), "androidacy_repo"); File cacheRoot = new File(this.mainApplication.getDataDir(), "repos/androidacy_repo");
SharedPreferences sharedPreferences = this.mainApplication.getSharedPreferences("mmm_androidacy_repo", Context.MODE_PRIVATE); SharedPreferences sharedPreferences = this.mainApplication.getSharedPreferences("mmm_androidacy_repo", Context.MODE_PRIVATE);
AndroidacyRepoData repoData = new AndroidacyRepoData(cacheRoot, sharedPreferences, MainApplication.isAndroidacyTestMode()); AndroidacyRepoData repoData = new AndroidacyRepoData(cacheRoot, sharedPreferences, MainApplication.isAndroidacyTestMode());
this.repoData.put(ANDROIDACY_MAGISK_REPO_ENDPOINT, repoData); this.repoData.put(ANDROIDACY_MAGISK_REPO_ENDPOINT, repoData);

@ -1,13 +1,12 @@
package com.fox2code.mmm.repo; package com.fox2code.mmm.repo;
import androidx.annotation.NonNull;
import com.fox2code.mmm.utils.io.Http; import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.realm.ModuleListCache; import com.fox2code.mmm.utils.realm.ModuleListCache;
import org.json.JSONException; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -84,8 +83,8 @@ public class RepoUpdater {
// iterate over modules, using this.supportedProperties as a template to attempt to get each property from the module. everything that is not null is added to the module // iterate over modules, using this.supportedProperties as a template to attempt to get each property from the module. everything that is not null is added to the module
// use realm to insert to // use realm to insert to
// props avail: // props avail:
// File cacheRoot = this.repoData.cacheRoot;
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ModuleListCache.realm").schemaVersion(1).deleteRealmIfMigrationNeeded().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 // array with module info default values
// supported properties for a module // supported properties for a module
//id=<string> //id=<string>
@ -109,32 +108,21 @@ public class RepoUpdater {
//installedVersionCode=<int> (only if installed) //installedVersionCode=<int> (only if installed)
// //
// all except first six can be null // all except first six can be null
// this.indexRaw is the raw index file (json) and the modules can be either under the "modules" key or the "data" key // this.indexRaw is the raw index file (json)
// both are arrays of modules
// try to get modules from "modules" key
JSONObject modules = new JSONObject(new String(this.indexRaw, StandardCharsets.UTF_8)); JSONObject modules = new JSONObject(new String(this.indexRaw, StandardCharsets.UTF_8));
// check if modules has key "modules" or "data" JSONArray modulesArray;
try { // androidacy repo uses "data" key, others should use "modules" key. Both are JSONArrays
if (modules.has("modules")) { if (this.repoData.getName().equals("Androidacy Modules Repo")) {
// get modules from "modules" key // get modules from "data" key. This is a JSONArray so we need to convert it to a JSONObject
modules = modules.getJSONObject("modules"); modulesArray = modules.getJSONArray("data");
} else if (modules.has("data")) { } else {
// get modules from "data" key // get modules from "modules" key. This is a JSONArray so we need to convert it to a JSONObject
modules = modules.getJSONObject("data"); modulesArray = modules.getJSONArray("modules");
}
} catch (JSONException e) {
// there's a possibility that the modules key is an array, so we need to convert it to a json object
// get modules array
JSONObject[] modulesArray = new JSONObject[]{modules};
// create new json object
modules = new JSONObject();
// iterate over modules array
for (int i = 0; i < modulesArray.length; i++) {
// put module in json object
modules.put(String.valueOf(i), modulesArray[i]);
}
} }
for (JSONObject module : new JSONObject[]{modules}) { // iterate over modules. pls dont hate me for this, its ugly but it works
for (int n = 0; n < modulesArray.length(); n++) {
// get module
JSONObject module = modulesArray.getJSONObject(n);
try { try {
// get module id // get module id
String id = module.getString("id"); String id = module.getString("id");
@ -149,23 +137,74 @@ public class RepoUpdater {
// get module description // get module description
String description = module.getString("description"); String description = module.getString("description");
// get module min api // get module min api
int minApi = module.getInt("minApi"); String minApi;
// get module max api if (module.has("minApi") && !module.getString("minApi").equals("")) {
int maxApi = module.getInt("maxApi"); minApi = module.getString("minApi");
} else {
minApi = "0";
}
// coerce min api to int
int minApiInt = Integer.parseInt(minApi);
// get module max api and set to 0 if it's "" or null
String maxApi;
if (module.has("maxApi") && !module.getString("maxApi").equals("")) {
maxApi = module.getString("maxApi");
} else {
maxApi = "0";
}
// coerce max api to int
int maxApiInt = Integer.parseInt(maxApi);
// get module min magisk // get module min magisk
int minMagisk = module.getInt("minMagisk"); String minMagisk;
if (module.has("minMagisk") && !module.getString("minMagisk").equals("")) {
minMagisk = module.getString("minMagisk");
} else {
minMagisk = "0";
}
// coerce min magisk to int
int minMagiskInt = Integer.parseInt(minMagisk);
// get module need ramdisk // get module need ramdisk
boolean needRamdisk = module.getBoolean("needRamdisk"); boolean needRamdisk;
if (module.has("needRamdisk")) {
needRamdisk = module.getBoolean("needRamdisk");
} else {
needRamdisk = false;
}
// get module support // get module support
String support = module.getString("support"); String support;
if (module.has("support")) {
support = module.getString("support");
} else {
support = "";
}
// get module donate // get module donate
String donate = module.getString("donate"); String donate;
if (module.has("donate")) {
donate = module.getString("donate");
} else {
donate = "";
}
// get module config // get module config
String config = module.getString("config"); String config;
if (module.has("config")) {
config = module.getString("config");
} else {
config = "";
}
// get module change boot // get module change boot
boolean changeBoot = module.getBoolean("changeBoot"); boolean changeBoot;
if (module.has("changeBoot")) {
changeBoot = module.getBoolean("changeBoot");
} else {
changeBoot = false;
}
// get module mmt reborn // get module mmt reborn
boolean mmtReborn = module.getBoolean("mmtReborn"); boolean mmtReborn;
if (module.has("mmtReborn")) {
mmtReborn = module.getBoolean("mmtReborn");
} else {
mmtReborn = false;
}
// get module repo id // get module repo id
String repoId = this.repoData.id; String repoId = this.repoData.id;
// get module installed // get module installed
@ -177,63 +216,53 @@ public class RepoUpdater {
// then insert to realm // then insert to realm
// then commit // then commit
// then close // then close
Realm.getInstanceAsync(realmConfiguration, new Realm.Callback() { Realm realm = Realm.getInstance(realmConfiguration);
@Override if (realm.isInTransaction()) {
public void onSuccess(@NonNull Realm realm) { realm.cancelTransaction();
realm.executeTransactionAsync(r -> { }
// create a new module realm.executeTransaction(r -> {
ModuleListCache moduleListCache = r.createObject(ModuleListCache.class); // create the object
// set module id // if it already exists, it will be updated
moduleListCache.setId(id); // create a new module
// set module name ModuleListCache moduleListCache = r.createObject(ModuleListCache.class, id);
moduleListCache.setName(name); // set module name
// set module version moduleListCache.setName(name);
moduleListCache.setVersion(version); // set module version
// set module version code moduleListCache.setVersion(version);
moduleListCache.setVersionCode(versionCode); // set module version code
// set module author moduleListCache.setVersionCode(versionCode);
moduleListCache.setAuthor(author); // set module author
// set module description moduleListCache.setAuthor(author);
moduleListCache.setDescription(description); // set module description
// set module min api moduleListCache.setDescription(description);
moduleListCache.setMinApi(minApi); // set module min api
// set module max api moduleListCache.setMinApi(minApiInt);
moduleListCache.setMaxApi(maxApi); // set module max api
// set module min magisk moduleListCache.setMaxApi(maxApiInt);
moduleListCache.setMinMagisk(minMagisk); // set module min magisk
// set module need ramdisk moduleListCache.setMinMagisk(minMagiskInt);
moduleListCache.setNeedRamdisk(needRamdisk); // set module need ramdisk
// set module support moduleListCache.setNeedRamdisk(needRamdisk);
moduleListCache.setSupport(support); // set module support
// set module donate moduleListCache.setSupport(support);
moduleListCache.setDonate(donate); // set module donate
// set module config moduleListCache.setDonate(donate);
moduleListCache.setConfig(config); // set module config
// set module change boot moduleListCache.setConfig(config);
moduleListCache.setChangeBoot(changeBoot); // set module change boot
// set module mmt reborn moduleListCache.setChangeBoot(changeBoot);
moduleListCache.setMmtReborn(mmtReborn); // set module mmt reborn
// set module repo id moduleListCache.setMmtReborn(mmtReborn);
moduleListCache.setRepoId(repoId); // set module repo id
// set module installed moduleListCache.setRepoId(repoId);
moduleListCache.setInstalled(installed); // set module installed
// set module installed version code moduleListCache.setInstalled(installed);
moduleListCache.setInstalledVersionCode(installedVersionCode); // set module installed version code
}, () -> { moduleListCache.setInstalledVersionCode(installedVersionCode);
// Transaction was a success.
Timber.d("onSuccess: Transaction was a success.");
// close realm
realm.close();
}, error -> {
// Transaction failed and was automatically canceled.
Timber.e(error);
// close realm
realm.close();
});
}
}); });
realm.close();
} catch ( } catch (
JSONException e) { Exception e) {
e.printStackTrace(); e.printStackTrace();
Timber.w("Failed to get module info from module " + module + " in repo " + this.repoData.id + " with error " + e.getMessage()); Timber.w("Failed to get module info from module " + module + " in repo " + this.repoData.id + " with error " + e.getMessage());
} }

@ -40,6 +40,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.sentry.android.okhttp.SentryOkHttpInterceptor;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.Cookie; import okhttp3.Cookie;
import okhttp3.CookieJar; import okhttp3.CookieJar;
@ -174,6 +175,10 @@ public class Http {
Timber.e(e, "Failed to init cronet"); Timber.e(e, "Failed to init cronet");
// Gracefully fallback to okhttp // Gracefully fallback to okhttp
} }
// add sentry interceptor
if (MainApplication.isCrashReportingEnabled()) {
httpclientBuilder.addInterceptor(new SentryOkHttpInterceptor());
}
// Fallback DNS cache responses in case request fail but already succeeded once in the past // Fallback DNS cache responses in case request fail but already succeeded once in the past
fallbackDNS = new FallBackDNS(mainApplication, dns, "github.com", "api.github.com", "raw.githubusercontent.com", "camo.githubusercontent.com", "user-images.githubusercontent.com", "cdn.jsdelivr.net", "img.shields.io", "magisk-modules-repo.github.io", "www.androidacy.com", "api.androidacy.com", "production-api.androidacy.com"); fallbackDNS = new FallBackDNS(mainApplication, dns, "github.com", "api.github.com", "raw.githubusercontent.com", "camo.githubusercontent.com", "user-images.githubusercontent.com", "cdn.jsdelivr.net", "img.shields.io", "magisk-modules-repo.github.io", "www.androidacy.com", "api.androidacy.com", "production-api.androidacy.com");
httpclientBuilder.cookieJar(new CDNCookieJar(cookieManager)); httpclientBuilder.cookieJar(new CDNCookieJar(cookieManager));

@ -15,6 +15,7 @@ import java.util.Objects;
import io.sentry.Sentry; import io.sentry.Sentry;
import io.sentry.android.core.SentryAndroid; import io.sentry.android.core.SentryAndroid;
import io.sentry.android.fragment.FragmentLifecycleIntegration; import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
public class SentryMain { public class SentryMain {
public static final boolean IS_SENTRY_INSTALLED = true; public static final boolean IS_SENTRY_INSTALLED = true;
@ -43,6 +44,8 @@ public class SentryMain {
intent.putExtra("exception", throwable); intent.putExtra("exception", throwable);
// add stacktrace as string // add stacktrace as string
intent.putExtra("stacktrace", throwable.getStackTrace()); intent.putExtra("stacktrace", throwable.getStackTrace());
// put lastEventId in intent (get from preferences)
intent.putExtra("lastEventId", String.valueOf(Sentry.getLastEventId()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mainApplication.startActivity(intent); mainApplication.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid()); android.os.Process.killProcess(android.os.Process.myPid());
@ -55,6 +58,10 @@ public class SentryMain {
} else { } else {
sentryEnabled = true; // Set sentry state to enabled sentryEnabled = true; // Set sentry state to enabled
options.addIntegration(new FragmentLifecycleIntegration(mainApplication, true, true)); options.addIntegration(new FragmentLifecycleIntegration(mainApplication, true, true));
// Enable automatic activity lifecycle breadcrumbs
options.setEnableActivityLifecycleBreadcrumbs(true);
// Enable automatic fragment lifecycle breadcrumbs
options.addIntegration(new SentryTimberIntegration());
options.setCollectAdditionalContext(true); options.setCollectAdditionalContext(true);
options.setAttachThreads(true); options.setAttachThreads(true);
options.setAttachStacktrace(true); options.setAttachStacktrace(true);
@ -84,8 +91,10 @@ public class SentryMain {
// Filter breadrcrumb content from crash report. // Filter breadrcrumb content from crash report.
options.setBeforeBreadcrumb((breadcrumb, hint) -> { options.setBeforeBreadcrumb((breadcrumb, hint) -> {
String url = (String) breadcrumb.getData("url"); String url = (String) breadcrumb.getData("url");
if (url == null || url.isEmpty()) return breadcrumb; if (url == null || url.isEmpty())
if ("cloudflare-dns.com".equals(Uri.parse(url).getHost())) return null; return breadcrumb;
if ("cloudflare-dns.com".equals(Uri.parse(url).getHost()))
return null;
if (AndroidacyUtil.isAndroidacyLink(url)) { if (AndroidacyUtil.isAndroidacyLink(url)) {
breadcrumb.setData("url", AndroidacyUtil.hideToken(url)); breadcrumb.setData("url", AndroidacyUtil.hideToken(url));
} }

@ -287,7 +287,7 @@
<string name="setup_language_button">Choose language</string> <string name="setup_language_button">Choose language</string>
<string name="title_activity_setup">Setup Wizard</string> <string name="title_activity_setup">Setup Wizard</string>
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<string name="repo_update_failed_message">The following repos have failed to update:\\n\\n%s</string> <string name="repo_update_failed_message">The following repos have failed to update:\n\n%s</string>
<string name="reset_api_key">Reset API keys</string> <string name="reset_api_key">Reset API keys</string>
<string name="upgrade_androidacy_promo">Upgrade to premium</string> <string name="upgrade_androidacy_promo">Upgrade to premium</string>
<string name="upgrade_androidacy_promo_desc">Upgrading to premium will remove ads, captchas, and downloads for the Androidacy Repository, and support Androidacy and the module authors.</string> <string name="upgrade_androidacy_promo_desc">Upgrading to premium will remove ads, captchas, and downloads for the Androidacy Repository, and support Androidacy and the module authors.</string>

@ -4,7 +4,12 @@ buildscript {
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
maven { url 'https://jitpack.io' } maven {
url 'https://jitpack.io'
}
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
} }
project.ext.latestAboutLibsRelease = "10.5.2" project.ext.latestAboutLibsRelease = "10.5.2"
project.ext.sentryConfigFile = new File(rootDir, "sentry.properties").getAbsoluteFile() project.ext.sentryConfigFile = new File(rootDir, "sentry.properties").getAbsoluteFile()
@ -22,7 +27,7 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath "io.realm:realm-gradle-plugin:10.11.1" classpath "io.realm:realm-gradle-plugin:10.13.1-transformer-api"
} }
} }

@ -6,6 +6,9 @@ dependencyResolutionManagement {
maven { maven {
url 'https://jitpack.io' url 'https://jitpack.io'
} }
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
} }
} }
rootProject.name = "MagiskModuleManager" rootProject.name = "MagiskModuleManager"

Loading…
Cancel
Save