move to Timber for logging

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/284/head
androidacy-user 1 year ago
parent 8898d0674c
commit f13ed32a22

@ -288,7 +288,7 @@ dependencies {
implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.9'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
implementation 'io.sentry:sentry-android:6.12.1'
implementation "io.sentry:sentry-android:$sentry_version"
// Markdown
implementation "io.noties.markwon:core:4.6.2"
@ -302,6 +302,12 @@ dependencies {
//implementation "androidx.appcompat:appcompat:${versions.appCompat}"
implementation 'androidx.core:core-ktx:1.9.0'
implementation "io.sentry:sentry-kotlin-extensions:$sentry_version"
// timber
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation "io.sentry:sentry-android-timber:$sentry_version"
}
if (hasSentryConfig) {

@ -1,7 +1,5 @@
package com.fox2code.mmm;
import android.util.Log;
import com.fox2code.mmm.utils.io.Files;
import com.fox2code.mmm.utils.io.Http;
@ -17,6 +15,8 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import timber.log.Timber;
// See https://docs.github.com/en/rest/reference/repos#releases
public class AppUpdateManager {
public static final int FLAG_COMPAT_LOW_QUALITY = 0x0001;
@ -29,10 +29,8 @@ public class AppUpdateManager {
public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080;
public static final int FLAG_COMPAT_MMT_REBORN = 0x0100;
public static final int FLAG_COMPAT_ZIP_WRAPPER = 0x0200;
private static final String TAG = "AppUpdateManager";
private static final AppUpdateManager INSTANCE = new AppUpdateManager();
private static final String RELEASES_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases";
private static final String COMPAT_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/issues/4";
private final HashMap<String, Integer> compatDataId = new HashMap<>();
private final Object updateLock = new Object();
private final File compatFile;
@ -120,15 +118,15 @@ public class AppUpdateManager {
this.preReleaseNewer = false;
}
if (BuildConfig.DEBUG)
Log.d(TAG, "Latest release: " + latestRelease);
Timber.d("Latest release: %s", latestRelease);
if (BuildConfig.DEBUG)
Log.d(TAG, "Latest pre-release: " + latestPreRelease);
Timber.d("Latest pre-release: %s", latestPreRelease);
if (BuildConfig.DEBUG)
Log.d(TAG, "Latest pre-release newer: " + preReleaseNewer);
Timber.d("Latest pre-release newer: %s", preReleaseNewer);
this.lastChecked = System.currentTimeMillis();
} catch (
Exception ioe) {
Log.e("AppUpdateManager", "Failed to check releases", ioe);
Timber.e(ioe);
}
}
return this.peekShouldUpdate();
@ -138,11 +136,12 @@ public class AppUpdateManager {
compatDataId.clear();
try {
Files.write(compatFile, new byte[0]);
} catch (IOException e) {
} catch (
IOException e) {
e.printStackTrace();
}
// There once lived an implementation that used a GitHub API to get the compatibility flags. It was removed because it was too slow and the API was rate limited.
Log.w(TAG, "Remote compatibility data flags are not implemented.");
Timber.w("Remote compatibility data flags are not implemented.");
}
public boolean peekShouldUpdate() {

@ -1,5 +1,7 @@
package com.fox2code.mmm;
import static com.fox2code.mmm.MainApplication.isOfficial;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
@ -13,13 +15,13 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
@ -56,9 +58,9 @@ import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
import java.net.URL;
import eightbitlab.com.blurview.BlurView;
import timber.log.Timber;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
private static final String TAG = "MainActivity";
private static final int PRECISION = 10000;
public static boolean doSetupNowRunning = true;
public final ModuleViewListBuilder moduleViewListBuilder;
@ -104,12 +106,12 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
URL.setURLStreamHandlerFactory(cronetURLStreamHandlerFactory);
} catch (
Error e) {
Log.e(TAG, "Failed to install Cronet URLStreamHandlerFactory");
Timber.e("Failed to install Cronet URLStreamHandlerFactory");
}
urlFactoryInstalled = true;
} catch (
Throwable t) {
Log.e(TAG, "Failed to install CronetURLStreamHandlerFactory - other");
Timber.e("Failed to install CronetURLStreamHandlerFactory - other");
}
}
if (doSetupRestarting) {
@ -117,6 +119,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
}
BackgroundUpdateChecker.onMainActivityCreate(this);
super.onCreate(savedInstanceState);
if (!isOfficial) {
Timber.w("You may be running an untrusted build.");
// Show a toast to warn the user
Toast.makeText(this, R.string.not_official_build, Toast.LENGTH_LONG).show();
}
if (!MainApplication.getSharedPreferences().getBoolean("first_time_user", true)) {
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
IntentHelper.startActivity(this, SettingsActivity.class);
@ -181,7 +188,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
Log.i(TAG, "Got magisk path: " + path);
Timber.i("Got magisk path: %s", path);
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
@ -193,21 +200,19 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@Override
public void onFailure(int error) {
Log.i(TAG, "Failed to get magisk path!");
Timber.i("Failed to get magisk path!");
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
this.commonNext();
}
public void commonNext() {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Common next");
Timber.d("Common next");
moduleViewListBuilder.addNotification(NotificationType.DEBUG);
}
updateScreenInsets(); // Fix an edge case
if (waitInitialSetupFinished()) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Initial setup not finished, waiting...");
}
Timber.d("waiting...");
return;
}
swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
@ -225,22 +230,22 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
// On every preferences change, log the change if debug is enabled
if (BuildConfig.DEBUG) {
Log.d("PrefsListener", "onCreate: Preferences: " + MainApplication.getSharedPreferences().getAll());
Timber.d("onCreate: Preferences: %s", MainApplication.getSharedPreferences().getAll());
// Log all preferences changes
MainApplication.getSharedPreferences().registerOnSharedPreferenceChangeListener((prefs, key) -> Log.i("PrefsListener", "onSharedPreferenceChanged: " + key + " = " + prefs.getAll().get(key)));
MainApplication.getSharedPreferences().registerOnSharedPreferenceChangeListener((prefs, key) -> Timber.i("onSharedPreferenceChanged: " + key + " = " + prefs.getAll().get(key)));
}
Log.i(TAG, "Scanning for modules!");
Timber.i("Scanning for modules!");
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Initialize Update");
Timber.i("Initialize Update");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
Log.w(TAG, "Need update on create?");
Timber.w("Need update on create?");
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Update Compat");
Timber.i("Check Update Compat");
AppUpdateManager.getAppUpdateManager().checkUpdateCompat();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Update");
Timber.i("Check Update");
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
// Add debug notification for debug builds
@ -255,11 +260,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check App Update");
Timber.i("Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Json Update");
Timber.i("Check Json Update");
if (max != 0) {
int current = 0;
@ -267,12 +272,12 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", localModuleInfo.id);
Timber.i(localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (
Exception e) {
Log.e("MainActivity", "Failed to fetch update of: " + localModuleInfo.id, e);
Timber.e(e);
}
current++;
final int currentTmp = current;
@ -289,11 +294,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
updateScreenInsets(getResources().getConfiguration());
});
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Apply");
Timber.i("Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
Log.i(TAG, "Finished app opening state!");
Timber.i("Finished app opening state!");
// noodleDebug.unbind();
}
}, true);
@ -331,7 +336,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
//this.actionBarBlur.invalidate();
this.overScrollInsetTop = combinedBarsHeight;
this.overScrollInsetBottom = bottomInset;
Log.i(TAG, "( " + bottomInset + ", " + this.searchCard.getHeight() + ")");
Timber.i("(" + this.searchCard.getHeight() + ")");
}
private void updateBlurState() {
@ -360,7 +365,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
if (this.initMode)
return;
this.initMode = true;
Log.i(TAG, "Item Before");
Timber.i("Item Before");
this.searchView.setQuery("", false);
this.searchView.clearFocus();
this.searchView.setIconified(true);
@ -368,7 +373,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.updateScreenInsets();
this.updateBlurState();
this.moduleViewListBuilder.setQuery(null);
Log.i(TAG, "Item After");
Timber.i("Item After");
this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter);
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
@ -399,7 +404,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
}
public void commonNext() {
Log.i(TAG, "Common Before");
Timber.i("Common Before");
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
@ -414,7 +419,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
progressIndicator.setMax(PRECISION);
});
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Update");
Timber.i("Check Update");
RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> progressIndicator.setProgressCompat((int) (value * PRECISION), true)));
runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true);
@ -422,11 +427,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
});
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Apply");
Timber.i("Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
Log.i(TAG, "Common Before applyTo");
Timber.i("Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
Log.i(TAG, "Common After");
Timber.i("Common After");
}
});
this.initMode = false;
@ -444,7 +449,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
return; // Do not double scan
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Refresh");
Timber.i("Refresh");
this.progressIndicator.setVisibility(View.VISIBLE);
this.progressIndicator.setProgressCompat(0, false);
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
@ -463,22 +468,22 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check App Update");
Timber.i("Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Json Update");
Timber.i("Check Json Update");
if (max != 0) {
int current = 0;
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", localModuleInfo.id);
Timber.i(localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (
Exception e) {
Log.e("MainActivity", "Failed to fetch update of: " + localModuleInfo.id, e);
Timber.e(e);
}
current++;
final int currentTmp = current;
@ -488,7 +493,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
}
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Apply");
Timber.i("Apply");
runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false);
@ -547,18 +552,18 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@SuppressLint("RestrictedApi")
private void ensurePermissions() {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Ensure Permissions");
Timber.i("Ensure Permissions");
// First, check if user has said don't ask again by checking if pref_dont_ask_again_notification_permission is true
if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("pref_dont_ask_again_notification_permission", false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Request Notification Permission");
Timber.i("Request Notification Permission");
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
// Show a dialog explaining why we need this permission, which is to show
// notifications for updates
runOnUiThread(() -> {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Show Notification Permission Dialog");
Timber.i("Show Notification Permission Dialog");
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.permission_notification_title);
builder.setMessage(R.string.permission_notification_message);
@ -582,16 +587,17 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
});
builder.show();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Show Notification Permission Dialog Done");
Timber.i("Show Notification Permission Dialog Done");
});
} else {
// Request the permission
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Request Notification Permission");
Timber.i("Request Notification Permission");
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
if (BuildConfig.DEBUG) {
// Log if granted via onRequestPermissionsResult
Log.i("NoodleDebug", "Request Notification Permission Done. Result: " + (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED));
boolean granted = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
Timber.i( "Request Notification Permission Done. Result: %s", granted);
}
doSetupNowRunning = false;
}
@ -630,7 +636,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
}
} else {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Notification Permission Already Granted or Don't Ask Again");
Timber.i("Notification Permission Already Granted or Don't Ask Again");
doSetupNowRunning = false;
}
}
@ -639,17 +645,17 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
@SuppressLint({"InflateParams", "RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref"})
private void checkShowInitialSetup() {
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "Checking if we need to run setup");
Timber.i("Checking if we need to run setup");
// Check if this is the first launch
SharedPreferences prefs = MainApplication.getSharedPreferences();
boolean firstLaunch = prefs.getBoolean("first_time_user", true);
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "First launch: " + firstLaunch);
Timber.i("First launch: %s", firstLaunch);
if (firstLaunch) {
doSetupNowRunning = true;
// Launch setup wizard
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "Launching setup wizard");
Timber.i("Launching setup wizard");
// Show setup activity
Intent intent = new Intent(this, SetupActivity.class);
finish();
@ -664,7 +670,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
*/
private boolean waitInitialSetupFinished() {
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "waitInitialSetupFinished");
Timber.i("waitInitialSetupFinished");
if (doSetupNowRunning)
updateScreenInsets(); // Fix an edge case
try {

@ -1,7 +1,9 @@
package com.fox2code.mmm;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@ -11,7 +13,6 @@ import android.content.res.Resources;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
@ -36,6 +37,7 @@ import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
@ -45,6 +47,9 @@ import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import io.realm.Realm;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
import timber.log.Timber;
public class MainApplication extends FoxApplication implements androidx.work.Configuration.Provider {
private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
@ -56,7 +61,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
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 HashSet<String> supportedLocales = new HashSet<>();
public static final HashSet<String> supportedLocales = new HashSet<>();
private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().getLocales().get(0);
private static SimpleDateFormat timeFormat = new SimpleDateFormat(timeFormatString, timeFormatLocale);
private static SharedPreferences bootSharedPreferences;
@ -215,7 +220,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
boolean monet = isMonetEnabled();
switch (theme = getSharedPreferences().getString("pref_theme", "system")) {
default:
Log.w("MainApplication", "Unknown theme id: " + theme);
Timber.w("Unknown theme id: %s", theme);
case "system":
themeResId = monet ? R.style.Theme_MagiskModuleManager_Monet : R.style.Theme_MagiskModuleManager;
break;
@ -230,7 +235,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
break;
case "transparent_light":
if (monet) {
Log.w("MainApplication", "Monet is not supported for transparent theme");
Timber.tag("MainApplication").w("Monet is not supported for transparent theme");
}
themeResId = R.style.Theme_MagiskModuleManager_Transparent_Light;
break;
@ -276,6 +281,9 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
@Override
public void onCreate() {
// init timber
if (BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree());
else Timber.plant(new ReleaseTree());
// supportedLocales.add("ar");
// supportedLocales.add("ar_SA");
supportedLocales.add("cs");
@ -303,18 +311,16 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
if (INSTANCE == null)
INSTANCE = this;
relPackageName = this.getPackageName();
if (BuildConfig.DEBUG) {
Log.d("MainApplication", "Starting FoxMMM version " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + "), commit " + BuildConfig.COMMIT_HASH);
}
Timber.d("Starting FoxMMM version " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + "), commit " + BuildConfig.COMMIT_HASH);
super.onCreate();
if (BuildConfig.DEBUG) {
Log.d("MainApplication", "FoxMMM is running in debug mode");
}
if (BuildConfig.DEBUG) {
Log.d("MainApplication", "Initializing Realm");
Timber.d("Initializing FoxMMM");
Timber.d("Started from background: %s", !isInForeground());
Timber.d("FoxMMM is running in debug mode");
Timber.d("Initializing Realm");
}
Realm.init(this);
Timber.d("Initialized Realm");
// Determine if this is an official build based on the signature
try {
// Get the signature of the key used to sign the app
@ -325,11 +331,6 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
} catch (
PackageManager.NameNotFoundException ignored) {
}
if (!isOfficial) {
Log.w("MainApplication", "This is not an official build of FoxMMM. This warning can be safely ignored if this is expected, otherwise you may be running an untrusted build.");
// Show a toast to warn the user
Toast.makeText(this, R.string.not_official_build, Toast.LENGTH_LONG).show();
}
SharedPreferences sharedPreferences = MainApplication.getSharedPreferences();
// We are only one process so it's ok to do this
SharedPreferences bootPrefs = MainApplication.bootSharedPreferences = this.getSharedPreferences("mmm_boot", MODE_PRIVATE);
@ -357,14 +358,14 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
fontRequestEmojiCompatConfig.setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
EmojiCompat emojiCompat = EmojiCompat.init(fontRequestEmojiCompatConfig);
new Thread(() -> {
Log.i("MainApplication", "Loading emoji compat...");
Timber.i("Loading emoji compat...");
emojiCompat.load();
Log.i("MainApplication", "Emoji compat loaded!");
Timber.i("Emoji compat loaded!");
}, "Emoji compat init.").start();
}
SentryMain.initialize(this);
if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) {
Log.w("MainApplication", "Androidacy client id is empty! Please set it in androidacy.properties. Will not enable Androidacy.");
Timber.w("Androidacy client id is empty! Please set it in androidacy.properties. Will not enable Androidacy.");
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("pref_androidacy_repo_enabled", false);
editor.apply();
@ -408,7 +409,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
if (!dataDir.exists()) {
if (!dataDir.mkdirs()) {
if (BuildConfig.DEBUG)
Log.w("MainApplication", "Failed to create directory " + dataDir);
Timber.w("Failed to create directory %s", dataDir);
}
}
return dataDir;
@ -427,31 +428,69 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
if (children != null) {
for (String s : children) {
if (BuildConfig.DEBUG)
Log.w("MainApplication", "Deleting " + s);
Timber.w("Deleting %s", s);
if (!s.equals("lib")) {
if (!new File(cacheDir, s).delete()) {
if (BuildConfig.DEBUG)
Log.w("MainApplication", "Failed to delete " + s);
Timber.w("Failed to delete %s", s);
}
}
}
}
}
if (BuildConfig.DEBUG)
Log.w("MainApplication", "Deleting cache dir");
Timber.w("Deleting cache dir");
this.deleteSharedPreferences("mmm_boot");
this.deleteSharedPreferences("mmm");
this.deleteSharedPreferences("sentry");
this.deleteSharedPreferences("androidacy");
if (BuildConfig.DEBUG)
Log.w("MainApplication", "Deleting shared prefs");
Timber.w("Deleting shared prefs");
this.getPackageManager().clearPackagePreferredActivities(this.getPackageName());
if (BuildConfig.DEBUG)
Log.w("MainApplication", "Done clearing app data");
Timber.w("Done clearing app data");
} catch (
Exception e) {
Log.e("MainApplication", "Failed to clear app data", e);
Timber.e(e);
}
}
public boolean isInForeground() {
// determine if the app is in the foreground
ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager != null) {
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses != null) {
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
for (String activeProcess : appProcess.pkgList) {
if (activeProcess.equals(this.getPackageName())) {
return true;
}
}
return false;
}
}
}
} else {
Timber.e("Failed to get activity manager");
}
return false;
}
private static class ReleaseTree extends Timber.Tree {
@SuppressWarnings("StatementWithEmptyBody")
@Override
protected void log(int priority, String tag, @NonNull String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
// silently ignore
} else if (priority == Log.INFO) {
Sentry.captureMessage(message, SentryLevel.INFO);
} else if (priority == Log.WARN) {
Sentry.captureMessage(message, SentryLevel.WARNING);
} else if (priority == Log.ERROR) {
Sentry.captureException(t);
}
}
}
}

@ -1,6 +1,5 @@
package com.fox2code.mmm;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@ -24,8 +23,9 @@ import java.io.InputStreamReader;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import timber.log.Timber;
interface NotificationTypeCst {
String TAG = "NotificationType";
}
public enum NotificationType implements NotificationTypeCst {
@ -119,7 +119,7 @@ public enum NotificationType implements NotificationTypeCst {
}
if (needPatch(d)) {
if (d.exists() && !d.delete())
Log.w(TAG, "Failed to delete non module zip");
Timber.w("Failed to delete non module zip");
Toast.makeText(compatActivity,
R.string.invalid_format, Toast.LENGTH_SHORT).show();
} else {
@ -131,7 +131,7 @@ public enum NotificationType implements NotificationTypeCst {
}
} catch (IOException ignored) {
if (d.exists() && !d.delete())
Log.w(TAG, "Failed to delete invalid module");
Timber.w("Failed to delete invalid module");
Toast.makeText(compatActivity,
R.string.invalid_format, Toast.LENGTH_SHORT).show();
}
@ -152,7 +152,7 @@ public enum NotificationType implements NotificationTypeCst {
}
};
public static boolean needPatch(File target) throws IOException {
public static boolean needPatch(File target) {
try (ZipFile zipFile = new ZipFile(target)) {
boolean validEntries = zipFile.getEntry("module.prop") != null;
// ensure there's no anykernel.sh

@ -7,7 +7,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@ -33,6 +32,7 @@ import java.util.Objects;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmResults;
import timber.log.Timber;
public class SetupActivity extends FoxActivity implements LanguageActivity {
@ -91,10 +91,10 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("magisk_alt_repo"));
// On debug builds, log when a switch is toggled
if (BuildConfig.DEBUG) {
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Background Update Check: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Crash Reporting: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Androidacy Repo: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Magisk Alt Repo: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Background Update Check: %s", isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Crash Reporting: %s", isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Androidacy Repo: %s", isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Timber.i("Magisk Alt Repo: %s", isChecked));
}
// Setup popup dialogue for the setup_theme_button
MaterialButton themeButton = view.findViewById(R.id.setup_theme_button);
@ -213,8 +213,8 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
}
// Log the changes if debug
if (BuildConfig.DEBUG) {
Log.d("SetupWizard", "Background update check: " + prefs.getBoolean("pref_background_update_check", false));
Log.i("SetupWizard", "Crash reporting: " + prefs.getBoolean("pref_crash_reporting", false));
Timber.d("Background update check: %s", prefs.getBoolean("pref_background_update_check", false));
Timber.i("Crash reporting: %s", prefs.getBoolean("pref_crash_reporting", false));
}
// Restart the activity
MainActivity.doSetupRestarting = true;
@ -274,9 +274,7 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
// creates the realm database
private void createRealmDatabase() {
if (BuildConfig.DEBUG) {
Log.d("Realm", "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
@ -333,20 +331,20 @@ public class SetupActivity extends FoxActivity implements LanguageActivity {
realm1.commitTransaction();
realm1.close();
if (BuildConfig.DEBUG) {
Log.d("Realm", "Realm databases created");
Timber.d("Realm databases created");
// log each database
Realm realm2 = Realm.getInstance(config);
RealmResults<ModuleListCache> moduleListCaches = realm2.where(ModuleListCache.class).findAll();
Log.d("Realm", "ModuleListCache.realm");
Timber.d("ModuleListCache.realm");
for (ModuleListCache moduleListCache : moduleListCaches) {
Log.d("Realm", moduleListCache.toString());
Timber.d(moduleListCache.toString());
}
realm2.close();
Realm realm3 = Realm.getInstance(config2);
RealmResults<ReposList> reposLists = realm3.where(ReposList.class).findAll();
Log.d("Realm", "ReposList.realm");
Timber.d("ReposList.realm");
for (ReposList reposList : reposLists) {
Log.d("Realm", reposList.toString());
Timber.d(reposList.toString());
}
realm3.close();
}

@ -6,7 +6,6 @@ import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback;
@ -32,8 +31,8 @@ import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.Http;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import java.io.ByteArrayInputStream;
@ -43,11 +42,12 @@ import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import timber.log.Timber;
/**
* Per Androidacy repo implementation agreement, no request of this WebView shall be modified.
*/
public final class AndroidacyActivity extends FoxActivity {
private static final String TAG = "AndroidacyActivity";
static {
if (BuildConfig.DEBUG) {
@ -72,18 +72,18 @@ public final class AndroidacyActivity extends FoxActivity {
Intent intent = this.getIntent();
Uri uri;
if (!MainApplication.checkSecret(intent) || (uri = intent.getData()) == null) {
Log.w(TAG, "Impersonation detected");
Timber.w("Impersonation detected");
this.forceBackPressed();
return;
}
String url = uri.toString();
if (!AndroidacyUtil.isAndroidacyLink(url, uri)) {
Log.w(TAG, "Calling non androidacy link in secure WebView: " + url);
Timber.w("Calling non androidacy link in secure WebView: %s", url);
this.forceBackPressed();
return;
}
if (!Http.hasWebView()) {
Log.w(TAG, "No WebView found to load url: " + url);
Timber.w("No WebView found to load url: %s", url);
this.forceBackPressed();
return;
}
@ -167,7 +167,7 @@ public final class AndroidacyActivity extends FoxActivity {
if (request.isForMainFrame() && !AndroidacyUtil.isAndroidacyLink(request.getUrl())) {
if (downloadMode || backOnResume)
return true;
Log.i(TAG, "Exiting WebView " + AndroidacyUtil.hideToken(request.getUrl().toString()));
Timber.i("Exiting WebView %s", AndroidacyUtil.hideToken(request.getUrl().toString()));
IntentHelper.openUri(view.getContext(), request.getUrl().toString());
return true;
}
@ -229,19 +229,19 @@ public final class AndroidacyActivity extends FoxActivity {
if (BuildConfig.DEBUG) {
switch (consoleMessage.messageLevel()) {
case TIP:
Log.v(TAG, consoleMessage.message());
Timber.v(consoleMessage.message());
break;
case LOG:
Log.i(TAG, consoleMessage.message());
Timber.i(consoleMessage.message());
break;
case WARNING:
Log.w(TAG, consoleMessage.message());
Timber.w(consoleMessage.message());
break;
case ERROR:
Log.e(TAG, consoleMessage.message());
Timber.e(consoleMessage.message());
break;
case DEBUG:
Log.d(TAG, consoleMessage.message());
Timber.d(consoleMessage.message());
break;
}
}
@ -278,7 +278,7 @@ public final class AndroidacyActivity extends FoxActivity {
return;
} else if (moduleId != null) {
// Download module
Log.i(TAG, "megaIntercept failure. Forcing onBackPress");
Timber.i("megaIntercept failure. Forcing onBackPress");
this.onBackPressed();
}
}
@ -286,7 +286,7 @@ public final class AndroidacyActivity extends FoxActivity {
androidacyWebAPI.downloadMode = false;
}
this.backOnResume = true;
Log.i(TAG, "Exiting WebView " + AndroidacyUtil.hideToken(downloadUrl));
Timber.i("Exiting WebView %s", AndroidacyUtil.hideToken(downloadUrl));
for (String prefix : new String[]{"https://production-api.androidacy.com/downloads/", "https://staging-api.androidacy.com/magisk/downloads/"}) {
if (downloadUrl.startsWith(prefix)) {
return;
@ -304,7 +304,7 @@ public final class AndroidacyActivity extends FoxActivity {
headers.put("Accept-Language", this.getResources().getConfiguration().locale.toLanguageTag());
if (BuildConfig.DEBUG) {
headers.put("X-Debug", "true");
Log.i(TAG, "Debug mode enabled for webview using URL: " + url + " with headers: " + headers);
Timber.i("Debug mode enabled for webview using URL: " + url + " with headers: " + headers);
}
this.webView.loadUrl(url, headers);
}
@ -364,13 +364,13 @@ public final class AndroidacyActivity extends FoxActivity {
if (pageUrl == null || fileUrl == null)
return false;
if (this.isFileUrl(fileUrl)) {
Log.i(TAG, "megaIntercept(" + AndroidacyUtil.hideToken(pageUrl) + ", " + AndroidacyUtil.hideToken(fileUrl) + ")");
Timber.i("megaIntercept(%s", AndroidacyUtil.hideToken(AndroidacyUtil.hideToken(fileUrl) ));
} else
return false;
final AndroidacyWebAPI androidacyWebAPI = this.androidacyWebAPI;
String moduleId = AndroidacyUtil.getModuleId(fileUrl);
if (moduleId == null) {
Log.i(TAG, "No module id?");
Timber.i("No module id?");
// Re-open the page
this.webView.loadUrl(pageUrl + "&force_refresh=" + System.currentTimeMillis());
}

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -40,11 +39,11 @@ import java.util.Objects;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import okhttp3.HttpUrl;
import timber.log.Timber;
@SuppressWarnings("KotlinInternalInJava")
public final class AndroidacyRepoData extends RepoData {
private static final String TAG = "AndroidacyRepoData";
public static String token = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).getString("pref_androidacy_api_token", null);
static {
@ -150,7 +149,7 @@ public final class AndroidacyRepoData extends RepoData {
if (status.equals("success")) {
return true;
} else {
Log.w(TAG, "Invalid token, resetting...");
Timber.w("Invalid token, resetting...");
// Remove saved preference
SharedPreferences.Editor editor = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).edit();
editor.remove("pref_androidacy_api_token");
@ -160,7 +159,7 @@ public final class AndroidacyRepoData extends RepoData {
} catch (
HttpException e) {
if (e.getErrorCode() == 401) {
Log.w(TAG, "Invalid token, resetting...");
Timber.w("Invalid token, resetting...");
// Remove saved preference
SharedPreferences.Editor editor = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).edit();
editor.remove("pref_androidacy_api_token");
@ -210,7 +209,7 @@ public final class AndroidacyRepoData extends RepoData {
}
} catch (
Exception e) {
Log.e(TAG, "Failed to ping server", e);
Timber.e(e, "Failed to ping server");
return false;
}
String deviceId = generateDeviceId();
@ -225,7 +224,7 @@ public final class AndroidacyRepoData extends RepoData {
if (token != null && !this.isValidToken(token)) {
token = null;
} else {
Log.i(TAG, "Using cached token");
Timber.i("Using cached token");
}
} else if (!this.isValidToken(token)) {
if (BuildConfig.DEBUG) {
@ -236,14 +235,14 @@ public final class AndroidacyRepoData extends RepoData {
} catch (
IOException e) {
if (HttpException.shouldTimeout(e)) {
Log.e(TAG, "We are being rate limited!", e);
Timber.e(e, "We are being rate limited!");
this.androidacyBlockade = time + 3_600_000L;
}
return false;
}
if (token == null) {
try {
Log.i(TAG, "Requesting new token...");
Timber.i("Requesting new token...");
// POST json request to https://produc/tion-api.androidacy.com/auth/register
token = new String(Http.doHttpPost("https://" + this.host + "/auth/register", "{\"device_id\":\"" + deviceId + "\"}", false));
// Parse token
@ -253,14 +252,14 @@ public final class AndroidacyRepoData extends RepoData {
memberLevel = jsonObject.getString("role");
} catch (
JSONException e) {
Log.e(TAG, "Failed to parse token", e);
Timber.e(e, "Failed to parse token");
// Show a toast
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_parse_token, Toast.LENGTH_LONG).show();
return false;
}
// Ensure token is valid
if (!isValidToken(token)) {
Log.e(TAG, "Failed to validate token");
Timber.e("Failed to validate token");
// Show a toast
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_validate_token, Toast.LENGTH_LONG).show();
return false;
@ -272,10 +271,10 @@ public final class AndroidacyRepoData extends RepoData {
} catch (
Exception e) {
if (HttpException.shouldTimeout(e)) {
Log.e(TAG, "We are being rate limited!", e);
Timber.e(e, "We are being rate limited!");
this.androidacyBlockade = time + 3_600_000L;
}
Log.e(TAG, "Failed to get a new token", e);
Timber.e(e, "Failed to get a new token");
return false;
}
}
@ -286,9 +285,7 @@ public final class AndroidacyRepoData extends RepoData {
@Override
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException {
if (BuildConfig.DEBUG) {
Log.d(TAG, "AndroidacyRepoData populate start");
}
Timber.d("AndroidacyRepoData populate start");
if (!jsonObject.getString("status").equals("success"))
throw new JSONException("Response is not a success!");
String name = jsonObject.optString("name", "Androidacy Modules Repo");
@ -409,12 +406,12 @@ public final class AndroidacyRepoData extends RepoData {
return url;
if (this.testMode) {
if (url.startsWith("https://production-api.androidacy.com/")) {
Log.e(TAG, "Got non test mode url: " + AndroidacyUtil.hideToken(url));
Timber.e("Got non test mode url: %s", AndroidacyUtil.hideToken(url));
url = "https://staging-api.androidacy.com/" + url.substring(38);
}
} else {
if (url.startsWith("https://staging-api.androidacy.com/")) {
Log.e(TAG, "Got test mode url: " + AndroidacyUtil.hideToken(url));
Timber.e("Got test mode url: %s", AndroidacyUtil.hideToken(url));
url = "https://production-api.androidacy.com/" + url.substring(35);
}
}

@ -5,7 +5,6 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.webkit.JavascriptInterface;
@ -26,20 +25,21 @@ import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.ExternalHelper;
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.IntentHelper;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import timber.log.Timber;
@Keep
public class AndroidacyWebAPI {
public static final int COMPAT_UNSUPPORTED = 0;
public static final int COMPAT_DOWNLOAD = 1;
private static final String TAG = "AndroidacyWebAPI";
private static final int MAX_COMPAT_MODE = 1;
private final AndroidacyActivity activity;
private final boolean allowInstall;
@ -63,7 +63,7 @@ public class AndroidacyWebAPI {
void openNativeModuleDialogRaw(String moduleUrl, String moduleId, String installTitle, String checksum, boolean canInstall) {
if (BuildConfig.DEBUG)
Log.d(TAG, "ModuleDialog, downloadUrl: " + AndroidacyUtil.hideToken(moduleUrl) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall);
Timber.d("ModuleDialog, downloadUrl: " + AndroidacyUtil.hideToken(moduleUrl) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall);
this.downloadMode = false;
RepoModule repoModule = AndroidacyRepoData.getInstance().moduleHashMap.get(installTitle);
String title, description;
@ -113,7 +113,7 @@ public class AndroidacyWebAPI {
return this.activity.downloadFileAsync(moduleUrl);
} catch (
IOException e) {
Log.e(TAG, "Failed to download module", e);
Timber.e(e, "Failed to download module");
AndroidacyWebAPI.this.activity.runOnUiThread(() -> Toast.makeText(AndroidacyWebAPI.this.activity, R.string.failed_download, Toast.LENGTH_SHORT).show());
return null;
}
@ -135,7 +135,7 @@ public class AndroidacyWebAPI {
if (this.consumedAction)
return;
if (BuildConfig.DEBUG)
Log.d(TAG, "Androidacy Compat mode: " + value);
Timber.d("Androidacy Compat mode: %s", value);
this.notifiedCompatMode = value;
if (value < 0) {
value = 0;
@ -173,7 +173,7 @@ public class AndroidacyWebAPI {
this.consumedAction = true;
this.downloadMode = false;
if (BuildConfig.DEBUG)
Log.d(TAG, "Received openUrl request: " + url);
Timber.d("Received openUrl request: %s", url);
if (Uri.parse(url).getScheme().equals("https")) {
IntentHelper.openUrl(this.activity, url);
}
@ -189,7 +189,7 @@ public class AndroidacyWebAPI {
this.consumedAction = true;
this.downloadMode = false;
if (BuildConfig.DEBUG)
Log.d(TAG, "Received openCustomTab request: " + url);
Timber.d("Received openCustomTab request: %s", url);
if (Uri.parse(url).getScheme().equals("https")) {
IntentHelper.openCustomTab(this.activity, url);
}
@ -233,14 +233,14 @@ public class AndroidacyWebAPI {
this.consumedAction = true;
this.downloadMode = false;
if (BuildConfig.DEBUG)
Log.d(TAG, "Received install request: " + moduleUrl + " " + installTitle + " " + checksum);
Timber.d("Received install request: " + moduleUrl + " " + installTitle + " " + checksum);
if (!AndroidacyUtil.isAndroidacyLink(moduleUrl)) {
this.forceQuitRaw("Non Androidacy module link used on Androidacy");
return;
}
checksum = Hashes.checkSumFormat(checksum);
if (checksum == null || checksum.isEmpty()) {
Log.w(TAG, "Androidacy WebView didn't provided a checksum!");
Timber.w("Androidacy WebView didn't provided a checksum!");
} else if (!Hashes.checkSumValid(checksum)) {
this.forceQuitRaw("Androidacy didn't provided a valid checksum");
return;
@ -284,7 +284,7 @@ public class AndroidacyWebAPI {
}
checksum = Hashes.checkSumFormat(checksum);
if (checksum == null || checksum.isEmpty()) {
Log.w(TAG, "Androidacy WebView didn't provided a checksum!");
Timber.w("Androidacy WebView didn't provided a checksum!");
} else if (!Hashes.checkSumValid(checksum)) {
this.forceQuitRaw("Androidacy didn't provided a valid checksum");
return;

@ -73,6 +73,9 @@ public class BackgroundUpdateChecker extends Worker {
if (ActivityCompat.checkSelfPermission(MainApplication.getINSTANCE(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return;
}
// check if app is in foreground. if so, don't show notification
if (MainApplication.getINSTANCE().isInForeground())
return;
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build());
}

@ -7,7 +7,6 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
@ -29,10 +28,10 @@ import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.module.ActionButtonType;
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.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.sentry.SentryBreadcrumb;
import com.fox2code.mmm.utils.sentry.SentryMain;
@ -56,9 +55,10 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import timber.log.Timber;
@SuppressWarnings("IOStreamConstructor")
public class InstallerActivity extends FoxActivity {
private static final String TAG = "InstallerActivity";
public LinearProgressIndicator progressIndicator;
public ExtendedFloatingActionButton rebootFloatingButton;
public InstallerTerminal installerTerminal;
@ -68,12 +68,14 @@ public class InstallerActivity extends FoxActivity {
private boolean canceled;
private boolean warnReboot;
private static final HashSet<String> extracted = new HashSet<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
this.warnReboot = false;
this.moduleCache = new File(this.getCacheDir(), "installer");
if (!this.moduleCache.exists() && !this.moduleCache.mkdirs())
Log.e(TAG, "Failed to mkdir module cache dir!");
Timber.e("Failed to mkdir module cache dir!");
super.onCreate(savedInstanceState);
this.setDisplayHomeAsUpEnabled(true);
setActionBarBackground(null);
@ -91,7 +93,7 @@ public class InstallerActivity extends FoxActivity {
// Should we allow 3rd part app to install modules?
if (Constants.INTENT_INSTALL_INTERNAL.equals(intent.getAction())) {
if (!MainApplication.checkSecret(intent)) {
Log.e(TAG, "Security check failed!");
Timber.e("Security check failed!");
this.forceBackPressed();
return;
}
@ -109,7 +111,7 @@ public class InstallerActivity extends FoxActivity {
this.forceBackPressed();
return;
}
Log.i(TAG, "Install link: " + target);
Timber.i("Install link: %s", target);
// Note: Sentry only send this info on crash.
if (MainApplication.isCrashReportingEnabled()) {
SentryBreadcrumb breadcrumb = new SentryBreadcrumb();
@ -158,13 +160,13 @@ public class InstallerActivity extends FoxActivity {
new File(this.moduleCache, "module.zip") : new File(target);
if (urlMode && moduleCache.exists() && !moduleCache.delete() &&
!new SuFile(moduleCache.getAbsolutePath()).delete())
Log.e(TAG, "Failed to delete module cache");
Timber.e("Failed to delete module cache");
String errMessage = "Failed to download module zip";
// Set this to the error message if it's a HTTP error
byte[] rawModule;
boolean androidacyBlame = false; // In case Androidacy mess-up again... yeah screw you too jk jk
try {
Log.i(TAG, (urlMode ? "Downloading: " : "Loading: ") + target);
Timber.i("%s%s", (urlMode ? "Downloading: " : "Loading: "), target);
rawModule = urlMode ? Http.doHttpGet(target, (progress, max, done) -> {
if (max <= 0 && this.progressIndicator.isIndeterminate())
return;
@ -181,7 +183,7 @@ public class InstallerActivity extends FoxActivity {
if (this.canceled) return;
androidacyBlame = urlMode && AndroidacyUtil.isAndroidacyFileUrl(target);
if (checksum != null && !checksum.isEmpty()) {
Log.i(TAG, "Checking for checksum: " + checksum);
Timber.i("Checking for checksum: %s", checksum);
this.runOnUiThread(() -> this.installerTerminal.addLine("- Checking file integrity"));
if (!Hashes.checkSumMatch(rawModule, checksum)) {
this.setInstallStateFinished(false,
@ -244,7 +246,7 @@ public class InstallerActivity extends FoxActivity {
} else {
errMessage = "Failed to patch module zip";
this.runOnUiThread(() -> this.installerTerminal.addLine("- Patching " + name));
Log.i(TAG, "Patching: " + moduleCache.getName());
Timber.i("Patching: %s", moduleCache.getName());
try (OutputStream outputStream = new FileOutputStream(moduleCache)) {
Files.patchModuleSimple(rawModule, outputStream);
outputStream.flush();
@ -257,7 +259,7 @@ public class InstallerActivity extends FoxActivity {
errMessage = "Failed to install module zip";
this.doInstall(moduleCache, noExtensions, rootless);
} catch (IOException e) {
Log.e(TAG, errMessage, e);
Timber.e(e);
if (androidacyBlame) {
errMessage += " (" + e.getLocalizedMessage() + ")";
}
@ -267,14 +269,13 @@ public class InstallerActivity extends FoxActivity {
rawModule = null; // Because reference is kept when calling setInstallStateFinished
if ("Failed to install module zip".equals(errMessage))
throw e; // Ignore if in installation state.
Log.e(TAG, "Module too large", e);
Timber.e(e);
this.setInstallStateFinished(false,
"! Module is too large to be loaded on this device", "");
}
}, "Module install Thread").start();
}
@Keep
private void doInstall(File file, boolean noExtensions, boolean rootless) {
if (this.canceled) return;
@ -282,7 +283,7 @@ public class InstallerActivity extends FoxActivity {
this.setOnBackPressedCallback(DISABLE_BACK_BUTTON);
this.setDisplayHomeAsUpEnabled(false);
});
Log.i(TAG, "Installing: " + moduleCache.getName());
Timber.i("Installing: %s", moduleCache.getName());
InstallerController installerController = new InstallerController(
this.progressIndicator, this.installerTerminal,
file.getAbsoluteFile(), noExtensions);
@ -306,12 +307,12 @@ public class InstallerActivity extends FoxActivity {
}
}
} catch (Exception e) {
Log.i(TAG, "Failed ot extract install script via java code", e);
Timber.i(e);
}
installerMonitor = new InstallerMonitor(installScript);
installJob = Shell.cmd("export MMM_EXT_SUPPORT=1",
"export MMM_USER_LANGUAGE=" + this.getResources()
.getConfiguration().locale.toLanguageTag(),
.getConfiguration().getLocales().get(0).toLanguageTag(),
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
AnsiConstants.ANSI_CMD_SUPPORT,
@ -469,7 +470,7 @@ public class InstallerActivity extends FoxActivity {
else this.installerTerminal.enableAnsi();
installJob = Shell.cmd(arch32, "export MMM_EXT_SUPPORT=1",
"export MMM_USER_LANGUAGE=" + this.getResources()
.getConfiguration().locale.toLanguageTag(),
.getConfiguration().getLocales().get(0).toLanguageTag(),
"export MMM_APP_VERSION=" + BuildConfig.VERSION_NAME,
"export MMM_TEXT_WRAP=" + (this.textWrap ? "1" : "0"),
this.installerTerminal.isAnsiEnabled() ?
@ -495,7 +496,7 @@ public class InstallerActivity extends FoxActivity {
SentryMain.addSentryBreadcrumb(breadcrumb);
}
if (mmtReborn && magiskCmdLine) {
Log.w(TAG, "mmtReborn and magiskCmdLine may not work well together");
Timber.w("mmtReborn and magiskCmdLine may not work well together");
}
}
boolean success = installJob.exec().isSuccess();
@ -515,6 +516,105 @@ public class InstallerActivity extends FoxActivity {
installerController.getSupportLink());
}
private File extractInstallScript(String script) {
File compatInstallScript = new File(this.moduleCache, script);
if (!compatInstallScript.exists() || compatInstallScript.length() == 0 ||
!extracted.contains(script)) {
try {
Files.write(compatInstallScript, Files.readAllBytes(
this.getAssets().open(script)));
extracted.add(script);
} catch (IOException e) {
if (compatInstallScript.delete())
extracted.remove(script);
Timber.e(e);
return null;
}
}
return compatInstallScript;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) return true;
return super.dispatchKeyEvent(event);
}
@SuppressLint("RestrictedApi")
@SuppressWarnings("SameParameterValue")
private void setInstallStateFinished(boolean success, String message, String optionalLink) {
this.installerTerminal.disableAnsi();
if (success && toDelete != null && !toDelete.delete()) {
SuFile suFile = new SuFile(toDelete.getAbsolutePath());
if (suFile.exists() && !suFile.delete())
Timber.w("Failed to delete zip file");
else toDelete = null;
} else toDelete = null;
this.runOnUiThread(() -> {
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0);
// Set the back press to finish the activity and return to the main activity
this.setOnBackPressedCallback(a -> {
this.finishAndRemoveTask();
startActivity(new Intent(this, MainActivity.class));
return true;
});
this.setDisplayHomeAsUpEnabled(true);
this.progressIndicator.setVisibility(View.GONE);
// This should be improved ?
String reboot_cmd = "/system/bin/svc power reboot || /system/bin/reboot || setprop sys.powerctl reboot";
this.rebootFloatingButton.setOnClickListener(_view -> {
if (this.warnReboot || MainApplication.shouldPreventReboot()) {
MaterialAlertDialogBuilder builder =
new MaterialAlertDialogBuilder(this);
builder
.setTitle(R.string.install_terminal_reboot_now)
.setMessage(R.string.install_terminal_reboot_now_message)
.setCancelable(false)
.setIcon(R.drawable.ic_reboot_24)
.setPositiveButton(R.string.ok, (x, y) -> Shell.cmd(reboot_cmd).submit())
.setNegativeButton(R.string.no, (x, y) -> x.dismiss()).show();
} else {
Shell.cmd(reboot_cmd).submit();
}
});
this.rebootFloatingButton.setVisibility(View.VISIBLE);
if (message != null && !message.isEmpty())
this.installerTerminal.addLine(message);
if (optionalLink != null && !optionalLink.isEmpty()) {
this.setActionBarExtraMenuButton(ActionButtonType.supportIconForUrl(optionalLink),
menu -> {
IntentHelper.openUrl(this, optionalLink);
return true;
});
} else if (success) {
final Intent intent = this.getIntent();
final String config = MainApplication.checkSecret(intent) ?
intent.getStringExtra(Constants.EXTRA_INSTALL_CONFIG) : null;
if (config != null && !config.isEmpty()) {
String configPkg = IntentHelper.getPackageOfConfig(config);
try {
XHooks.checkConfigTargetExists(this, configPkg, config);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_app_settings_alt_24, menu -> {
IntentHelper.openConfig(this, config);
return true;
});
} catch (PackageManager.NameNotFoundException e) {
Timber.w("Config package \"" +
configPkg + "\" missing for installer view");
this.installerTerminal.addLine(String.format(
this.getString(R.string.install_terminal_config_missing),
configPkg));
}
}
}
});
}
public static class InstallerController extends CallbackList<String> {
private final LinearProgressIndicator progressIndicator;
private final InstallerTerminal terminal;
@ -538,7 +638,7 @@ public class InstallerActivity extends FoxActivity {
@Override
public void onAddElement(String s) {
if (!this.enabled) return;
Log.i(TAG, "MSG: " + s);
Timber.i("MSG: %s", s);
if ("#!useExt".equals(s.trim()) && !this.noExtension) {
this.useExt = true;
return;
@ -668,14 +768,6 @@ public class InstallerActivity extends FoxActivity {
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) return true;
return super.dispatchKeyEvent(event);
}
public static class InstallerMonitor extends CallbackList<String> {
private static final String DEFAULT_ERR = "! Install failed";
private final String installScriptErr;
@ -691,7 +783,7 @@ public class InstallerActivity extends FoxActivity {
@Override
public void onAddElement(String s) {
Log.i(TAG, "Monitor: " + s);
Timber.i("Monitor: %s", s);
this.lastCommand = s;
}
@ -710,107 +802,15 @@ public class InstallerActivity extends FoxActivity {
SuFile moduleUpdate = new SuFile("/data/adb/modules_update/" + module);
if (moduleUpdate.exists()) {
if (!moduleUpdate.deleteRecursive())
Log.e(TAG, "Failed to delete failed update");
Timber.e("Failed to delete failed update");
return "Error: " + installScriptErr.substring(i + 1);
}
} else if (this.forCleanUp != null) {
SuFile moduleUpdate = new SuFile("/data/adb/modules_update/" + this.forCleanUp);
if (moduleUpdate.exists() && !moduleUpdate.deleteRecursive())
Log.e(TAG, "Failed to delete failed update");
Timber.e("Failed to delete failed update");
}
return DEFAULT_ERR;
}
}
private static final HashSet<String> extracted = new HashSet<>();
private File extractInstallScript(String script) {
File compatInstallScript = new File(this.moduleCache, script);
if (!compatInstallScript.exists() || compatInstallScript.length() == 0 ||
!extracted.contains(script)) {
try {
Files.write(compatInstallScript, Files.readAllBytes(
this.getAssets().open(script)));
extracted.add(script);
} catch (IOException e) {
if (compatInstallScript.delete())
extracted.remove(script);
Log.e(TAG, "Failed to extract " + script, e);
return null;
}
}
return compatInstallScript;
}
@SuppressLint("RestrictedApi")
@SuppressWarnings("SameParameterValue")
private void setInstallStateFinished(boolean success, String message, String optionalLink) {
this.installerTerminal.disableAnsi();
if (success && toDelete != null && !toDelete.delete()) {
SuFile suFile = new SuFile(toDelete.getAbsolutePath());
if (suFile.exists() && !suFile.delete())
Log.w(TAG, "Failed to delete zip file");
else toDelete = null;
} else toDelete = null;
this.runOnUiThread(() -> {
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0);
// Set the back press to finish the activity and return to the main activity
this.setOnBackPressedCallback(a -> {
this.finishAndRemoveTask();
startActivity(new Intent(this, MainActivity.class));
return true;
});
this.setDisplayHomeAsUpEnabled(true);
this.progressIndicator.setVisibility(View.GONE);
// This should be improved ?
String reboot_cmd = "/system/bin/svc power reboot || /system/bin/reboot || setprop sys.powerctl reboot";
this.rebootFloatingButton.setOnClickListener(_view -> {
if (this.warnReboot || MainApplication.shouldPreventReboot()) {
MaterialAlertDialogBuilder builder =
new MaterialAlertDialogBuilder(this);
builder
.setTitle(R.string.install_terminal_reboot_now)
.setMessage(R.string.install_terminal_reboot_now_message)
.setCancelable(false)
.setIcon(R.drawable.ic_reboot_24)
.setPositiveButton(R.string.ok, (x, y) -> Shell.cmd(reboot_cmd).submit())
.setNegativeButton(R.string.no, (x, y) -> x.dismiss()).show();
} else {
Shell.cmd(reboot_cmd).submit();
}
});
this.rebootFloatingButton.setVisibility(View.VISIBLE);
if (message != null && !message.isEmpty())
this.installerTerminal.addLine(message);
if (optionalLink != null && !optionalLink.isEmpty()) {
this.setActionBarExtraMenuButton(ActionButtonType.supportIconForUrl(optionalLink),
menu -> {
IntentHelper.openUrl(this, optionalLink);
return true;
});
} else if (success) {
final Intent intent = this.getIntent();
final String config = MainApplication.checkSecret(intent) ?
intent.getStringExtra(Constants.EXTRA_INSTALL_CONFIG) : null;
if (config != null && !config.isEmpty()) {
String configPkg = IntentHelper.getPackageOfConfig(config);
try {
XHooks.checkConfigTargetExists(this, configPkg, config);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_app_settings_alt_24, menu -> {
IntentHelper.openConfig(this, config);
return true;
});
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Config package \"" +
configPkg + "\" missing for installer view");
this.installerTerminal.addLine(String.format(
this.getString(R.string.install_terminal_config_missing),
configPkg));
}
}
}
});
}
}

@ -1,7 +1,6 @@
package com.fox2code.mmm.installer;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -16,8 +15,9 @@ import com.topjohnwu.superuser.Shell;
import java.io.File;
import java.util.ArrayList;
import timber.log.Timber;
public class InstallerInitializer extends Shell.Initializer {
private static final String TAG = "InstallerInitializer";
private static final File MAGISK_SBIN =
new File("/sbin/magisk");
private static final File MAGISK_SYSTEM =
@ -30,7 +30,6 @@ public class InstallerInitializer extends Shell.Initializer {
private static int MAGISK_VERSION_CODE;
private static boolean HAS_RAMDISK;
public static final int ERROR_OK = 0;
public static final int ERROR_NO_PATH = 1;
public static final int ERROR_NO_SU = 2;
public static final int ERROR_OTHER = 3;
@ -98,10 +97,10 @@ public class InstallerInitializer extends Shell.Initializer {
error = ERROR_NO_PATH;
} catch (NoShellException e) {
error = ERROR_NO_SU;
Log.w(TAG, "Device doesn't have root!", e);
Timber.w(e);
} catch (Throwable e) {
error = ERROR_OTHER;
Log.e(TAG, "Something bad happened", e);
Timber.e(e);
}
if (forceCheck) {
InstallerInitializer.MAGISK_PATH = MAGISK_PATH;
@ -138,9 +137,9 @@ public class InstallerInitializer extends Shell.Initializer {
return null;
}
MAGISK_PATH = output.size() < 3 ? "" : output.get(2);
Log.i(TAG, "Magisk runtime path: " + MAGISK_PATH);
Timber.i("Magisk runtime path: %s", MAGISK_PATH);
MAGISK_VERSION_CODE = Integer.parseInt(output.get(1));
Log.i(TAG, "Magisk version code: " + MAGISK_VERSION_CODE);
Timber.i("Magisk version code: %s", MAGISK_VERSION_CODE);
if (MAGISK_VERSION_CODE >= Constants.MAGISK_VER_CODE_FLAT_MODULES &&
MAGISK_VERSION_CODE < Constants.MAGISK_VER_CODE_PATH_SUPPORT &&
(MAGISK_PATH.isEmpty() || !new File(MAGISK_PATH).exists())) {
@ -149,7 +148,7 @@ public class InstallerInitializer extends Shell.Initializer {
if (MAGISK_PATH.length() != 0 && Files.existsSU(new File(MAGISK_PATH))) {
InstallerInitializer.MAGISK_PATH = MAGISK_PATH;
} else {
Log.e(TAG, "Failed to get Magisk path (Got " + MAGISK_PATH + ")");
Timber.e("Failed to get Magisk path (Got " + MAGISK_PATH + ")");
MAGISK_PATH = null;
}
InstallerInitializer.MAGISK_VERSION_CODE = MAGISK_VERSION_CODE;

@ -1,7 +1,5 @@
package com.fox2code.mmm.manager;
import android.util.Log;
import com.fox2code.mmm.markdown.MarkdownUrlLinker;
import com.fox2code.mmm.utils.FastException;
import com.fox2code.mmm.utils.io.Http;
@ -12,6 +10,8 @@ import org.json.JSONObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import timber.log.Timber;
public class LocalModuleInfo extends ModuleInfo {
public String updateVersion;
public long updateVersionCode = Long.MIN_VALUE;
@ -48,7 +48,7 @@ public class LocalModuleInfo extends ModuleInfo {
this.updateVersion = PropUtils.shortenVersionName(
this.updateVersion.trim(), this.updateVersionCode);
if (this.updateChangeLog.length() > 1000)
this.updateChangeLog = this.updateChangeLog.substring(0, 1000);
this.updateChangeLog = this.updateChangeLog.substring(1000);
this.updateChangeLog = MarkdownUrlLinker.urlLinkify(this.updateChangeLog);
} catch (Exception e) {
this.updateVersion = null;
@ -56,8 +56,7 @@ public class LocalModuleInfo extends ModuleInfo {
this.updateZipUrl = null;
this.updateChangeLog = "";
this.updateChecksum = null;
Log.w("LocalModuleInfo",
"Failed update checking for module: " + this.id, e);
Timber.w(e, "Failed update checking for module: %s", this.id);
}
}
}

@ -1,15 +1,14 @@
package com.fox2code.mmm.manager;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.SyncManager;
import com.fox2code.mmm.utils.io.PropUtils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
@ -21,14 +20,14 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import timber.log.Timber;
public final class ModuleManager extends SyncManager {
// New method is not really effective, this flag force app to use old method
public static final boolean FORCE_NEED_FALLBACK = true;
private static final String TAG = "ModuleManager";
private static final int FLAG_MM_INVALID = ModuleInfo.FLAG_METADATA_INVALID;
private static final int FLAG_MM_UNPROCESSED = ModuleInfo.FLAG_CUSTOM_INTERNAL;
private static final int FLAGS_KEEP_INIT = FLAG_MM_UNPROCESSED |
ModuleInfo.FLAGS_MODULE_ACTIVE | ModuleInfo.FLAG_MODULE_UPDATING_ONLY;
private static final int FLAGS_KEEP_INIT = FLAG_MM_UNPROCESSED | ModuleInfo.FLAGS_MODULE_ACTIVE | ModuleInfo.FLAG_MODULE_UPDATING_ONLY;
private static final int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED;
private static final ModuleManager INSTANCE = new ModuleManager();
private final HashMap<String, LocalModuleInfo> moduleInfos;
@ -65,17 +64,18 @@ public final class ModuleManager extends SyncManager {
}
String modulesPath = InstallerInitializer.peekModulesPath();
String[] modules = new SuFile("/data/adb/modules").list();
boolean needFallback = FORCE_NEED_FALLBACK ||
modulesPath == null || !new SuFile(modulesPath).exists();
boolean needFallback = FORCE_NEED_FALLBACK || modulesPath == null || !new SuFile(modulesPath).exists();
if (!FORCE_NEED_FALLBACK && needFallback) {
Log.e(TAG, "Failed to detect modules folder, using fallback instead.");
Timber.e("using fallback instead.");
}
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Scan");
if (BuildConfig.DEBUG)
Timber.d("Scan");
if (modules != null) {
for (String module : modules) {
if (!new SuFile("/data/adb/modules/" + module).isDirectory())
continue; // Ignore non directory files inside modules folder
if (BuildConfig.DEBUG) Log.d("NoodleDebug", module);
if (BuildConfig.DEBUG)
Timber.d(module);
LocalModuleInfo moduleInfo = moduleInfos.get(module);
if (moduleInfo == null) {
moduleInfo = new LocalModuleInfo(module);
@ -93,8 +93,7 @@ public final class ModuleManager extends SyncManager {
if (new SuFile("/data/adb/modules/" + module + "/remove").exists()) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UNINSTALLING;
}
if ((firstScan && !needFallback && new SuFile(modulesPath, module).exists()) ||
bootPrefs.getBoolean("module_" + moduleInfo.id + "_active", false)) {
if ((firstScan && !needFallback && new SuFile(modulesPath, module).exists()) || bootPrefs.getBoolean("module_" + moduleInfo.id + "_active", false)) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_ACTIVE;
if (firstScan) {
editor.putBoolean("module_" + moduleInfo.id + "_active", true);
@ -102,29 +101,28 @@ public final class ModuleManager extends SyncManager {
} else if (!needFallback) {
moduleInfo.flags &= ~ModuleInfo.FLAG_MODULE_ACTIVE;
}
if ((moduleInfo.flags & ModuleInfo.FLAGS_MODULE_ACTIVE) != 0
&& (new SuFile("/data/adb/modules/" + module + "/system").exists() ||
new SuFile("/data/adb/modules/" + module + "/vendor").exists() ||
new SuFile("/data/adb/modules/" + module + "/zygisk").exists() ||
new SuFile("/data/adb/modules/" + module + "/riru").exists())) {
if ((moduleInfo.flags & ModuleInfo.FLAGS_MODULE_ACTIVE) != 0 && (new SuFile("/data/adb/modules/" + module + "/system").exists() || new SuFile("/data/adb/modules/" + module + "/vendor").exists() || new SuFile("/data/adb/modules/" + module + "/zygisk").exists() || new SuFile("/data/adb/modules/" + module + "/riru").exists())) {
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT;
}
try {
PropUtils.readProperties(moduleInfo,
"/data/adb/modules/" + module + "/module.prop", true);
} catch (Exception e) {
if (BuildConfig.DEBUG) Log.d(TAG, "Failed to parse metadata!", e);
PropUtils.readProperties(moduleInfo, "/data/adb/modules/" + module + "/module.prop", true);
} catch (
Exception e) {
if (BuildConfig.DEBUG)
Timber.d(e);
moduleInfo.flags |= FLAG_MM_INVALID;
}
}
}
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Scan update");
if (BuildConfig.DEBUG)
Timber.d("Scan update");
String[] modules_update = new SuFile("/data/adb/modules_update").list();
if (modules_update != null) {
for (String module : modules_update) {
if (!new SuFile("/data/adb/modules_update/" + module).isDirectory())
continue; // Ignore non directory files inside modules folder
if (BuildConfig.DEBUG) Log.d("NoodleDebug", module);
if (BuildConfig.DEBUG)
Timber.d(module);
LocalModuleInfo moduleInfo = moduleInfos.get(module);
if (moduleInfo == null) {
moduleInfo = new LocalModuleInfo(module);
@ -133,21 +131,23 @@ public final class ModuleManager extends SyncManager {
moduleInfo.flags &= ~FLAGS_RESET_UPDATE;
moduleInfo.flags |= ModuleInfo.FLAG_MODULE_UPDATING;
try {
PropUtils.readProperties(moduleInfo,
"/data/adb/modules_update/" + module + "/module.prop", true);
} catch (Exception e) {
if (BuildConfig.DEBUG) Log.d(TAG, "Failed to parse metadata!", e);
PropUtils.readProperties(moduleInfo, "/data/adb/modules_update/" + module + "/module.prop", true);
} catch (
Exception e) {
if (BuildConfig.DEBUG)
Timber.d(e);
moduleInfo.flags |= FLAG_MM_INVALID;
}
}
}
if (BuildConfig.DEBUG) Log.d("NoodleDebug", "Finalize scan");
if (BuildConfig.DEBUG)
Timber.d("Finalize scan");
this.updatableModuleCount = 0;
Iterator<LocalModuleInfo> moduleInfoIterator =
this.moduleInfos.values().iterator();
Iterator<LocalModuleInfo> moduleInfoIterator = this.moduleInfos.values().iterator();
while (moduleInfoIterator.hasNext()) {
LocalModuleInfo moduleInfo = moduleInfoIterator.next();
if (BuildConfig.DEBUG) Log.d("NoodleDebug", moduleInfo.id);
if (BuildConfig.DEBUG)
Timber.d(moduleInfo.id);
if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) {
moduleInfoIterator.remove();
continue; // Don't process fallbacks if unreferenced
@ -161,8 +161,7 @@ public final class ModuleManager extends SyncManager {
moduleInfo.updateChangeLog = "";
}
if (moduleInfo.name == null || (moduleInfo.name.equals(moduleInfo.id))) {
moduleInfo.name = Character.toUpperCase(moduleInfo.id.charAt(0)) +
moduleInfo.id.substring(1).replace('_', ' ');
moduleInfo.name = Character.toUpperCase(moduleInfo.id.charAt(0)) + moduleInfo.id.substring(1).replace('_', ' ');
}
if (moduleInfo.version == null || moduleInfo.version.trim().isEmpty()) {
moduleInfo.version = "v" + moduleInfo.versionCode;
@ -186,7 +185,8 @@ public final class ModuleManager extends SyncManager {
}
public boolean setEnabledState(ModuleInfo moduleInfo, boolean checked) {
if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) && !checked) return false;
if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING) && !checked)
return false;
SuFile disable = new SuFile("/data/adb/modules/" + moduleInfo.id + "/disable");
if (checked) {
if (disable.exists() && !disable.delete()) {
@ -204,7 +204,8 @@ public final class ModuleManager extends SyncManager {
}
public boolean setUninstallState(ModuleInfo moduleInfo, boolean checked) {
if (checked && moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING)) return false;
if (checked && moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING))
return false;
SuFile disable = new SuFile("/data/adb/modules/" + moduleInfo.id + "/remove");
if (checked) {
if (!disable.exists() && !disable.createNewFile()) {
@ -222,26 +223,22 @@ public final class ModuleManager extends SyncManager {
}
public boolean masterClear(ModuleInfo moduleInfo) {
if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT)) return false;
String escapedId = moduleInfo.id.replace("\\", "\\\\")
.replace("\"", "\\\"").replace(" ", "\\ ");
if (moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT))
return false;
String escapedId = moduleInfo.id.replace("\\", "\\\\").replace("\"", "\\\"").replace(" ", "\\ ");
try { // Check for module that declare having file outside their own folder.
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
SuFileInputStream.open("/data/adb/modules/." + moduleInfo.id + "-files"),
StandardCharsets.UTF_8))) {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(SuFileInputStream.open("/data/adb/modules/." + moduleInfo.id + "-files"), StandardCharsets.UTF_8))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
line = line.trim().replace(' ', '.');
if (!line.startsWith("/data/adb/") || line.contains("*") ||
line.contains("/../") || line.endsWith("/..") ||
line.startsWith("/data/adb/modules") ||
line.equals("/data/adb/magisk.db")) continue;
line = line.replace("\\", "\\\\")
.replace("\"", "\\\"");
if (!line.startsWith("/data/adb/") || line.contains("*") || line.contains("/../") || line.endsWith("/..") || line.startsWith("/data/adb/modules") || line.equals("/data/adb/magisk.db"))
continue;
line = line.replace("\\", "\\\\").replace("\"", "\\\"");
Shell.cmd("rm -rf \"" + line + "\"").exec();
}
}
} catch (IOException ignored) {
} catch (
IOException ignored) {
}
Shell.cmd("rm -rf /data/adb/modules/" + escapedId + "/").exec();
Shell.cmd("rm -f /data/adb/modules/." + escapedId + "-files").exec();

@ -8,7 +8,6 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@ -24,8 +23,8 @@ import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.utils.BlurUtils;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.Http;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -36,9 +35,9 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import eightbitlab.com.blurview.BlurView;
import timber.log.Timber;
public class MarkdownActivity extends FoxActivity {
private static final String TAG = "MarkdownActivity";
private static final HashMap<String, String> redirects = new HashMap<>(4);
private static final String[] variants = new String[]{
"readme.md", "README.MD", ".github/README.md"
@ -81,7 +80,7 @@ public class MarkdownActivity extends FoxActivity {
this.setDisplayHomeAsUpEnabled(true);
Intent intent = this.getIntent();
if (!MainApplication.checkSecret(intent)) {
Log.e(TAG, "Impersonation detected!");
Timber.e("Impersonation detected!");
this.forceBackPressed();
return;
}
@ -116,11 +115,11 @@ public class MarkdownActivity extends FoxActivity {
return true;
});
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Config package \"" +
Timber.w("Config package \"" +
configPkg + "\" missing for markdown view");
}
}
Log.i(TAG, "Url for markdown " + url);
Timber.i("Url for markdown %s", url);
setContentView(R.layout.markdown_view);
final ViewGroup markdownBackground = findViewById(R.id.markdownBackground);
final TextView textView = findViewById(R.id.markdownView);
@ -145,11 +144,11 @@ public class MarkdownActivity extends FoxActivity {
new Thread(() -> {
try {
Log.i(TAG, "Downloading");
Timber.i("Downloading");
byte[] rawMarkdown = getRawMarkdown(url);
Log.i(TAG, "Encoding");
Timber.i("Encoding");
String markdown = new String(rawMarkdown, StandardCharsets.UTF_8);
Log.i(TAG, "Done!");
Timber.i("Done!");
runOnUiThread(() -> {
findViewById(R.id.markdownFooter)
.setMinimumHeight(this.getNavigationBarHeight());
@ -160,7 +159,7 @@ public class MarkdownActivity extends FoxActivity {
}
});
} catch (Exception e) {
Log.e(TAG, "Failed download", e);
Timber.e(e);
runOnUiThread(() -> Toast.makeText(this, R.string.failed_download,
Toast.LENGTH_SHORT).show());
}

@ -1,50 +1,44 @@
package com.fox2code.mmm.markdown;
import android.util.Log;
import com.fox2code.mmm.BuildConfig;
import java.util.ArrayList;
import timber.log.Timber;
public class MarkdownUrlLinker {
private static final String TAG = "MarkdownUrlLinker";
public static String urlLinkify(String url) {
int index = url.indexOf("https://");
if (index == -1) return url;
if (index == -1)
return url;
ArrayList<LinkifyTask> linkifyTasks = new ArrayList<>();
int extra = 0;
while (index != -1) {
int end = url.indexOf(' ', index);
end = end == -1 ? url.indexOf('\n', index) :
Math.min(url.indexOf('\n', index), end);
if (end == -1) end = url.length();
if (index == 0 ||
'\n' == url.charAt(index - 1) ||
' ' == url.charAt(index - 1)) {
end = end == -1 ? url.indexOf('\n', index) : Math.min(url.indexOf('\n', index), end);
if (end == -1)
end = url.length();
if (index == 0 || '\n' == url.charAt(index - 1) || ' ' == url.charAt(index - 1)) {
int endDomain = url.indexOf('/', index + 9);
char endCh = url.charAt(end - 1);
if (endDomain != -1 && endDomain < end &&
endCh != '>' && endCh != ')' && endCh != ']') {
if (endDomain != -1 && endDomain < end && endCh != '>' && endCh != ')' && endCh != ']') {
linkifyTasks.add(new LinkifyTask(index, end));
extra += (end - index) + 4;
if (BuildConfig.DEBUG) { Log.d(TAG, "Linkify url: " + url.substring(index, end));
}
Timber.d("Linkify url: %s", url.substring(end));
}
}
index = url.indexOf("https://", end);
}
if (linkifyTasks.isEmpty()) return url;
if (linkifyTasks.isEmpty())
return url;
LinkifyTask prev = LinkifyTask.NULL;
StringBuilder stringBuilder = new StringBuilder(url.length() + extra);
for (LinkifyTask linkifyTask : linkifyTasks) {
stringBuilder.append(url, prev.end, linkifyTask.start)
.append('[').append(url, linkifyTask.start, linkifyTask.end)
.append("](").append(url, linkifyTask.start, linkifyTask.end).append(')');
stringBuilder.append(url, prev.end, linkifyTask.start).append('[').append(url, linkifyTask.start, linkifyTask.end).append("](").append(url, linkifyTask.start, linkifyTask.end).append(')');
prev = linkifyTask;
}
if (prev.end != url.length()) stringBuilder.append(url, prev.end, url.length());
Log.i(TAG, "Added Markdown link to " + linkifyTasks.size() + " urls");
if (prev.end != url.length())
stringBuilder.append(url, prev.end, url.length());
Timber.i("Added Markdown link to " + linkifyTasks.size() + " urls");
return stringBuilder.toString();
}

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.text.Spanned;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
@ -27,6 +26,7 @@ import com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import io.noties.markwon.Markwon;
import timber.log.Timber;
@SuppressLint("UseCompatLoadingForDrawables")
public enum ActionButtonType {
@ -95,7 +95,7 @@ public enum ActionButtonType {
} else {
builder.setMessage(desc);
}
Log.i("Test", "URL: " + updateZipUrl);
Timber.i("URL: %s", updateZipUrl);
builder.setNegativeButton(R.string.download_module, (x, y) -> IntentHelper.openCustomTab(button.getContext(), updateZipUrl));
if (hasRoot) {
builder.setPositiveButton(moduleHolder.hasUpdate() ? R.string.update_module : R.string.install_module, (x, y) -> {
@ -132,9 +132,9 @@ public enum ActionButtonType {
doActionLong(button, moduleHolder);
return;
}
Log.i("ActionButtonType", Integer.toHexString(moduleHolder.moduleInfo.flags));
Timber.i(Integer.toHexString(moduleHolder.moduleInfo.flags));
if (!ModuleManager.getINSTANCE().setUninstallState(moduleHolder.moduleInfo, !moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UNINSTALLING))) {
Log.e("ActionButtonType", "Failed to switch uninstalled state!");
Timber.e("Failed to switch uninstalled state!");
}
update(button, moduleHolder);
}
@ -144,17 +144,22 @@ public enum ActionButtonType {
// Actually a module having mount is the only issue when deleting module
if (moduleHolder.moduleInfo.hasFlag(ModuleInfo.FLAG_MODULE_HAS_ACTIVE_MOUNT))
return false; // We can't trust active flag on first boot
new AlertDialog.Builder(button.getContext()).setTitle(R.string.master_delete).setPositiveButton(R.string.master_delete_yes, (v, i) -> {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(button.getContext());
builder.setTitle(R.string.master_delete);
builder.setPositiveButton(R.string.master_delete_yes, (dialog, which) -> {
String moduleId = moduleHolder.moduleInfo.id;
if (!ModuleManager.getINSTANCE().masterClear(moduleHolder.moduleInfo)) {
Toast.makeText(button.getContext(), R.string.master_delete_fail, Toast.LENGTH_SHORT).show();
} else {
moduleHolder.moduleInfo = null;
FoxActivity.getFoxActivity(button).refreshUI();
Log.e("ActionButtonType", "Cleared: " + moduleId);
Timber.e("Cleared: %s", moduleId);
}
}).setNegativeButton(R.string.master_delete_no, (v, i) -> {
}).create().show();
});
builder.setNegativeButton(R.string.master_delete_no, (v, i) -> {
});
builder.create();
builder.show();
return true;
}
}, CONFIG() {

@ -2,7 +2,6 @@ package com.fox2code.mmm.module;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
@ -15,8 +14,8 @@ import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.io.PropUtils;
import java.util.Comparator;
@ -24,8 +23,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import timber.log.Timber;
public final class ModuleHolder implements Comparable<ModuleHolder> {
private static final String TAG = "ModuleHolder";
public final String moduleId;
public final NotificationType notificationType;
@ -196,7 +196,7 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
XHooks.checkConfigTargetExists(context, pkg, config);
buttonTypeList.add(ActionButtonType.CONFIG);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Config package \"" + pkg +
Timber.w("Config package \"" + pkg +
"\" missing for module \"" + this.moduleId + "\"");
}
}

@ -5,7 +5,6 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@ -22,7 +21,6 @@ import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.foxcompat.view.FoxDisplay;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.NotificationType;
import com.fox2code.mmm.R;
@ -38,16 +36,21 @@ import com.topjohnwu.superuser.internal.UiThreadHandler;
import java.util.ArrayList;
import java.util.Objects;
import timber.log.Timber;
public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdapter.ViewHolder> {
private static final boolean DEBUG = false;
public final ArrayList<ModuleHolder> moduleHolders = new ArrayList<>();
private static String formatType(ModuleHolder.Type type) {
return type.name().substring(0, 3) + "_" + type.ordinal();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.module_entry, parent, false);
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.module_entry, parent, false);
return new ViewHolder(view);
}
@ -83,9 +86,9 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
private final TextView updateText;
private final Chip[] actionsButtons;
private final ArrayList<ActionButtonType> actionButtonsTypes;
private boolean initState;
public ModuleHolder moduleHolder;
public Drawable background;
private boolean initState;
public ViewHolder(@NonNull View itemView) {
super(itemView);
@ -117,14 +120,16 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
onClickListener.onClick(v);
} else if (moduleHolder.notificationType != null) {
onClickListener = moduleHolder.notificationType.onClickListener;
if (onClickListener != null) onClickListener.onClick(v);
if (onClickListener != null)
onClickListener.onClick(v);
}
}
});
this.buttonAction.setClickable(false);
this.switchMaterial.setEnabled(false);
this.switchMaterial.setOnCheckedChangeListener((v, checked) -> {
if (this.initState) return; // Skip if non user
if (this.initState)
return; // Skip if non user
ModuleHolder moduleHolder = this.moduleHolder;
if (moduleHolder != null && moduleHolder.moduleInfo != null) {
ModuleInfo moduleInfo = moduleHolder.moduleInfo;
@ -138,23 +143,23 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
for (int i = 0; i < this.actionsButtons.length; i++) {
final int index = i;
this.actionsButtons[i].setOnClickListener(v -> {
if (this.initState) return; // Skip if non user
if (this.initState)
return; // Skip if non user
ModuleHolder moduleHolder = this.moduleHolder;
if (index < this.actionButtonsTypes.size() && moduleHolder != null) {
this.actionButtonsTypes.get(index)
.doAction((Chip) v, moduleHolder);
this.actionButtonsTypes.get(index).doAction((Chip) v, moduleHolder);
if (moduleHolder.shouldRemove()) {
this.cardView.setVisibility(View.GONE);
}
}
});
this.actionsButtons[i].setOnLongClickListener(v -> {
if (this.initState) return false; // Skip if non user
if (this.initState)
return false; // Skip if non user
ModuleHolder moduleHolder = this.moduleHolder;
boolean didSomething = false;
if (index < this.actionButtonsTypes.size() && moduleHolder != null) {
didSomething = this.actionButtonsTypes.get(index)
.doActionLong((Chip) v, moduleHolder);
didSomething = this.actionButtonsTypes.get(index).doActionLong((Chip) v, moduleHolder);
if (moduleHolder.shouldRemove()) {
this.cardView.setVisibility(View.GONE);
}
@ -190,8 +195,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
if (localModuleInfo != null) {
localModuleInfo.verify();
this.switchMaterial.setVisibility(View.VISIBLE);
this.switchMaterial.setChecked((localModuleInfo.flags &
ModuleInfo.FLAG_MODULE_DISABLED) == 0);
this.switchMaterial.setChecked((localModuleInfo.flags & ModuleInfo.FLAG_MODULE_DISABLED) == 0);
} else {
this.switchMaterial.setVisibility(View.GONE);
}
@ -203,24 +207,10 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
moduleInfo.verify();
this.titleText.setText(moduleInfo.name);
if (localModuleInfo == null || moduleInfo.versionCode >
localModuleInfo.updateVersionCode) {
this.creditText.setText((localModuleInfo == null ||
Objects.equals(moduleInfo.version, localModuleInfo.version) ?
moduleInfo.version : localModuleInfo.version + " (" +
this.getString(R.string.module_last_update) + " " +
moduleInfo.version + ")") + " " +
this.getString(R.string.module_by) + " " + moduleInfo.author);
if (localModuleInfo == null || moduleInfo.versionCode > localModuleInfo.updateVersionCode) {
this.creditText.setText((localModuleInfo == null || Objects.equals(moduleInfo.version, localModuleInfo.version) ? moduleInfo.version : localModuleInfo.version + " (" + this.getString(R.string.module_last_update) + " " + moduleInfo.version + ")") + " " + this.getString(R.string.module_by) + " " + moduleInfo.author);
} else {
this.creditText.setText(localModuleInfo.version + (
(localModuleInfo.updateVersion != null && (Objects.equals(
localModuleInfo.version, localModuleInfo.updateVersion) ||
Objects.equals(localModuleInfo.version,
localModuleInfo.updateVersion + " (" +
localModuleInfo.updateVersionCode + ")"))) ?
"" : " (" + this.getString(R.string.module_last_update) +
" " + localModuleInfo.updateVersion + ")") + " " +
this.getString(R.string.module_by) + " " + localModuleInfo.author);
this.creditText.setText(localModuleInfo.version + ((localModuleInfo.updateVersion != null && (Objects.equals(localModuleInfo.version, localModuleInfo.updateVersion) || Objects.equals(localModuleInfo.version, localModuleInfo.updateVersion + " (" + localModuleInfo.updateVersionCode + ")"))) ? "" : " (" + this.getString(R.string.module_last_update) + " " + localModuleInfo.updateVersion + ")") + " " + this.getString(R.string.module_by) + " " + localModuleInfo.author);
}
if (moduleInfo.description == null || moduleInfo.description.isEmpty()) {
this.descriptionText.setText(R.string.no_desc_found);
@ -232,12 +222,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
if (!updateText.isEmpty()) {
RepoModule repoModule = moduleHolder.repoModule;
this.updateText.setVisibility(View.VISIBLE);
this.updateText.setText(
this.getString(R.string.module_last_update) + " " + updateText + "\n" +
this.getString(R.string.module_repo) + " " + moduleHolder.getRepoName() +
(repoModule.qualityText == 0 ? "" : (
"\n" + this.getString(repoModule.qualityText) +
" " + repoModule.qualityValue)));
this.updateText.setText(this.getString(R.string.module_last_update) + " " + updateText + "\n" + this.getString(R.string.module_repo) + " " + moduleHolder.getRepoName() + (repoModule.qualityText == 0 ? "" : ("\n" + this.getString(repoModule.qualityText) + " " + repoModule.qualityValue)));
} else if (moduleHolder.moduleId.equals("hosts")) {
this.updateText.setVisibility(View.VISIBLE);
this.updateText.setText(R.string.magisk_builtin_module);
@ -250,21 +235,18 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
}
this.actionButtonsTypes.clear();
moduleHolder.getButtons(itemView.getContext(), this.actionButtonsTypes, showCaseMode);
this.switchMaterial.setEnabled(!showCaseMode &&
!moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING));
this.switchMaterial.setEnabled(!showCaseMode && !moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING));
for (int i = 0; i < this.actionsButtons.length; i++) {
Chip imageButton = this.actionsButtons[i];
if (i < this.actionButtonsTypes.size()) {
imageButton.setVisibility(View.VISIBLE);
imageButton.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
imageButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
ActionButtonType button = this.actionButtonsTypes.get(i);
button.update(imageButton, moduleHolder);
imageButton.setContentDescription(button.name());
} else {
imageButton.setVisibility(View.GONE);
imageButton.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_NO);
imageButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
imageButton.setContentDescription(null);
}
}
@ -272,14 +254,12 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.moduleOptionsHolder.setVisibility(View.GONE);
this.moduleLayoutHelper.setVisibility(View.GONE);
} else if (this.actionButtonsTypes.size() > 2 || !hasUpdateText) {
this.moduleLayoutHelper.setMinHeight(Math.max(FoxDisplay.dpToPixel(36F),
this.moduleOptionsHolder.getHeight() - FoxDisplay.dpToPixel(14F)));
this.moduleLayoutHelper.setMinHeight(Math.max(FoxDisplay.dpToPixel(36F), this.moduleOptionsHolder.getHeight() - FoxDisplay.dpToPixel(14F)));
} else {
this.moduleLayoutHelper.setMinHeight(FoxDisplay.dpToPixel(4F));
}
this.cardView.setClickable(false);
if (moduleHolder.isModuleHolder() &&
moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)) {
if (moduleHolder.isModuleHolder() && moduleHolder.hasFlag(ModuleInfo.FLAG_MODULE_ACTIVE)) {
this.titleText.setTypeface(Typeface.DEFAULT_BOLD);
} else {
this.titleText.setTypeface(Typeface.DEFAULT);
@ -290,9 +270,7 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.buttonAction.setImageResource(moduleHolder.filterLevel);
this.buttonAction.setBackgroundResource(R.drawable.bg_baseline_circle_24);
} else {
this.buttonAction.setVisibility(
type == ModuleHolder.Type.NOTIFICATION ?
View.VISIBLE : View.GONE);
this.buttonAction.setVisibility(type == ModuleHolder.Type.NOTIFICATION ? View.VISIBLE : View.GONE);
this.buttonAction.setBackground(null);
}
this.switchMaterial.setVisibility(View.GONE);
@ -308,19 +286,15 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.actionButtonsTypes.clear();
for (Chip button : this.actionsButtons) {
button.setVisibility(View.GONE);
button.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_NO);
button.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
button.setContentDescription(null);
}
if (type == ModuleHolder.Type.NOTIFICATION) {
NotificationType notificationType = moduleHolder.notificationType;
this.titleText.setText(notificationType.textId);
this.buttonAction.setImageResource(notificationType.iconId);
this.cardView.setClickable(
notificationType.onClickListener != null ||
moduleHolder.onClickListener != null);
this.titleText.setTypeface(notificationType.special ?
Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
this.cardView.setClickable(notificationType.onClickListener != null || moduleHolder.onClickListener != null);
this.titleText.setTypeface(notificationType.special ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
} else {
this.cardView.setClickable(moduleHolder.onClickListener != null);
this.titleText.setTypeface(Typeface.DEFAULT);
@ -330,12 +304,12 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
this.titleText.setText(moduleHolder.separator.title);
}
if (DEBUG) {
this.titleText.setText(this.titleText.getText() + " " +
formatType(type) + " " + formatType(vType));
this.titleText.setText(this.titleText.getText() + " " + formatType(type) + " " + formatType(vType));
}
// Coloration system
Drawable drawable = this.cardView.getBackground();
if (drawable != null) this.background = drawable;
if (drawable != null)
this.background = drawable;
this.invalidPropsChip.setVisibility(View.GONE);
if (type.hasBackground) {
if (drawable == null) {
@ -346,15 +320,10 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
if (type == ModuleHolder.Type.NOTIFICATION) {
foregroundAttr = moduleHolder.notificationType.foregroundAttr;
backgroundAttr = moduleHolder.notificationType.backgroundAttr;
} else if (type == ModuleHolder.Type.INSTALLED &&
moduleHolder.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) {
} else if (type == ModuleHolder.Type.INSTALLED && moduleHolder.hasFlag(ModuleInfo.FLAG_METADATA_INVALID)) {
this.invalidPropsChip.setOnClickListener(_view -> {
MaterialAlertDialogBuilder builder =
new MaterialAlertDialogBuilder(_view.getContext());
builder.setTitle(R.string.low_quality_module)
.setMessage("Actual description for Low-quality module")
.setCancelable(true)
.setPositiveButton(R.string.ok, (x, y) -> x.dismiss()).show();
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(_view.getContext());
builder.setTitle(R.string.low_quality_module).setMessage("Actual description for Low-quality module").setCancelable(true).setPositiveButton(R.string.ok, (x, y) -> x.dismiss()).show();
});
// Backup restore
// foregroundAttr = R.attr.colorOnError;
@ -373,11 +342,8 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
// if force_transparency is true or theme is transparent_light, set diff bgColor
// get string value of Theme
String themeName = theme.toString();
if (theme.getResources().getBoolean(R.bool.force_transparency) ||
themeName.contains("transparent")) {
if (BuildConfig.DEBUG) {
Log.d("NoodleDebug", "Theme is transparent, fixing bgColor");
}
if (theme.getResources().getBoolean(R.bool.force_transparency) || themeName.contains("transparent")) {
Timber.d("Theme is transparent, fixing bgColor");
bgColor = ColorUtils.setAlphaComponent(bgColor, 0x70);
}
this.titleText.setTextColor(fgColor);
@ -401,8 +367,4 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter<ModuleViewAdap
return false;
}
}
private static String formatType(ModuleHolder.Type type) {
return type.name().substring(0, 3) + "_" + type.ordinal();
}
}

@ -2,7 +2,6 @@ package com.fox2code.mmm.module;
import android.app.Activity;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@ -18,14 +17,14 @@ import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import timber.log.Timber;
public class ModuleViewListBuilder {
private static final String TAG = "ModuleViewListBuilder";
private static final Runnable RUNNABLE = () -> {};
private final EnumSet<NotificationType> notifications = EnumSet.noneOf(NotificationType.class);
private final HashMap<String, ModuleHolder> mappedModuleHolders = new HashMap<>();
@ -44,9 +43,28 @@ public class ModuleViewListBuilder {
this.activity = activity;
}
private static void notifySizeChanged(ModuleViewAdapter moduleViewAdapter,
int index, int oldLen, int newLen) {
// Timber.i("A: " + index + " " + oldLen + " " + newLen);
if (oldLen == newLen) {
if (newLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
} else if (oldLen < newLen) {
if (oldLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, oldLen);
moduleViewAdapter.notifyItemRangeInserted(
index + oldLen, newLen - oldLen);
} else {
if (newLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
moduleViewAdapter.notifyItemRangeRemoved(
index + newLen, oldLen - newLen);
}
}
public void addNotification(NotificationType notificationType) {
if (notificationType == null) {
Log.w(TAG, "addNotification(null) called!");
Timber.w("addNotification(null) called!");
return;
}
synchronized (this.updateLock) {
@ -61,7 +79,7 @@ public class ModuleViewListBuilder {
}
ModuleManager moduleManager = ModuleManager.getINSTANCE();
moduleManager.runAfterScan(() -> {
Log.i(TAG, "A1: " + moduleManager.getModules().size());
Timber.i("A1: %s", moduleManager.getModules().size());
for (LocalModuleInfo moduleInfo : moduleManager.getModules().values()) {
ModuleHolder moduleHolder = this.mappedModuleHolders.get(moduleInfo.id);
if (moduleHolder == null) {
@ -82,7 +100,7 @@ public class ModuleViewListBuilder {
}
RepoManager repoManager = RepoManager.getINSTANCE();
repoManager.runAfterUpdate(() -> {
Log.i(TAG, "A2: " + repoManager.getModules().size());
Timber.i("A2: %s", repoManager.getModules().size());
boolean no32bitSupport = Build.SUPPORTED_32_BIT_ABIS.length == 0;
for (RepoModule repoModule : repoManager.getModules().values()) {
if (!repoModule.repoData.isEnabled()) continue;
@ -111,6 +129,37 @@ public class ModuleViewListBuilder {
}
}
public void refreshNotificationsUI(ModuleViewAdapter moduleViewAdapter) {
final int notificationCount = this.notifications.size();
notifySizeChanged(moduleViewAdapter, 0,
notificationCount, notificationCount);
}
private boolean matchFilter(ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
String query = this.query;
String idLw = moduleInfo.id.toLowerCase(Locale.ROOT);
String nameLw = moduleInfo.name.toLowerCase(Locale.ROOT);
String authorLw = moduleInfo.author == null ? "" :
moduleInfo.author.toLowerCase(Locale.ROOT);
if (query.isEmpty() || query.equals(idLw) ||
query.equals(nameLw) || query.equals(authorLw)) {
moduleHolder.filterLevel = 0; // Lower = better
return true;
}
if (idLw.contains(query) || nameLw.contains(query)) {
moduleHolder.filterLevel = 1;
return true;
}
if (authorLw.contains(query) || (moduleInfo.description != null &&
moduleInfo.description.toLowerCase(Locale.ROOT).contains(query))) {
moduleHolder.filterLevel = 2;
return true;
}
moduleHolder.filterLevel = 3;
return false;
}
public void applyTo(final RecyclerView moduleList,final ModuleViewAdapter moduleViewAdapter) {
if (this.updating) return;
this.updating = true;
@ -170,14 +219,14 @@ public class ModuleViewListBuilder {
}
}
}
Collections.sort(moduleHolders, this.moduleSorter);
moduleHolders.sort(this.moduleSorter);
// Header is always first
moduleHolders.add(0, headerFooter[0] =
new ModuleHolder(this.headerPx, true));
// Footer is always last
moduleHolders.add(headerFooter[1] =
new ModuleHolder(this.footerPx, false));
Log.i(TAG, "Got " + moduleHolders.size() + " entries!");
Timber.i("Got " + moduleHolders.size() + " entries!");
// Build end
}
} finally {
@ -251,59 +300,9 @@ public class ModuleViewListBuilder {
});
}
public void refreshNotificationsUI(ModuleViewAdapter moduleViewAdapter) {
final int notificationCount = this.notifications.size();
notifySizeChanged(moduleViewAdapter, 0,
notificationCount, notificationCount);
}
private boolean matchFilter(ModuleHolder moduleHolder) {
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
String query = this.query;
String idLw = moduleInfo.id.toLowerCase(Locale.ROOT);
String nameLw = moduleInfo.name.toLowerCase(Locale.ROOT);
String authorLw = moduleInfo.author == null ? "" :
moduleInfo.author.toLowerCase(Locale.ROOT);
if (query.isEmpty() || query.equals(idLw) ||
query.equals(nameLw) || query.equals(authorLw)) {
moduleHolder.filterLevel = 0; // Lower = better
return true;
}
if (idLw.contains(query) || nameLw.contains(query)) {
moduleHolder.filterLevel = 1;
return true;
}
if (authorLw.contains(query) || (moduleInfo.description != null &&
moduleInfo.description.toLowerCase(Locale.ROOT).contains(query))) {
moduleHolder.filterLevel = 2;
return true;
}
moduleHolder.filterLevel = 3;
return false;
}
private static void notifySizeChanged(ModuleViewAdapter moduleViewAdapter,
int index, int oldLen, int newLen) {
// Log.i(TAG, "A: " + index + " " + oldLen + " " + newLen);
if (oldLen == newLen) {
if (newLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
} else if (oldLen < newLen) {
if (oldLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, oldLen);
moduleViewAdapter.notifyItemRangeInserted(
index + oldLen, newLen - oldLen);
} else {
if (newLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
moduleViewAdapter.notifyItemRangeRemoved(
index + newLen, oldLen - newLen);
}
}
public void setQuery(String query) {
synchronized (this.queryLock) {
Log.i(TAG, "Query " + this.query + " -> " + query);
Timber.i("Query " + this.query + " -> " + query);
this.query = query == null ? "" :
query.trim().toLowerCase(Locale.ROOT);
}
@ -313,7 +312,7 @@ public class ModuleViewListBuilder {
synchronized (this.queryLock) {
String newQuery = query == null ? "" :
query.trim().toLowerCase(Locale.ROOT);
Log.i(TAG, "Query change " + this.query + " -> " + newQuery);
Timber.i("Query change " + this.query + " -> " + newQuery);
if (this.query.equals(newQuery))
return false;
this.query = newQuery;

@ -2,7 +2,6 @@ package com.fox2code.mmm.repo;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
@ -32,6 +31,7 @@ import java.util.List;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import timber.log.Timber;
public class RepoData extends XRepo {
public final String url;
@ -43,7 +43,7 @@ public class RepoData extends XRepo {
private final Object populateLock = new Object();
public long lastUpdate;
public String name, website, support, donate, submitModule;
public JSONObject supportedProperties = new JSONObject();
public final JSONObject supportedProperties = new JSONObject();
protected String defaultName, defaultWebsite, defaultSupport, defaultDonate, defaultSubmitModule;
@ -112,9 +112,7 @@ public class RepoData extends XRepo {
.build();
Realm realm = Realm.getInstance(realmConfiguration);
ReposList reposList = realm.where(ReposList.class).equalTo("id", this.id).findFirst();
if (BuildConfig.DEBUG) {
Log.d("RepoData", "RepoData: " + this.id + ". record in database: " + (reposList != null ? reposList.toString() : "none"));
}
Timber.d("RepoData: " + this.id + ". record in database: " + (reposList != null ? reposList.toString() : "none"));
this.enabled = (!this.forceHide && reposList != null && reposList.isEnabled());
this.enabled = (!this.forceHide) && MainApplication.getSharedPreferences().getBoolean("pref_" + this.getPreferenceId() + "_enabled", true);
this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/";
@ -122,7 +120,7 @@ public class RepoData extends XRepo {
// load metadata from realm database
if (this.enabled) {
try {
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).build();
RealmConfiguration realmConfiguration2 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
// load metadata from realm database
Realm.getInstance(realmConfiguration2);
this.metaDataCache = ModuleListCache.getRepoModulesAsJson(this.id);
@ -135,7 +133,7 @@ public class RepoData extends XRepo {
this.submitModule = this.defaultSubmitModule;
} else {
// get everything from ReposList realm database
RealmConfiguration realmConfiguration3 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).build();
RealmConfiguration realmConfiguration3 = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
// load metadata from realm database
Realm.getInstance(realmConfiguration3);
this.name = ReposList.getRepo(this.id).getName();
@ -146,7 +144,7 @@ public class RepoData extends XRepo {
}
} catch (Exception e) {
e.printStackTrace();
Log.w("RepoData", "Failed to load repo metadata from realm database. If this is a first time install, this is normal.");
Timber.w("If this is a first install, this is normal.");
}
}
}
@ -179,7 +177,7 @@ public class RepoData extends XRepo {
}
// If module id start with a dot, warn user
if (moduleId.charAt(0) == '.') {
Log.w("MMM", "Module ID " + moduleId + " in repo " + this.url + " start with a dot, this is not recommended and may indicate an attempt to hide the module");
Timber.w("This is not recommended and may indicate an attempt to hide the module");
}
long moduleLastUpdate = module.getLong("last_update");
String moduleNotesUrl = module.getString("notes_url");
@ -283,9 +281,7 @@ public class RepoData extends XRepo {
SharedPreferences preferenceManager = MainApplication.getSharedPreferences();
boolean enabled = preferenceManager.getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault());
if (this.enabled != enabled) {
if (BuildConfig.DEBUG) {
Log.d("NoodleDebug", "Repo " + this.id + " enable mismatch: " + this.enabled + " vs " + enabled);
}
Timber.d("Repo " + this.id + " enable mismatch: " + this.enabled + " vs " + enabled);
this.enabled = enabled;
}
return this.enabled;
@ -294,9 +290,7 @@ public class RepoData extends XRepo {
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled && !this.forceHide;
if (BuildConfig.DEBUG) {
Log.d("RepoData", "Repo " + this.id + " enabled: " + this.enabled + " (forced: " + this.forceHide + ") with preferenceID: " + this.getPreferenceId());
}
Timber.d("Repo " + this.id + " enabled: " + this.enabled + " (forced: " + this.forceHide + ") with preferenceID: " + this.getPreferenceId());
MainApplication.getSharedPreferences().edit().putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply();
}
@ -306,9 +300,7 @@ public class RepoData extends XRepo {
return;
}
this.forceHide = AppUpdateManager.shouldForceHide(this.id);
if (BuildConfig.DEBUG) {
Log.d("RepoData", "Repo " + this.id + " update enabled: " + this.enabled + " (forced: " + this.forceHide + ") with preferenceID: " + this.getPreferenceId());
}
Timber.d("Repo " + this.id + " update enabled: " + this.enabled + " (forced: " + this.forceHide + ") with preferenceID: " + this.getPreferenceId());
this.enabled = (!this.forceHide) && MainApplication.getSharedPreferences().getBoolean("pref_" + this.getPreferenceId() + "_enabled", true);
}

@ -6,7 +6,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -40,6 +39,8 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Collectors;
import timber.log.Timber;
public final class RepoManager extends SyncManager {
public static final String MAGISK_REPO = "https://raw.githubusercontent.com/Magisk-Modules-Repo/submission/modules/modules.json";
public static final String MAGISK_REPO_HOMEPAGE = "https://github.com/Magisk-Modules-Repo";
@ -49,7 +50,6 @@ public final class RepoManager extends SyncManager {
public static final String ANDROIDACY_MAGISK_REPO_ENDPOINT = "https://production-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT = "https://staging-api.androidacy.com/magisk/repo";
public static final String ANDROIDACY_MAGISK_REPO_HOMEPAGE = "https://www.androidacy.com/modules-repo";
private static final String TAG = "RepoManager";
private static final String MAGISK_REPO_MANAGER = "https://magisk-modules-repo.github.io/submission/modules.json";
private static final Object lock = new Object();
private static final double STEP1 = 0.1D;
@ -174,7 +174,7 @@ public final class RepoManager extends SyncManager {
this.modules.put(repoModule.id, repoModule);
}
} else {
Log.e(TAG, "Detected module with invalid metadata: " + repoModule.repoName + "/" + repoModule.id);
Timber.e("Detected module with invalid metadata: " + repoModule.repoName + "/" + repoModule.id);
}
}
}
@ -232,58 +232,50 @@ public final class RepoManager extends SyncManager {
// Attempt to contact connectivitycheck.gstatic.com/generate_204
// If we can't, we don't have internet connection
try {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Checking internet connection...");
}
Timber.d("Checking internet connection...");
// this url is actually hosted by Cloudflare and is not dependent on Androidacy servers being up
HttpURLConnection urlConnection = (HttpURLConnection) new URL("https://production-api.androidacy.com/cdn-cgi/trace").openConnection();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Opened connection to " + urlConnection.getURL());
}
Timber.d("Opened connection to %s", urlConnection.getURL());
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setReadTimeout(1000);
urlConnection.setUseCaches(false);
urlConnection.getInputStream().close();
// should return a 200 and the content should contain "visit_scheme=https" and ip=<some ip>
if (BuildConfig.DEBUG) {
Log.d(TAG, "Response code: " + urlConnection.getResponseCode());
}
Timber.d("Response code: %s", urlConnection.getResponseCode());
// get the response body
String responseBody = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())).lines().collect(Collectors.joining("\n"));
if (BuildConfig.DEBUG) {
Log.d(TAG, "Response body: " + responseBody);
}
Timber.d("Response body: %s", responseBody);
// check if the response body contains the expected content
if (urlConnection.getResponseCode() == 200 && responseBody.contains("visit_scheme=https") && responseBody.contains("ip=")) {
this.hasInternet = true;
} else {
Log.e(TAG, "Failed to check internet connection");
Timber.e("Failed to check internet connection");
}
} catch (
IOException e) {
Log.e(TAG, "Failed to check internet connection", e);
Timber.e(e);
}
for (int i = 0; i < repoDatas.length; i++) {
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Preparing to fetch: " + repoDatas[i].getName());
Timber.d("Preparing to fetch: %s", repoDatas[i].getName());
moduleToUpdate += (repoUpdaters[i] = new RepoUpdater(repoDatas[i])).fetchIndex();
updateListener.update(STEP1 / repoDatas.length * (i + 1));
}
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Updating meta-data");
Timber.d("Updating meta-data");
int updatedModules = 0;
boolean allowLowQualityModules = MainApplication.isDisableLowQualityModuleFilter();
for (int i = 0; i < repoUpdaters.length; i++) {
// Check if the repo is enabled
if (!repoUpdaters[i].repoData.isEnabled()) {
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Skipping disabled repo: " + repoUpdaters[i].repoData.getName());
Timber.d("Skipping disabled repo: %s", repoUpdaters[i].repoData.getName());
continue;
}
List<RepoModule> repoModules = repoUpdaters[i].toUpdate();
RepoData repoData = repoDatas[i];
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Registering " + repoData.getName());
Timber.d("Registering %s", repoData.getName());
for (RepoModule repoModule : repoModules) {
try {
if (repoModule.propUrl != null && !repoModule.propUrl.isEmpty()) {
@ -305,7 +297,7 @@ public final class RepoManager extends SyncManager {
}
} catch (
Exception e) {
Log.e(TAG, "Failed to get \"" + repoModule.id + "\" metadata", e);
Timber.e(e);
}
updatedModules++;
updateListener.update(STEP1 + (STEP2 / moduleToUpdate * updatedModules));
@ -326,20 +318,20 @@ public final class RepoManager extends SyncManager {
}
}
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Finishing update");
Timber.d("Finishing update");
if (hasInternet) {
for (int i = 0; i < repoDatas.length; i++) {
// If repo is not enabled, skip
if (!repoDatas[i].isEnabled()) {
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Skipping " + repoDatas[i].getName() + " because it's disabled");
Timber.d("Skipping " + repoDatas[i].getName() + " because it's disabled");
continue;
}
if (BuildConfig.DEBUG)
Log.d("RepoManager", "Finishing: " + repoUpdaters[i].repoData.getName());
Timber.d("Finishing: %s", repoUpdaters[i].repoData.getName());
this.repoLastSuccess = repoUpdaters[i].finish();
if (!this.repoLastSuccess) {
Log.e(TAG, "Failed to update " + repoUpdaters[i].repoData.getName());
Timber.e("Failed to update %s", repoUpdaters[i].repoData.getName());
// Show snackbar on main looper and add some bottom padding
int finalI = i;
Activity context = MainApplication.getINSTANCE().getLastCompatActivity();
@ -366,7 +358,7 @@ public final class RepoManager extends SyncManager {
updateListener.update(STEP1 + STEP2 + (STEP3 / repoDatas.length * (i + 1)));
}
}
Log.i(TAG, "Got " + this.modules.size() + " modules!");
Timber.i("Got " + this.modules.size() + " modules!");
updateListener.update(1D);
}

@ -1,7 +1,5 @@
package com.fox2code.mmm.repo;
import android.util.Log;
import androidx.annotation.NonNull;
import com.fox2code.mmm.utils.io.Http;
@ -18,9 +16,9 @@ import java.util.List;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import timber.log.Timber;
public class RepoUpdater {
private static final String TAG = "RepoUpdater";
public final RepoData repoData;
public byte[] indexRaw;
private List<RepoModule> toUpdate;
@ -59,7 +57,7 @@ public class RepoUpdater {
return this.toUpdate.size();
} catch (
Exception e) {
Log.e(TAG, "Failed to get manifest of " + this.repoData.id, e);
Timber.e(e);
this.indexRaw = null;
this.toUpdate = Collections.emptyList();
this.toApply = Collections.emptySet();
@ -115,12 +113,26 @@ public class RepoUpdater {
// both are arrays of modules
// try to get modules from "modules" key
JSONObject modules = new JSONObject(new String(this.indexRaw, StandardCharsets.UTF_8));
// check if modules has key "modules" or "data"
try {
// get modules
modules = modules.getJSONObject("modules");
if (modules.has("modules")) {
// get modules from "modules" key
modules = modules.getJSONObject("modules");
} else if (modules.has("data")) {
// get modules from "data" key
modules = modules.getJSONObject("data");
}
} catch (JSONException e) {
// if it fails, try to get modules from "data" key
modules = modules.getJSONObject("data");
// 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}) {
try {
@ -209,12 +221,12 @@ public class RepoUpdater {
moduleListCache.setInstalledVersionCode(installedVersionCode);
}, () -> {
// Transaction was a success.
Log.d(TAG, "onSuccess: Transaction was a success.");
Timber.d("onSuccess: Transaction was a success.");
// close realm
realm.close();
}, error -> {
// Transaction failed and was automatically canceled.
Log.e(TAG, "onError: Transaction failed and was automatically canceled.", error);
Timber.e(error);
// close realm
realm.close();
});
@ -223,7 +235,7 @@ public class RepoUpdater {
} catch (
JSONException e) {
e.printStackTrace();
Log.w(TAG, "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());
}
}
} catch (

@ -19,7 +19,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
@ -61,6 +60,7 @@ import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.ProcessHelper;
import com.fox2code.mmm.utils.io.Http;
import com.fox2code.mmm.utils.realm.ReposList;
import com.fox2code.mmm.utils.sentry.SentryMain;
import com.fox2code.rosettax.LanguageActivity;
import com.fox2code.rosettax.LanguageSwitcher;
@ -84,13 +84,16 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Random;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import timber.log.Timber;
public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Shamelessly adapted from https://github.com/DrKLO/Telegram/blob/2c71f6c92b45386f0c2b25f1442596462404bb39/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java#L1254
public final static int PERFORMANCE_CLASS_LOW = 0;
public final static int PERFORMANCE_CLASS_AVERAGE = 1;
public final static int PERFORMANCE_CLASS_HIGH = 2;
private static final int LANGUAGE_SUPPORT_LEVEL = 1;
private static final String TAG = "SettingsActivity";
private static boolean devModeStepFirstBootIgnore = MainApplication.isDeveloper();
private static int devModeStep = 0;
@ -125,9 +128,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
devicePerformanceClass = PERFORMANCE_CLASS_HIGH;
}
if (BuildConfig.DEBUG) {
Log.d(TAG, "getDevicePerformanceClass: androidVersion=" + androidVersion + " cpuCount=" + cpuCount + " memoryClass=" + memoryClass + " maxCpuFreq=" + maxCpuFreq + " devicePerformanceClass=" + devicePerformanceClass);
}
Timber.d("getDevicePerformanceClass: androidVersion=" + androidVersion + " cpuCount=" + cpuCount + " memoryClass=" + memoryClass + " maxCpuFreq=" + maxCpuFreq + " devicePerformanceClass=" + devicePerformanceClass);
return devicePerformanceClass;
}
@ -176,9 +177,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
ListPreference themePreference = findPreference("pref_theme");
// If transparent theme(s) are set, disable monet
if (themePreference.getValue().equals("transparent_light")) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Transparent theme is set, disabling monet");
}
Timber.d("disabling monet");
findPreference("pref_enable_monet").setEnabled(false);
// Toggle monet off
((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false);
@ -197,17 +196,13 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// You need to reboot your device at least once to be able to access dev-mode
if (devModeStepFirstBootIgnore || !MainApplication.isFirstBoot())
devModeStep = 1;
if (BuildConfig.DEBUG) {
Log.d(TAG, "Theme changed, refreshing activity. New value: " + newValue);
}
Timber.d("refreshing activity. New value: %s", newValue);
// Immediately save
SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit();
editor.putString("pref_theme", (String) newValue).apply();
// If theme contains "transparent" then disable monet
if (newValue.toString().contains("transparent")) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Transparent theme is being set, disabling monet");
}
Timber.d("disabling monet");
// Show a dialogue warning the user about issues with transparent themes and
// that blur/monet will be disabled
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.transparent_theme_dialogue_title).setMessage(R.string.transparent_theme_dialogue_message).setPositiveButton(R.string.ok, (dialog, which) -> {
@ -276,9 +271,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save crash reporting preference: " + newValue);
}
Timber.d("Restarting app to save crash reporting preference: %s", newValue);
System.exit(0); // Exit app process
});
// Do not reverse the change if the user cancels the dialog
@ -358,7 +351,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
int level = this.currentLanguageLevel();
if (level != LANGUAGE_SUPPORT_LEVEL) {
Log.e(TAG, "Detected language level " + level + ", latest is " + LANGUAGE_SUPPORT_LEVEL);
Timber.e("latest is %s", LANGUAGE_SUPPORT_LEVEL);
languageSelector.setSummary(R.string.language_support_outdated);
} else {
String translatedBy = this.getString(R.string.language_translated_by);
@ -375,7 +368,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
}
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
Log.i(TAG, String.format("Sentry installed: %s, debug: %s, magisk path: %s", SentryMain.IS_SENTRY_INSTALLED, BuildConfig.DEBUG, InstallerInitializer.peekMagiskPath()));
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);
@ -398,7 +391,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
} else {
Log.e(TAG, String.format("Something is null: %s, %s", findPreference("pref_test_crash"), findPreference("pref_clear_data")));
Timber.e("Something is null: %s, %s", findPreference("pref_clear_data"), findPreference("pref_test_crash"));
}
}
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND || !MainApplication.isDeveloper()) {
@ -563,7 +556,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
fileOutputStream.write((line + "\n").getBytes());
}
fileOutputStream.close();
} catch (IOException e) {
} catch (
IOException e) {
e.printStackTrace();
Toast.makeText(requireContext(), R.string.error_saving_logs, Toast.LENGTH_SHORT).show();
return true;
@ -711,9 +705,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save staging endpoint preference: " + newValue);
}
Timber.d("Restarting app to save staging endpoint preference: %s", newValue);
System.exit(0); // Exit app process
}).setNegativeButton(android.R.string.cancel, (dialog, which) -> {
// User cancelled the dialog
@ -736,15 +728,35 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save staging endpoint preference: " + newValue);
}
Timber.d("Restarting app to save staging endpoint preference: %s", newValue);
System.exit(0); // Exit app process
}).show();
}
return true;
});
}
// 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();
Realm realm1 = Realm.getInstance(realmConfig);
ReposList reposList = realm1.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst();
if (reposList != null) {
// Set the switch to the current state
SwitchPreferenceCompat magiskAltRepoEnabled = Objects.requireNonNull(findPreference("pref_magisk_alt_repo_enabled"));
magiskAltRepoEnabled.setChecked(reposList.isEnabled());
}
// add listener to magisk_alt_repo_enabled switch to update realm db
Preference magiskAltRepoEnabled = Objects.requireNonNull(findPreference("pref_magisk_alt_repo_enabled"));
magiskAltRepoEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
// Update realm db
Realm realm = Realm.getInstance(realmConfig);
realm.executeTransaction(realm2 -> {
ReposList reposList1 = realm2.where(ReposList.class).equalTo("id", "magisk_alt_repo").findFirst();
if (reposList1 != null) {
reposList1.setEnabled(Boolean.parseBoolean(String.valueOf(newValue)));
}
});
return true;
});
// Disable toggling the pref_androidacy_repo_enabled on builds without an
// ANDROIDACY_CLIENT_ID or where the ANDROIDACY_CLIENT_ID is empty
Preference androidacyRepoEnabled = Objects.requireNonNull(findPreference("pref_androidacy_repo_enabled"));
@ -758,13 +770,25 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
// Revert the switch to off
SwitchPreferenceCompat switchPreferenceCompat = (SwitchPreferenceCompat) androidacyRepoEnabled;
switchPreferenceCompat.setChecked(false);
// Save the preference
MainApplication.getSharedPreferences().edit().putBoolean("pref_androidacy_repo_enabled", false).apply();
// Disable in realm db
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
Realm realm = Realm.getInstance(realmConfiguration);
realm.executeTransaction(realm2 -> {
ReposList repoRealmResults = realm2.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst();
assert repoRealmResults != null;
repoRealmResults.setEnabled(false);
realm2.insertOrUpdate(repoRealmResults);
realm2.close();
});
return false;
});
}
// get if androidacy repo is enabled
boolean androidacyRepoEnabledPref = MainApplication.getSharedPreferences().getBoolean("pref_androidacy_repo_enabled", false);
// 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();
Realm realm = Realm.getInstance(realmConfiguration);
ReposList repoRealmResults = realm.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst();
assert repoRealmResults != null;
boolean androidacyRepoEnabledPref = repoRealmResults.isEnabled();
if (androidacyRepoEnabledPref) {
String[] originalApiKeyRef = new String[]{MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).getString("pref_androidacy_api_token", "")};
// Get the dummy pref_androidacy_repo_api_token preference with id pref_androidacy_repo_api_token
@ -816,9 +840,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save token preference: " + newValue);
}
Timber.d("Restarting app to save token preference: %s", newValue);
System.exit(0); // Exit app process
}).show();
});
@ -866,9 +888,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Restarting app to save token preference: " + newValue);
}
Timber.d("Restarting app to save token preference: %s", newValue);
System.exit(0); // Exit app process
}).show();
});
@ -940,7 +960,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
IOException |
JSONException |
NoSuchAlgorithmException e) {
Log.e(TAG, "Failed to preload repo values", e);
Timber.e(e);
}
UiThreadHandler.handler.post(() -> updateCustomRepoList(false));
}
@ -986,9 +1006,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
if (preference == null)
return;
if (!preferenceName.contains("androidacy") && !preferenceName.contains("magisk_alt_repo")) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Setting preference " + preferenceName + " because it is not the Androidacy repo or the Magisk Alt Repo");
}
Timber.d("Setting preference " + preferenceName + " because it is not the Androidacy repo or the Magisk Alt Repo");
if (repoData == null || repoData.isForceHide()) {
hideRepoData(preferenceName);
return;

@ -9,7 +9,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
@ -21,44 +20,41 @@ import com.topjohnwu.superuser.internal.UiThreadHandler;
import java.util.List;
import timber.log.Timber;
public final class ExternalHelper {
private static final String TAG = "ExternalHelper";
public static final ExternalHelper INSTANCE = new ExternalHelper();
private static final boolean TEST_MODE = false;
private static final String FOX_MMM_OPEN_EXTERNAL =
"com.fox2code.mmm.utils.intent.action.OPEN_EXTERNAL";
private static final String FOX_MMM_OPEN_EXTERNAL = "com.fox2code.mmm.utils.intent.action.OPEN_EXTERNAL";
private static final String FOX_MMM_EXTRA_REPO_ID = "extra_repo_id";
public static final ExternalHelper INSTANCE = new ExternalHelper();
private ComponentName fallback;
private CharSequence label;
private boolean multi;
private ExternalHelper() {}
private ExternalHelper() {
}
public void refreshHelper(Context context) {
Intent intent = new Intent(FOX_MMM_OPEN_EXTERNAL,
Uri.parse("https://fox2code.com/module.zip"));
List<ResolveInfo> resolveInfos = context.getPackageManager()
.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
Intent intent = new Intent(FOX_MMM_OPEN_EXTERNAL, Uri.parse("https://fox2code.com/module.zip"));
List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
if (resolveInfos == null || resolveInfos.isEmpty()) {
Log.i(TAG, "No external provider installed!");
Timber.i("No external provider installed!");
label = TEST_MODE ? "External" : null;
multi = TEST_MODE;
fallback = null;
} else {
ResolveInfo resolveInfo = resolveInfos.get(0);
Log.i(TAG, "Found external provider: " + resolveInfo.activityInfo.packageName);
fallback = new ComponentName(
resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
Timber.i("Found external provider: %s", resolveInfo.activityInfo.packageName);
fallback = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
label = resolveInfo.loadLabel(context.getPackageManager());
multi = resolveInfos.size() >= 2;
}
}
public boolean openExternal(Context context, Uri uri, String repoId) {
if (label == null) return false;
Bundle param = ActivityOptionsCompat.makeCustomAnimation(context,
android.R.anim.fade_in, android.R.anim.fade_out).toBundle();
if (label == null)
return false;
Bundle param = ActivityOptionsCompat.makeCustomAnimation(context, android.R.anim.fade_in, android.R.anim.fade_out).toBundle();
Intent intent = new Intent(FOX_MMM_OPEN_EXTERNAL, uri);
intent.setFlags(IntentHelper.FLAG_GRANT_URI_PERMISSION);
intent.putExtra(FOX_MMM_EXTRA_REPO_ID, repoId);
@ -74,8 +70,9 @@ public final class ExternalHelper {
context.startActivity(intent, param);
}
return true;
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch activity", e);
} catch (
ActivityNotFoundException e) {
Timber.e(e);
}
if (fallback != null) {
if (multi) {
@ -87,27 +84,28 @@ public final class ExternalHelper {
try {
context.startActivity(intent, param);
return true;
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch fallback", e);
} catch (
ActivityNotFoundException e) {
Timber.e(e);
}
}
return false;
}
public void injectButton(AlertDialog.Builder builder, Supplier<Uri> uriSupplier, String repoId) {
if (label == null) return;
if (label == null)
return;
builder.setNeutralButton(label, (dialog, button) -> {
Context context = ((Dialog) dialog).getContext();
new Thread("Async downloader") {
@Override
public void run() {
final Uri uri = uriSupplier.get();
if (uri == null) return;
if (uri == null)
return;
UiThreadHandler.run(() -> {
if (!openExternal(context, uri, repoId)) {
Toast.makeText(context,
"Failed to launch external activity",
Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Failed to launch external activity", Toast.LENGTH_SHORT).show();
}
});
}

@ -10,7 +10,6 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.util.TypedValue;
import android.widget.Toast;
@ -38,8 +37,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import timber.log.Timber;
public class IntentHelper {
private static final String TAG = "IntentHelper";
private static final String EXTRA_TAB_SESSION =
"android.support.customtabs.extra.SESSION";
private static final String EXTRA_TAB_COLOR_SCHEME =
@ -57,7 +57,7 @@ public class IntentHelper {
try {
startActivity(context, Intent.parseUri(uri, Intent.URI_INTENT_SCHEME), false);
} catch (URISyntaxException | ActivityNotFoundException e) {
Log.e(TAG, "Failed launch of " + uri, e);
Timber.e(e);
}
} else openUrl(context, uri);
}
@ -103,7 +103,7 @@ public class IntentHelper {
public static void openUrlAndroidacy(Context context, String url, boolean allowInstall,
String title,String config) {
if (!Http.hasWebView()) {
Log.w(TAG, "Using custom tab for: " + url);
Timber.w("Using custom tab for: %s", url);
openCustomTab(context, url);
return;
}
@ -151,7 +151,7 @@ public class IntentHelper {
.to(new CallbackList<>() {
@Override
public void onAddElement(String str) {
Log.i(TAG, "LSPosed: " + str);
Timber.i("LSPosed: %s", str);
}
}).submit();
return;
@ -343,7 +343,7 @@ public class IntentHelper {
callback.onReceived(destination, null, RESPONSE_ERROR);
return;
}
Log.i(TAG, "FilePicker returned " + uri);
Timber.i("FilePicker returned %s", uri);
if ("http".equals(uri.getScheme()) ||
"https".equals(uri.getScheme())) {
callback.onReceived(destination, uri, RESPONSE_URL);
@ -373,10 +373,10 @@ public class IntentHelper {
}
outputStream = new FileOutputStream(destination);
Files.copy(inputStream, outputStream);
Log.i(TAG, "File saved at " + destination);
Timber.i("File saved at %s", destination);
success = true;
} catch (Exception e) {
Log.e(TAG, "failed copy of " + uri, e);
Timber.e(e);
Toast.makeText(compatActivity,
R.string.file_picker_failure,
Toast.LENGTH_SHORT).show();
@ -384,7 +384,7 @@ public class IntentHelper {
Files.closeSilently(inputStream);
Files.closeSilently(outputStream);
if (!success && destination.exists() && !destination.delete())
Log.e(TAG, "Failed to delete artefact!");
Timber.e("Failed to delete artefact!");
}
callback.onReceived(destination, uri, success ? RESPONSE_FILE : RESPONSE_ERROR);
});

@ -23,6 +23,8 @@ import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import timber.log.Timber;
public class ZipFileOpener extends FoxActivity {
AlertDialog loading = null;
@ -33,13 +35,11 @@ public class ZipFileOpener extends FoxActivity {
super.onCreate(savedInstanceState);
loading = BudgetProgressDialog.build(this, R.string.loading, R.string.zip_unpacking);
new Thread(() -> {
if (BuildConfig.DEBUG) {
Log.d("ZipFileOpener", "onCreate: " + getIntent());
}
Timber.d("onCreate: %s", getIntent());
File zipFile;
Uri uri = getIntent().getData();
if (uri == null) {
Log.e("ZipFileOpener", "onCreate: No data provided");
Timber.e("onCreate: No data provided");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
@ -57,7 +57,7 @@ public class ZipFileOpener extends FoxActivity {
zipFile = File.createTempFile("module", ".zip", getCacheDir());
try (InputStream inputStream = getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(zipFile)) {
if (inputStream == null) {
Log.e("ZipFileOpener", "onCreate: Failed to open input stream");
Timber.e("onCreate: Failed to open input stream");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
@ -72,7 +72,7 @@ public class ZipFileOpener extends FoxActivity {
}
} catch (
Exception e) {
Log.e("ZipFileOpener", "onCreate: Failed to copy zip file", e);
Timber.e(e, "onCreate: Failed to copy zip file");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
@ -81,16 +81,14 @@ public class ZipFileOpener extends FoxActivity {
}
// Ensure zip is not empty
if (zipFile.length() == 0) {
Log.e("ZipFileOpener", "onCreate: Zip file is empty");
Timber.e("onCreate: Zip file is empty");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
return;
} else {
if (BuildConfig.DEBUG) {
Log.d("ZipFileOpener", "onCreate: Zip file is " + zipFile.length() + " bytes");
}
Timber.d("onCreate: Zip file is " + zipFile.length() + " bytes");
}
ZipEntry entry;
ZipFile zip = null;
@ -100,12 +98,12 @@ public class ZipFileOpener extends FoxActivity {
try {
zip = new ZipFile(zipFile);
if ((entry = zip.getEntry("module.prop")) == null) {
Log.e("ZipFileOpener", "onCreate: Zip file is not a valid magisk module");
Timber.e("onCreate: Zip file is not a valid magisk module");
if (BuildConfig.DEBUG) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.d("ZipFileOpener", "onCreate: Zip file contents: " + zip.stream().map(ZipEntry::getName).reduce((a, b) -> a + ", " + b).orElse("empty"));
Timber.d("onCreate: Zip file contents: %s", zip.stream().map(ZipEntry::getName).reduce((a, b) -> a + ", " + b).orElse("empty"));
} else {
Log.d("ZipFileOpener", "onCreate: Zip file contents cannot be listed on this version of android");
Timber.d("onCreate: Zip file contents cannot be listed on this version of android");
}
}
runOnUiThread(() -> {
@ -116,7 +114,7 @@ public class ZipFileOpener extends FoxActivity {
}
} catch (
Exception e) {
Log.e("ZipFileOpener", "onCreate: Failed to open zip file", e);
Timber.e(e, "onCreate: Failed to open zip file");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
@ -125,14 +123,12 @@ public class ZipFileOpener extends FoxActivity {
try {
zip.close();
} catch (IOException exception) {
Log.e("ZipFileOpener", Log.getStackTraceString(exception));
Timber.e(Log.getStackTraceString(exception));
}
}
return;
}
if (BuildConfig.DEBUG) {
Log.d("ZipFileOpener", "onCreate: Zip file is valid");
}
Timber.d("onCreate: Zip file is valid");
String moduleInfo;
try {
moduleInfo = PropUtils.readModulePropSimple(zip.getInputStream(entry), "name");
@ -144,7 +140,7 @@ public class ZipFileOpener extends FoxActivity {
}
} catch (
Exception e) {
Log.e("ZipFileOpener", "onCreate: Failed to load module id", e);
Timber.e(e, "onCreate: Failed to load module id");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_prop_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
@ -152,14 +148,14 @@ public class ZipFileOpener extends FoxActivity {
try {
zip.close();
} catch (IOException exception) {
Log.e("ZipFileOpener", Log.getStackTraceString(exception));
Timber.e(Log.getStackTraceString(exception));
}
return;
}
try {
zip.close();
} catch (IOException exception) {
Log.e("ZipFileOpener", Log.getStackTraceString(exception));
Timber.e(Log.getStackTraceString(exception));
}
String finalModuleInfo = moduleInfo;
runOnUiThread(() -> {

@ -27,6 +27,8 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import timber.log.Timber;
public class Files {
private static final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
@ -73,7 +75,7 @@ public class Files {
result = new File(uri.getPath()).length();
}
} catch (Exception e) {
Log.e("Files", Log.getStackTraceString(e));
Timber.e(Log.getStackTraceString(e));
return result;
}
return result;

@ -26,35 +26,37 @@ package com.fox2code.mmm.utils.io;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
/** Open implementation of ProviderInstaller.installIfNeeded
import timber.log.Timber;
/**
* Open implementation of ProviderInstaller.installIfNeeded
* (Compatible with MicroG even without signature spoofing)
*/
// Note: This code is MIT because I took it from another unpublished project I had
// I might upstream this to MicroG at some point
public class GMSProviderInstaller {
private static final String TAG = "GMSProviderInstaller";
private static boolean called = false;
public static void installIfNeeded(final Context context) {
if (context == null) {
throw new NullPointerException("Context must not be null");
}
if (called) return;
if (called)
return;
called = true;
try {
// Trust default GMS implementation
Context remote = context.createPackageContext("com.google.android.gms",
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
Class<?> cl = remote.getClassLoader().loadClass(
"com.google.android.gms.common.security.ProviderInstallerImpl");
Context remote = context.createPackageContext("com.google.android.gms", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
Class<?> cl = remote.getClassLoader().loadClass("com.google.android.gms.common.security.ProviderInstallerImpl");
cl.getDeclaredMethod("insertProvider", Context.class).invoke(null, remote);
Log.i(TAG, "Installed GMS security providers!");
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "No GMS Implementation are installed on this device");
} catch (Exception e) {
Log.w(TAG, "Failed to install the provider of the current GMS Implementation", e);
Timber.i("Installed GMS security providers!");
} catch (
PackageManager.NameNotFoundException e) {
Timber.w("No GMS Implementation are installed on this device");
} catch (
Exception e) {
Timber.w(e);
}
}
}

@ -1,18 +1,16 @@
package com.fox2code.mmm.utils.io;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import java.util.regex.Pattern;
import timber.log.Timber;
public class Hashes {
private static final String TAG = "Hashes";
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
private static final Pattern nonAlphaNum = Pattern.compile("[^a-zA-Z0-9]");
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
@ -24,12 +22,13 @@ public class Hashes {
}
public static String hashMd5(byte[] input) {
Log.w(TAG, "hashMd5: This method is insecure, use hashSha256 instead");
Timber.w("hashMd5: This method is insecure, use hashSha256 instead");
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return bytesToHex(md.digest(input));
} catch (NoSuchAlgorithmException e) {
} catch (
NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@ -39,7 +38,8 @@ public class Hashes {
MessageDigest md = MessageDigest.getInstance("SHA-1");
return bytesToHex(md.digest(input));
} catch (NoSuchAlgorithmException e) {
} catch (
NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@ -49,7 +49,8 @@ public class Hashes {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return bytesToHex(md.digest(input));
} catch (NoSuchAlgorithmException e) {
} catch (
NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@ -59,7 +60,8 @@ public class Hashes {
MessageDigest md = MessageDigest.getInstance("SHA-512");
return bytesToHex(md.digest(input));
} catch (NoSuchAlgorithmException e) {
} catch (
NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@ -70,58 +72,35 @@ public class Hashes {
*/
public static boolean checkSumMatch(byte[] data, String checksum) {
String hash;
if (checksum == null) return false;
if (checksum == null)
return false;
switch (checksum.length()) {
case 0:
return true; // No checksum
case 32:
hash = Hashes.hashMd5(data); break;
hash = Hashes.hashMd5(data);
break;
case 40:
hash = Hashes.hashSha1(data); break;
hash = Hashes.hashSha1(data);
break;
case 64:
hash = Hashes.hashSha256(data); break;
hash = Hashes.hashSha256(data);
break;
case 128:
hash = Hashes.hashSha512(data); break;
hash = Hashes.hashSha512(data);
break;
default:
Log.e(TAG, "No hash algorithm for " +
checksum.length() * 8 + "bit checksums");
Timber.e("No hash algorithm for " + checksum.length() * 8 + "bit checksums");
return false;
}
Log.i(TAG, "Checksum result (data: " + hash+ ",expected: " + checksum + ")");
return hash.equals(checksum.toLowerCase(Locale.ROOT));
}
/**
* Check if the checksum match a file by picking the correct
* hashing algorithm depending on the length of the checksum
*/
public static boolean checkSumMatch(InputStream data, String checksum) throws IOException {
String hash;
if (checksum == null) return false;
String checksumAlgorithm = checkSumName(checksum);
if (checksumAlgorithm == null) {
Log.e(TAG, "No hash algorithm for " +
checksum.length() * 8 + "bit checksums");
return false;
}
try {
MessageDigest md = MessageDigest.getInstance(checksumAlgorithm);
byte[] bytes = new byte[2048];
int nRead;
while ((nRead = data.read(bytes)) > 0) {
md.update(bytes, 0, nRead);
}
hash = bytesToHex(md.digest());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
Log.i(TAG, "Checksum result (data: " + hash + ",expected: " + checksum + ")");
Timber.i("Checksum result (data: " + hash + ",expected: " + checksum + ")");
return hash.equals(checksum.toLowerCase(Locale.ROOT));
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean checkSumValid(String checksum) {
if (checksum == null) return false;
if (checksum == null)
return false;
switch (checksum.length()) {
case 0:
default:
@ -133,16 +112,19 @@ public class Hashes {
final int len = checksum.length();
for (int i = 0; i < len; i++) {
char c = checksum.charAt(i);
if (c < '0' || c > 'f') return false;
if (c < '0' || c > 'f')
return false;
if (c > '9' && // Easier working with bits
(c & 0b01011111) < 'A') return false;
(c & 0b01011111) < 'A')
return false;
}
return true;
}
}
public static String checkSumName(String checksum) {
if (checksum == null) return null;
if (checksum == null)
return null;
switch (checksum.length()) {
case 0:
default:
@ -159,7 +141,8 @@ public class Hashes {
}
public static String checkSumFormat(String checksum) {
if (checksum == null) return null;
if (checksum == null)
return null;
// Remove all non-alphanumeric characters
return nonAlphaNum.matcher(checksum.trim()).replaceAll("");
}

@ -7,7 +7,6 @@ import android.net.Uri;
import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.WebSettings;
@ -54,9 +53,9 @@ import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.dnsoverhttps.DnsOverHttps;
import okio.BufferedSink;
import timber.log.Timber;
public class Http {
private static final String TAG = "Http";
private static final OkHttpClient httpClient;
private static final OkHttpClient httpClientDoH;
private static final OkHttpClient httpClientWithCache;
@ -72,7 +71,7 @@ public class Http {
if (mainApplication == null) {
Error error = new Error("Initialized Http too soon!");
error.fillInStackTrace();
Log.e(TAG, "Initialized Http too soon!", error);
Timber.e(error, "Initialized Http too soon!");
System.out.flush();
System.err.flush();
try {
@ -91,7 +90,7 @@ public class Http {
} catch (
Throwable t) {
cookieManager = null;
Log.e(TAG, "No WebView support!", t);
Timber.e(t, "No WebView support!");
}
hasWebView = cookieManager != null;
OkHttpClient.Builder httpclientBuilder = new OkHttpClient.Builder();
@ -115,7 +114,7 @@ public class Http {
} catch (
UnknownHostException |
RuntimeException e) {
Log.e(TAG, "Failed to init DoH", e);
Timber.e(e, "Failed to init DoH");
}
// Add cookie support.
httpclientBuilder.addInterceptor(new AddCookiesInterceptor(MainApplication.getINSTANCE().getApplicationContext())); // VERY VERY IMPORTANT
@ -172,7 +171,7 @@ public class Http {
httpclientBuilder.addInterceptor(CronetInterceptor.newBuilder(engine).build());
} catch (
Exception e) {
Log.e(TAG, "Failed to init cronet", e);
Timber.e(e, "Failed to init cronet");
// Gracefully fallback to okhttp
}
// Fallback DNS cache responses in case request fail but already succeeded once in the past
@ -189,7 +188,7 @@ public class Http {
httpClientWithCache = followRedirects(httpclientBuilder, true).build();
httpclientBuilder.dns(fallbackDNS);
httpClientWithCacheDoH = followRedirects(httpclientBuilder, true).build();
Log.i(TAG, "Initialized Http successfully!");
Timber.i("Initialized Http successfully!");
doh = MainApplication.isDohEnabled();
}
@ -228,15 +227,15 @@ public class Http {
public static byte[] doHttpGet(String url, boolean allowCache) throws IOException {
if (BuildConfig.DEBUG_HTTP) {
// Log, but set all query parameters values to "****" while keeping the keys
Log.d(TAG, "doHttpGet: " + url.replaceAll("=[^&]*", "=****"));
Timber.d("doHttpGet: %s", url.replaceAll("=[^&]*", "=****"));
}
Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute();
if (BuildConfig.DEBUG_HTTP) {
Log.d(TAG, "doHttpGet: request executed");
Timber.d("doHttpGet: request executed");
}
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
Log.e(TAG, "Failed to fetch " + url.replaceAll("=[^&]*", "=****") + " with code " + response.code());
Timber.e("Failed to fetch " + url.replaceAll("=[^&]*", "=****") + " with code " + response.code());
checkNeedCaptchaAndroidacy(url, response.code());
// If it's a 401, and an androidacy link, it's probably an invalid token
if (response.code() == 401 && AndroidacyUtil.isAndroidacyLink(url)) {
@ -246,7 +245,7 @@ public class Http {
throw new HttpException(response.code());
}
if (BuildConfig.DEBUG_HTTP) {
Log.d(TAG, "doHttpGet: " + url.replaceAll("=[^&]*", "=****") + " succeeded");
Timber.d("doHttpGet: " + url.replaceAll("=[^&]*", "=****") + " succeeded");
}
ResponseBody responseBody = response.body();
// Use cache api if used cached response
@ -256,7 +255,7 @@ public class Http {
responseBody = response.body();
}
if (BuildConfig.DEBUG_HTTP) {
Log.d(TAG, "doHttpGet: returning " + responseBody.contentLength() + " bytes");
Timber.d("doHttpGet: returning " + responseBody.contentLength() + " bytes");
}
return responseBody.bytes();
}
@ -268,7 +267,7 @@ public class Http {
@SuppressWarnings("resource")
private static Object doHttpPostRaw(String url, String data, boolean allowCache) throws IOException {
if (BuildConfig.DEBUG)
Log.i(TAG, "POST " + url + " " + data);
Timber.i("POST " + url + " " + data);
Response response;
response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute();
if (response.isRedirect()) {
@ -277,7 +276,7 @@ public class Http {
// 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) {
if (BuildConfig.DEBUG)
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code() + ", body: " + response.body().string());
Timber.e("Failed to fetch " + url + ", code: " + response.code() + ", body: " + response.body().string());
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
@ -293,10 +292,10 @@ public class Http {
public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException {
if (BuildConfig.DEBUG)
Log.i("Http", "GET " + url.split("\\?")[0]);
Timber.i("GET %s", url.split("\\?")[0]);
Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute();
if (response.code() != 200 && response.code() != 204) {
Log.e(TAG, "Failed to fetch " + url + ", code: " + response.code());
Timber.e("Failed to fetch " + url + ", code: " + response.code());
checkNeedCaptchaAndroidacy(url, response.code());
throw new HttpException(response.code());
}
@ -313,7 +312,7 @@ public class Http {
final long UPDATE_INTERVAL = 100;
long nextUpdate = System.currentTimeMillis() + UPDATE_INTERVAL;
long currentUpdate;
Log.i("Http", "Target: " + target + " Divider: " + divider);
Timber.i("Target: " + target + " Divider: " + divider);
progressListener.onUpdate(0, (int) (target / divider), false);
while (true) {
int read = inputStream.read(buff);
@ -343,7 +342,7 @@ public class Http {
}
public static void setDoh(boolean doh) {
Log.i(TAG, "DoH: " + Http.doh + " -> " + doh);
Timber.i("DoH: " + Http.doh + " -> " + doh);
Http.doh = doh;
}
@ -357,44 +356,44 @@ public class Http {
File webviewCacheDir = new File(cacheDir, "WebView");
if (!webviewCacheDir.exists()) {
if (!webviewCacheDir.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
File webviewCacheDirCache = new File(webviewCacheDir, "Default");
if (!webviewCacheDirCache.exists()) {
if (!webviewCacheDirCache.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCache = new File(webviewCacheDirCache, "HTTP Cache");
if (!webviewCacheDirCacheCodeCache.exists()) {
if (!webviewCacheDirCacheCodeCache.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCacheIndex = new File(webviewCacheDirCacheCodeCache, "Code Cache");
if (!webviewCacheDirCacheCodeCacheIndex.exists()) {
if (!webviewCacheDirCacheCodeCacheIndex.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCacheIndexIndex = new File(webviewCacheDirCacheCodeCacheIndex, "Index");
if (!webviewCacheDirCacheCodeCacheIndexIndex.exists()) {
if (!webviewCacheDirCacheCodeCacheIndexIndex.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
// Create the js and wasm dirs
File webviewCacheDirCacheCodeCacheIndexIndexJs = new File(webviewCacheDirCacheCodeCache, "js");
if (!webviewCacheDirCacheCodeCacheIndexIndexJs.exists()) {
if (!webviewCacheDirCacheCodeCacheIndexIndexJs.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
File webviewCacheDirCacheCodeCacheIndexIndexWasm = new File(webviewCacheDirCacheCodeCache, "wasm");
if (!webviewCacheDirCacheCodeCacheIndexIndexWasm.exists()) {
if (!webviewCacheDirCacheCodeCacheIndexIndexWasm.mkdirs()) {
Log.e(TAG, "Failed to create webview cache dir");
Timber.e("Failed to create webview cache dir");
}
}
}

@ -5,7 +5,6 @@ import static com.fox2code.mmm.AppUpdateManager.getFlagsForModule;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.fox2code.mmm.AppUpdateManager;
import com.fox2code.mmm.manager.ModuleInfo;
@ -21,6 +20,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import timber.log.Timber;
public class PropUtils {
private static final HashMap<String, String> moduleSupportsFallbacks = new HashMap<>();
private static final HashMap<String, String> moduleConfigsFallbacks = new HashMap<>();
@ -342,7 +343,7 @@ public class PropUtils {
}
}
} catch (IOException e) {
Log.i("PropUtils", "Failed to get moduleId", e);
Timber.i(e);
}
return moduleId;
}

@ -14,6 +14,7 @@ buildscript {
flavorAware: true
]
project.ext.kotlin_version = "1.8.0"
project.ext.sentry_version = "6.12.1"
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

Loading…
Cancel
Save