You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1631 lines
104 KiB
Java
1631 lines
104 KiB
Java
/*
|
|
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
|
|
*/
|
|
|
|
package com.fox2code.mmm.settings;
|
|
|
|
import static com.fox2code.mmm.settings.SettingsActivity.RepoFragment.applyMaterial3;
|
|
import static java.lang.Integer.parseInt;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.ActivityManager;
|
|
import android.app.AlarmManager;
|
|
import android.app.PendingIntent;
|
|
import android.content.ClipData;
|
|
import android.content.ClipboardManager;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.provider.Settings;
|
|
import android.text.Editable;
|
|
import android.text.InputType;
|
|
import android.text.TextWatcher;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.inputmethod.EditorInfo;
|
|
import android.widget.Button;
|
|
import android.widget.EditText;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.ScrollView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.annotation.StringRes;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.core.content.FileProvider;
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.fragment.app.FragmentTransaction;
|
|
import androidx.preference.EditTextPreference;
|
|
import androidx.preference.ListPreference;
|
|
import androidx.preference.Preference;
|
|
import androidx.preference.PreferenceFragmentCompat;
|
|
import androidx.preference.PreferenceGroup;
|
|
import androidx.preference.PreferenceManager;
|
|
import androidx.preference.SwitchPreferenceCompat;
|
|
import androidx.preference.TwoStatePreference;
|
|
import androidx.security.crypto.EncryptedSharedPreferences;
|
|
import androidx.security.crypto.MasterKey;
|
|
|
|
import com.fox2code.foxcompat.app.FoxActivity;
|
|
import com.fox2code.foxcompat.view.FoxDisplay;
|
|
import com.fox2code.foxcompat.view.FoxViewCompat;
|
|
import com.fox2code.mmm.AppUpdateManager;
|
|
import com.fox2code.mmm.BuildConfig;
|
|
import com.fox2code.mmm.Constants;
|
|
import com.fox2code.mmm.MainActivity;
|
|
import com.fox2code.mmm.MainApplication;
|
|
import com.fox2code.mmm.R;
|
|
import com.fox2code.mmm.UpdateActivity;
|
|
import com.fox2code.mmm.androidacy.AndroidacyRepoData;
|
|
import com.fox2code.mmm.background.BackgroundUpdateChecker;
|
|
import com.fox2code.mmm.installer.InstallerInitializer;
|
|
import com.fox2code.mmm.manager.LocalModuleInfo;
|
|
import com.fox2code.mmm.manager.ModuleManager;
|
|
import com.fox2code.mmm.module.ActionButtonType;
|
|
import com.fox2code.mmm.repo.CustomRepoData;
|
|
import com.fox2code.mmm.repo.CustomRepoManager;
|
|
import com.fox2code.mmm.repo.RepoData;
|
|
import com.fox2code.mmm.repo.RepoManager;
|
|
import com.fox2code.mmm.utils.ExternalHelper;
|
|
import com.fox2code.mmm.utils.IntentHelper;
|
|
import com.fox2code.mmm.utils.ProcessHelper;
|
|
import com.fox2code.mmm.utils.io.net.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;
|
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
import com.google.android.material.navigation.NavigationBarView;
|
|
import com.google.android.material.snackbar.BaseTransientBottomBar;
|
|
import com.google.android.material.snackbar.Snackbar;
|
|
import com.google.android.material.textview.MaterialTextView;
|
|
import com.mikepenz.aboutlibraries.LibsBuilder;
|
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|
|
|
import org.apache.commons.io.FileUtils;
|
|
import org.matomo.sdk.extra.TrackHelper;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.io.RandomAccessFile;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Locale;
|
|
import java.util.Objects;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
|
|
import io.realm.Realm;
|
|
import io.realm.RealmConfiguration;
|
|
import io.realm.RealmResults;
|
|
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 boolean devModeStepFirstBootIgnore = MainApplication.Companion.isDeveloper();
|
|
private static int devModeStep = 0;
|
|
@SuppressLint("RestrictedApi")
|
|
private final NavigationBarView.OnItemSelectedListener onItemSelectedListener = item -> {
|
|
int itemId = item.getItemId();
|
|
if (itemId == R.id.back) {
|
|
TrackHelper.track().event("view_list", "main_modules").with(Objects.requireNonNull(MainApplication.getINSTANCE()).getTracker());
|
|
startActivity(new Intent(this, MainActivity.class));
|
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
|
finish();
|
|
return true;
|
|
} else //noinspection RedundantIfStatement
|
|
if (itemId == R.id.settings_menu_item) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
@PerformanceClass
|
|
public static int getDevicePerformanceClass() {
|
|
int devicePerformanceClass;
|
|
int androidVersion = Build.VERSION.SDK_INT;
|
|
int cpuCount = Runtime.getRuntime().availableProcessors();
|
|
int memoryClass = ((ActivityManager) Objects.requireNonNull(MainApplication.getINSTANCE()).getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
|
|
int totalCpuFreq = 0;
|
|
int freqResolved = 0;
|
|
for (int i = 0; i < cpuCount; i++) {
|
|
try (RandomAccessFile reader = new RandomAccessFile(String.format(Locale.ENGLISH, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i), "r")) {
|
|
String line = reader.readLine();
|
|
if (line != null) {
|
|
totalCpuFreq += parseInt(line) / 1000;
|
|
freqResolved++;
|
|
}
|
|
} catch (Exception ignore) {
|
|
}
|
|
}
|
|
int maxCpuFreq = freqResolved == 0 ? -1 : (int) Math.ceil(totalCpuFreq / (float) freqResolved);
|
|
|
|
if (androidVersion < 21 || cpuCount <= 2 || memoryClass <= 100 || cpuCount <= 4 && maxCpuFreq != -1 && maxCpuFreq <= 1250 || cpuCount <= 4 && maxCpuFreq <= 1600 && memoryClass <= 128 && androidVersion == 21 || cpuCount <= 4 && maxCpuFreq <= 1300 && memoryClass <= 128 && androidVersion <= 24) {
|
|
devicePerformanceClass = PERFORMANCE_CLASS_LOW;
|
|
} else if (cpuCount < 8 || memoryClass <= 160 || maxCpuFreq != -1 && maxCpuFreq <= 2050 || maxCpuFreq == -1 && cpuCount == 8 && androidVersion <= 23) {
|
|
devicePerformanceClass = PERFORMANCE_CLASS_AVERAGE;
|
|
} else {
|
|
devicePerformanceClass = PERFORMANCE_CLASS_HIGH;
|
|
}
|
|
|
|
Timber.d("getDevicePerformanceClass: androidVersion=" + androidVersion + " cpuCount=" + cpuCount + " memoryClass=" + memoryClass + " maxCpuFreq=" + maxCpuFreq + " devicePerformanceClass=" + devicePerformanceClass);
|
|
|
|
return devicePerformanceClass;
|
|
}
|
|
|
|
@SuppressLint("RestrictedApi")
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
devModeStep = 0;
|
|
super.onCreate(savedInstanceState);
|
|
TrackHelper.track().screen(this).with(Objects.requireNonNull(MainApplication.getINSTANCE()).getTracker());
|
|
setContentView(R.layout.settings_activity);
|
|
setTitle(R.string.app_name);
|
|
//hideActionBar();
|
|
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
|
|
bottomNavigationView.setOnItemSelectedListener(onItemSelectedListener);
|
|
if (savedInstanceState == null) {
|
|
SettingsFragment settingsFragment = new SettingsFragment();
|
|
getSupportFragmentManager().beginTransaction().replace(R.id.settings, settingsFragment).commit();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@SuppressLint("InlinedApi")
|
|
public void refreshRosettaX() {
|
|
ProcessHelper.restartApplicationProcess(this);
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
BackgroundUpdateChecker.onMainActivityResume(this);
|
|
super.onPause();
|
|
}
|
|
|
|
public @interface PerformanceClass {
|
|
}
|
|
|
|
public static class SettingsFragment extends PreferenceFragmentCompat implements FoxActivity.OnBackPressedCallback {
|
|
|
|
@SuppressLint("UnspecifiedImmutableFlag")
|
|
@Override
|
|
@SuppressWarnings("ConstantConditions")
|
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
|
String name = "mmmx";
|
|
Context context = MainApplication.getINSTANCE();
|
|
MasterKey masterKey;
|
|
PreferenceManager preferenceManager = getPreferenceManager();
|
|
SharedPreferenceDataStore dataStore;
|
|
SharedPreferences.Editor editor;
|
|
try {
|
|
masterKey = new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();
|
|
dataStore = new SharedPreferenceDataStore(EncryptedSharedPreferences.create(context, name, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM));
|
|
preferenceManager.setPreferenceDataStore(dataStore);
|
|
preferenceManager.setSharedPreferencesName("mmm");
|
|
editor = dataStore.getSharedPreferences().edit();
|
|
} catch (Exception e) {
|
|
Timber.e(e, "Failed to create encrypted shared preferences");
|
|
throw new RuntimeException(getString(R.string.error_encrypted_shared_preferences));
|
|
}
|
|
assert preferenceManager != null;
|
|
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
|
applyMaterial3(getPreferenceScreen());
|
|
// track all non empty values
|
|
SharedPreferences sharedPreferences = dataStore.getSharedPreferences();
|
|
// disabled until EncryptedSharedPreferences fixes getAll()
|
|
// add bottom navigation bar to the settings
|
|
BottomNavigationView bottomNavigationView = requireActivity().findViewById(R.id.bottom_navigation);
|
|
if (bottomNavigationView != null) {
|
|
bottomNavigationView.setVisibility(View.VISIBLE);
|
|
bottomNavigationView.getMenu().findItem(R.id.settings_menu_item).setChecked(true);
|
|
}
|
|
findPreference("pref_manage_repos").setOnPreferenceClickListener(p -> {
|
|
devModeStep = 0;
|
|
openFragment(new RepoFragment(), R.string.manage_repos_pref);
|
|
return true;
|
|
});
|
|
ListPreference themePreference = findPreference("pref_theme");
|
|
// If transparent theme(s) are set, disable monet
|
|
if (themePreference.getValue().equals("transparent_light")) {
|
|
Timber.d("disabling monet");
|
|
findPreference("pref_enable_monet").setEnabled(false);
|
|
// Toggle monet off
|
|
((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false);
|
|
editor.putBoolean("pref_enable_monet", false).apply();
|
|
// Set summary
|
|
findPreference("pref_enable_monet").setSummary(R.string.monet_disabled_summary);
|
|
// Same for blur
|
|
findPreference("pref_enable_blur").setEnabled(false);
|
|
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false);
|
|
editor.putBoolean("pref_enable_blur", false).apply();
|
|
findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary);
|
|
}
|
|
themePreference.setSummaryProvider(p -> themePreference.getEntry());
|
|
themePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
// You need to reboot your device at least once to be able to access dev-mode
|
|
if (devModeStepFirstBootIgnore || !MainApplication.isFirstBoot()) devModeStep = 1;
|
|
Timber.d("refreshing activity. New value: %s", newValue);
|
|
editor.putString("pref_theme", (String) newValue).apply();
|
|
// If theme contains "transparent" then disable monet
|
|
if (newValue.toString().contains("transparent")) {
|
|
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) -> {
|
|
// Toggle monet off
|
|
((TwoStatePreference) findPreference("pref_enable_monet")).setChecked(false);
|
|
editor.putBoolean("pref_enable_monet", false).apply();
|
|
// Set summary
|
|
findPreference("pref_enable_monet").setSummary(R.string.monet_disabled_summary);
|
|
// Same for blur
|
|
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false);
|
|
editor.putBoolean("pref_enable_blur", false).apply();
|
|
findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary);
|
|
// Refresh activity
|
|
devModeStep = 0;
|
|
UiThreadHandler.handler.postDelayed(() -> {
|
|
MainApplication.getINSTANCE().updateTheme();
|
|
FoxActivity.getFoxActivity(this).setThemeRecreate(MainApplication.getINSTANCE().getManagerThemeResId());
|
|
}, 1);
|
|
Intent intent = new Intent(requireContext(), SettingsActivity.class);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
startActivity(intent);
|
|
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
|
|
// Revert to system theme
|
|
((ListPreference) findPreference("pref_theme")).setValue("system");
|
|
// Refresh activity
|
|
devModeStep = 0;
|
|
}).show();
|
|
} else {
|
|
findPreference("pref_enable_monet").setEnabled(true);
|
|
findPreference("pref_enable_monet").setSummary(null);
|
|
findPreference("pref_enable_blur").setEnabled(true);
|
|
findPreference("pref_enable_blur").setSummary(null);
|
|
devModeStep = 0;
|
|
}
|
|
UiThreadHandler.handler.postDelayed(() -> {
|
|
MainApplication.getINSTANCE().updateTheme();
|
|
FoxActivity.getFoxActivity(this).setThemeRecreate(MainApplication.getINSTANCE().getManagerThemeResId());
|
|
}, 1);
|
|
return true;
|
|
});
|
|
// Crash reporting
|
|
TwoStatePreference crashReportingPreference = findPreference("pref_crash_reporting");
|
|
if (!SentryMain.IS_SENTRY_INSTALLED) crashReportingPreference.setVisible(false);
|
|
crashReportingPreference.setChecked(MainApplication.Companion.isCrashReportingEnabled());
|
|
final Object initialValue = MainApplication.Companion.isCrashReportingEnabled();
|
|
crashReportingPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
devModeStepFirstBootIgnore = true;
|
|
devModeStep = 0;
|
|
if (initialValue == newValue) return true;
|
|
// Show a dialog to restart the app
|
|
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(requireContext());
|
|
materialAlertDialogBuilder.setTitle(R.string.crash_reporting_restart_title);
|
|
materialAlertDialogBuilder.setMessage(R.string.crash_reporting_restart_message);
|
|
materialAlertDialogBuilder.setPositiveButton(R.string.restart, (dialog, which) -> {
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
// If < 23, FLAG_IMMUTABLE is not available
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
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
|
|
materialAlertDialogBuilder.setNegativeButton(R.string.no, (dialog, which) -> {
|
|
});
|
|
materialAlertDialogBuilder.show();
|
|
return true;
|
|
});
|
|
Preference enableBlur = findPreference("pref_enable_blur");
|
|
// Disable blur on low performance devices
|
|
if (getDevicePerformanceClass() < PERFORMANCE_CLASS_AVERAGE) {
|
|
// Show a warning
|
|
enableBlur.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
if (newValue.equals(true)) {
|
|
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.low_performance_device_dialogue_title).setMessage(R.string.low_performance_device_dialogue_message).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
// Toggle blur on
|
|
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(true);
|
|
editor.putBoolean("pref_enable_blur", true).apply();
|
|
// Set summary
|
|
findPreference("pref_enable_blur").setSummary(R.string.blur_disabled_summary);
|
|
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
|
|
// Revert to blur on
|
|
((TwoStatePreference) findPreference("pref_enable_blur")).setChecked(false);
|
|
editor.putBoolean("pref_enable_blur", false).apply();
|
|
// Set summary
|
|
findPreference("pref_enable_blur").setSummary(null);
|
|
}).show();
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
Preference disableMonet = findPreference("pref_enable_monet");
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
disableMonet.setSummary(R.string.require_android_12);
|
|
disableMonet.setEnabled(false);
|
|
}
|
|
disableMonet.setOnPreferenceClickListener(preference -> {
|
|
UiThreadHandler.handler.postDelayed(() -> {
|
|
MainApplication.getINSTANCE().updateTheme();
|
|
((FoxActivity) this.requireActivity()).setThemeRecreate(MainApplication.getINSTANCE().getManagerThemeResId());
|
|
}, 1);
|
|
return true;
|
|
});
|
|
|
|
findPreference("pref_dns_over_https").setOnPreferenceChangeListener((p, v) -> {
|
|
Http.setDoh((Boolean) v);
|
|
return true;
|
|
});
|
|
|
|
// handle restart required for showcase mode
|
|
findPreference("pref_showcase_mode").setOnPreferenceChangeListener((p, v) -> {
|
|
if (v.equals(true)) {
|
|
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.restart).setMessage(R.string.showcase_mode_dialogue_message).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
// Toggle showcase mode on
|
|
((TwoStatePreference) findPreference("pref_showcase_mode")).setChecked(true);
|
|
editor.putBoolean("pref_showcase_mode", true).apply();
|
|
// restart app
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
Timber.d("Restarting app to save showcase mode preference: %s", v);
|
|
System.exit(0); // Exit app process
|
|
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
|
|
// Revert to showcase mode on
|
|
((TwoStatePreference) findPreference("pref_showcase_mode")).setChecked(false);
|
|
editor.putBoolean("pref_showcase_mode", false).apply();
|
|
// restart app
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
Timber.d("Restarting app to save showcase mode preference: %s", v);
|
|
System.exit(0); // Exit app process
|
|
}).show();
|
|
}
|
|
return true;
|
|
});
|
|
|
|
Preference languageSelector = findPreference("pref_language_selector");
|
|
languageSelector.setOnPreferenceClickListener(preference -> {
|
|
LanguageSwitcher ls = new LanguageSwitcher(getActivity());
|
|
ls.setSupportedStringLocales(MainApplication.supportedLocales);
|
|
ls.showChangeLanguageDialog(getActivity());
|
|
return true;
|
|
});
|
|
|
|
// Handle pref_language_selector_cta by taking user to https://translate.nift4.org/engage/foxmmm/
|
|
LongClickablePreference languageSelectorCta = findPreference("pref_language_selector_cta");
|
|
languageSelectorCta.setOnPreferenceClickListener(preference -> {
|
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://translate.nift4.org/engage/foxmmm/"));
|
|
startActivity(browserIntent);
|
|
return true;
|
|
});
|
|
|
|
// Long click to copy url
|
|
languageSelectorCta.setOnPreferenceLongClickListener(v -> {
|
|
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
ClipData clip = ClipData.newPlainText("URL", "https://translate.nift4.org/engage/foxmmm/");
|
|
clipboard.setPrimaryClip(clip);
|
|
Toast.makeText(requireContext(), R.string.link_copied, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
|
|
int level = this.currentLanguageLevel();
|
|
if (level != 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);
|
|
// I don't "translate" english
|
|
if (!("Translated by Fox2Code (Put your name here)".equals(translatedBy) || "Translated by Fox2Code".equals(translatedBy))) {
|
|
languageSelector.setSummary(R.string.language_translated_by);
|
|
} else {
|
|
languageSelector.setSummary(null);
|
|
}
|
|
}
|
|
|
|
if (!MainApplication.isDeveloper()) {
|
|
findPreference("pref_disable_low_quality_module_filter").setVisible(false);
|
|
// Find pref_clear_data and set it invisible
|
|
Objects.requireNonNull((Preference) findPreference("pref_clear_data")).setVisible(false);
|
|
}
|
|
// hande clear cache
|
|
findPreference("pref_clear_cache").setOnPreferenceClickListener(preference -> {
|
|
// Clear cache
|
|
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.clear_cache_dialogue_title).setMessage(R.string.clear_cache_dialogue_message).setPositiveButton(R.string.yes, (dialog, which) -> {
|
|
// Clear app cache
|
|
try {
|
|
// use apache commons IO to delete the cache
|
|
FileUtils.deleteDirectory(requireContext().getCacheDir());
|
|
// create a new cache dir
|
|
FileUtils.forceMkdir(requireContext().getCacheDir());
|
|
// create cache dirs for cronet and webview
|
|
FileUtils.forceMkdir(new File(requireContext().getCacheDir(), "cronet"));
|
|
FileUtils.forceMkdir(new File(MainApplication.getINSTANCE().getDataDir() + "/cache/WebView/Default/HTTP Cache/Code Cache/wasm"));
|
|
FileUtils.forceMkdir(new File(MainApplication.getINSTANCE().getDataDir() + "/cache/WebView/Default/HTTP Cache/Code Cache/js"));
|
|
Toast.makeText(requireContext(), R.string.cache_cleared, Toast.LENGTH_SHORT).show();
|
|
} catch (Exception e) {
|
|
Timber.e(e);
|
|
Toast.makeText(requireContext(), R.string.cache_clear_failed, Toast.LENGTH_SHORT).show();
|
|
}
|
|
}).setNegativeButton(R.string.no, (dialog, which) -> {
|
|
// Do nothing
|
|
}).show();
|
|
return true;
|
|
});
|
|
if (!SentryMain.IS_SENTRY_INSTALLED || !BuildConfig.DEBUG || InstallerInitializer.peekMagiskPath() == null) {
|
|
// Hide the pref_crash option if not in debug mode - stop users from purposely crashing the app
|
|
Timber.i(InstallerInitializer.peekMagiskPath());
|
|
Objects.requireNonNull((Preference) findPreference("pref_test_crash")).setVisible(false);
|
|
} else {
|
|
if (findPreference("pref_test_crash") != null && findPreference("pref_clear_data") != null) {
|
|
findPreference("pref_test_crash").setOnPreferenceClickListener(preference -> {
|
|
// Hard crash the app
|
|
// we need a stacktrace to see if the crash is from the app or from the system
|
|
throw new RuntimeException("This is a test crash with a stupidly long description to show off the crash handler. Are we having fun yet?");
|
|
});
|
|
findPreference("pref_clear_data").setOnPreferenceClickListener(preference -> {
|
|
// Clear app data
|
|
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.clear_data_dialogue_title).setMessage(R.string.clear_data_dialogue_message).setPositiveButton(R.string.yes, (dialog, which) -> {
|
|
// Clear app data
|
|
MainApplication.getINSTANCE().resetApp();
|
|
}).setNegativeButton(R.string.no, (dialog, which) -> {
|
|
}).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
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()) {
|
|
findPreference("pref_use_magisk_install_command").setVisible(false);
|
|
}
|
|
Preference debugNotification = findPreference("pref_background_update_check_debug");
|
|
Preference updateCheckExcludes = findPreference("pref_background_update_check_excludes");
|
|
Preference updateCheckVersionExcludes = findPreference("pref_background_update_check_excludes_version");
|
|
debugNotification.setEnabled(MainApplication.Companion.isBackgroundUpdateCheckEnabled());
|
|
debugNotification.setVisible(MainApplication.isDeveloper() && !MainApplication.isWrapped && MainApplication.Companion.isBackgroundUpdateCheckEnabled());
|
|
debugNotification.setOnPreferenceClickListener(preference -> {
|
|
// fake updatable modules hashmap
|
|
HashMap<String, String> updateableModules = new HashMap<>();
|
|
// count of modules to fake must match the count in the random number generator
|
|
Random random = new Random();
|
|
int count;
|
|
do {
|
|
count = random.nextInt(4) + 2;
|
|
} while (count == 2);
|
|
for (int i = 0; i < count; i++) {
|
|
int fakeVersion;
|
|
do {
|
|
fakeVersion = random.nextInt(10);
|
|
} while (fakeVersion == 0);
|
|
Timber.d("Fake version: %s, count: %s", fakeVersion, i);
|
|
updateableModules.put("FakeModule " + i, "1.0." + fakeVersion);
|
|
}
|
|
BackgroundUpdateChecker.postNotification(this.requireContext(), updateableModules, count, true);
|
|
return true;
|
|
});
|
|
Preference backgroundUpdateCheck = findPreference("pref_background_update_check");
|
|
backgroundUpdateCheck.setVisible(!MainApplication.isWrapped);
|
|
// Make uncheckable if POST_NOTIFICATIONS permission is not granted
|
|
if (!MainApplication.isNotificationPermissionGranted()) {
|
|
// Instead of disabling the preference, we make it uncheckable and when the user
|
|
// clicks on it, we show a dialog explaining why the permission is needed
|
|
backgroundUpdateCheck.setOnPreferenceClickListener(preference -> {
|
|
// set the box to unchecked
|
|
((SwitchPreferenceCompat) backgroundUpdateCheck).setChecked(false);
|
|
// ensure that the preference is false
|
|
MainApplication.getSharedPreferences("mmm").edit().putBoolean("pref_background_update_check", false).apply();
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.permission_notification_title).setMessage(R.string.permission_notification_message).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
// Open the app settings
|
|
Intent intent = new Intent();
|
|
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
Uri uri = Uri.fromParts("package", this.requireContext().getPackageName(), null);
|
|
intent.setData(uri);
|
|
this.startActivity(intent);
|
|
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
|
|
}).show();
|
|
return true;
|
|
});
|
|
backgroundUpdateCheck.setSummary(R.string.background_update_check_permission_required);
|
|
}
|
|
updateCheckExcludes.setVisible(MainApplication.Companion.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped);
|
|
backgroundUpdateCheck.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
boolean enabled = Boolean.parseBoolean(String.valueOf(newValue));
|
|
debugNotification.setEnabled(enabled);
|
|
debugNotification.setVisible(MainApplication.isDeveloper() && !MainApplication.isWrapped && enabled);
|
|
updateCheckExcludes.setEnabled(enabled);
|
|
updateCheckExcludes.setVisible(enabled && !MainApplication.isWrapped);
|
|
if (!enabled) {
|
|
BackgroundUpdateChecker.onMainActivityResume(this.requireContext());
|
|
}
|
|
return true;
|
|
});
|
|
// updateCheckExcludes saves to pref_background_update_check_excludes as a stringset. On clicking, it should open a dialog with a list of all installed modules
|
|
updateCheckExcludes.setOnPreferenceClickListener(preference -> {
|
|
Collection<LocalModuleInfo> localModuleInfos = ModuleManager.getInstance().getModules().values();
|
|
// make sure we have modules
|
|
boolean[] checkedItems;
|
|
if (!localModuleInfos.isEmpty()) {
|
|
String[] moduleNames = new String[localModuleInfos.size()];
|
|
checkedItems = new boolean[localModuleInfos.size()];
|
|
int i = 0;
|
|
// get the stringset pref_background_update_check_excludes
|
|
Set<String> stringSetTemp = sharedPreferences.getStringSet("pref_background_update_check_excludes", new HashSet<>());
|
|
// copy to a new set so we can modify it
|
|
Set<String> stringSet = new HashSet<>(stringSetTemp);
|
|
for (LocalModuleInfo localModuleInfo : localModuleInfos) {
|
|
moduleNames[i] = localModuleInfo.name;
|
|
// Stringset uses id, we show name
|
|
checkedItems[i] = stringSet.contains(localModuleInfo.id);
|
|
Timber.d("name: %s, checked: %s", moduleNames[i], checkedItems[i]);
|
|
i++;
|
|
}
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.background_update_check_excludes).setMultiChoiceItems(moduleNames, checkedItems, (dialog, which, isChecked) -> {
|
|
// get id from name
|
|
String id;
|
|
if (localModuleInfos.stream().anyMatch(localModuleInfo -> localModuleInfo.name.equals(moduleNames[which]))) {
|
|
id = localModuleInfos.stream().filter(localModuleInfo -> localModuleInfo.name.equals(moduleNames[which])).findFirst().orElse(null).id;
|
|
} else {
|
|
id = "";
|
|
}
|
|
if (!id.isEmpty()) {
|
|
if (isChecked) {
|
|
stringSet.add(id);
|
|
} else {
|
|
stringSet.remove(id);
|
|
}
|
|
}
|
|
sharedPreferences.edit().putStringSet("pref_background_update_check_excludes", stringSet).apply();
|
|
}).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
}).show();
|
|
} else {
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.background_update_check_excludes).setMessage(R.string.background_update_check_excludes_no_modules).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
}).show();
|
|
}
|
|
return true;
|
|
});
|
|
// now handle pref_background_update_check_excludes_version
|
|
updateCheckVersionExcludes.setVisible(MainApplication.Companion.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped);
|
|
updateCheckVersionExcludes.setOnPreferenceClickListener(preference -> {
|
|
// get the stringset pref_background_update_check_excludes_version
|
|
Set<String> stringSet = sharedPreferences.getStringSet("pref_background_update_check_excludes_version", new HashSet<>());
|
|
Timber.d("stringSet: %s", stringSet);
|
|
// for every module, add it's name and a text field to the dialog. the text field should accept a comma separated list of versions
|
|
Collection<LocalModuleInfo> localModuleInfos = ModuleManager.getInstance().getModules().values();
|
|
// make sure we have modules
|
|
if (localModuleInfos.isEmpty()) {
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.background_update_check_excludes).setMessage(R.string.background_update_check_excludes_no_modules).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
}).show();
|
|
} else {
|
|
LinearLayout layout = new LinearLayout(this.requireContext());
|
|
layout.setOrientation(LinearLayout.VERTICAL);
|
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
params.setMargins(48, 0, 48, 0);
|
|
// add a summary
|
|
MaterialTextView textView = new MaterialTextView(this.requireContext());
|
|
textView.setLayoutParams(params);
|
|
textView.setText(R.string.background_update_check_excludes_version_summary);
|
|
for (LocalModuleInfo localModuleInfo : localModuleInfos) {
|
|
// two views: materialtextview for name, edittext for version
|
|
MaterialTextView materialTextView = new MaterialTextView(this.requireContext());
|
|
materialTextView.setLayoutParams(params);
|
|
materialTextView.setPadding(12, 8, 12, 8);
|
|
materialTextView.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1);
|
|
materialTextView.setText(localModuleInfo.name);
|
|
layout.addView(materialTextView);
|
|
EditText editText = new EditText(this.requireContext());
|
|
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
|
editText.setLayoutParams(params);
|
|
editText.setHint(R.string.background_update_check_excludes_version_hint);
|
|
// stringset uses id:version, we show version for name
|
|
// so we need to get id from name, then get version from stringset
|
|
String id = localModuleInfos.stream().filter(localModuleInfo1 -> localModuleInfo1.name.equals(localModuleInfo.name)).findFirst().orElse(null).id;
|
|
String version = stringSet.stream().filter(s -> s.startsWith(id)).findFirst().orElse("");
|
|
if (!version.isEmpty()) {
|
|
editText.setText(version.split(":")[1]);
|
|
}
|
|
layout.addView(editText);
|
|
}
|
|
ScrollView scrollView = new ScrollView(this.requireContext());
|
|
scrollView.addView(layout);
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.background_update_check_excludes_version).setView(scrollView).setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
Timber.d("ok clicked");
|
|
// for every module, get the text field and save it to the stringset
|
|
Set<String> stringSetTemp = new HashSet<>();
|
|
String prevMod = "";
|
|
for (int i = 0; i < layout.getChildCount(); i++) {
|
|
if (layout.getChildAt(i) instanceof MaterialTextView mv) {
|
|
prevMod = mv.getText().toString();
|
|
continue;
|
|
}
|
|
EditText editText = (EditText) layout.getChildAt(i);
|
|
String text = editText.getText().toString();
|
|
if (!text.isEmpty()) {
|
|
// text can only contain numbers and the characters ^ and $
|
|
// so we remove all non-numbers and non ^ and $
|
|
text = text.replaceAll("[^0-9^$]", "");
|
|
// we have to use module id even though we show name
|
|
String finalprevMod = prevMod;
|
|
stringSetTemp.add(localModuleInfos.stream().filter(localModuleInfo -> localModuleInfo.name.equals(finalprevMod)).findFirst().orElse(null).id + ":" + text);
|
|
Timber.d("text is %s for %s", text, editText.getHint().toString());
|
|
} else {
|
|
Timber.d("text is empty for %s", editText.getHint().toString());
|
|
}
|
|
}
|
|
sharedPreferences.edit().putStringSet("pref_background_update_check_excludes_version", stringSetTemp).apply();
|
|
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
|
|
}).show();
|
|
}
|
|
return true;
|
|
});
|
|
final LibsBuilder libsBuilder = new LibsBuilder().withShowLoadingProgress(false).withLicenseShown(true).withAboutMinimalDesign(false);
|
|
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
LongClickablePreference linkClickable = findPreference("pref_update");
|
|
linkClickable.setVisible(BuildConfig.ENABLE_AUTO_UPDATER && (BuildConfig.DEBUG || AppUpdateManager.Companion.getAppUpdateManager().peekHasUpdate()));
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
devModeStep = 0;
|
|
// open UpdateActivity with CHECK action
|
|
Intent intent = new Intent(requireContext(), UpdateActivity.class);
|
|
intent.setAction(UpdateActivity.ACTIONS.CHECK.name());
|
|
startActivity(intent);
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Androidacy/MagiskModuleManager/releases/latest"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
// for pref_background_update_check_debug_download, do the same as pref_update except with DOWNLOAD action
|
|
Preference debugDownload = findPreference("pref_background_update_check_debug_download");
|
|
debugDownload.setVisible(MainApplication.isDeveloper() && MainApplication.Companion.isBackgroundUpdateCheckEnabled() && !MainApplication.isWrapped);
|
|
debugDownload.setOnPreferenceClickListener(p -> {
|
|
devModeStep = 0;
|
|
Intent intent = new Intent(requireContext(), UpdateActivity.class);
|
|
intent.setAction(UpdateActivity.ACTIONS.DOWNLOAD.name());
|
|
startActivity(intent);
|
|
return true;
|
|
});
|
|
if (BuildConfig.DEBUG || BuildConfig.ENABLE_AUTO_UPDATER) {
|
|
linkClickable = findPreference("pref_report_bug");
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
devModeStep = 0;
|
|
devModeStepFirstBootIgnore = true;
|
|
IntentHelper.Companion.openUrl(p.getContext(), "https://github.com/Androidacy/MagiskModuleManager/issues");
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Androidacy/MagiskModuleManager/issues"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
findPreference("pref_report_bug").setVisible(false);
|
|
}
|
|
linkClickable = findPreference("pref_source_code");
|
|
// Set summary to the last commit this build was built from @ User/Repo
|
|
// Build userRepo by removing all parts of REMOTE_URL that are not the user/repo
|
|
String userRepo = BuildConfig.REMOTE_URL;
|
|
// remove .git
|
|
userRepo = userRepo.replaceAll("\\.git$", "");
|
|
Timber.d("userRepo: %s", userRepo);
|
|
|
|
// finalUserRepo is the user/repo part of REMOTE_URL
|
|
// get everything after .com/ or .org/ or .io/ or .me/ or .net/ or .xyz/ or .tk/ or .co/ minus .git
|
|
String finalUserRepo = userRepo.replaceAll("^(https?://)?(www\\.)?(github\\.com|gitlab\\.com|bitbucket\\.org|git\\.io|git\\.me|git\\.net|git\\.xyz|git\\.tk|git\\.co)/", "");
|
|
linkClickable.setSummary(String.format(getString(R.string.source_code_summary), BuildConfig.COMMIT_HASH, finalUserRepo));
|
|
Timber.d("finalUserRepo: %s", finalUserRepo);
|
|
String finalUserRepo1 = userRepo;
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
if (devModeStep == 2) {
|
|
devModeStep = 0;
|
|
if (MainApplication.isDeveloper() && !BuildConfig.DEBUG) {
|
|
MainApplication.getSharedPreferences("mmm").edit().putBoolean("developer", false).apply();
|
|
Toast.makeText(getContext(), // Tell the user something changed
|
|
R.string.dev_mode_disabled, Toast.LENGTH_SHORT).show();
|
|
} else {
|
|
MainApplication.getSharedPreferences("mmm").edit().putBoolean("developer", true).apply();
|
|
Toast.makeText(getContext(), // Tell the user something changed
|
|
R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show();
|
|
}
|
|
ExternalHelper.INSTANCE.refreshHelper(getContext());
|
|
return true;
|
|
}
|
|
// build url from BuildConfig.REMOTE_URL and BuildConfig.COMMIT_HASH. May have to remove the .git at the end
|
|
IntentHelper.Companion.openUrl(p.getContext(), finalUserRepo1.replace(".git", "") + "/tree/" + BuildConfig.COMMIT_HASH);
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, BuildConfig.REMOTE_URL + "/tree/" + BuildConfig.COMMIT_HASH));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
// Next, the pref_androidacy_thanks should lead to the androidacy website
|
|
linkClickable = findPreference("pref_androidacy_thanks");
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
IntentHelper.Companion.openUrl(p.getContext(), "https://www.androidacy.com?utm_source=FoxMagiskModuleManager&utm_medium=app&utm_campaign=FoxMagiskModuleManager");
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://www.androidacy.com?utm_source=FoxMagiskModuleManager&utm_medium=app&utm_campaign=FoxMagiskModuleManager"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
// pref_fox2code_thanks should lead to https://github.com/Fox2Code
|
|
linkClickable = findPreference("pref_fox2code_thanks");
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
IntentHelper.Companion.openUrl(p.getContext(), "https://github.com/Fox2Code");
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Fox2Code"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
// handle pref_save_logs which saves logs to our external storage and shares them
|
|
Preference saveLogs = findPreference("pref_save_logs");
|
|
saveLogs.setOnPreferenceClickListener(p -> {
|
|
// Save logs to external storage
|
|
File logsFile = new File(requireContext().getExternalFilesDir(null), "logs.txt");
|
|
FileOutputStream fileOutputStream = null;
|
|
try {
|
|
//noinspection ResultOfMethodCallIgnored
|
|
logsFile.createNewFile();
|
|
fileOutputStream = new FileOutputStream(logsFile);
|
|
// first, write some info about the device
|
|
fileOutputStream.write(("FoxMagiskModuleManager version: " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")\n").getBytes());
|
|
fileOutputStream.write(("Android version: " + Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")\n").getBytes());
|
|
fileOutputStream.write(("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")\n").getBytes());
|
|
fileOutputStream.write(("Magisk version: " + InstallerInitializer.peekMagiskVersion() + "\n").getBytes());
|
|
fileOutputStream.write(("Has internet: " + (RepoManager.getINSTANCE().hasConnectivity() ? "Yes" : "No") + "\n").getBytes());
|
|
// read our logcat but format the output to be more readable
|
|
Process process = Runtime.getRuntime().exec("logcat -d -v tag");
|
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
|
String line;
|
|
while ((line = bufferedReader.readLine()) != null) {
|
|
fileOutputStream.write((line + "\n").getBytes());
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
Toast.makeText(requireContext(), R.string.error_saving_logs, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
} finally {
|
|
if (fileOutputStream != null) {
|
|
try {
|
|
fileOutputStream.close();
|
|
} catch (IOException ignored) {
|
|
}
|
|
}
|
|
}
|
|
// Share logs
|
|
Intent shareIntent = new Intent();
|
|
// create a new intent and grantUriPermission to the file provider
|
|
shareIntent.setAction(Intent.ACTION_SEND);
|
|
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
shareIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(requireContext(), BuildConfig.APPLICATION_ID + ".file-provider", logsFile));
|
|
shareIntent.setType("text/plain");
|
|
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_logs)));
|
|
return true;
|
|
});
|
|
// pref_contributors should lead to the contributors page
|
|
linkClickable = findPreference("pref_contributors");
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
// Remove the .git if it exists and add /graphs/contributors
|
|
String url = BuildConfig.REMOTE_URL;
|
|
if (url.endsWith(".git")) {
|
|
url = url.substring(0, url.length() - 4);
|
|
}
|
|
url += "/graphs/contributors";
|
|
IntentHelper.Companion.openUrl(p.getContext(), url);
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
// Remove the .git if it exists and add /graphs/contributors
|
|
String url = BuildConfig.REMOTE_URL;
|
|
if (url.endsWith(".git")) {
|
|
url = url.substring(0, url.length() - 4);
|
|
}
|
|
url += "/graphs/contributors";
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, url));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
linkClickable = findPreference("pref_support");
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
devModeStep = 0;
|
|
IntentHelper.Companion.openUrl(p.getContext(), "https://t.me/Fox2Code_Chat");
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://t.me/Fox2Code_Chat"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
// pref_announcements to https://t.me/androidacy
|
|
linkClickable = findPreference("pref_announcements");
|
|
linkClickable.setOnPreferenceClickListener(p -> {
|
|
devModeStep = 0;
|
|
IntentHelper.Companion.openUrl(p.getContext(), "https://t.me/androidacy");
|
|
return true;
|
|
});
|
|
linkClickable.setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://t.me/androidacy"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
findPreference("pref_show_licenses").setOnPreferenceClickListener(p -> {
|
|
devModeStep = devModeStep == 1 ? 2 : 0;
|
|
BackgroundUpdateChecker.onMainActivityResume(this.requireContext());
|
|
openFragment(libsBuilder.supportFragment(), R.string.licenses);
|
|
return true;
|
|
});
|
|
// Determine if this is an official build based on the signature
|
|
String flavor = BuildConfig.FLAVOR;
|
|
String type = BuildConfig.BUILD_TYPE;
|
|
// Set the summary of pref_pkg_info to something like default-debug v1.0 (123) (Official)
|
|
String pkgInfo = getString(R.string.pref_pkg_info_summary, flavor + "-" + type, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, MainApplication.o ? getString(R.string.official) : getString(R.string.unofficial));
|
|
findPreference("pref_pkg_info").setSummary(pkgInfo);
|
|
// special easter egg :)
|
|
var ref = new Object() {
|
|
int versionClicks = 0;
|
|
};
|
|
findPreference("pref_pkg_info").setOnPreferenceClickListener(p -> {
|
|
ref.versionClicks++;
|
|
Timber.d("Version clicks: %d", ref.versionClicks);
|
|
// if it's been 3 clicks, toast "yer a wizard, harry" or "keep tapping to enter hogwarts"
|
|
if (ref.versionClicks == 3) {
|
|
// random choice of 1 or 2
|
|
Random rand = new Random();
|
|
int n = rand.nextInt(2) + 1;
|
|
Toast.makeText(p.getContext(), n == 1 ? R.string.yer_a_wizard_harry : R.string.keep_tapping_to_enter_hogwarts, Toast.LENGTH_SHORT).show();
|
|
}
|
|
if (ref.versionClicks == 7) {
|
|
ref.versionClicks = 0;
|
|
IntentHelper.Companion.openUrl(p.getContext(), "https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
|
}
|
|
return true;
|
|
});
|
|
|
|
LongClickablePreference pref_donate_fox = findPreference("pref_donate_fox");
|
|
if (!BuildConfig.FLAVOR.equals("play")) {
|
|
pref_donate_fox.setOnPreferenceClickListener(p -> {
|
|
// open fox
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), "https://paypal.me/fox2code");
|
|
return true;
|
|
});
|
|
// handle long click on pref_donate_fox
|
|
pref_donate_fox.setOnPreferenceLongClickListener(p -> {
|
|
// copy to clipboard
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://paypal.me/fox2code"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
pref_donate_fox.setVisible(false);
|
|
}
|
|
// now handle pref_donate_androidacy
|
|
LongClickablePreference pref_donate_androidacy = findPreference("pref_donate_androidacy");
|
|
if (!BuildConfig.FLAVOR.equals("play")) {
|
|
if (AndroidacyRepoData.getInstance().isEnabled() && Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest") || AndroidacyRepoData.getInstance().memberLevel == null) {
|
|
pref_donate_androidacy.setOnPreferenceClickListener(p -> {
|
|
// copy FOX2CODE promo code to clipboard and toast user that they can use it for half off any subscription
|
|
String toastText = requireContext().getString(R.string.promo_code_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "FOX2CODE"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
// open androidacy
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate");
|
|
return true;
|
|
});
|
|
// handle long click on pref_donate_androidacy
|
|
pref_donate_androidacy.setOnPreferenceLongClickListener(p -> {
|
|
// copy to clipboard
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm_medium=app&utm_campaign=donate"));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
// set text to "Thank you for your support!"
|
|
pref_donate_androidacy.setSummary(R.string.androidacy_thanks_up);
|
|
pref_donate_androidacy.setTitle(R.string.androidacy_thanks_up_title);
|
|
}
|
|
} else {
|
|
pref_donate_androidacy.setVisible(false);
|
|
}
|
|
}
|
|
|
|
private void openFragment(Fragment fragment, @StringRes int title) {
|
|
FoxActivity compatActivity = getFoxActivity(this);
|
|
compatActivity.setOnBackPressedCallback(this);
|
|
compatActivity.setTitle(title);
|
|
compatActivity.getSupportFragmentManager().beginTransaction().replace(R.id.settings, fragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).commit();
|
|
}
|
|
|
|
@Override
|
|
public boolean onBackPressed(FoxActivity compatActivity) {
|
|
compatActivity.setTitle(R.string.app_name);
|
|
compatActivity.getSupportFragmentManager().beginTransaction().replace(R.id.settings, this).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).commit();
|
|
return true;
|
|
}
|
|
|
|
private int currentLanguageLevel() {
|
|
int declaredLanguageLevel = this.getResources().getInteger(R.integer.language_support_level);
|
|
if (declaredLanguageLevel != LANGUAGE_SUPPORT_LEVEL) return declaredLanguageLevel;
|
|
if (!this.getResources().getConfiguration().getLocales().get(0).getLanguage().equals("en") && this.getResources().getString(R.string.notification_update_pref).equals("Background modules update check") && this.getResources().getString(R.string.notification_update_desc).equals("May increase battery usage")) {
|
|
return 0;
|
|
}
|
|
return LANGUAGE_SUPPORT_LEVEL;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("ConstantConditions")
|
|
public static class RepoFragment extends PreferenceFragmentCompat {
|
|
|
|
/**
|
|
* <i>says proudly</i>: I stole it
|
|
* <p>
|
|
* namely, from <a href="https://github.com/NeoApplications/Neo-Wellbeing/blob/9fca4136263780c022f9ec6433c0b43d159166db/app/src/main/java/org/eu/droid_ng/wellbeing/prefs/SettingsActivity.java#L101">neo wellbeing</a>
|
|
*/
|
|
public static void applyMaterial3(Preference p) {
|
|
if (p instanceof PreferenceGroup pg) {
|
|
for (int i = 0; i < pg.getPreferenceCount(); i++) {
|
|
applyMaterial3(pg.getPreference(i));
|
|
}
|
|
}
|
|
if (p instanceof SwitchPreferenceCompat) {
|
|
p.setWidgetLayoutResource(R.layout.preference_material_switch);
|
|
}
|
|
}
|
|
|
|
@SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"})
|
|
public void onCreatePreferencesAndroidacy() {
|
|
// Bind the pref_show_captcha_webview to captchaWebview('https://production-api.androidacy.com/')
|
|
// Also require dev mode
|
|
// CaptchaWebview.setVisible(false);
|
|
Preference androidacyTestMode = Objects.requireNonNull(findPreference("pref_androidacy_test_mode"));
|
|
if (!MainApplication.isDeveloper()) {
|
|
androidacyTestMode.setVisible(false);
|
|
} else {
|
|
// Show a warning if user tries to enable test mode
|
|
androidacyTestMode.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
if (Boolean.parseBoolean(String.valueOf(newValue))) {
|
|
// Use MaterialAlertDialogBuilder
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.warning).setCancelable(false).setMessage(R.string.androidacy_test_mode_warning).setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
// User clicked OK button
|
|
MainApplication.getSharedPreferences("mmm").edit().putBoolean("androidacy_test_mode", true).apply();
|
|
// Check the switch
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
// If < 23, FLAG_IMMUTABLE is not available
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
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
|
|
// Uncheck the switch
|
|
SwitchPreferenceCompat switchPreferenceCompat = (SwitchPreferenceCompat) androidacyTestMode;
|
|
switchPreferenceCompat.setChecked(false);
|
|
// There's probably a better way to do this than duplicate code but I'm too lazy to figure it out
|
|
MainApplication.getSharedPreferences("mmm").edit().putBoolean("androidacy_test_mode", false).apply();
|
|
}).show();
|
|
} else {
|
|
MainApplication.getSharedPreferences("mmm").edit().putBoolean("androidacy_test_mode", false).apply();
|
|
// Show dialog to restart app with ok button
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.warning).setCancelable(false).setMessage(R.string.androidacy_test_mode_disable_warning).setNeutralButton(android.R.string.ok, (dialog, which) -> {
|
|
// User clicked OK button
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
// If < 23, FLAG_IMMUTABLE is not available
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
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").encryptionKey(MainApplication.getINSTANCE().getKey()).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)));
|
|
} else {
|
|
Timber.e("Alt Repo not found in realm db");
|
|
}
|
|
});
|
|
return true;
|
|
});
|
|
// Disable toggling the pref_androidacy_repo_enabled on builds without an
|
|
// ANDROIDACY_CLIENT_ID or where the ANDROIDACY_CLIENT_ID is empty
|
|
SwitchPreferenceCompat androidacyRepoEnabled = Objects.requireNonNull(findPreference("pref_androidacy_repo_enabled"));
|
|
if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) {
|
|
androidacyRepoEnabled.setOnPreferenceClickListener(preference -> {
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.androidacy_repo_disabled).setCancelable(false).setMessage(R.string.androidacy_repo_disabled_message).setPositiveButton(R.string.download_full_app, (dialog, which) -> {
|
|
// User clicked OK button. Open GitHub releases page
|
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.androidacy.com/downloads/?view=FoxMMM&utm_source=FoxMMM&utm_medium=app&utm_campaign=FoxMMM"));
|
|
startActivity(browserIntent);
|
|
}).show();
|
|
// Revert the switch to off
|
|
androidacyRepoEnabled.setChecked(false);
|
|
// Disable in realm db
|
|
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).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;
|
|
});
|
|
} else {
|
|
// get if androidacy repo is enabled from realm db
|
|
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
|
|
Realm realm = Realm.getInstance(realmConfiguration);
|
|
ReposList repoRealmResults = realm.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst();
|
|
if (repoRealmResults == null) {
|
|
throw new IllegalStateException("Androidacy repo not found in realm db");
|
|
}
|
|
boolean androidacyRepoEnabledPref = repoRealmResults.isEnabled();
|
|
// set the switch to the current state
|
|
androidacyRepoEnabled.setChecked(androidacyRepoEnabledPref);
|
|
// add a click listener to the switch
|
|
androidacyRepoEnabled.setOnPreferenceClickListener(preference -> {
|
|
boolean enabled = androidacyRepoEnabled.isChecked();
|
|
// save the new state
|
|
realm.executeTransaction(realm2 -> {
|
|
ReposList repoRealmResults1 = realm2.where(ReposList.class).equalTo("id", "androidacy_repo").findFirst();
|
|
repoRealmResults1.setEnabled(enabled);
|
|
});
|
|
return true;
|
|
});
|
|
if (androidacyRepoEnabledPref) {
|
|
// get user role from AndroidacyRepoData.userInfo
|
|
String[][] userInfo = AndroidacyRepoData.getInstance().userInfo;
|
|
if (userInfo != null) {
|
|
String userRole = userInfo[0][1];
|
|
if (Objects.nonNull(userRole) && !Objects.equals(userRole, "Guest")) {
|
|
// Disable the pref_androidacy_repo_api_donate preference
|
|
LongClickablePreference prefAndroidacyRepoApiD = Objects.requireNonNull(findPreference("pref_androidacy_repo_donate"));
|
|
prefAndroidacyRepoApiD.setEnabled(false);
|
|
prefAndroidacyRepoApiD.setSummary(R.string.upgraded_summary);
|
|
prefAndroidacyRepoApiD.setTitle(R.string.upgraded);
|
|
prefAndroidacyRepoApiD.setIcon(R.drawable.baseline_check_24);
|
|
} else if (BuildConfig.FLAVOR.equals("play")) {
|
|
// Disable the pref_androidacy_repo_api_token preference and hide the donate button
|
|
LongClickablePreference prefAndroidacyRepoApiD = Objects.requireNonNull(findPreference("pref_androidacy_repo_donate"));
|
|
prefAndroidacyRepoApiD.setEnabled(false);
|
|
prefAndroidacyRepoApiD.setVisible(false);
|
|
}
|
|
}
|
|
String[] originalApiKeyRef = new String[]{MainApplication.getSharedPreferences("androidacy").getString("pref_androidacy_api_token", "")};
|
|
// Get the dummy pref_androidacy_repo_api_token preference with id pref_androidacy_repo_api_token
|
|
// we have to use the id because the key is different
|
|
EditTextPreference prefAndroidacyRepoApiKey = Objects.requireNonNull(findPreference("pref_androidacy_repo_api_token"));
|
|
// add validation to the EditTextPreference
|
|
// string must be 64 characters long, and only allows alphanumeric characters
|
|
prefAndroidacyRepoApiKey.setTitle(R.string.api_key);
|
|
prefAndroidacyRepoApiKey.setSummary(R.string.api_key_summary);
|
|
prefAndroidacyRepoApiKey.setDialogTitle(R.string.api_key);
|
|
prefAndroidacyRepoApiKey.setDefaultValue(originalApiKeyRef[0]);
|
|
// Set the value to the current value
|
|
prefAndroidacyRepoApiKey.setText(originalApiKeyRef[0]);
|
|
prefAndroidacyRepoApiKey.setVisible(true);
|
|
prefAndroidacyRepoApiKey.setOnBindEditTextListener(editText -> {
|
|
editText.setSingleLine();
|
|
// Make the single line wrap
|
|
editText.setHorizontallyScrolling(false);
|
|
// Set the height to the maximum required to fit the text
|
|
editText.setMaxLines(Integer.MAX_VALUE);
|
|
// Make ok button say "Save"
|
|
editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
|
});
|
|
prefAndroidacyRepoApiKey.setPositiveButtonText(R.string.save_api_key);
|
|
prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
// validate the api key client side first. should be 64 characters long, and only allow alphanumeric characters
|
|
if (!newValue.toString().matches("[a-zA-Z0-9]{64}")) {
|
|
// Show snack bar with error
|
|
Snackbar.make(requireView(), R.string.api_key_mismatch, BaseTransientBottomBar.LENGTH_LONG).show();
|
|
// Restore the original api key
|
|
prefAndroidacyRepoApiKey.setText(originalApiKeyRef[0]);
|
|
prefAndroidacyRepoApiKey.performClick();
|
|
return false;
|
|
}
|
|
// Make sure originalApiKeyRef is not null
|
|
if (originalApiKeyRef[0].equals(newValue)) return true;
|
|
// get original api key
|
|
String apiKey = String.valueOf(newValue);
|
|
// Show snack bar with indeterminate progress
|
|
Snackbar.make(requireView(), R.string.checking_api_key, BaseTransientBottomBar.LENGTH_INDEFINITE).setAction(R.string.cancel, v -> {
|
|
// Restore the original api key
|
|
prefAndroidacyRepoApiKey.setText(originalApiKeyRef[0]);
|
|
}).show();
|
|
// Check the API key on a background thread
|
|
new Thread(() -> {
|
|
// If key is empty, just remove it and change the text of the snack bar
|
|
if (apiKey.isEmpty()) {
|
|
MainApplication.getSharedPreferences("androidacy").edit().remove("pref_androidacy_api_token").apply();
|
|
new Handler(Looper.getMainLooper()).post(() -> {
|
|
Snackbar.make(requireView(), R.string.api_key_removed, BaseTransientBottomBar.LENGTH_SHORT).show();
|
|
// Show dialog to restart app with ok button
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.restart).setCancelable(false).setMessage(R.string.api_key_restart).setNeutralButton(android.R.string.ok, (dialog, which) -> {
|
|
// User clicked OK button
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
// If < 23, FLAG_IMMUTABLE is not available
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
Timber.d("Restarting app to save token preference: %s", newValue);
|
|
System.exit(0); // Exit app process
|
|
}).show();
|
|
});
|
|
} else {
|
|
// If key < 64 chars, it's not valid
|
|
if (apiKey.length() < 64) {
|
|
new Handler(Looper.getMainLooper()).post(() -> {
|
|
Snackbar.make(requireView(), R.string.api_key_invalid, BaseTransientBottomBar.LENGTH_SHORT).show();
|
|
// Save the original key
|
|
MainApplication.getSharedPreferences("androidacy").edit().putString("pref_androidacy_api_token", originalApiKeyRef[0]).apply();
|
|
// Re-show the dialog with an error
|
|
prefAndroidacyRepoApiKey.performClick();
|
|
// Show error
|
|
prefAndroidacyRepoApiKey.setDialogMessage(getString(R.string.api_key_invalid));
|
|
});
|
|
} else {
|
|
// If the key is the same as the original, just show a snack bar
|
|
if (apiKey.equals(originalApiKeyRef[0])) {
|
|
new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), R.string.api_key_unchanged, BaseTransientBottomBar.LENGTH_SHORT).show());
|
|
return;
|
|
}
|
|
boolean valid = false;
|
|
try {
|
|
valid = AndroidacyRepoData.getInstance().isValidToken(apiKey);
|
|
} catch (IOException ignored) {
|
|
}
|
|
// If the key is valid, save it
|
|
if (valid) {
|
|
originalApiKeyRef[0] = apiKey;
|
|
RepoManager.getINSTANCE().getAndroidacyRepoData().setToken(apiKey);
|
|
MainApplication.getSharedPreferences("androidacy").edit().putString("pref_androidacy_api_token", apiKey).apply();
|
|
// Snackbar with success and restart button
|
|
new Handler(Looper.getMainLooper()).post(() -> {
|
|
Snackbar.make(requireView(), R.string.api_key_valid, BaseTransientBottomBar.LENGTH_SHORT).show();
|
|
// Show dialog to restart app with ok button
|
|
new MaterialAlertDialogBuilder(this.requireContext()).setTitle(R.string.restart).setCancelable(false).setMessage(R.string.api_key_restart).setNeutralButton(android.R.string.ok, (dialog, which) -> {
|
|
// User clicked OK button
|
|
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
|
|
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
int mPendingIntentId = 123456;
|
|
// If < 23, FLAG_IMMUTABLE is not available
|
|
PendingIntent mPendingIntent;
|
|
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);
|
|
Timber.d("Restarting app to save token preference: %s", newValue);
|
|
System.exit(0); // Exit app process
|
|
}).show();
|
|
});
|
|
} else {
|
|
new Handler(Looper.getMainLooper()).post(() -> {
|
|
Snackbar.make(requireView(), R.string.api_key_invalid, BaseTransientBottomBar.LENGTH_SHORT).show();
|
|
// Save the original key
|
|
MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).edit().putString("pref_androidacy_api_token", originalApiKeyRef[0]).apply();
|
|
// Re-show the dialog with an error
|
|
prefAndroidacyRepoApiKey.performClick();
|
|
// Show error
|
|
prefAndroidacyRepoApiKey.setDialogMessage(getString(R.string.api_key_invalid));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}).start();
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("RestrictedApi")
|
|
public void updateCustomRepoList(boolean initial) {
|
|
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
|
|
Realm realm = Realm.getInstance(realmConfiguration);
|
|
// get all repos that are not built-in
|
|
int CUSTOM_REPO_ENTRIES = 0;
|
|
// array of custom repos
|
|
ArrayList<String> customRepos = new ArrayList<>();
|
|
RealmResults<ReposList> customRepoDataDB = realm.where(ReposList.class).findAll();
|
|
for (ReposList repo : customRepoDataDB) {
|
|
if (!repo.getId().equals("androidacy_repo") && !repo.getId().equals("magisk_alt_repo")) {
|
|
CUSTOM_REPO_ENTRIES++;
|
|
customRepos.add(repo.getUrl());
|
|
}
|
|
}
|
|
Timber.d("%d repos: %s", CUSTOM_REPO_ENTRIES, customRepos);
|
|
final CustomRepoManager customRepoManager = RepoManager.getINSTANCE().getCustomRepoManager();
|
|
for (int i = 0; i < CUSTOM_REPO_ENTRIES; i++) {
|
|
// get the id of the repo at current index in customRepos
|
|
CustomRepoData repoData = customRepoManager.getRepo(customRepos.get(i));
|
|
assert repoData != null;
|
|
// convert repoData to a json string for logging
|
|
Timber.d("RepoData for %d is %s", i, repoData.toJSON());
|
|
setRepoData(repoData, "pref_custom_repo_" + i);
|
|
if (initial) {
|
|
Preference preference = findPreference("pref_custom_repo_" + i + "_delete");
|
|
if (preference == null) continue;
|
|
final int index = i;
|
|
preference.setOnPreferenceClickListener(preference1 -> {
|
|
if (realm.isInTransaction()) {
|
|
realm.commitTransaction();
|
|
}
|
|
realm.beginTransaction();
|
|
Objects.requireNonNull(realm.where(ReposList.class).equalTo("id", repoData.preferenceId).findFirst()).deleteFromRealm();
|
|
realm.commitTransaction();
|
|
customRepoManager.removeRepo(index);
|
|
updateCustomRepoList(false);
|
|
preference1.setVisible(false);
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
// any custom repo prefs larger than the number of custom repos need to be hidden. max is 5
|
|
// loop up until 5, and for each that's greater than the number of custom repos, hide it. we start at 0
|
|
// if custom repos is zero, just hide them all
|
|
if (CUSTOM_REPO_ENTRIES == 0) {
|
|
for (int i = 0; i < 5; i++) {
|
|
Preference preference = findPreference("pref_custom_repo_" + i);
|
|
if (preference == null) continue;
|
|
preference.setVisible(false);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < 5; i++) {
|
|
Preference preference = findPreference("pref_custom_repo_" + i);
|
|
if (preference == null) continue;
|
|
if (i >= CUSTOM_REPO_ENTRIES) {
|
|
preference.setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
Preference preference = findPreference("pref_custom_add_repo");
|
|
if (preference == null) return;
|
|
preference.setVisible(customRepoManager.canAddRepo() && customRepoManager.getRepoCount() < 5);
|
|
if (initial) { // Custom repo add button part.
|
|
preference = findPreference("pref_custom_add_repo_button");
|
|
if (preference == null) return;
|
|
preference.setOnPreferenceClickListener(preference1 -> {
|
|
final Context context = this.requireContext();
|
|
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
|
final EditText input = new EditText(context);
|
|
input.setHint(R.string.custom_url);
|
|
input.setHorizontallyScrolling(true);
|
|
input.setMaxLines(1);
|
|
builder.setIcon(R.drawable.ic_baseline_add_box_24);
|
|
builder.setTitle(R.string.add_repo);
|
|
// make link in message clickable
|
|
builder.setMessage(R.string.add_repo_message);
|
|
builder.setView(input);
|
|
builder.setPositiveButton("OK", (dialog, which) -> {
|
|
String text = String.valueOf(input.getText());
|
|
text = text.trim();
|
|
// string should not be empty, start with https://, and not contain any spaces. http links are not allowed.
|
|
if (text.matches("^https://.*") && !text.contains(" ") && !text.isEmpty()) {
|
|
if (customRepoManager.canAddRepo(text)) {
|
|
final CustomRepoData customRepoData = customRepoManager.addRepo(text);
|
|
new Thread("Add Custom Repo Thread") {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
customRepoData.quickPrePopulate();
|
|
UiThreadHandler.handler.post(() -> updateCustomRepoList(false));
|
|
} catch (Exception e) {
|
|
Timber.e(e);
|
|
// show new dialog
|
|
new Handler(Looper.getMainLooper()).post(() -> new MaterialAlertDialogBuilder(context).setTitle(R.string.error_adding).setMessage(e.getMessage()).setPositiveButton(android.R.string.ok, (dialog1, which1) -> {
|
|
}).show());
|
|
}
|
|
}
|
|
}.start();
|
|
} else {
|
|
Snackbar.make(requireView(), R.string.invalid_repo_url, BaseTransientBottomBar.LENGTH_LONG).show();
|
|
}
|
|
} else {
|
|
Snackbar.make(requireView(), R.string.invalid_repo_url, BaseTransientBottomBar.LENGTH_LONG).show();
|
|
}
|
|
});
|
|
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
|
|
builder.setNeutralButton("Docs", (dialog, which) -> {
|
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Androidacy/MagiskModuleManager/blob/master/docs/DEVELOPERS.md#custom-repo-format"));
|
|
startActivity(intent);
|
|
});
|
|
AlertDialog alertDialog = builder.show();
|
|
final Button positiveButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
|
// validate as they type
|
|
input.addTextChangedListener(new TextWatcher() {
|
|
@Override
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
}
|
|
|
|
@Override
|
|
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
|
Timber.i("checking repo url validity");
|
|
// show error if string is empty, does not start with https://, or contains spaces
|
|
if (charSequence.toString().isEmpty()) {
|
|
input.setError(getString(R.string.empty_field));
|
|
Timber.d("No input for repo");
|
|
positiveButton.setEnabled(false);
|
|
} else if (!charSequence.toString().matches("^https://.*")) {
|
|
input.setError(getString(R.string.invalid_repo_url));
|
|
Timber.d("Non https link for repo");
|
|
positiveButton.setEnabled(false);
|
|
} else if (charSequence.toString().contains(" ")) {
|
|
input.setError(getString(R.string.invalid_repo_url));
|
|
Timber.d("Repo url has space");
|
|
positiveButton.setEnabled(false);
|
|
} else if (!customRepoManager.canAddRepo(charSequence.toString())) {
|
|
input.setError(getString(R.string.repo_already_added));
|
|
Timber.d("Could not add repo for misc reason");
|
|
positiveButton.setEnabled(false);
|
|
} else {
|
|
// enable ok button
|
|
Timber.d("Repo URL is ok");
|
|
positiveButton.setEnabled(true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void afterTextChanged(Editable s) {
|
|
}
|
|
});
|
|
positiveButton.setEnabled(false);
|
|
int dp10 = FoxDisplay.dpToPixel(10), dp20 = FoxDisplay.dpToPixel(20);
|
|
FoxViewCompat.setMargin(input, dp20, dp10, dp20, dp10);
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
private void setRepoData(String url) {
|
|
final RepoData repoData = RepoManager.getINSTANCE().get(url);
|
|
setRepoData(repoData, "pref_" + (repoData == null ? RepoManager.internalIdOfUrl(url) : repoData.preferenceId));
|
|
}
|
|
|
|
private void setRepoData(final RepoData repoData, String preferenceName) {
|
|
if (repoData == null) return;
|
|
Timber.d("Setting preference " + preferenceName + " to " + repoData);
|
|
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
Preference preference = findPreference(preferenceName);
|
|
if (preference == null) return;
|
|
if (!preferenceName.contains("androidacy") && !preferenceName.contains("magisk_alt_repo")) {
|
|
if (repoData != null) {
|
|
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).build();
|
|
Realm realm = Realm.getInstance(realmConfiguration);
|
|
RealmResults<ReposList> repoDataRealmResults = realm.where(ReposList.class).equalTo("id", repoData.preferenceId).findAll();
|
|
Timber.d("Setting preference " + preferenceName + " because it is not the Androidacy repo or the Magisk Alt Repo");
|
|
if (repoData.isForceHide() || repoDataRealmResults.isEmpty()) {
|
|
Timber.d("Hiding preference " + preferenceName + " because it is null or force hidden");
|
|
hideRepoData(preferenceName);
|
|
return;
|
|
} else {
|
|
Timber.d("Showing preference %s because the forceHide status is %s and the RealmResults is %s", preferenceName, repoData.isForceHide(), repoDataRealmResults.toString());
|
|
preference.setTitle(repoData.getName());
|
|
preference.setVisible(true);
|
|
// set website, support, and submitmodule as well as donate
|
|
if (repoData.getWebsite() != null) {
|
|
findPreference(preferenceName + "_website").setOnPreferenceClickListener((preference1 -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), repoData.getWebsite());
|
|
return true;
|
|
}));
|
|
} else {
|
|
findPreference(preferenceName + "_website").setVisible(false);
|
|
}
|
|
if (repoData.getSupport() != null) {
|
|
findPreference(preferenceName + "_support").setOnPreferenceClickListener((preference1 -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), repoData.getSupport());
|
|
return true;
|
|
}));
|
|
} else {
|
|
findPreference(preferenceName + "_support").setVisible(false);
|
|
}
|
|
if (repoData.getSubmitModule() != null) {
|
|
findPreference(preferenceName + "_submit").setOnPreferenceClickListener((preference1 -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), repoData.getSubmitModule());
|
|
return true;
|
|
}));
|
|
} else {
|
|
findPreference(preferenceName + "_submit").setVisible(false);
|
|
}
|
|
if (repoData.getDonate() != null) {
|
|
findPreference(preferenceName + "_donate").setOnPreferenceClickListener((preference1 -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), repoData.getDonate());
|
|
return true;
|
|
}));
|
|
} else {
|
|
findPreference(preferenceName + "_donate").setVisible(false);
|
|
}
|
|
}
|
|
realm.close();
|
|
} else {
|
|
Timber.d("Hiding preference " + preferenceName + " because it's data is null");
|
|
hideRepoData(preferenceName);
|
|
return;
|
|
}
|
|
}
|
|
preference = findPreference(preferenceName + "_enabled");
|
|
if (preference != null) {
|
|
// Handle custom repo separately
|
|
if (repoData instanceof CustomRepoData) {
|
|
preference.setTitle(R.string.custom_repo_always_on);
|
|
// Disable the preference
|
|
preference.setEnabled(false);
|
|
return;
|
|
} else {
|
|
((TwoStatePreference) preference).setChecked(repoData.isEnabled());
|
|
preference.setTitle(repoData.isEnabled() ? R.string.repo_enabled : R.string.repo_disabled);
|
|
preference.setOnPreferenceChangeListener((p, newValue) -> {
|
|
p.setTitle(((Boolean) newValue) ? R.string.repo_enabled : R.string.repo_disabled);
|
|
// Show snackbar telling the user to refresh the modules list or restart the app
|
|
Snackbar.make(requireView(), R.string.repo_enabled_changed, BaseTransientBottomBar.LENGTH_LONG).show();
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
preference = findPreference(preferenceName + "_website");
|
|
String homepage = repoData.getWebsite();
|
|
if (preference != null) {
|
|
if (!homepage.isEmpty()) {
|
|
preference.setVisible(true);
|
|
preference.setOnPreferenceClickListener(p -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), homepage);
|
|
return true;
|
|
});
|
|
((LongClickablePreference) preference).setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, homepage));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
preference.setVisible(false);
|
|
}
|
|
}
|
|
preference = findPreference(preferenceName + "_support");
|
|
String supportUrl = repoData.getSupport();
|
|
if (preference != null) {
|
|
if (supportUrl != null && !supportUrl.isEmpty()) {
|
|
preference.setVisible(true);
|
|
preference.setIcon(ActionButtonType.supportIconForUrl(supportUrl));
|
|
preference.setOnPreferenceClickListener(p -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), supportUrl);
|
|
return true;
|
|
});
|
|
((LongClickablePreference) preference).setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, supportUrl));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
preference.setVisible(false);
|
|
}
|
|
}
|
|
preference = findPreference(preferenceName + "_donate");
|
|
String donateUrl = repoData.getDonate();
|
|
if (preference != null) {
|
|
if (donateUrl != null) {
|
|
preference.setVisible(true);
|
|
preference.setIcon(ActionButtonType.donateIconForUrl(donateUrl));
|
|
preference.setOnPreferenceClickListener(p -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), donateUrl);
|
|
return true;
|
|
});
|
|
((LongClickablePreference) preference).setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, donateUrl));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
preference.setVisible(false);
|
|
}
|
|
}
|
|
preference = findPreference(preferenceName + "_submit");
|
|
String submissionUrl = repoData.getSubmitModule();
|
|
if (preference != null) {
|
|
if (submissionUrl != null && !submissionUrl.isEmpty()) {
|
|
preference.setVisible(true);
|
|
preference.setOnPreferenceClickListener(p -> {
|
|
IntentHelper.Companion.openUrl(getFoxActivity(this), submissionUrl);
|
|
return true;
|
|
});
|
|
((LongClickablePreference) preference).setOnPreferenceLongClickListener(p -> {
|
|
String toastText = requireContext().getString(R.string.link_copied);
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(toastText, submissionUrl));
|
|
Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show();
|
|
return true;
|
|
});
|
|
} else {
|
|
preference.setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void hideRepoData(String preferenceName) {
|
|
Preference preference = findPreference(preferenceName);
|
|
if (preference == null) return;
|
|
preference.setVisible(false);
|
|
}
|
|
|
|
@Override
|
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
|
getPreferenceManager().setSharedPreferencesName("mmm");
|
|
setPreferencesFromResource(R.xml.repo_preferences, rootKey);
|
|
applyMaterial3(getPreferenceScreen());
|
|
setRepoData(RepoManager.MAGISK_ALT_REPO);
|
|
setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT);
|
|
updateCustomRepoList(true);
|
|
onCreatePreferencesAndroidacy();
|
|
}
|
|
}
|
|
}
|