Rewrite configs with Kotlin delagate properties

This commit is contained in:
topjohnwu 2019-06-10 04:37:56 -07:00
parent 3e58d502d0
commit 7756e10779
58 changed files with 988 additions and 1272 deletions

View File

@ -1,399 +0,0 @@
package com.topjohnwu.magisk;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Xml;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import static com.topjohnwu.magisk.ConfigLeanback.getPrefs;
import static com.topjohnwu.magisk.utils.XAndroidKt.getPackageName;
public final class Config {
private static final ArrayMap<String, Object> defs = new ArrayMap<>();
public static int magiskVersionCode = -1;
// Current status
public static String magiskVersionString = "";
// Update Info
public static String remoteMagiskVersionString = "";
public static int remoteMagiskVersionCode = -1;
public static String magiskLink = "";
public static String magiskNoteLink = "";
public static String magiskMD5 = "";
public static String remoteManagerVersionString = "";
public static int remoteManagerVersionCode = -1;
public static String managerLink = "";
public static String managerNoteLink = "";
public static String uninstallerLink = "";
// Install flags
public static boolean keepVerity = false;
public static boolean keepEnc = false;
public static boolean recovery = false;
public static int suLogTimeout = 14;
public static class Key {
// su configs
public static final String ROOT_ACCESS = "root_access";
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
public static final String SU_MNT_NS = "mnt_ns";
public static final String SU_MANAGER = "requester";
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
public static final String SU_AUTO_RESPONSE = "su_auto_response";
public static final String SU_NOTIFICATION = "su_notification";
public static final String SU_REAUTH = "su_reauth";
public static final String SU_FINGERPRINT = "su_fingerprint";
// prefs
public static final String CHECK_UPDATES = "check_update";
public static final String UPDATE_CHANNEL = "update_channel";
public static final String CUSTOM_CHANNEL = "custom_channel";
public static final String LOCALE = "locale";
public static final String DARK_THEME = "dark_theme";
public static final String ETAG_KEY = "ETag";
public static final String REPO_ORDER = "repo_order";
public static final String SHOW_SYSTEM_APP = "show_system";
// system state
public static final String UPDATE_SERVICE_VER = "update_service_version";
public static final String MAGISKHIDE = "magiskhide";
public static final String COREONLY = "disable";
}
public static class Value {
public static final int DEFAULT_CHANNEL = -1;
public static final int STABLE_CHANNEL = 0;
public static final int BETA_CHANNEL = 1;
public static final int CUSTOM_CHANNEL = 2;
public static final int CANARY_CHANNEL = 3;
public static final int CANARY_DEBUG_CHANNEL = 4;
public static final int ROOT_ACCESS_DISABLED = 0;
public static final int ROOT_ACCESS_APPS_ONLY = 1;
public static final int ROOT_ACCESS_ADB_ONLY = 2;
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
public static final int MULTIUSER_MODE_USER = 2;
public static final int NAMESPACE_MODE_GLOBAL = 0;
public static final int NAMESPACE_MODE_REQUESTER = 1;
public static final int NAMESPACE_MODE_ISOLATE = 2;
public static final int NO_NOTIFICATION = 0;
public static final int NOTIFICATION_TOAST = 1;
public static final int SU_PROMPT = 0;
public static final int SU_AUTO_DENY = 1;
public static final int SU_AUTO_ALLOW = 2;
public static final int[] TIMEOUT_LIST = {0, -1, 10, 20, 30, 60};
public static final int ORDER_NAME = 0;
public static final int ORDER_DATE = 1;
}
private static boolean magiskHide = false;
public static void loadMagiskInfo() {
try {
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
} catch (NumberFormatException ignored) {
}
}
public static void export() {
// Flush prefs to disk
getPrefs().edit().apply();
Context context = ConfigLeanback.getProtectedContext();
File xml = new File(context.getFilesDir().getParent() + "/shared_prefs",
getPackageName() + "_preferences.xml");
Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec();
}
private static final int PREF_INT = 0;
private static final int PREF_STR_INT = 1;
private static final int PREF_BOOL = 2;
private static final int PREF_STR = 3;
private static final int DB_INT = 4;
private static final int DB_BOOL = 5;
private static final int DB_STR = 6;
private static int getConfigType(String key) {
switch (key) {
case Key.REPO_ORDER:
return PREF_INT;
case Key.SU_REQUEST_TIMEOUT:
case Key.SU_AUTO_RESPONSE:
case Key.SU_NOTIFICATION:
case Key.UPDATE_CHANNEL:
return PREF_STR_INT;
case Key.DARK_THEME:
case Key.SU_REAUTH:
case Key.CHECK_UPDATES:
case Key.MAGISKHIDE:
case Key.COREONLY:
case Key.SHOW_SYSTEM_APP:
return PREF_BOOL;
case Key.CUSTOM_CHANNEL:
case Key.LOCALE:
case Key.ETAG_KEY:
return PREF_STR;
case Key.ROOT_ACCESS:
case Key.SU_MNT_NS:
case Key.SU_MULTIUSER_MODE:
return DB_INT;
case Key.SU_FINGERPRINT:
return DB_BOOL;
case Key.SU_MANAGER:
return DB_STR;
default:
throw new IllegalArgumentException();
}
}
public static void initialize() {
SharedPreferences pref = getPrefs();
SharedPreferences.Editor editor = pref.edit();
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
if (config.exists()) {
try {
SuFileInputStream is = new SuFileInputStream(config);
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(is, "UTF-8");
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "map");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG)
continue;
String key = parser.getAttributeValue(null, "name");
String value = parser.getAttributeValue(null, "value");
switch (parser.getName()) {
case "string":
parser.require(XmlPullParser.START_TAG, null, "string");
editor.putString(key, parser.nextText());
parser.require(XmlPullParser.END_TAG, null, "string");
break;
case "boolean":
parser.require(XmlPullParser.START_TAG, null, "boolean");
editor.putBoolean(key, Boolean.parseBoolean(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "boolean");
break;
case "int":
parser.require(XmlPullParser.START_TAG, null, "int");
editor.putInt(key, Integer.parseInt(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "int");
break;
case "long":
parser.require(XmlPullParser.START_TAG, null, "long");
editor.putLong(key, Long.parseLong(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "long");
break;
case "float":
parser.require(XmlPullParser.START_TAG, null, "int");
editor.putFloat(key, Float.parseFloat(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "int");
break;
default:
parser.next();
}
}
} catch (IOException | XmlPullParserException e) {
e.printStackTrace();
}
editor.remove(Key.ETAG_KEY);
editor.apply();
editor = pref.edit();
config.delete();
}
// Set defaults if not set
setDefs(pref, editor);
// These settings are from actual device state
editor.putBoolean(Key.MAGISKHIDE, magiskHide)
.putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
.putInt(Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
.apply();
}
@SuppressWarnings("unchecked")
public static <T> T get(String key) {
switch (getConfigType(key)) {
case PREF_INT:
return (T) (Integer) getPrefs().getInt(key, getDef(key));
case PREF_STR_INT:
return (T) (Integer) Utils.getPrefsInt(getPrefs(), key, getDef(key));
case PREF_BOOL:
return (T) (Boolean) getPrefs().getBoolean(key, getDef(key));
case PREF_STR:
return (T) getPrefs().getString(key, getDef(key));
case DB_INT:
return (T) (Integer) ConfigLeanback.get(key, (Integer) getDef(key));
case DB_BOOL:
return (T) (Boolean) (ConfigLeanback.get(key, getDef(key) ? 1 : 0) != 0);
case DB_STR:
return (T) ConfigLeanback.get(key, getDef(key));
}
/* Will never get here (IllegalArgumentException in getConfigType) */
return null;
}
public static void set(String key, Object val) {
switch (getConfigType(key)) {
case PREF_INT:
getPrefs().edit().putInt(key, (int) val).apply();
break;
case PREF_STR_INT:
getPrefs().edit().putString(key, String.valueOf(val)).apply();
break;
case PREF_BOOL:
getPrefs().edit().putBoolean(key, (boolean) val).apply();
break;
case PREF_STR:
getPrefs().edit().putString(key, (String) val).apply();
break;
case DB_INT:
ConfigLeanback.put(key, (int) val);
break;
case DB_BOOL:
ConfigLeanback.put(key, (boolean) val ? 1 : 0);
break;
case DB_STR:
ConfigLeanback.put(key, (String) val);
break;
}
}
public static void remove(String key) {
switch (getConfigType(key)) {
case PREF_INT:
case PREF_STR_INT:
case PREF_BOOL:
case PREF_STR:
getPrefs().edit().remove(key).apply();
break;
case DB_BOOL:
case DB_INT:
ConfigLeanback.delete(key);
break;
case DB_STR:
ConfigLeanback.put(key, null);
break;
}
}
static {
/* Set default configurations */
// prefs int
defs.put(Key.REPO_ORDER, Value.ORDER_DATE);
// prefs string int
defs.put(Key.SU_REQUEST_TIMEOUT, 10);
defs.put(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT);
defs.put(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST);
defs.put(Key.UPDATE_CHANNEL, Utils.isCanary() ?
Value.CANARY_DEBUG_CHANNEL : Value.DEFAULT_CHANNEL);
// prefs bool
defs.put(Key.CHECK_UPDATES, true);
defs.put(Key.DARK_THEME, true);
//defs.put(Key.SU_REAUTH, false);
//defs.put(Key.SHOW_SYSTEM_APP, false);
// prefs string
defs.put(Key.CUSTOM_CHANNEL, "");
defs.put(Key.LOCALE, "");
//defs.put(Key.ETAG_KEY, null);
// db int
defs.put(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB);
defs.put(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER);
defs.put(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY);
// db bool
//defs.put(Key.SU_FINGERPRINT, false);
// db strings
//defs.put(Key.SU_MANAGER, null);
}
@SuppressWarnings("unchecked")
@Nullable
private static <T> T getDef(String key) throws IllegalArgumentException {
Object val = defs.get(key);
switch (getConfigType(key)) {
case PREF_INT:
case DB_INT:
case PREF_STR_INT:
return val != null ? (T) val : (T) (Integer) 0;
case DB_BOOL:
case PREF_BOOL:
return val != null ? (T) val : (T) (Boolean) false;
case DB_STR:
case PREF_STR:
return (T) val;
}
/* Will never get here (IllegalArgumentException in getConfigType) */
return null;
}
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor) {
for (String key : defs.keySet()) {
Object value = defs.get(key);
int type = getConfigType(key);
switch (type) {
case DB_INT:
editor.putString(key, String.valueOf(ConfigLeanback.get(key, (Integer) value)));
continue;
case DB_STR:
editor.putString(key, ConfigLeanback.get(key, String.valueOf(value)));
continue;
case DB_BOOL:
int bs = ConfigLeanback.get(key, -1);
editor.putBoolean(key, bs < 0 ? (Boolean) value : bs != 0);
continue;
}
if (pref.contains(key))
continue;
switch (type) {
case PREF_INT:
editor.putInt(key, (Integer) value);
break;
case PREF_STR_INT:
editor.putString(key, String.valueOf(value));
break;
case PREF_STR:
editor.putString(key, (String) value);
break;
case PREF_BOOL:
editor.putBoolean(key, (Boolean) value);
break;
}
}
}
}

View File

@ -0,0 +1,196 @@
package com.topjohnwu.magisk
import android.content.Context
import android.util.Xml
import androidx.core.content.edit
import com.topjohnwu.magisk.data.repository.DBConfig
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.data.database.StringDao
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.model.preference.PreferenceModel
import com.topjohnwu.magisk.utils.*
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import org.xmlpull.v1.XmlPullParser
import java.io.File
object Config : PreferenceModel, DBConfig {
override val stringDao: StringDao by inject()
override val settingsDao: SettingsDao by inject()
override val context: Context by inject(Protected)
object Key {
// db configs
const val ROOT_ACCESS = "root_access"
const val SU_MULTIUSER_MODE = "multiuser_mode"
const val SU_MNT_NS = "mnt_ns"
const val SU_MANAGER = "requester"
const val SU_FINGERPRINT = "su_fingerprint"
// prefs
const val SU_REQUEST_TIMEOUT = "su_request_timeout"
const val SU_AUTO_RESPONSE = "su_auto_response"
const val SU_NOTIFICATION = "su_notification"
const val SU_REAUTH = "su_reauth"
const val CHECK_UPDATES = "check_update"
const val UPDATE_CHANNEL = "update_channel"
const val CUSTOM_CHANNEL = "custom_channel"
const val LOCALE = "locale"
const val DARK_THEME = "dark_theme"
const val ETAG_KEY = "ETag"
const val REPO_ORDER = "repo_order"
const val SHOW_SYSTEM_APP = "show_system"
// system state
const val MAGISKHIDE = "magiskhide"
const val COREONLY = "disable"
}
object Value {
// Update channels
const val DEFAULT_CHANNEL = -1
const val STABLE_CHANNEL = 0
const val BETA_CHANNEL = 1
const val CUSTOM_CHANNEL = 2
const val CANARY_CHANNEL = 3
const val CANARY_DEBUG_CHANNEL = 4
// root access mode
const val ROOT_ACCESS_DISABLED = 0
const val ROOT_ACCESS_APPS_ONLY = 1
const val ROOT_ACCESS_ADB_ONLY = 2
const val ROOT_ACCESS_APPS_AND_ADB = 3
// su multiuser
const val MULTIUSER_MODE_OWNER_ONLY = 0
const val MULTIUSER_MODE_OWNER_MANAGED = 1
const val MULTIUSER_MODE_USER = 2
// su mnt ns
const val NAMESPACE_MODE_GLOBAL = 0
const val NAMESPACE_MODE_REQUESTER = 1
const val NAMESPACE_MODE_ISOLATE = 2
// su notification
const val NO_NOTIFICATION = 0
const val NOTIFICATION_TOAST = 1
// su auto response
const val SU_PROMPT = 0
const val SU_AUTO_DENY = 1
const val SU_AUTO_ALLOW = 2
// su timeout
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
// repo order
const val ORDER_NAME = 0
const val ORDER_DATE = 1
}
private val defaultChannel =
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
else Value.DEFAULT_CHANNEL
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
var suAutoReponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
var darkTheme by preference(Key.DARK_THEME, true)
var suReAuth by preference(Key.SU_REAUTH, false)
var checkUpdate by preference(Key.CHECK_UPDATES, true)
@JvmStatic
var magiskHide by preference(Key.MAGISKHIDE, true)
var coreOnly by preference(Key.COREONLY, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
var locale by preference(Key.LOCALE, "")
@JvmStatic
var etagKey by preference(Key.ETAG_KEY, "")
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false)
@JvmStatic
var suManager by dbStrings(Key.SU_MANAGER, "")
fun initialize() = prefs.edit {
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
if (config.exists()) runCatching {
val input = SuFileInputStream(config).buffered()
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(input, "UTF-8")
parser.nextTag()
parser.require(XmlPullParser.START_TAG, null, "map")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG)
continue
val key: String = parser.getAttributeValue(null, "name")
val value: String = parser.getAttributeValue(null, "value")
when (parser.name) {
"string" -> {
parser.require(XmlPullParser.START_TAG, null, "string")
putString(key, parser.nextText())
parser.require(XmlPullParser.END_TAG, null, "string")
}
"boolean" -> {
parser.require(XmlPullParser.START_TAG, null, "boolean")
putBoolean(key, value.toBoolean())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "boolean")
}
"int" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putInt(key, value.toInt())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
"long" -> {
parser.require(XmlPullParser.START_TAG, null, "long")
putLong(key, value.toLong())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "long")
}
"float" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putFloat(key, value.toFloat())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
else -> parser.next()
}
}
config.delete()
}
remove(Key.ETAG_KEY)
if (!prefs.contains(Key.UPDATE_CHANNEL))
putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
// Get actual state
putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
// Write database configs
putString(Key.ROOT_ACCESS, rootMode.toString())
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
}
@JvmStatic
fun export() {
// Flush prefs to disk
prefs.edit().apply()
val xml = File("${get<Context>(Protected).filesDir.parent}/shared_prefs",
"${packageName}_preferences.xml")
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
}
}

View File

@ -1,52 +0,0 @@
package com.topjohnwu.magisk
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.data.repository.SettingRepository
import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.utils.inject
object ConfigLeanback {
@JvmStatic
val protectedContext: Context by inject(Protected)
@JvmStatic
val prefs: SharedPreferences by inject()
private val settingRepo: SettingRepository by inject()
private val stringRepo: StringRepository by inject()
@JvmStatic
@AnyThread
fun put(key: String, value: Int) {
settingRepo.put(key, value).subscribeK()
}
@JvmStatic
@WorkerThread
fun get(key: String, defaultValue: Int): Int =
settingRepo.fetch(key, defaultValue).blockingGet()
@JvmStatic
@AnyThread
fun put(key: String, value: String?) {
val task = value?.let { stringRepo.put(key, it) } ?: stringRepo.delete(key)
task.subscribeK()
}
@JvmStatic
@WorkerThread
fun get(key: String, defaultValue: String?): String =
stringRepo.fetch(key, defaultValue.orEmpty()).blockingGet()
@JvmStatic
@AnyThread
fun delete(key: String) {
settingRepo.delete(key).subscribeK()
}
}

View File

@ -19,7 +19,6 @@ object Const {
const val MAGISK_LOG = "/cache/magisk.log" const val MAGISK_LOG = "/cache/magisk.log"
// Versions // Versions
const val UPDATE_SERVICE_VER = 1
const val SNET_EXT_VER = 12 const val SNET_EXT_VER = 12
const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107" const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77" const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"

View File

@ -0,0 +1,36 @@
package com.topjohnwu.magisk;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
public final class Info {
public static int magiskVersionCode = -1;
// Current status
public static String magiskVersionString = "";
// Update Info
public static String remoteMagiskVersionString = "";
public static int remoteMagiskVersionCode = -1;
public static String magiskLink = "";
public static String magiskNoteLink = "";
public static String magiskMD5 = "";
public static String remoteManagerVersionString = "";
public static int remoteManagerVersionCode = -1;
public static String managerLink = "";
public static String managerNoteLink = "";
public static String uninstallerLink = "";
// Install flags
public static boolean keepVerity = false;
public static boolean keepEnc = false;
public static boolean recovery = false;
public static void loadMagiskInfo() {
try {
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
Config.setMagiskHide(Shell.su("magiskhide --status").exec().isSuccess());
} catch (NumberFormatException ignored) {
}
}
}

View File

@ -1,46 +0,0 @@
package com.topjohnwu.magisk
import android.content.Context
import com.topjohnwu.magisk.KConfig.UpdateChannel.STABLE
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.model.preference.PreferenceModel
import com.topjohnwu.magisk.utils.inject
object KConfig : PreferenceModel {
override val context: Context by inject(Protected)
private var internalUpdateChannel by preference(Config.Key.UPDATE_CHANNEL, STABLE.id.toString())
var useCustomTabs by preference("useCustomTabs", true)
@JvmStatic
var customUpdateChannel by preference(Config.Key.CUSTOM_CHANNEL, "")
@JvmStatic
var updateChannel: UpdateChannel
get() = UpdateChannel.byId(internalUpdateChannel.toIntOrNull() ?: STABLE.id)
set(value) {
internalUpdateChannel = value.id.toString()
}
internal const val DEFAULT_CHANNEL = "topjohnwu/magisk_files"
enum class UpdateChannel(val id: Int) {
STABLE(Config.Value.STABLE_CHANNEL),
BETA(Config.Value.BETA_CHANNEL),
CANARY(Config.Value.CANARY_CHANNEL),
CANARY_DEBUG(Config.Value.CANARY_DEBUG_CHANNEL),
CUSTOM(Config.Value.CUSTOM_CHANNEL);
companion object {
fun byId(id: Int) = when (id) {
Config.Value.STABLE_CHANNEL -> STABLE
Config.Value.BETA_CHANNEL -> BETA
Config.Value.CUSTOM_CHANNEL -> CUSTOM
Config.Value.CANARY_CHANNEL -> CANARY
Config.Value.CANARY_DEBUG_CHANNEL -> CANARY_DEBUG
else -> STABLE
}
}
}
}

View File

@ -1,6 +1,5 @@
package com.topjohnwu.magisk.data.database package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.data.database.base.* import com.topjohnwu.magisk.data.database.base.*
import com.topjohnwu.magisk.model.entity.MagiskLog import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.toLog import com.topjohnwu.magisk.model.entity.toLog
@ -12,7 +11,7 @@ class LogDao : BaseDao() {
override val table = DatabaseDefinition.Table.LOG override val table = DatabaseDefinition.Table.LOG
fun deleteOutdated( fun deleteOutdated(
suTimeout: Long = Config.suLogTimeout * TimeUnit.DAYS.toMillis(1) suTimeout: Long = TimeUnit.DAYS.toMillis(14)
) = query<Delete> { ) = query<Delete> {
condition { condition {
lessThan("time", suTimeout.toString()) lessThan("time", suTimeout.toString())

View File

@ -1,118 +0,0 @@
package com.topjohnwu.magisk.data.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.model.entity.Repo;
import java.util.HashSet;
import java.util.Set;
@Deprecated
public class RepoDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 5;
private static final String TABLE_NAME = "repos";
private final SQLiteDatabase mDb;
@Deprecated
public RepoDatabaseHelper(Context context) {
super(context, "repo.db", null, DATABASE_VER);
mDb = getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, DATABASE_VER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
// Nuke old DB and create new table
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
Config.remove(Config.Key.ETAG_KEY);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, 0, DATABASE_VER);
}
@Deprecated
public void clearRepo() {
mDb.delete(TABLE_NAME, null, null);
}
@Deprecated
public void removeRepo(String id) {
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
}
@Deprecated
public void removeRepo(Repo repo) {
removeRepo(repo.getId());
}
@Deprecated
public void removeRepo(Iterable<String> list) {
for (String id : list) {
if (id == null) continue;
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
}
}
@Deprecated
public void addRepo(Repo repo) {
mDb.replace(TABLE_NAME, null, repo.getContentValues());
}
@Deprecated
public Repo getRepo(String id) {
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[]{id}, null, null, null)) {
if (c.moveToNext()) {
return new Repo(c);
}
}
return null;
}
@Deprecated
public Cursor getRawCursor() {
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
}
@Deprecated
public Cursor getRepoCursor() {
String orderBy = null;
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
case Config.Value.ORDER_NAME:
orderBy = "name COLLATE NOCASE";
break;
case Config.Value.ORDER_DATE:
orderBy = "last_update DESC";
}
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
}
@Deprecated
public Set<String> getRepoIDSet() {
HashSet<String> set = new HashSet<>(300);
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
while (c.moveToNext()) {
set.add(c.getString(c.getColumnIndex("id")));
}
}
return set;
}
}

View File

@ -0,0 +1,109 @@
package com.topjohnwu.magisk.data.database
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.edit
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.model.entity.Repo
import java.util.*
@Deprecated("")
class RepoDatabaseHelper
constructor(context: Context) : SQLiteOpenHelper(context, "repo.db", null, DATABASE_VER) {
private val mDb: SQLiteDatabase = writableDatabase
val rawCursor: Cursor
@Deprecated("")
get() = mDb.query(TABLE_NAME, null, null, null, null, null, null)
val repoCursor: Cursor
@Deprecated("")
get() {
var orderBy: String? = null
when (Config.repoOrder) {
Config.Value.ORDER_NAME -> orderBy = "name COLLATE NOCASE"
Config.Value.ORDER_DATE -> orderBy = "last_update DESC"
}
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy)
}
val repoIDSet: Set<String>
@Deprecated("")
get() {
val set = HashSet<String>(300)
mDb.query(TABLE_NAME, null, null, null, null, null, null).use { c ->
while (c.moveToNext()) {
set.add(c.getString(c.getColumnIndex("id")))
}
}
return set
}
override fun onCreate(db: SQLiteDatabase) {
onUpgrade(db, 0, DATABASE_VER)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion != newVersion) {
// Nuke old DB and create new table
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))")
Config.prefs.edit {
remove(Config.Key.ETAG_KEY)
}
}
}
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
onUpgrade(db, 0, DATABASE_VER)
}
@Deprecated("")
fun clearRepo() {
mDb.delete(TABLE_NAME, null, null)
}
@Deprecated("")
fun removeRepo(id: String) {
mDb.delete(TABLE_NAME, "id=?", arrayOf(id))
}
@Deprecated("")
fun removeRepo(repo: Repo) {
removeRepo(repo.id)
}
@Deprecated("")
fun removeRepo(list: Iterable<String>) {
list.forEach {
mDb.delete(TABLE_NAME, "id=?", arrayOf(it))
}
}
@Deprecated("")
fun addRepo(repo: Repo) {
mDb.replace(TABLE_NAME, null, repo.contentValues)
}
@Deprecated("")
fun getRepo(id: String): Repo? {
mDb.query(TABLE_NAME, null, "id=?", arrayOf(id), null, null, null).use { c ->
if (c.moveToNext()) {
return Repo(c)
}
}
return null
}
companion object {
private val DATABASE_VER = 5
private val TABLE_NAME = "repos"
}
}

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.data.network package com.topjohnwu.magisk.data.network
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.model.entity.MagiskConfig import com.topjohnwu.magisk.model.entity.MagiskConfig
import io.reactivex.Single import io.reactivex.Single
import okhttp3.ResponseBody import okhttp3.ResponseBody
@ -16,19 +15,19 @@ interface GithubRawApiServices {
//region topjohnwu/magisk_files //region topjohnwu/magisk_files
@GET("$MAGISK_FILES/master/stable.json") @GET("$MAGISK_FILES/master/stable.json")
fun fetchConfig(): Single<MagiskConfig> fun fetchStableUpdate(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/beta.json") @GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaConfig(): Single<MagiskConfig> fun fetchBetaUpdate(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/canary_builds/release.json") @GET("$MAGISK_FILES/master/canary_builds/release.json")
fun fetchCanaryConfig(): Single<MagiskConfig> fun fetchCanaryUpdate(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/canary_builds/canary.json") @GET("$MAGISK_FILES/master/canary_builds/canary.json")
fun fetchCanaryDebugConfig(): Single<MagiskConfig> fun fetchCanaryDebugUpdate(): Single<MagiskConfig>
@GET @GET
fun fetchCustomConfig(@Url url: String): Single<MagiskConfig> fun fetchCustomUpdate(@Url url: String): Single<MagiskConfig>
@GET("$MAGISK_FILES/{$REVISION}/snet.apk") @GET("$MAGISK_FILES/{$REVISION}/snet.apk")
@Streaming @Streaming
@ -55,7 +54,7 @@ interface GithubRawApiServices {
private const val FILE = "file" private const val FILE = "file"
private const val MAGISK_FILES = KConfig.DEFAULT_CHANNEL private const val MAGISK_FILES = "topjohnwu/magisk_files"
private const val MAGISK_MASTER = "topjohnwu/Magisk/master" private const val MAGISK_MASTER = "topjohnwu/Magisk/master"
private const val MAGISK_MODULES = "Magisk-Modules-Repo" private const val MAGISK_MODULES = "Magisk-Modules-Repo"
} }

View File

@ -0,0 +1,95 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.data.database.StringDao
import com.topjohnwu.magisk.utils.trimEmptyToNull
import io.reactivex.schedulers.Schedulers
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface DBConfig {
val settingsDao: SettingsDao
val stringDao: StringDao
fun dbSettings(
name: String,
default: Int
) = DBSettingsValue(name, default)
fun dbSettings(
name: String,
default: Boolean
) = DBBoolSettings(name, default)
fun dbStrings(
name: String,
default: String
) = DBStringsValue(name, default)
}
class DBSettingsValue(
private val name: String,
private val default: Int
) : ReadWriteProperty<DBConfig, Int> {
private var value: Int? = null
private fun getKey(property: KProperty<*>) = name.trimEmptyToNull() ?: property.name
@Synchronized
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
if (value == null)
value = thisRef.settingsDao.fetch(getKey(property), default).blockingGet()
return value!!
}
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Int) {
synchronized(this) {
this.value = value
}
thisRef.settingsDao.put(getKey(property), value)
.subscribeOn(Schedulers.io())
.subscribe()
}
}
class DBBoolSettings(
name: String,
default: Boolean
) : ReadWriteProperty<DBConfig, Boolean> {
val base = DBSettingsValue(name, if (default) 1 else 0)
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean
= base.getValue(thisRef, property) != 0
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
base.setValue(thisRef, property, if (value) 1 else 0)
}
class DBStringsValue(
private val name: String,
private val default: String
) : ReadWriteProperty<DBConfig, String> {
private var value: String? = null
private fun getKey(property: KProperty<*>) = name.trimEmptyToNull() ?: property.name
@Synchronized
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
if (value == null)
value = thisRef.stringDao.fetch(getKey(property), default).blockingGet()
return value!!
}
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: String) {
synchronized(this) {
this.value = value
}
thisRef.stringDao.put(getKey(property), value)
.subscribeOn(Schedulers.io())
.subscribe()
}
}

View File

@ -4,20 +4,17 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import com.topjohnwu.magisk.App import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.KConfig import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.database.base.su import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.data.database.base.suRaw
import com.topjohnwu.magisk.data.network.GithubRawApiServices import com.topjohnwu.magisk.data.network.GithubRawApiServices
import com.topjohnwu.magisk.model.entity.HideAppInfo import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.Version
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.inject import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.magisk.utils.toSingle import com.topjohnwu.magisk.utils.toSingle
import com.topjohnwu.magisk.utils.writeToFile import com.topjohnwu.magisk.utils.writeToFile
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.functions.BiFunction
class MagiskRepository( class MagiskRepository(
private val context: Context, private val context: Context,
@ -25,15 +22,15 @@ class MagiskRepository(
private val packageManager: PackageManager private val packageManager: PackageManager
) { ) {
fun fetchMagisk() = fetchConfig() fun fetchMagisk() = fetchUpdate()
.flatMap { apiRaw.fetchFile(it.magisk.link) } .flatMap { apiRaw.fetchFile(it.magisk.link) }
.map { it.writeToFile(context, FILE_MAGISK_ZIP) } .map { it.writeToFile(context, FILE_MAGISK_ZIP) }
fun fetchManager() = fetchConfig() fun fetchManager() = fetchUpdate()
.flatMap { apiRaw.fetchFile(it.app.link) } .flatMap { apiRaw.fetchFile(it.app.link) }
.map { it.writeToFile(context, FILE_MAGISK_APK) } .map { it.writeToFile(context, FILE_MAGISK_APK) }
fun fetchUninstaller() = fetchConfig() fun fetchUninstaller() = fetchUpdate()
.flatMap { apiRaw.fetchFile(it.uninstaller.link) } .flatMap { apiRaw.fetchFile(it.uninstaller.link) }
.map { it.writeToFile(context, FILE_UNINSTALLER_ZIP) } .map { it.writeToFile(context, FILE_UNINSTALLER_ZIP) }
@ -44,23 +41,35 @@ class MagiskRepository(
.map { it.writeToFile(context, FILE_BOOTCTL_SH) } .map { it.writeToFile(context, FILE_BOOTCTL_SH) }
fun fetchConfig() = when (KConfig.updateChannel) { fun fetchUpdate() = when (Config.updateChannel) {
KConfig.UpdateChannel.STABLE -> apiRaw.fetchConfig() Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate()
KConfig.UpdateChannel.BETA -> apiRaw.fetchBetaConfig() Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
KConfig.UpdateChannel.CANARY -> apiRaw.fetchCanaryConfig() Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
KConfig.UpdateChannel.CANARY_DEBUG -> apiRaw.fetchCanaryDebugConfig() Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
KConfig.UpdateChannel.CUSTOM -> apiRaw.fetchCustomConfig(KConfig.customUpdateChannel) Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl)
} else -> throw IllegalArgumentException()
.doOnSuccess { }.flatMap {
Config.remoteMagiskVersionCode = it.magisk.versionCode.toIntOrNull() ?: -1 // If remote version is lower than current installed, try switching to beta
Config.magiskLink = it.magisk.link if (it.magisk.versionCode.toIntOrNull() ?: -1 < Info.magiskVersionCode
Config.magiskNoteLink = it.magisk.note && Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
Config.magiskMD5 = it.magisk.hash Config.updateChannel = Config.Value.BETA_CHANNEL
Config.remoteManagerVersionCode = it.app.versionCode.toIntOrNull() ?: -1 apiRaw.fetchBetaUpdate()
Config.remoteManagerVersionString = it.app.version } else {
Config.managerLink = it.app.link Single.just(it)
Config.managerNoteLink = it.app.note }
Config.uninstallerLink = it.uninstaller.link }.doOnSuccess {
Info.remoteMagiskVersionString = it.magisk.version
Info.remoteMagiskVersionCode = it.magisk.versionCode.toIntOrNull() ?: -1
Info.magiskLink = it.magisk.link
Info.magiskNoteLink = it.magisk.note
Info.magiskMD5 = it.magisk.hash
Info.remoteManagerVersionString = it.app.version
Info.remoteManagerVersionCode = it.app.versionCode.toIntOrNull() ?: -1
Info.managerLink = it.app.link
Info.managerNoteLink = it.app.note
Info.uninstallerLink = it.uninstaller.link
} }
fun fetchApps() = fun fetchApps() =

View File

@ -1,11 +0,0 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.SettingsDao
class SettingRepository(private val settingsDao: SettingsDao) {
fun fetch(key: String, default: Int) = settingsDao.fetch(key, default)
fun put(key: String, value: Int) = settingsDao.put(key, value)
fun delete(key: String) = settingsDao.delete(key)
}

View File

@ -1,11 +0,0 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.StringDao
class StringRepository(private val stringDao: StringDao) {
fun fetch(key: String, default: String) = stringDao.fetch(key, default)
fun put(key: String, value: String) = stringDao.put(key, value)
fun delete(key: String) = stringDao.delete(key)
}

View File

@ -8,6 +8,4 @@ val repositoryModule = module {
single { MagiskRepository(get(), get(), get()) } single { MagiskRepository(get(), get(), get()) }
single { LogRepository(get()) } single { LogRepository(get()) }
single { AppRepository(get()) } single { AppRepository(get()) }
single { SettingRepository(get()) }
single { StringRepository(get()) }
} }

View File

@ -6,6 +6,8 @@ import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import androidx.annotation.Nullable;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
@ -29,8 +31,6 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import androidx.annotation.Nullable;
public class DownloadModuleService extends Service { public class DownloadModuleService extends Service {
private List<ProgressNotification> notifications; private List<ProgressNotification> notifications;

View File

@ -5,10 +5,10 @@ import android.database.Cursor;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.util.List;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.List;
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable { public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
private String mId, mName, mVersion, mAuthor, mDescription; private String mId, mName, mVersion, mAuthor, mDescription;

View File

@ -4,10 +4,10 @@ import android.content.ContentValues;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import com.topjohnwu.magisk.utils.Utils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.topjohnwu.magisk.utils.Utils;
public class Policy implements Comparable<Policy>{ public class Policy implements Comparable<Policy>{
public static final int INTERACTIVE = 0; public static final int INTERACTIVE = 0;
@ -27,7 +27,7 @@ public class Policy implements Comparable<Policy>{
this.uid = uid; this.uid = uid;
packageName = pkgs[0]; packageName = pkgs[0];
info = pm.getApplicationInfo(packageName, 0); info = pm.getApplicationInfo(packageName, 0);
appName = Utils.getAppLabel(info, pm); appName = Utils.INSTANCE.getAppLabel(info, pm);
} }
public Policy(ContentValues values, PackageManager pm) throws PackageManager.NameNotFoundException { public Policy(ContentValues values, PackageManager pm) throws PackageManager.NameNotFoundException {

View File

@ -54,7 +54,7 @@ public class Repo extends BaseModule {
} }
public void update() throws IllegalRepoException { public void update() throws IllegalRepoException {
String[] props = Utils.dlString(getPropUrl()).split("\\n"); String[] props = Utils.INSTANCE.dlString(getPropUrl()).split("\\n");
try { try {
parseProps(props); parseProps(props);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -103,7 +103,7 @@ public class Repo extends BaseModule {
} }
public String getDownloadFilename() { public String getDownloadFilename() {
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip"); return Utils.INSTANCE.getLegalFilename(getName() + "-" + getVersion() + ".zip");
} }
public class IllegalRepoException extends Exception { public class IllegalRepoException extends Exception {

View File

@ -2,6 +2,8 @@ package com.topjohnwu.magisk.model.preference
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface PreferenceModel { interface PreferenceModel {
@ -14,6 +16,20 @@ interface PreferenceModel {
val prefs: SharedPreferences val prefs: SharedPreferences
get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE) get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
fun preferenceStrInt(
name: String,
default: Int,
writeDefault: Boolean = false,
commit: Boolean = commitPrefs
) = object: ReadWriteProperty<PreferenceModel, Int> {
val base = StringProperty(name, default.toString(), commit)
override fun getValue(thisRef: PreferenceModel, property: KProperty<*>): Int =
base.getValue(thisRef, property).toInt()
override fun setValue(thisRef: PreferenceModel, property: KProperty<*>, value: Int) =
base.setValue(thisRef, property, value.toString())
}
fun preference( fun preference(
name: String, name: String,
default: Boolean, default: Boolean,

View File

@ -6,6 +6,7 @@ import android.content.Intent
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.database.base.su import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.data.repository.AppRepository import com.topjohnwu.magisk.data.repository.AppRepository
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
@ -13,7 +14,6 @@ import com.topjohnwu.magisk.utils.DownloadApp
import com.topjohnwu.magisk.utils.RootUtils import com.topjohnwu.magisk.utils.RootUtils
import com.topjohnwu.magisk.utils.SuLogger import com.topjohnwu.magisk.utils.SuLogger
import com.topjohnwu.magisk.utils.inject import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.magisk.utils.get
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@ -64,9 +64,8 @@ open class GeneralReceiver : BroadcastReceiver() {
} }
Intent.ACTION_PACKAGE_REPLACED -> Intent.ACTION_PACKAGE_REPLACED ->
// This will only work pre-O // This will only work pre-O
if (Config.get<Boolean>(Config.Key.SU_REAUTH)!!) { if (Config.suReAuth)
appRepo.delete(getPkg(intent)).blockingGet() appRepo.delete(getPkg(intent)).blockingGet()
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> { Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent) val pkg = getPkg(intent)
appRepo.delete(pkg).blockingGet() appRepo.delete(pkg).blockingGet()
@ -74,7 +73,7 @@ open class GeneralReceiver : BroadcastReceiver() {
} }
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context) Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
Const.Key.BROADCAST_MANAGER_UPDATE -> { Const.Key.BROADCAST_MANAGER_UPDATE -> {
Config.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK) Info.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK)
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME)) DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME))
} }
Const.Key.BROADCAST_REBOOT -> RootUtils.reboot() Const.Key.BROADCAST_REBOOT -> RootUtils.reboot()

View File

@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.update
import androidx.work.ListenableWorker import androidx.work.ListenableWorker
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.model.worker.DelegateWorker import com.topjohnwu.magisk.model.worker.DelegateWorker
import com.topjohnwu.magisk.utils.inject import com.topjohnwu.magisk.utils.inject
@ -14,10 +14,10 @@ class UpdateCheckService : DelegateWorker() {
override fun doWork(): ListenableWorker.Result { override fun doWork(): ListenableWorker.Result {
return runCatching { return runCatching {
magiskRepo.fetchConfig().blockingGet() magiskRepo.fetchUpdate().blockingGet()
if (BuildConfig.VERSION_CODE < Config.remoteManagerVersionCode) if (BuildConfig.VERSION_CODE < Info.remoteManagerVersionCode)
Notifications.managerUpdate() Notifications.managerUpdate()
else if (Config.magiskVersionCode < Config.remoteManagerVersionCode) else if (Info.magiskVersionCode < Info.remoteManagerVersionCode)
Notifications.magiskUpdate() Notifications.magiskUpdate()
ListenableWorker.Result.success() ListenableWorker.Result.success()
}.getOrElse { }.getOrElse {

View File

@ -4,12 +4,6 @@ import android.content.Context;
import android.net.Network; import android.net.Network;
import android.net.Uri; import android.net.Uri;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -17,6 +11,12 @@ import androidx.annotation.RequiresApi;
import androidx.work.Data; import androidx.work.Data;
import androidx.work.ListenableWorker; import androidx.work.ListenableWorker;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class DelegateWorker { public abstract class DelegateWorker {
private ListenableWorker worker; private ListenableWorker worker;

View File

@ -8,8 +8,8 @@ import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.net.DownloadProgressListener; import com.topjohnwu.net.DownloadProgressListener;
import com.topjohnwu.net.Networking; import com.topjohnwu.net.Networking;
@ -123,9 +123,9 @@ public abstract class MagiskInstaller {
File zip = new File(App.self.getCacheDir(), "magisk.zip"); File zip = new File(App.self.getCacheDir(), "magisk.zip");
if (!ShellUtils.checkSum("MD5", zip, Config.magiskMD5)) { if (!ShellUtils.checkSum("MD5", zip, Info.magiskMD5)) {
console.add("- Downloading zip"); console.add("- Downloading zip");
Networking.get(Config.magiskLink) Networking.get(Info.magiskLink)
.setDownloadProgressListener(new ProgressLog()) .setDownloadProgressListener(new ProgressLog())
.execForFile(zip); .execForFile(zip);
} else { } else {
@ -282,10 +282,10 @@ public abstract class MagiskInstaller {
return false; return false;
} }
if (!Shell.sh(Utils.fmt( if (!Shell.sh(Utils.INSTANCE.fmt(
"KEEPFORCEENCRYPT=%b KEEPVERITY=%b RECOVERYMODE=%b " + "KEEPFORCEENCRYPT=%b KEEPVERITY=%b RECOVERYMODE=%b " +
"sh update-binary sh boot_patch.sh %s", "sh update-binary sh boot_patch.sh %s",
Config.keepEnc, Config.keepVerity, Config.recovery, srcBoot)) Info.keepEnc, Info.keepVerity, Info.recovery, srcBoot))
.to(console, logs).exec().isSuccess()) .to(console, logs).exec().isSuccess())
return false; return false;
@ -311,10 +311,10 @@ public abstract class MagiskInstaller {
} }
protected boolean flashBoot() { protected boolean flashBoot() {
if (!Shell.su(Utils.fmt("direct_install %s %s", installDir, srcBoot)) if (!Shell.su(Utils.INSTANCE.fmt("direct_install %s %s", installDir, srcBoot))
.to(console, logs).exec().isSuccess()) .to(console, logs).exec().isSuccess())
return false; return false;
if (!Config.keepVerity) if (!Info.keepVerity)
Shell.su("patch_dtbo_image").to(console, logs).exec(); Shell.su("patch_dtbo_image").to(console, logs).exec();
return true; return true;
} }

View File

@ -3,6 +3,8 @@ package com.topjohnwu.magisk.tasks;
import android.database.Cursor; import android.database.Cursor;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config; import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
@ -31,7 +33,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import androidx.annotation.NonNull;
import io.reactivex.Single; import io.reactivex.Single;
@Deprecated @Deprecated
@ -74,10 +75,10 @@ public class UpdateRepos {
* first page is updated to determine whether the online repo database is changed * first page is updated to determine whether the online repo database is changed
*/ */
private boolean parsePage(int page) { private boolean parsePage(int page) {
Request req = Networking.get(Utils.fmt(Const.Url.REPO_URL, page + 1)); Request req = Networking.get(Utils.INSTANCE.fmt(Const.Url.REPO_URL, page + 1));
if (page == 0) { if (page == 0) {
String etag = Config.get(Config.Key.ETAG_KEY); String etag = Config.getEtagKey();
if (etag != null) if (!etag.isEmpty())
req.addHeaders(Const.Key.IF_NONE_MATCH, etag); req.addHeaders(Const.Key.IF_NONE_MATCH, etag);
} }
Request.Result<JSONArray> res = req.execForJSONArray(); Request.Result<JSONArray> res = req.execForJSONArray();
@ -110,7 +111,7 @@ public class UpdateRepos {
String etag = res.getConnection().getHeaderField(Config.Key.ETAG_KEY); String etag = res.getConnection().getHeaderField(Config.Key.ETAG_KEY);
if (etag != null) { if (etag != null) {
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1); etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
Config.set(Config.Key.ETAG_KEY, etag); Config.setEtagKey(etag);
} }
} }

View File

@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.ClassMap import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ActivityMainBinding import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
@ -105,11 +106,11 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
private fun checkHideSection() { private fun checkHideSection() {
val menu = binding.navView.menu val menu = binding.navView.menu
menu.findItem(R.id.magiskHideFragment).isVisible = menu.findItem(R.id.magiskHideFragment).isVisible =
Shell.rootAccess() && Config.get<Any>(Config.Key.MAGISKHIDE) as Boolean Shell.rootAccess() && Config.magiskHide
menu.findItem(R.id.modulesFragment).isVisible = menu.findItem(R.id.modulesFragment).isVisible =
Shell.rootAccess() && Config.magiskVersionCode >= 0 Shell.rootAccess() && Info.magiskVersionCode >= 0
menu.findItem(R.id.reposFragment).isVisible = menu.findItem(R.id.reposFragment).isVisible =
(Networking.checkNetworkStatus(this) && Shell.rootAccess() && Config.magiskVersionCode >= 0) (Networking.checkNetworkStatus(this) && Shell.rootAccess() && Info.magiskVersionCode >= 0)
menu.findItem(R.id.logFragment).isVisible = menu.findItem(R.id.logFragment).isVisible =
Shell.rootAccess() Shell.rootAccess()
menu.findItem(R.id.superuserFragment).isVisible = menu.findItem(R.id.superuserFragment).isVisible =

View File

@ -7,6 +7,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.skoumal.teanity.extensions.subscribeK import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.tasks.UpdateRepos import com.topjohnwu.magisk.tasks.UpdateRepos
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
@ -21,7 +22,7 @@ open class SplashActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Shell.getShell { Shell.getShell {
if (Config.magiskVersionCode > 0 && Config.magiskVersionCode < Const.MagiskVersion.MIN_SUPPORT) { if (Info.magiskVersionCode > 0 && Info.magiskVersionCode < Const.MagiskVersion.MIN_SUPPORT) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.unsupport_magisk_title) .setTitle(R.string.unsupport_magisk_title)
.setMessage(R.string.unsupport_magisk_message) .setMessage(R.string.unsupport_magisk_message)
@ -35,9 +36,9 @@ open class SplashActivity : AppCompatActivity() {
} }
private fun initAndStart() { private fun initAndStart() {
val pkg = Config.get<String>(Config.Key.SU_MANAGER) val pkg = Config.suManager
if (pkg != null && packageName == BuildConfig.APPLICATION_ID) { if (Config.suManager.isNotEmpty() && packageName == BuildConfig.APPLICATION_ID) {
Config.remove(Config.Key.SU_MANAGER) get<SettingsDao>().delete(Config.Key.SU_MANAGER)
Shell.su("pm uninstall $pkg").submit() Shell.su("pm uninstall $pkg").submit()
} }
if (TextUtils.equals(pkg, packageName)) { if (TextUtils.equals(pkg, packageName)) {
@ -56,15 +57,12 @@ open class SplashActivity : AppCompatActivity() {
// Schedule periodic update checks // Schedule periodic update checks
Utils.scheduleUpdateCheck() Utils.scheduleUpdateCheck()
//CheckUpdates.check()
// Setup shortcuts // Setup shortcuts
Shortcuts.setup(this) Shortcuts.setup(this)
// Magisk working as expected // Magisk working as expected
if (Shell.rootAccess() && Config.magiskVersionCode > 0) { if (Shell.rootAccess() && Info.magiskVersionCode > 0) {
// Load modules
//Utils.loadModules(false)
// Load repos // Load repos
if (Networking.checkNetworkStatus(this)) { if (Networking.checkNetworkStatus(this)) {
get<UpdateRepos>().exec().subscribeK() get<UpdateRepos>().exec().subscribeK()

View File

@ -12,8 +12,6 @@ import androidx.core.view.isVisible
import androidx.preference.* import androidx.preference.*
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.App import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -63,22 +61,6 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom) view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
} }
protected fun setCustomUpdateChannel(userRepo: String) {
KConfig.customUpdateChannel = userRepo
}
protected fun getChannelCompat(channel: Int): KConfig.UpdateChannel {
return when (channel) {
Config.Value.STABLE_CHANNEL,
Config.Value.DEFAULT_CHANNEL -> KConfig.UpdateChannel.STABLE
Config.Value.BETA_CHANNEL -> KConfig.UpdateChannel.BETA
Config.Value.CANARY_CHANNEL -> KConfig.UpdateChannel.CANARY
Config.Value.CANARY_DEBUG_CHANNEL -> KConfig.UpdateChannel.CANARY_DEBUG
Config.Value.CUSTOM_CHANNEL -> KConfig.UpdateChannel.CUSTOM
else -> KConfig.updateChannel
}
}
protected fun <T: Preference> findPref(key: CharSequence): T { protected fun <T: Preference> findPref(key: CharSequence): T {
return findPreference(key) as T return findPreference(key) as T
} }

View File

@ -51,14 +51,12 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
get() = navigationController?.let { it.currentStackIndex != defaultPosition } ?: false get() = navigationController?.let { it.currentStackIndex != defaultPosition } ?: false
init { init {
val isDarkTheme = Config.get<Boolean>(Config.Key.DARK_THEME) val theme = if (Config.darkTheme) {
val theme = if (isDarkTheme) {
AppCompatDelegate.MODE_NIGHT_YES AppCompatDelegate.MODE_NIGHT_YES
} else { } else {
AppCompatDelegate.MODE_NIGHT_NO AppCompatDelegate.MODE_NIGHT_NO
} }
AppCompatDelegate.setDefaultNightMode(theme) AppCompatDelegate.setDefaultNightMode(theme)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
} }
override fun applyOverrideConfiguration(config: Configuration?) { override fun applyOverrideConfiguration(config: Configuration?) {

View File

@ -28,7 +28,7 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
(findItem(R.id.app_search).actionView as? SearchView) (findItem(R.id.app_search).actionView as? SearchView)
?.setOnQueryTextListener(this@MagiskHideFragment) ?.setOnQueryTextListener(this@MagiskHideFragment)
val showSystem = Config.get<Boolean>(Config.Key.SHOW_SYSTEM_APP) val showSystem = Config.showSystemApp
findItem(R.id.show_system).isChecked = showSystem findItem(R.id.show_system).isChecked = showSystem
viewModel.isShowSystem.value = showSystem viewModel.isShowSystem.value = showSystem
@ -39,10 +39,8 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
if (item.itemId == R.id.show_system) { if (item.itemId == R.id.show_system) {
val showSystem = !item.isChecked val showSystem = !item.isChecked
item.isChecked = showSystem item.isChecked = showSystem
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem) Config.showSystemApp = showSystem
viewModel.isShowSystem.value = showSystem viewModel.isShowSystem.value = showSystem
//adapter!!.setShowSystem(showSystem)
//adapter!!.filter(search!!.query.toString())
} }
return true return true
} }
@ -56,9 +54,4 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
viewModel.query.value = query.orEmpty() viewModel.query.value = query.orEmpty()
return false return false
} }
/*override fun onEvent(event: Int) {
//mSwipeRefreshLayout!!.isRefreshing = false
adapter!!.filter(search!!.query.toString())
}*/
} }

View File

@ -56,7 +56,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private fun installMagisk() { private fun installMagisk() {
// Show Manager update first // Show Manager update first
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) { if (Info.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
installManager() installManager()
return return
} }

View File

@ -4,10 +4,7 @@ import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSubscribeUi import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.observer.Observer import com.topjohnwu.magisk.model.observer.Observer
@ -25,8 +22,8 @@ class HomeViewModel(
val isAdvancedExpanded = KObservableField(false) val isAdvancedExpanded = KObservableField(false)
val isForceEncryption = KObservableField(Config.keepEnc) val isForceEncryption = KObservableField(Info.keepEnc)
val isKeepVerity = KObservableField(Config.keepVerity) val isKeepVerity = KObservableField(Info.keepVerity)
val magiskState = KObservableField(MagiskState.LOADING) val magiskState = KObservableField(MagiskState.LOADING)
val magiskStateText = Observer(magiskState) { val magiskStateText = Observer(magiskState) {
@ -41,7 +38,7 @@ class HomeViewModel(
val magiskCurrentVersion = KObservableField("") val magiskCurrentVersion = KObservableField("")
val magiskLatestVersion = KObservableField("") val magiskLatestVersion = KObservableField("")
val magiskAdditionalInfo = Observer(magiskState) { val magiskAdditionalInfo = Observer(magiskState) {
if (Config.get<Boolean>(Config.Key.COREONLY)) if (Config.coreOnly)
R.string.core_only_enabled.res() R.string.core_only_enabled.res()
else else
"" ""
@ -87,10 +84,10 @@ class HomeViewModel(
init { init {
isForceEncryption.addOnPropertyChangedCallback { isForceEncryption.addOnPropertyChangedCallback {
Config.keepEnc = it ?: return@addOnPropertyChangedCallback Info.keepEnc = it ?: return@addOnPropertyChangedCallback
} }
isKeepVerity.addOnPropertyChangedCallback { isKeepVerity.addOnPropertyChangedCallback {
Config.keepVerity = it ?: return@addOnPropertyChangedCallback Info.keepVerity = it ?: return@addOnPropertyChangedCallback
} }
refresh() refresh()
@ -154,7 +151,7 @@ class HomeViewModel(
} }
fun refresh() { fun refresh() {
magiskRepo.fetchConfig() magiskRepo.fetchUpdate()
.applyViewModel(this) .applyViewModel(this)
.doOnSubscribeUi { .doOnSubscribeUi {
magiskState.value = MagiskState.LOADING magiskState.value = MagiskState.LOADING
@ -164,14 +161,6 @@ class HomeViewModel(
safetyNetTitle.value = R.string.safetyNet_check_text safetyNetTitle.value = R.string.safetyNet_check_text
} }
.subscribeK { .subscribeK {
it.app.let {
Config.remoteManagerVersionCode = it.versionCode.toIntOrNull() ?: -1
Config.remoteManagerVersionString = it.version
}
it.magisk.let {
Config.remoteMagiskVersionCode = it.versionCode.toIntOrNull() ?: -1
Config.remoteMagiskVersionString = it.version
}
updateSelf() updateSelf()
ensureEnv() ensureEnv()
} }
@ -181,22 +170,22 @@ class HomeViewModel(
private fun updateSelf() { private fun updateSelf() {
state = State.LOADED state = State.LOADED
magiskState.value = when (Config.magiskVersionCode) { magiskState.value = when (Info.magiskVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
!in Config.remoteMagiskVersionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE !in Info.remoteMagiskVersionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE else -> MagiskState.UP_TO_DATE
} }
magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) { magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) {
version.format(Config.magiskVersionString, Config.magiskVersionCode) version.format(Info.magiskVersionString, Info.magiskVersionCode)
} else { } else {
"" ""
} }
magiskLatestVersion.value = version magiskLatestVersion.value = version
.format(Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode) .format(Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode)
managerState.value = when (Config.remoteManagerVersionCode) { managerState.value = when (Info.remoteManagerVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED //wrong update channel in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED //wrong update channel
in (BuildConfig.VERSION_CODE + 1)..Int.MAX_VALUE -> MagiskState.OBSOLETE in (BuildConfig.VERSION_CODE + 1)..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE else -> MagiskState.UP_TO_DATE
@ -206,7 +195,7 @@ class HomeViewModel(
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) .format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
managerLatestVersion.value = version managerLatestVersion.value = version
.format(Config.remoteManagerVersionString, Config.remoteManagerVersionCode) .format(Info.remoteManagerVersionString, Info.remoteManagerVersionCode)
} }
private fun ensureEnv() { private fun ensureEnv() {

View File

@ -53,9 +53,9 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
.setTitle(R.string.sorting_order) .setTitle(R.string.sorting_order)
.setSingleChoiceItems( .setSingleChoiceItems(
R.array.sorting_orders, R.array.sorting_orders,
Config.get<Int>(Config.Key.REPO_ORDER)!! Config.repoOrder
) { d, which -> ) { d, which ->
Config.set(Config.Key.REPO_ORDER, which) Config.repoOrder = which
viewModel.refresh(false) viewModel.refresh(false)
d.dismiss() d.dismiss()
}.show() }.show()

View File

@ -13,10 +13,11 @@ import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import com.skoumal.teanity.extensions.subscribeK import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.KConfig.UpdateChannel import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
import com.topjohnwu.magisk.data.repository.SettingRepository
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment import com.topjohnwu.magisk.ui.base.BasePreferenceFragment
import com.topjohnwu.magisk.utils.* import com.topjohnwu.magisk.utils.*
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
@ -27,7 +28,6 @@ import org.koin.android.ext.android.inject
class SettingsFragment : BasePreferenceFragment() { class SettingsFragment : BasePreferenceFragment() {
private val repoDatabase: RepoDatabaseHelper by inject() private val repoDatabase: RepoDatabaseHelper by inject()
private val settingRepo: SettingRepository by inject()
private lateinit var updateChannel: ListPreference private lateinit var updateChannel: ListPreference
private lateinit var autoRes: ListPreference private lateinit var autoRes: ListPreference
@ -53,7 +53,7 @@ class SettingsFragment : BasePreferenceFragment() {
requestTimeout = findPref(Config.Key.SU_REQUEST_TIMEOUT) requestTimeout = findPref(Config.Key.SU_REQUEST_TIMEOUT)
suNotification = findPref(Config.Key.SU_NOTIFICATION) suNotification = findPref(Config.Key.SU_NOTIFICATION)
multiuserConfig = findPref(Config.Key.SU_MULTIUSER_MODE) multiuserConfig = findPref(Config.Key.SU_MULTIUSER_MODE)
nsConfig = findPref(Config.Key.SU_MNT_NS) as ListPreference nsConfig = findPref(Config.Key.SU_MNT_NS)
val reauth = findPreference(Config.Key.SU_REAUTH) as SwitchPreferenceCompat val reauth = findPreference(Config.Key.SU_REAUTH) as SwitchPreferenceCompat
val fingerprint = findPreference(Config.Key.SU_FINGERPRINT) as SwitchPreferenceCompat val fingerprint = findPreference(Config.Key.SU_FINGERPRINT) as SwitchPreferenceCompat
val generalCatagory = findPreference("general") as PreferenceCategory val generalCatagory = findPreference("general") as PreferenceCategory
@ -70,7 +70,9 @@ class SettingsFragment : BasePreferenceFragment() {
true true
} }
findPreference("clear").setOnPreferenceClickListener { findPreference("clear").setOnPreferenceClickListener {
prefs.edit().remove(Config.Key.ETAG_KEY).apply() prefs.edit {
remove(Config.Key.ETAG_KEY)
}
repoDatabase.clearRepo() repoDatabase.clearRepo()
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT) Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
true true
@ -81,30 +83,23 @@ class SettingsFragment : BasePreferenceFragment() {
true true
} }
prefs.edit {
putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
}
updateChannel.setOnPreferenceChangeListener { _, value -> updateChannel.setOnPreferenceChangeListener { _, value ->
val channel = Integer.parseInt(value as String) val channel = Integer.parseInt(value as String)
val previous = Config.updateChannel
val previousUpdateChannel = KConfig.updateChannel if (channel == Config.Value.CUSTOM_CHANNEL) {
val updateChannel = getChannelCompat(channel) val v = LayoutInflater.from(requireActivity())
.inflate(R.layout.custom_channel_dialog, null)
KConfig.updateChannel = updateChannel
if (updateChannel === UpdateChannel.CUSTOM) {
val v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null)
val url = v.findViewById<EditText>(R.id.custom_url) val url = v.findViewById<EditText>(R.id.custom_url)
url.setText(KConfig.customUpdateChannel) url.setText(Config.customChannelUrl)
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
.setTitle(R.string.settings_update_custom) .setTitle(R.string.settings_update_custom)
.setView(v) .setView(v)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
setCustomUpdateChannel(url.text.toString()) } Config.customChannelUrl = url.text.toString() }
.setNegativeButton(R.string.close) { _, _ -> .setNegativeButton(R.string.close) { _, _ ->
KConfig.updateChannel = previousUpdateChannel } Config.updateChannel = previous }
.setOnCancelListener { KConfig.updateChannel = previousUpdateChannel } .setOnCancelListener { Config.updateChannel = previous }
.show() .show()
} }
true true
@ -114,7 +109,7 @@ class SettingsFragment : BasePreferenceFragment() {
/* We only show canary channels if user is already on canary channel /* We only show canary channels if user is already on canary channel
* or the user have already chosen canary channel */ * or the user have already chosen canary channel */
if (!Utils.isCanary() && Config.get<Int>(Config.Key.UPDATE_CHANNEL) < Config.Value.CANARY_CHANNEL) { if (!Utils.isCanary && Config.updateChannel < Config.Value.CANARY_CHANNEL) {
// Remove the last 2 entries // Remove the last 2 entries
val entries = updateChannel.entries val entries = updateChannel.entries
updateChannel.entries = entries.copyOf(entries.size - 2) updateChannel.entries = entries.copyOf(entries.size - 2)
@ -168,8 +163,9 @@ class SettingsFragment : BasePreferenceFragment() {
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
when (key) { when (key) {
Config.Key.ROOT_ACCESS, Config.Key.SU_MULTIUSER_MODE, Config.Key.SU_MNT_NS -> Config.Key.ROOT_ACCESS -> Config.rootMode = Utils.getPrefsInt(prefs, key)
settingRepo.put(key, Utils.getPrefsInt(prefs, key)).subscribe() Config.Key.SU_MULTIUSER_MODE -> Config.suMultiuserMode = Utils.getPrefsInt(prefs, key)
Config.Key.SU_MNT_NS -> Config.suMntNamespaceMode = Utils.getPrefsInt(prefs, key)
Config.Key.DARK_THEME -> requireActivity().recreate() Config.Key.DARK_THEME -> requireActivity().recreate()
Config.Key.COREONLY -> { Config.Key.COREONLY -> {
if (prefs.getBoolean(key, false)) { if (prefs.getBoolean(key, false)) {
@ -196,14 +192,13 @@ class SettingsFragment : BasePreferenceFragment() {
} }
override fun onPreferenceTreeClick(preference: Preference): Boolean { override fun onPreferenceTreeClick(preference: Preference): Boolean {
val key = preference.key when (preference.key) {
when (key) {
Config.Key.SU_FINGERPRINT -> { Config.Key.SU_FINGERPRINT -> {
val checked = (preference as SwitchPreferenceCompat).isChecked val checked = (preference as SwitchPreferenceCompat).isChecked
preference.isChecked = !checked preference.isChecked = !checked
FingerprintAuthDialog(requireActivity()) { FingerprintAuthDialog(requireActivity()) {
preference.isChecked = checked preference.isChecked = checked
Config.set(key, checked) Config.suFingerprint = checked
}.show() }.show()
} }
} }
@ -237,34 +232,34 @@ class SettingsFragment : BasePreferenceFragment() {
private fun setSummary(key: String) { private fun setSummary(key: String) {
when (key) { when (key) {
Config.Key.ROOT_ACCESS -> rootConfig.summary = resources
.getStringArray(R.array.su_access)[Config.rootMode]
Config.Key.SU_MULTIUSER_MODE -> multiuserConfig.summary = resources
.getStringArray(R.array.multiuser_summary)[Config.suMultiuserMode]
Config.Key.SU_MNT_NS -> nsConfig.summary = resources
.getStringArray(R.array.namespace_summary)[Config.suMntNamespaceMode]
Config.Key.UPDATE_CHANNEL -> { Config.Key.UPDATE_CHANNEL -> {
var ch = Config.get<Int>(key) var ch = Config.updateChannel
ch = if (ch < 0) Config.Value.STABLE_CHANNEL else ch ch = if (ch < 0) Config.Value.STABLE_CHANNEL else ch
updateChannel.summary = resources updateChannel.summary = resources
.getStringArray(R.array.update_channel)[ch] .getStringArray(R.array.update_channel)[ch]
} }
Config.Key.ROOT_ACCESS -> rootConfig.summary = resources
.getStringArray(R.array.su_access)[Config.get<Int>(key)]
Config.Key.SU_AUTO_RESPONSE -> autoRes.summary = resources Config.Key.SU_AUTO_RESPONSE -> autoRes.summary = resources
.getStringArray(R.array.auto_response)[Config.get<Int>(key)] .getStringArray(R.array.auto_response)[Config.suAutoReponse]
Config.Key.SU_NOTIFICATION -> suNotification.summary = resources Config.Key.SU_NOTIFICATION -> suNotification.summary = resources
.getStringArray(R.array.su_notification)[Config.get<Int>(key)] .getStringArray(R.array.su_notification)[Config.suNotification]
Config.Key.SU_REQUEST_TIMEOUT -> requestTimeout.summary = Config.Key.SU_REQUEST_TIMEOUT -> requestTimeout.summary =
getString(R.string.request_timeout_summary, Config.get<Int>(key)) getString(R.string.request_timeout_summary, Config.suDefaultTimeout)
Config.Key.SU_MULTIUSER_MODE -> multiuserConfig.summary =
resources.getStringArray(R.array.multiuser_summary)[Config.get<Int>(key)]
Config.Key.SU_MNT_NS -> nsConfig.summary = resources
.getStringArray(R.array.namespace_summary)[Config.get<Int>(key)]
} }
} }
private fun setSummary() { private fun setSummary() {
setSummary(Config.Key.UPDATE_CHANNEL)
setSummary(Config.Key.ROOT_ACCESS) setSummary(Config.Key.ROOT_ACCESS)
setSummary(Config.Key.SU_MULTIUSER_MODE)
setSummary(Config.Key.SU_MNT_NS)
setSummary(Config.Key.UPDATE_CHANNEL)
setSummary(Config.Key.SU_AUTO_RESPONSE) setSummary(Config.Key.SU_AUTO_RESPONSE)
setSummary(Config.Key.SU_NOTIFICATION) setSummary(Config.Key.SU_NOTIFICATION)
setSummary(Config.Key.SU_REQUEST_TIMEOUT) setSummary(Config.Key.SU_REQUEST_TIMEOUT)
setSummary(Config.Key.SU_MULTIUSER_MODE)
setSummary(Config.Key.SU_MNT_NS)
} }
} }

View File

@ -173,7 +173,7 @@ class SuRequestViewModel(
return true return true
} }
when (Config.get<Int>(Config.Key.SU_AUTO_RESPONSE)) { when (Config.suAutoReponse) {
Config.Value.SU_AUTO_DENY -> { Config.Value.SU_AUTO_DENY -> {
handler?.handleAction(Policy.DENY, 0) handler?.handleAction(Policy.DENY, 0)
return true return true
@ -190,8 +190,7 @@ class SuRequestViewModel(
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun showUI() { private fun showUI() {
val seconds = Config.get<Int>(Config.Key.SU_REQUEST_TIMEOUT).toLong() val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
val millis = SECONDS.toMillis(seconds)
timer = object : CountDownTimer(millis, 1000) { timer = object : CountDownTimer(millis, 1000) {
override fun onTick(remains: Long) { override fun onTick(remains: Long) {
denyText.value = "%s (%d)" denyText.value = "%s (%d)"

View File

@ -6,6 +6,7 @@ import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.BuildConfig; import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config; import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.SplashActivity; import com.topjohnwu.magisk.ui.SplashActivity;
import com.topjohnwu.magisk.view.ProgressNotification; import com.topjohnwu.magisk.view.ProgressNotification;
@ -24,8 +25,8 @@ public class DownloadApp {
} }
public static void restore() { public static void restore() {
String name = Utils.fmt("MagiskManager v%s(%d)", String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode); Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
dlInstall(name, new RestoreManager()); dlInstall(name, new RestoreManager());
} }
@ -33,7 +34,7 @@ public class DownloadApp {
File apk = new File(App.self.getCacheDir(), "manager.apk"); File apk = new File(App.self.getCacheDir(), "manager.apk");
ProgressNotification progress = new ProgressNotification(name); ProgressNotification progress = new ProgressNotification(name);
listener.progress = progress; listener.progress = progress;
Networking.get(Config.managerLink) Networking.get(Info.managerLink)
.setExecutor(App.THREAD_POOL) .setExecutor(App.THREAD_POOL)
.setDownloadProgressListener(progress) .setDownloadProgressListener(progress)
.setErrorHandler((conn, e) -> progress.dlFail()) .setErrorHandler((conn, e) -> progress.dlFail())

View File

@ -1,124 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@TargetApi(Build.VERSION_CODES.M)
public abstract class FingerprintHelper {
private FingerprintManager manager;
private Cipher cipher;
private CancellationSignal cancel;
private static final String SU_KEYSTORE_KEY = "su_key";
public static boolean useFingerprint() {
boolean fp = Config.get(Config.Key.SU_FINGERPRINT);
if (fp && !canUseFingerprint()) {
Config.set(Config.Key.SU_FINGERPRINT, false);
fp = false;
}
return fp;
}
public static boolean canUseFingerprint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return false;
KeyguardManager km = App.self.getSystemService(KeyguardManager.class);
FingerprintManager fm = App.self.getSystemService(FingerprintManager.class);
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
}
protected FingerprintHelper() throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
manager = App.self.getSystemService(FingerprintManager.class);
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(SU_KEYSTORE_KEY, null);
if (key == null) {
key = generateKey();
}
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (KeyPermanentlyInvalidatedException e) {
// Only happens on Marshmallow
key = generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);
}
}
public abstract void onAuthenticationError(int errorCode, CharSequence errString);
public abstract void onAuthenticationHelp(int helpCode, CharSequence helpString);
public abstract void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result);
public abstract void onAuthenticationFailed();
public void authenticate() {
cancel = new CancellationSignal();
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
manager.authenticate(cryptoObject, cancel, 0, new Callback(), null);
}
public void cancel() {
if (cancel != null)
cancel.cancel();
}
private SecretKey generateKey() throws Exception {
KeyGenerator keygen = KeyGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
SU_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(false);
}
keygen.init(builder.build());
return keygen.generateKey();
}
private class Callback extends FingerprintManager.AuthenticationCallback {
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
FingerprintHelper.this.onAuthenticationError(errorCode, errString);
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
FingerprintHelper.this.onAuthenticationSucceeded(result);
}
@Override
public void onAuthenticationFailed() {
FingerprintHelper.this.onAuthenticationFailed();
}
}
}

View File

@ -0,0 +1,119 @@
package com.topjohnwu.magisk.utils
import android.annotation.TargetApi
import android.app.KeyguardManager
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.os.CancellationSignal
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import com.topjohnwu.magisk.Config
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
@TargetApi(Build.VERSION_CODES.M)
abstract class FingerprintHelper @Throws(Exception::class)
protected constructor() {
private val manager: FingerprintManager?
private val cipher: Cipher
private var cancel: CancellationSignal? = null
private val context: Context by inject()
init {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
manager = context.getSystemService(FingerprintManager::class.java)
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
keyStore.load(null)
var key = keyStore.getKey(SU_KEYSTORE_KEY, null) as SecretKey? ?: generateKey()
runCatching {
cipher.init(Cipher.ENCRYPT_MODE, key)
}.onFailure {
// Only happens on Marshmallow
key = generateKey()
cipher.init(Cipher.ENCRYPT_MODE, key)
}
}
abstract fun onAuthenticationError(errorCode: Int, errString: CharSequence)
abstract fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence)
abstract fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult)
abstract fun onAuthenticationFailed()
fun authenticate() {
cancel = CancellationSignal()
val cryptoObject = FingerprintManager.CryptoObject(cipher)
manager!!.authenticate(cryptoObject, cancel, 0, Callback(), null)
}
fun cancel() {
if (cancel != null)
cancel!!.cancel()
}
@Throws(Exception::class)
private fun generateKey(): SecretKey {
val keygen = KeyGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val builder = KeyGenParameterSpec.Builder(
SU_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(false)
}
keygen.init(builder.build())
return keygen.generateKey()
}
private inner class Callback : FingerprintManager.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
this@FingerprintHelper.onAuthenticationError(errorCode, errString)
}
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
this@FingerprintHelper.onAuthenticationHelp(helpCode, helpString)
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
this@FingerprintHelper.onAuthenticationSucceeded(result)
}
override fun onAuthenticationFailed() {
this@FingerprintHelper.onAuthenticationFailed()
}
}
companion object {
private const val SU_KEYSTORE_KEY = "su_key"
fun useFingerprint(): Boolean {
var fp = Config.suFingerprint
if (fp && !canUseFingerprint()) {
Config.suFingerprint = false
fp = false
}
return fp
}
fun canUseFingerprint(context: Context = get()): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return false
val km = context.getSystemService(KeyguardManager::class.java)
val fm = context.getSystemService(FingerprintManager::class.java)
return km?.isKeyguardSecure ?: false &&
fm != null && fm.isHardwareDetected && fm.hasEnrolledFingerprints()
}
}
}

View File

@ -107,9 +107,9 @@ object LocaleManager {
@JvmStatic @JvmStatic
fun setLocale(wrapper: ContextWrapper) { fun setLocale(wrapper: ContextWrapper) {
val localeConfig = Config.get<String>(Config.Key.LOCALE) val localeConfig = Config.locale
locale = when { locale = when {
localeConfig.isNullOrEmpty() -> defaultLocale localeConfig.isEmpty() -> defaultLocale
else -> forLanguageTag(localeConfig) else -> forLanguageTag(localeConfig)
} }
Locale.setDefault(locale) Locale.setDefault(locale)

View File

@ -13,7 +13,7 @@ public class Logger {
} }
public static void debug(String fmt, Object... args) { public static void debug(String fmt, Object... args) {
debug(Utils.fmt(fmt, args)); debug(Utils.INSTANCE.fmt(fmt, args));
} }
public static void error(String line) { public static void error(String line) {
@ -21,6 +21,6 @@ public class Logger {
} }
public static void error(String fmt, Object... args) { public static void error(String fmt, Object... args) {
error(Utils.fmt(fmt, args)); error(Utils.INSTANCE.fmt(fmt, args));
} }
} }

View File

@ -3,6 +3,8 @@ package com.topjohnwu.magisk.utils;
import android.content.ComponentName; import android.content.ComponentName;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.BuildConfig; import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
@ -27,8 +29,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import androidx.core.app.NotificationCompat;
public class PatchAPK { public class PatchAPK {
public static final String LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"; public static final String LOWERALPHA = "abcdefghijklmnopqrstuvwxyz";
@ -110,7 +110,7 @@ public class PatchAPK {
if (!Shell.su("pm install " + repack).exec().isSuccess()) if (!Shell.su("pm install " + repack).exec().isSuccess())
return false; return false;
Config.set(Config.Key.SU_MANAGER, pkg); Config.setSuManager(pkg);
Config.export(); Config.export();
RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID, RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID,
new ComponentName(pkg, ClassMap.get(SplashActivity.class).getName())); new ComponentName(pkg, ClassMap.get(SplashActivity.class).getName()));
@ -145,7 +145,7 @@ public class PatchAPK {
Notifications.progress(app.getString(R.string.hide_manager_title)); Notifications.progress(app.getString(R.string.hide_manager_title));
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build()); Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build());
if(!patchAndHide()) if(!patchAndHide())
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG); Utils.INSTANCE.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID); Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID);
}); });
} }

View File

@ -4,8 +4,8 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
@ -132,7 +132,7 @@ class RootUtils : Shell.Initializer() {
job.add(context.rawResource(R.raw.util_functions)) job.add(context.rawResource(R.raw.util_functions))
.add(context.rawResource(R.raw.utils)) .add(context.rawResource(R.raw.utils))
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk") Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
Config.loadMagiskInfo() Info.loadMagiskInfo()
} else { } else {
job.add(context.rawResource(R.raw.nonroot_utils)) job.add(context.rawResource(R.raw.nonroot_utils))
} }
@ -143,9 +143,9 @@ class RootUtils : Shell.Initializer() {
"export BOOTMODE=true") "export BOOTMODE=true")
.exec() .exec()
Config.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean() Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
Config.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean() Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
Config.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean() Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
return true return true
} }
@ -158,7 +158,7 @@ class RootUtils : Shell.Initializer() {
@JvmStatic @JvmStatic
fun reboot() { fun reboot() {
Shell.su("/system/bin/reboot ${if (Config.recovery) "recovery" else ""}").submit() Shell.su("/system/bin/reboot ${if (Info.recovery) "recovery" else ""}").submit()
} }
} }
} }

View File

@ -1,10 +1,10 @@
package com.topjohnwu.magisk.utils package com.topjohnwu.magisk.utils
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Process import android.os.Process
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.AppRepository import com.topjohnwu.magisk.data.repository.AppRepository
@ -17,6 +17,8 @@ import java.util.*
object SuLogger { object SuLogger {
private val context: Context by inject()
@JvmStatic @JvmStatic
fun handleLogs(intent: Intent) { fun handleLogs(intent: Intent) {
@ -66,9 +68,9 @@ object SuLogger {
} }
private fun handleNotify(policy: MagiskPolicy) { private fun handleNotify(policy: MagiskPolicy) {
if (policy.notification && Config.get<Any>(Config.Key.SU_NOTIFICATION) as Int == Config.Value.NOTIFICATION_TOAST) { if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
Utils.toast( Utils.toast(
App.self.getString( context.getString(
if (policy.policy == Policy.ALLOW) if (policy.policy == Policy.ALLOW)
R.string.su_allow_toast R.string.su_allow_toast
else else

View File

@ -1,142 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
import android.widget.Toast;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.entity.OldModule;
import com.topjohnwu.magisk.model.update.UpdateCheckService;
import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import com.topjohnwu.superuser.io.SuFile;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import androidx.annotation.WorkerThread;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ListenableWorker;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
public class Utils {
public static void toast(CharSequence msg, int duration) {
UiThreadHandler.run(() -> Toast.makeText(App.self, msg, duration).show());
}
public static void toast(int resId, int duration) {
UiThreadHandler.run(() -> Toast.makeText(App.self, resId, duration).show());
}
public static String dlString(String url) {
String s = Networking.get(url).execForString().getResult();
return s == null ? "" : s;
}
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
}
public static int getPrefsInt(SharedPreferences prefs, String key) {
return getPrefsInt(prefs, key, 0);
}
public static int dpInPx(int dp) {
float scale = App.self.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5);
}
public static String fmt(String fmt, Object... args) {
return String.format(Locale.US, fmt, args);
}
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
try {
if (info.labelRes > 0) {
Resources res = pm.getResourcesForApplication(info);
Configuration config = new Configuration();
config.setLocale(LocaleManager.getLocale());
res.updateConfiguration(config, res.getDisplayMetrics());
return res.getString(info.labelRes);
}
} catch (Exception ignored) {}
return info.loadLabel(pm).toString();
}
public static String getLegalFilename(CharSequence filename) {
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
.replace("#", "").replace("@", "").replace("\\", "_");
}
@WorkerThread
public static Map<String, OldModule> loadModulesLeanback() {
final Map<String, OldModule> moduleMap = new ValueSortedMap<>();
final SuFile path = new SuFile(Const.MAGISK_PATH);
final SuFile[] modules = path.listFiles((file, name) ->
!name.equals("lost+found") && !name.equals(".core")
);
for (SuFile file : modules) {
if (file.isFile()) continue;
OldModule module = new OldModule(Const.MAGISK_PATH + "/" + file.getName());
moduleMap.put(module.getId(), module);
}
return moduleMap;
}
public static boolean showSuperUser() {
return Shell.rootAccess() && (Const.USER_ID == 0 ||
(int) Config.get(Config.Key.SU_MULTIUSER_MODE) !=
Config.Value.MULTIUSER_MODE_OWNER_MANAGED);
}
public static boolean isCanary() {
return BuildConfig.VERSION_NAME.contains("-");
}
public static void scheduleUpdateCheck() {
if (Config.get(Config.Key.CHECK_UPDATES)) {
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.build();
PeriodicWorkRequest request = new PeriodicWorkRequest
.Builder(ClassMap.get(UpdateCheckService.class), 12, TimeUnit.HOURS)
.setConstraints(constraints)
.build();
WorkManager.getInstance().enqueueUniquePeriodicWork(
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
ExistingPeriodicWorkPolicy.REPLACE, request);
} else {
WorkManager.getInstance().cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID);
}
}
public static void openLink(Context context, Uri link) {
Intent intent = new Intent(Intent.ACTION_VIEW, link);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(intent);
} else {
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT);
}
}
}

View File

@ -0,0 +1,123 @@
package com.topjohnwu.magisk.utils
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.net.Uri
import android.widget.Toast
import androidx.annotation.WorkerThread
import androidx.work.*
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.OldModule
import com.topjohnwu.magisk.model.update.UpdateCheckService
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.io.SuFile
import java.util.*
import java.util.concurrent.TimeUnit
object Utils {
val isCanary: Boolean
get() = BuildConfig.VERSION_NAME.contains("-")
fun toast(msg: CharSequence, duration: Int) {
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
}
fun toast(resId: Int, duration: Int) {
UiThreadHandler.run { Toast.makeText(get(), resId, duration).show() }
}
fun dlString(url: String): String {
val s = Networking.get(url).execForString().result
return s ?: ""
}
fun getPrefsInt(prefs: SharedPreferences, key: String, def: Int = 0): Int {
return prefs.getString(key, def.toString())!!.toInt()
}
fun dpInPx(dp: Int): Int {
val scale = get<Resources>().displayMetrics.density
return (dp * scale + 0.5).toInt()
}
fun fmt(fmt: String, vararg args: Any): String {
return String.format(Locale.US, fmt, *args)
}
fun getAppLabel(info: ApplicationInfo, pm: PackageManager): String {
try {
if (info.labelRes > 0) {
val res = pm.getResourcesForApplication(info)
val config = Configuration()
config.setLocale(LocaleManager.locale)
res.updateConfiguration(config, res.displayMetrics)
return res.getString(info.labelRes)
}
} catch (ignored: Exception) {
}
return info.loadLabel(pm).toString()
}
fun getLegalFilename(filename: CharSequence): String {
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
.replace("#", "").replace("@", "").replace("\\", "_")
}
@WorkerThread
fun loadModulesLeanback(): Map<String, OldModule> {
val moduleMap = ValueSortedMap<String, OldModule>()
val path = SuFile(Const.MAGISK_PATH)
val modules = path.listFiles { _, name -> name != "lost+found" && name != ".core" }
for (file in modules!!) {
if (file.isFile) continue
val module = OldModule(Const.MAGISK_PATH + "/" + file.name)
moduleMap[module.id] = module
}
return moduleMap
}
fun showSuperUser(): Boolean {
return Shell.rootAccess() && (Const.USER_ID == 0
|| Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED)
}
fun scheduleUpdateCheck() {
if (Config.checkUpdate) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.build()
val request = PeriodicWorkRequest
.Builder(ClassMap[UpdateCheckService::class.java], 12, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
ExistingPeriodicWorkPolicy.REPLACE, request)
} else {
WorkManager.getInstance().cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
}
}
fun openLink(context: Context, link: Uri) {
val intent = Intent(Intent.ACTION_VIEW, link)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT)
}
}
}

View File

@ -1,12 +1,6 @@
package com.topjohnwu.magisk.utils package com.topjohnwu.magisk.utils
import android.content.Context import android.content.Context
import android.content.Intent
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.R
import okhttp3.ResponseBody import okhttp3.ResponseBody
import java.io.File import java.io.File
@ -19,36 +13,3 @@ fun ResponseBody.writeToFile(context: Context, fileName: String): File {
} }
fun ResponseBody.writeToString() = string() fun ResponseBody.writeToString() = string()
fun String.launch() = if (KConfig.useCustomTabs) {
launchWithCustomTabs()
} else {
launchWithIntent()
}
private fun String.launchWithCustomTabs() {
val context: Context by inject()
val primaryColor = ContextCompat.getColor(context, R.color.colorPrimary)
val secondaryColor = ContextCompat.getColor(context, R.color.colorSecondary)
CustomTabsIntent.Builder()
.enableUrlBarHiding()
.setShowTitle(true)
.setToolbarColor(primaryColor)
.setSecondaryToolbarColor(secondaryColor)
.build()
.apply { intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK }
.launchUrl(context, this.toUri())
}
private fun String.launchWithIntent() {
val context: Context by inject()
Intent(Intent.ACTION_VIEW)
.apply {
data = toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
.startActivity(context)
}

View File

@ -5,6 +5,8 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.net.Networking; import com.topjohnwu.net.Networking;
import com.topjohnwu.net.ResponseListener; import com.topjohnwu.net.ResponseListener;
@ -12,7 +14,6 @@ import com.topjohnwu.net.ResponseListener;
import java.io.InputStream; import java.io.InputStream;
import java.util.Scanner; import java.util.Scanner;
import androidx.appcompat.app.AlertDialog;
import ru.noties.markwon.Markwon; import ru.noties.markwon.Markwon;
import ru.noties.markwon.html.HtmlPlugin; import ru.noties.markwon.html.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.image.ImagesPlugin;

View File

@ -7,19 +7,19 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.TaskStackBuilder;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.receiver.GeneralReceiver; import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
import com.topjohnwu.magisk.ui.SplashActivity; import com.topjohnwu.magisk.ui.SplashActivity;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.TaskStackBuilder;
public class Notifications { public class Notifications {
public static NotificationManagerCompat mgr = NotificationManagerCompat.from(App.self); public static NotificationManagerCompat mgr = NotificationManagerCompat.from(App.self);
@ -62,12 +62,12 @@ public class Notifications {
public static void managerUpdate() { public static void managerUpdate() {
App app = App.self; App app = App.self;
String name = Utils.fmt("MagiskManager v%s(%d)", String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode); Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
Intent intent = new Intent(app, ClassMap.get(GeneralReceiver.class)); Intent intent = new Intent(app, ClassMap.get(GeneralReceiver.class));
intent.setAction(Const.Key.BROADCAST_MANAGER_UPDATE); intent.setAction(Const.Key.BROADCAST_MANAGER_UPDATE);
intent.putExtra(Const.Key.INTENT_SET_LINK, Config.managerLink); intent.putExtra(Const.Key.INTENT_SET_LINK, Info.managerLink);
intent.putExtra(Const.Key.INTENT_SET_NAME, name); intent.putExtra(Const.Key.INTENT_SET_NAME, name);
PendingIntent pendingIntent = PendingIntent.getBroadcast(app, PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT); Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);

View File

@ -5,13 +5,13 @@ import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.net.DownloadProgressListener; import com.topjohnwu.net.DownloadProgressListener;
import androidx.core.app.NotificationCompat;
public class ProgressNotification implements DownloadProgressListener { public class ProgressNotification implements DownloadProgressListener {
private NotificationCompat.Builder builder; private NotificationCompat.Builder builder;
@ -22,7 +22,7 @@ public class ProgressNotification implements DownloadProgressListener {
builder = Notifications.progress(title); builder = Notifications.progress(title);
prevTime = System.currentTimeMillis(); prevTime = System.currentTimeMillis();
update(); update();
Utils.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT); Utils.INSTANCE.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT);
} }
@Override @Override

View File

@ -1,79 +0,0 @@
package com.topjohnwu.magisk.view;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.Build;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.SplashActivity;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import java.util.ArrayList;
import androidx.annotation.RequiresApi;
public class Shortcuts {
public static void setup(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
manager.setDynamicShortcuts(getShortCuts(context));
}
}
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
private static ArrayList<ShortcutInfo> getShortCuts(Context context) {
ArrayList<ShortcutInfo> shortCuts = new ArrayList<>();
boolean root = Shell.rootAccess();
if (Utils.showSuperUser()) {
shortCuts.add(new ShortcutInfo.Builder(context, "superuser")
.setShortLabel(context.getString(R.string.superuser))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.putExtra(Const.Key.OPEN_SECTION, "superuser")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_superuser))
.setRank(0)
.build());
}
if (root && (boolean) Config.get(Config.Key.MAGISKHIDE)) {
shortCuts.add(new ShortcutInfo.Builder(context, "magiskhide")
.setShortLabel(context.getString(R.string.magiskhide))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide))
.setRank(1)
.build());
}
if (!(boolean) Config.get(Config.Key.COREONLY) && root && Config.magiskVersionCode >= 0) {
shortCuts.add(new ShortcutInfo.Builder(context, "modules")
.setShortLabel(context.getString(R.string.modules))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.putExtra(Const.Key.OPEN_SECTION, "modules")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_extension))
.setRank(3)
.build());
shortCuts.add(new ShortcutInfo.Builder(context, "downloads")
.setShortLabel(context.getString(R.string.downloads))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.putExtra(Const.Key.OPEN_SECTION, "downloads")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download))
.setRank(2)
.build());
}
return shortCuts;
}
}

View File

@ -0,0 +1,72 @@
package com.topjohnwu.magisk.view
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
object Shortcuts {
fun setup(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val manager = context.getSystemService(ShortcutManager::class.java)
manager?.dynamicShortcuts = getShortCuts(context)
}
}
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
private fun getShortCuts(context: Context): List<ShortcutInfo> {
val shortCuts = mutableListOf<ShortcutInfo>()
val root = Shell.rootAccess()
if (Utils.showSuperUser()) {
shortCuts.add(ShortcutInfo.Builder(context, "superuser")
.setShortLabel(context.getString(R.string.superuser))
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
.putExtra(Const.Key.OPEN_SECTION, "superuser")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_superuser))
.setRank(0)
.build())
}
if (root && Config.magiskHide) {
shortCuts.add(ShortcutInfo.Builder(context, "magiskhide")
.setShortLabel(context.getString(R.string.magiskhide))
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide))
.setRank(1)
.build())
}
if (!Config.coreOnly && root && Info.magiskVersionCode >= 0) {
shortCuts.add(ShortcutInfo.Builder(context, "modules")
.setShortLabel(context.getString(R.string.modules))
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
.putExtra(Const.Key.OPEN_SECTION, "modules")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_extension))
.setRank(3)
.build())
shortCuts.add(ShortcutInfo.Builder(context, "downloads")
.setShortLabel(context.getString(R.string.downloads))
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
.putExtra(Const.Key.OPEN_SECTION, "downloads")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download))
.setRank(2)
.build())
}
return shortCuts
}
}

View File

@ -4,6 +4,8 @@ import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.tasks.MagiskInstaller; import com.topjohnwu.magisk.tasks.MagiskInstaller;
import com.topjohnwu.magisk.utils.RootUtils; import com.topjohnwu.magisk.utils.RootUtils;
@ -12,8 +14,6 @@ import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.internal.UiThreadHandler; import com.topjohnwu.superuser.internal.UiThreadHandler;
import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFile;
import androidx.annotation.NonNull;
public class EnvFixDialog extends CustomAlertDialog { public class EnvFixDialog extends CustomAlertDialog {
public EnvFixDialog(@NonNull Activity activity) { public EnvFixDialog(@NonNull Activity activity) {
@ -36,7 +36,7 @@ public class EnvFixDialog extends CustomAlertDialog {
@Override @Override
protected void onResult(boolean success) { protected void onResult(boolean success) {
pd.dismiss(); pd.dismiss();
Utils.toast(success ? R.string.reboot_delay_toast : R.string.setup_fail, Toast.LENGTH_LONG); Utils.INSTANCE.toast(success ? R.string.reboot_delay_toast : R.string.setup_fail, Toast.LENGTH_LONG);
if (success) if (success)
UiThreadHandler.handler.postDelayed(RootUtils::reboot, 5000); UiThreadHandler.handler.postDelayed(RootUtils::reboot, 5000);
} }

View File

@ -11,14 +11,14 @@ import android.os.Build;
import android.view.Gravity; import android.view.Gravity;
import android.widget.Toast; import android.widget.Toast;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.Utils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.Utils;
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
public class FingerprintAuthDialog extends CustomAlertDialog { public class FingerprintAuthDialog extends CustomAlertDialog {
@ -31,13 +31,13 @@ public class FingerprintAuthDialog extends CustomAlertDialog {
super(activity); super(activity);
callback = onSuccess; callback = onSuccess;
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint); Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50)); fingerprint.setBounds(0, 0, Utils.INSTANCE.dpInPx(50), Utils.INSTANCE.dpInPx(50));
Resources.Theme theme = activity.getTheme(); Resources.Theme theme = activity.getTheme();
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint}); TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
fingerprint.setTint(ta.getColor(0, Color.GRAY)); fingerprint.setTint(ta.getColor(0, Color.GRAY));
ta.recycle(); ta.recycle();
binding.message.setCompoundDrawables(null, null, null, fingerprint); binding.message.setCompoundDrawables(null, null, null, fingerprint);
binding.message.setCompoundDrawablePadding(Utils.dpInPx(20)); binding.message.setCompoundDrawablePadding(Utils.INSTANCE.dpInPx(20));
binding.message.setGravity(Gravity.CENTER); binding.message.setGravity(Gravity.CENTER);
setMessage(R.string.auth_fingerprint); setMessage(R.string.auth_fingerprint);
setNegativeButton(R.string.close, (d, w) -> { setNegativeButton(R.string.close, (d, w) -> {
@ -54,7 +54,9 @@ public class FingerprintAuthDialog extends CustomAlertDialog {
}); });
try { try {
helper = new DialogFingerprintHelper(); helper = new DialogFingerprintHelper();
} catch (Exception ignored) {} } catch (Exception ignored) {
ignored.printStackTrace();
}
} }
public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess, @NonNull Runnable onFailure) { public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess, @NonNull Runnable onFailure) {
@ -67,7 +69,7 @@ public class FingerprintAuthDialog extends CustomAlertDialog {
create(); create();
if (helper == null) { if (helper == null) {
dialog.dismiss(); dialog.dismiss();
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT); Utils.INSTANCE.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
} else { } else {
helper.authenticate(); helper.authenticate();
dialog.show(); dialog.show();

View File

@ -4,10 +4,12 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.IBaseLeanback; import com.topjohnwu.magisk.ui.base.IBaseLeanback;
import com.topjohnwu.magisk.ui.flash.FlashActivity; import com.topjohnwu.magisk.ui.flash.FlashActivity;
@ -19,8 +21,6 @@ import com.topjohnwu.net.Networking;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import androidx.appcompat.app.AlertDialog;
class InstallMethodDialog extends AlertDialog.Builder { class InstallMethodDialog extends AlertDialog.Builder {
<Ctxt extends Activity & IBaseLeanback> InstallMethodDialog(Ctxt activity, List<String> options) { <Ctxt extends Activity & IBaseLeanback> InstallMethodDialog(Ctxt activity, List<String> options) {
@ -50,7 +50,7 @@ class InstallMethodDialog extends AlertDialog.Builder {
private <Ctxt extends Activity & IBaseLeanback> void patchBoot(Ctxt activity) { private <Ctxt extends Activity & IBaseLeanback> void patchBoot(Ctxt activity) {
activity.runWithExternalRW(() -> { activity.runWithExternalRW(() -> {
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG); Utils.INSTANCE.toast(R.string.patch_file_msg, Toast.LENGTH_LONG);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT) Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
.setType("*/*") .setType("*/*")
.addCategory(Intent.CATEGORY_OPENABLE); .addCategory(Intent.CATEGORY_OPENABLE);
@ -68,11 +68,11 @@ class InstallMethodDialog extends AlertDialog.Builder {
private <Ctxt extends Activity & IBaseLeanback> void downloadOnly(Ctxt activity) { private <Ctxt extends Activity & IBaseLeanback> void downloadOnly(Ctxt activity) {
activity.runWithExternalRW(() -> { activity.runWithExternalRW(() -> {
String filename = Utils.fmt("Magisk-v%s(%d).zip", String filename = Utils.INSTANCE.fmt("Magisk-v%s(%d).zip",
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode); Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode);
File zip = new File(Const.EXTERNAL_PATH, filename); File zip = new File(Const.EXTERNAL_PATH, filename);
ProgressNotification progress = new ProgressNotification(filename); ProgressNotification progress = new ProgressNotification(filename);
Networking.get(Config.magiskLink) Networking.get(Info.magiskLink)
.setDownloadProgressListener(progress) .setDownloadProgressListener(progress)
.setErrorHandler((conn, e) -> progress.dlFail()) .setErrorHandler((conn, e) -> progress.dlFail())
.getAsFile(zip, f -> { .getAsFile(zip, f -> {

View File

@ -4,7 +4,7 @@ import android.app.Activity;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import com.topjohnwu.magisk.Config; import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.IBaseLeanback; import com.topjohnwu.magisk.ui.base.IBaseLeanback;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
@ -18,8 +18,8 @@ import java.util.List;
public class MagiskInstallDialog extends CustomAlertDialog { public class MagiskInstallDialog extends CustomAlertDialog {
public <Ctxt extends Activity & IBaseLeanback> MagiskInstallDialog(Ctxt a) { public <Ctxt extends Activity & IBaseLeanback> MagiskInstallDialog(Ctxt a) {
super(a); super(a);
String filename = Utils.fmt("Magisk-v%s(%d).zip", String filename = Utils.INSTANCE.fmt("Magisk-v%s(%d).zip",
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode); Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode);
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk))); setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)));
setMessage(a.getString(R.string.repo_install_msg, filename)); setMessage(a.getString(R.string.repo_install_msg, filename));
setCancelable(true); setCancelable(true);
@ -36,13 +36,13 @@ public class MagiskInstallDialog extends CustomAlertDialog {
} }
new InstallMethodDialog(a, options).show(); new InstallMethodDialog(a, options).show();
}); });
if (!TextUtils.isEmpty(Config.magiskNoteLink)) { if (!TextUtils.isEmpty(Info.magiskNoteLink)) {
setNeutralButton(R.string.release_notes, (d, i) -> { setNeutralButton(R.string.release_notes, (d, i) -> {
if (Config.magiskNoteLink.contains("forum.xda-developers")) { if (Info.magiskNoteLink.contains("forum.xda-developers")) {
// Open forum links in browser // Open forum links in browser
Utils.openLink(a, Uri.parse(Config.magiskNoteLink)); Utils.INSTANCE.openLink(a, Uri.parse(Info.magiskNoteLink));
} else { } else {
MarkDownWindow.show(a, null, Config.magiskNoteLink); MarkDownWindow.show(a, null, Info.magiskNoteLink);
} }
}); });
} }

View File

@ -5,7 +5,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.topjohnwu.magisk.Config; import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.DownloadApp; import com.topjohnwu.magisk.utils.DownloadApp;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
@ -15,14 +15,14 @@ public class ManagerInstallDialog extends CustomAlertDialog {
public ManagerInstallDialog(@NonNull Activity a) { public ManagerInstallDialog(@NonNull Activity a) {
super(a); super(a);
String name = Utils.fmt("MagiskManager v%s(%d)", String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode); Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name))); setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)));
setMessage(a.getString(R.string.repo_install_msg, name)); setMessage(a.getString(R.string.repo_install_msg, name));
setCancelable(true); setCancelable(true);
setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name)); setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name));
if (!TextUtils.isEmpty(Config.managerNoteLink)) { if (!TextUtils.isEmpty(Info.managerNoteLink)) {
setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Config.managerNoteLink)); setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Info.managerNoteLink));
} }
} }
} }

View File

@ -7,9 +7,11 @@ import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.flash.FlashActivity; import com.topjohnwu.magisk.ui.flash.FlashActivity;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
@ -19,8 +21,6 @@ import com.topjohnwu.superuser.Shell;
import java.io.File; import java.io.File;
import androidx.annotation.NonNull;
public class UninstallDialog extends CustomAlertDialog { public class UninstallDialog extends CustomAlertDialog {
public UninstallDialog(@NonNull Activity activity) { public UninstallDialog(@NonNull Activity activity) {
@ -34,17 +34,17 @@ public class UninstallDialog extends CustomAlertDialog {
Shell.su("restore_imgs").submit(result -> { Shell.su("restore_imgs").submit(result -> {
dialog.cancel(); dialog.cancel();
if (result.isSuccess()) { if (result.isSuccess()) {
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT); Utils.INSTANCE.toast(R.string.restore_done, Toast.LENGTH_SHORT);
} else { } else {
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG); Utils.INSTANCE.toast(R.string.restore_fail, Toast.LENGTH_LONG);
} }
}); });
}); });
if (!TextUtils.isEmpty(Config.uninstallerLink)) { if (!TextUtils.isEmpty(Info.uninstallerLink)) {
setPositiveButton(R.string.complete_uninstall, (d, i) -> { setPositiveButton(R.string.complete_uninstall, (d, i) -> {
File zip = new File(activity.getFilesDir(), "uninstaller.zip"); File zip = new File(activity.getFilesDir(), "uninstaller.zip");
ProgressNotification progress = new ProgressNotification(zip.getName()); ProgressNotification progress = new ProgressNotification(zip.getName());
Networking.get(Config.uninstallerLink) Networking.get(Info.uninstallerLink)
.setDownloadProgressListener(progress) .setDownloadProgressListener(progress)
.setErrorHandler(((conn, e) -> progress.dlFail())) .setErrorHandler(((conn, e) -> progress.dlFail()))
.getAsFile(zip, f -> { .getAsFile(zip, f -> {

View File

@ -6,6 +6,7 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="dark_theme" android:key="dark_theme"
android:defaultValue="true"
android:title="@string/settings_dark_theme_title" android:title="@string/settings_dark_theme_title"
android:summary="@string/settings_dark_theme_summary" /> android:summary="@string/settings_dark_theme_summary" />
@ -95,28 +96,33 @@
<ListPreference <ListPreference
android:key="su_auto_response" android:key="su_auto_response"
android:title="@string/auto_response" android:title="@string/auto_response"
android:defaultValue="0"
android:entries="@array/auto_response" android:entries="@array/auto_response"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array" />
<ListPreference <ListPreference
android:key="su_request_timeout" android:key="su_request_timeout"
android:title="@string/request_timeout" android:title="@string/request_timeout"
android:defaultValue="10"
android:entries="@array/request_timeout" android:entries="@array/request_timeout"
android:entryValues="@array/request_timeout_value" /> android:entryValues="@array/request_timeout_value" />
<ListPreference <ListPreference
android:key="su_notification" android:key="su_notification"
android:title="@string/superuser_notification" android:title="@string/superuser_notification"
android:defaultValue="1"
android:entries="@array/su_notification" android:entries="@array/su_notification"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="su_fingerprint" android:key="su_fingerprint"
android:defaultValue="false"
android:title="@string/settings_su_fingerprint_title" android:title="@string/settings_su_fingerprint_title"
android:summary="@string/settings_su_fingerprint_summary"/> android:summary="@string/settings_su_fingerprint_summary"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="su_reauth" android:key="su_reauth"
android:defaultValue="false"
android:title="@string/settings_su_reauth_title" android:title="@string/settings_su_reauth_title"
android:summary="@string/settings_su_reauth_summary"/> android:summary="@string/settings_su_reauth_summary"/>