Commit 7756e107 authored by topjohnwu's avatar topjohnwu

Rewrite configs with Kotlin delagate properties

parent 3e58d502
This diff is collapsed.
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()
}
}
\ No newline at end of file
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()
}
}
\ No newline at end of file
......@@ -19,7 +19,6 @@ object Const {
const val MAGISK_LOG = "/cache/magisk.log"
// Versions
const val UPDATE_SERVICE_VER = 1
const val SNET_EXT_VER = 12
const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
......
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) {
}
}
}
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
}
}
}
}
\ No newline at end of file
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.data.database.base.*
import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.toLog
......@@ -12,7 +11,7 @@ class LogDao : BaseDao() {
override val table = DatabaseDefinition.Table.LOG
fun deleteOutdated(
suTimeout: Long = Config.suLogTimeout * TimeUnit.DAYS.toMillis(1)
suTimeout: Long = TimeUnit.DAYS.toMillis(14)
) = query<Delete> {
condition {
lessThan("time", suTimeout.toString())
......
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;
}
}
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"
}
}
package com.topjohnwu.magisk.data.network
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.model.entity.MagiskConfig
import io.reactivex.Single
import okhttp3.ResponseBody
......@@ -16,19 +15,19 @@ interface GithubRawApiServices {
//region topjohnwu/magisk_files
@GET("$MAGISK_FILES/master/stable.json")
fun fetchConfig(): Single<MagiskConfig>
fun fetchStableUpdate(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaConfig(): Single<MagiskConfig>
fun fetchBetaUpdate(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/canary_builds/release.json")
fun fetchCanaryConfig(): Single<MagiskConfig>
fun fetchCanaryUpdate(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
fun fetchCanaryDebugConfig(): Single<MagiskConfig>
fun fetchCanaryDebugUpdate(): Single<MagiskConfig>
@GET
fun fetchCustomConfig(@Url url: String): Single<MagiskConfig>
fun fetchCustomUpdate(@Url url: String): Single<MagiskConfig>
@GET("$MAGISK_FILES/{$REVISION}/snet.apk")
@Streaming
......@@ -55,7 +54,7 @@ interface GithubRawApiServices {
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_MODULES = "Magisk-Modules-Repo"
}
......
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()
}
}
......@@ -4,20 +4,17 @@ import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.App
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.suRaw
import com.topjohnwu.magisk.data.network.GithubRawApiServices
import com.topjohnwu.magisk.model.entity.HideAppInfo
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.inject
import com.topjohnwu.magisk.utils.toSingle
import com.topjohnwu.magisk.utils.writeToFile
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
import io.reactivex.functions.BiFunction
class MagiskRepository(
private val context: Context,
......@@ -25,15 +22,15 @@ class MagiskRepository(
private val packageManager: PackageManager
) {
fun fetchMagisk() = fetchConfig()
fun fetchMagisk() = fetchUpdate()
.flatMap { apiRaw.fetchFile(it.magisk.link) }
.map { it.writeToFile(context, FILE_MAGISK_ZIP) }
fun fetchManager() = fetchConfig()
fun fetchManager() = fetchUpdate()
.flatMap { apiRaw.fetchFile(it.app.link) }
.map { it.writeToFile(context, FILE_MAGISK_APK) }
fun fetchUninstaller() = fetchConfig()
fun fetchUninstaller() = fetchUpdate()
.flatMap { apiRaw.fetchFile(it.uninstaller.link) }
.map { it.writeToFile(context, FILE_UNINSTALLER_ZIP) }
......@@ -44,23 +41,35 @@ class MagiskRepository(
.map { it.writeToFile(context, FILE_BOOTCTL_SH) }
fun fetchConfig() = when (KConfig.updateChannel) {
KConfig.UpdateChannel.STABLE -> apiRaw.fetchConfig()
KConfig.UpdateChannel.BETA -> apiRaw.fetchBetaConfig()
KConfig.UpdateChannel.CANARY -> apiRaw.fetchCanaryConfig()
KConfig.UpdateChannel.CANARY_DEBUG -> apiRaw.fetchCanaryDebugConfig()
KConfig.UpdateChannel.CUSTOM -> apiRaw.fetchCustomConfig(KConfig.customUpdateChannel)
}
.doOnSuccess {
Config.remoteMagiskVersionCode = it.magisk.versionCode.toIntOrNull() ?: -1
Config.magiskLink = it.magisk.link
Config.magiskNoteLink = it.magisk.note
Config.magiskMD5 = it.magisk.hash
Config.remoteManagerVersionCode = it.app.versionCode.toIntOrNull() ?: -1
Config.remoteManagerVersionString = it.app.version
Config.managerLink = it.app.link
Config.managerNoteLink = it.app.note
Config.uninstallerLink = it.uninstaller.link
fun fetchUpdate() = when (Config.updateChannel) {
Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate()
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl)
else -> throw IllegalArgumentException()
}.flatMap {
// If remote version is lower than current installed, try switching to beta
if (it.magisk.versionCode.toIntOrNull() ?: -1 < Info.magiskVersionCode
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
Config.updateChannel = Config.Value.BETA_CHANNEL
apiRaw.fetchBetaUpdate()
} else {
Single.just(it)
}
}.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() =
......
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)
}
\ No newline at end of file
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)
}
\ No newline at end of file
......@@ -8,6 +8,4 @@ val repositoryModule = module {
single { MagiskRepository(get(), get(), get()) }
single { LogRepository(get()) }
single { AppRepository(get()) }
single { SettingRepository(get()) }
single { StringRepository(get()) }
}
......@@ -6,6 +6,8 @@ import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import androidx.annotation.Nullable;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Const;
......@@ -29,8 +31,6 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import androidx.annotation.Nullable;
public class DownloadModuleService extends Service {
private List<ProgressNotification> notifications;
......
......@@ -5,10 +5,10 @@ import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
import androidx.annotation.NonNull;
import java.util.List;
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
private String mId, mName, mVersion, mAuthor, mDescription;
......
......@@ -4,10 +4,10 @@ import android.content.ContentValues;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import com.topjohnwu.magisk.utils.Utils;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.utils.Utils;
public class Policy implements Comparable<Policy>{
public static final int INTERACTIVE = 0;
......@@ -27,7 +27,7 @@ public class Policy implements Comparable<Policy>{
this.uid = uid;
packageName = pkgs[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 {
......
......@@ -54,7 +54,7 @@ public class Repo extends BaseModule {
}
public void update() throws IllegalRepoException {
String[] props = Utils.dlString(getPropUrl()).split("\\n");
String[] props = Utils.INSTANCE.dlString(getPropUrl()).split("\\n");
try {
parseProps(props);
} catch (NumberFormatException e) {
......@@ -103,7 +103,7 @@ public class Repo extends BaseModule {
}
public String getDownloadFilename() {
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip");
return Utils.INSTANCE.getLegalFilename(getName() + "-" + getVersion() + ".zip");
}
public class IllegalRepoException extends Exception {
......
......@@ -2,6 +2,8 @@ package com.topjohnwu.magisk.model.preference
import android.content.Context
import android.content.SharedPreferences
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface PreferenceModel {
......@@ -14,6 +16,20 @@ interface PreferenceModel {
val prefs: SharedPreferences
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(
name: String,
default: Boolean,
......
......@@ -6,6 +6,7 @@ import android.content.Intent
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.data.repository.AppRepository
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.SuLogger
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.magisk.utils.get
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
......@@ -64,9 +64,8 @@ open class GeneralReceiver : BroadcastReceiver() {
}
Intent.ACTION_PACKAGE_REPLACED ->
// This will only work pre-O
if (Config.get<Boolean>(Config.Key.SU_REAUTH)!!) {
if (Config.suReAuth)
appRepo.delete(getPkg(intent)).blockingGet()
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent)
appRepo.delete(pkg).blockingGet()
......@@ -74,7 +73,7 @@ open class GeneralReceiver : BroadcastReceiver() {
}
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
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))
}
Const.Key.BROADCAST_REBOOT -> RootUtils.reboot()
......
......@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.model.update
import androidx.work.ListenableWorker
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.model.worker.DelegateWorker
import com.topjohnwu.magisk.utils.inject
......@@ -14,10 +14,10 @@ class UpdateCheckService : DelegateWorker() {
override fun doWork(): ListenableWorker.Result {
return runCatching {
magiskRepo.fetchConfig().blockingGet()
if (BuildConfig.VERSION_CODE < Config.remoteManagerVersionCode)
magiskRepo.fetchUpdate().blockingGet()
if (BuildConfig.VERSION_CODE < Info.remoteManagerVersionCode)
Notifications.managerUpdate()
else if (Config.magiskVersionCode < Config.remoteManagerVersionCode)
else if (Info.magiskVersionCode < Info.remoteManagerVersionCode)
Notifications.magiskUpdate()
ListenableWorker.Result.success()
}.getOrElse {
......
......@@ -4,12 +4,6 @@ import android.content.Context;
import android.net.Network;
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.NonNull;
import androidx.annotation.Nullable;
......@@ -17,6 +11,12 @@ import androidx.annotation.RequiresApi;
import androidx.work.Data;
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 {
private ListenableWorker worker;
......
......@@ -8,8 +8,8 @@ import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.net.DownloadProgressListener;
import com.topjohnwu.net.Networking;
......@@ -123,9 +123,9 @@ public abstract class MagiskInstaller {
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");
Networking.get(Config.magiskLink)
Networking.get(Info.magiskLink)
.setDownloadProgressListener(new ProgressLog())
.execForFile(zip);
} else {
......@@ -282,10 +282,10 @@ public abstract class MagiskInstaller {
return false;
}
if (!Shell.sh(Utils.fmt(
if (!Shell.sh(Utils.INSTANCE.fmt(
"KEEPFORCEENCRYPT=%b KEEPVERITY=%b RECOVERYMODE=%b " +
"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())
return false;
......@@ -311,10 +311,10 @@ public abstract class MagiskInstaller {
}
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())
return false;
if (!Config.keepVerity)
if (!Info.keepVerity)
Shell.su("patch_dtbo_image").to(console, logs).exec();
return true;
}
......
......@@ -3,6 +3,8 @@ package com.topjohnwu.magisk.tasks;
import android.database.Cursor;
import android.util.Pair;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
......@@ -31,7 +33,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import androidx.annotation.NonNull;
import io.reactivex.Single;
@Deprecated
......@@ -74,10 +75,10 @@ public class UpdateRepos {
* first page is updated to determine whether the online repo database is changed
*/
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) {
String etag = Config.get(Config.Key.ETAG_KEY);
if (etag != null)
String etag = Config.getEtagKey();
if (!etag.isEmpty())
req.addHeaders(Const.Key.IF_NONE_MATCH, etag);
}
Request.Result<JSONArray> res = req.execForJSONArray();
......@@ -110,7 +111,7 @@ public class UpdateRepos {
String etag = res.getConnection().getHeaderField(Config.Key.ETAG_KEY);
if (etag != null) {
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
Config.set(Config.Key.ETAG_KEY, etag);
Config.setEtagKey(etag);
}
}
......
......@@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.model.navigation.Navigation
......@@ -105,11 +106,11 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
private fun checkHideSection() {
val menu = binding.navView.menu
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 =
Shell.rootAccess() && Config.magiskVersionCode >= 0
Shell.rootAccess() && Info.magiskVersionCode >= 0
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 =
Shell.rootAccess()
menu.findItem(R.id.superuserFragment).isVisible =
......
......@@ -7,6 +7,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.tasks.UpdateRepos
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.Notifications
......@@ -21,7 +22,7 @@ open class SplashActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
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)
.setTitle(R.string.unsupport_magisk_title)
.setMessage(R.string.unsupport_magisk_message)
......@@ -35,9 +36,9 @@ open class SplashActivity : AppCompatActivity() {
}
private fun initAndStart() {
val pkg = Config.get<String>(Config.Key.SU_MANAGER)
if (pkg != null && packageName == BuildConfig.APPLICATION_ID) {
Config.remove(Config.Key.SU_MANAGER)
val pkg = Config.suManager
if (Config.suManager.isNotEmpty() && packageName == BuildConfig.APPLICATION_ID) {
get<SettingsDao>().delete(Config.Key.SU_MANAGER)
Shell.su("pm uninstall $pkg").submit()
}
if (TextUtils.equals(pkg, packageName)) {
......@@ -56,15 +57,12 @@ open class SplashActivity : AppCompatActivity() {
// Schedule periodic update checks
Utils.scheduleUpdateCheck()
//CheckUpdates.check()
// Setup shortcuts
Shortcuts.setup(this)
// Magisk working as expected
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
// Load modules
//Utils.loadModules(false)
if (Shell.rootAccess() && Info.magiskVersionCode > 0) {
// Load repos
if (Networking.checkNetworkStatus(this)) {
get<UpdateRepos>().exec().subscribeK()
......
......@@ -12,8 +12,6 @@ import androidx.core.view.isVisible
import androidx.preference.*
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.R
import org.koin.android.ext.android.inject
......@@ -63,22 +61,6 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
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 {
return findPreference(key) as T
}
......
......@@ -51,14 +51,12 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
get() = navigationController?.let { it.currentStackIndex != defaultPosition } ?: false
init {
val isDarkTheme = Config.get<Boolean>(Config.Key.DARK_THEME)
val theme = if (isDarkTheme) {
val theme = if (Config.darkTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(theme)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
}
override fun applyOverrideConfiguration(config: Configuration?) {
......
......@@ -28,7 +28,7 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
(findItem(R.id.app_search).actionView as? SearchView)
?.setOnQueryTextListener(this@MagiskHideFragment)
val showSystem = Config.get<Boolean>(Config.Key.SHOW_SYSTEM_APP)
val showSystem = Config.showSystemApp
findItem(R.id.show_system).isChecked = showSystem
viewModel.isShowSystem.value = showSystem
......@@ -39,10 +39,8 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
if (item.itemId == R.id.show_system) {
val showSystem = !item.isChecked
item.isChecked = showSystem
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem)
Config.showSystemApp = showSystem
viewModel.isShowSystem.value = showSystem
//adapter!!.setShowSystem(showSystem)
//adapter!!.filter(search!!.query.toString())
}
return true
}
......@@ -56,9 +54,4 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
viewModel.query.value = query.orEmpty()
return false
}
/*override fun onEvent(event: Int) {
//mSwipeRefreshLayout!!.isRefreshing = false
adapter!!.filter(search!!.query.toString())
}*/
}
......@@ -56,7 +56,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private fun installMagisk() {
// Show Manager update first
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
if (Info.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
installManager()
return
}
......
......@@ -4,10 +4,7 @@ import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.doOnSubscribeUi
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.observer.Observer
......@@ -25,8 +22,8 @@ class HomeViewModel(
val isAdvancedExpanded = KObservableField(false)
val isForceEncryption = KObservableField(Config.keepEnc)
val isKeepVerity = KObservableField(Config.keepVerity)
val isForceEncryption = KObservableField(Info.keepEnc)
val isKeepVerity = KObservableField(Info.keepVerity)
val magiskState = KObservableField(MagiskState.LOADING)
val magiskStateText = Observer(magiskState) {
......@@ -41,7 +38,7 @@ class HomeViewModel(
val magiskCurrentVersion = KObservableField("")
val magiskLatestVersion = KObservableField("")
val magiskAdditionalInfo = Observer(magiskState) {
if (Config.get<Boolean>(Config.Key.COREONLY))
if (Config.coreOnly)
R.string.core_only_enabled.res()
else
""
......@@ -87,10 +84,10 @@ class HomeViewModel(
init {
isForceEncryption.addOnPropertyChangedCallback {
Config.keepEnc = it ?: return@addOnPropertyChangedCallback
Info.keepEnc = it ?: return@addOnPropertyChangedCallback
}
isKeepVerity.addOnPropertyChangedCallback {
Config.keepVerity = it ?: return@addOnPropertyChangedCallback
Info.keepVerity = it ?: return@addOnPropertyChangedCallback
}
refresh()
......@@ -154,7 +151,7 @@ class HomeViewModel(
}
fun refresh() {
magiskRepo.fetchConfig()
magiskRepo.fetchUpdate()
.applyViewModel(this)
.doOnSubscribeUi {
magiskState.value = MagiskState.LOADING
......@@ -164,14 +161,6 @@ class HomeViewModel(
safetyNetTitle.value = R.string.safetyNet_check_text
}
.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()
ensureEnv()
}
......@@ -181,22 +170,22 @@ class HomeViewModel(
private fun updateSelf() {
state = State.LOADED
magiskState.value = when (Config.magiskVersionCode) {
magiskState.value = when (Info.magiskVersionCode) {
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
}
magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) {
version.format(Config.magiskVersionString, Config.magiskVersionCode)
version.format(Info.magiskVersionString, Info.magiskVersionCode)
} else {
""
}
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 (BuildConfig.VERSION_CODE + 1)..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
......@@ -206,7 +195,7 @@ class HomeViewModel(
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
managerLatestVersion.value = version
.format(Config.remoteManagerVersionString, Config.remoteManagerVersionCode)
.format(Info.remoteManagerVersionString, Info.remoteManagerVersionCode)
}
private fun ensureEnv() {
......
......@@ -53,9 +53,9 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
.setTitle(R.string.sorting_order)
.setSingleChoiceItems(
R.array.sorting_orders,
Config.get<Int>(Config.Key.REPO_ORDER)!!
Config.repoOrder
) { d, which ->
Config.set(Config.Key.REPO_ORDER, which)
Config.repoOrder = which
viewModel.refresh(false)
d.dismiss()
}.show()
......
......@@ -13,10 +13,11 @@ import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreferenceCompat
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.KConfig.UpdateChannel
import com.topjohnwu.magisk.BuildConfig
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.repository.SettingRepository
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment
import com.topjohnwu.magisk.utils.*
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
......@@ -27,7 +28,6 @@ import org.koin.android.ext.android.inject
class SettingsFragment : BasePreferenceFragment() {
private val repoDatabase: RepoDatabaseHelper by inject()
private val settingRepo: SettingRepository by inject()
private lateinit var updateChannel: ListPreference
private lateinit var autoRes: ListPreference
......@@ -53,7 +53,7 @@ class SettingsFragment : BasePreferenceFragment() {
requestTimeout = findPref(Config.Key.SU_REQUEST_TIMEOUT)
suNotification = findPref(Config.Key.SU_NOTIFICATION)
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 fingerprint = findPreference(Config.Key.SU_FINGERPRINT) as SwitchPreferenceCompat
val generalCatagory = findPreference("general") as PreferenceCategory
......@@ -70,7 +70,9 @@ class SettingsFragment : BasePreferenceFragment() {
true
}
findPreference("clear").setOnPreferenceClickListener {
prefs.edit().remove(Config.Key.ETAG_KEY).apply()
prefs.edit {
remove(Config.Key.ETAG_KEY)
}
repoDatabase.clearRepo()
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
true
......@@ -81,30 +83,23 @@ class SettingsFragment : BasePreferenceFragment() {
true
}
prefs.edit {
putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
}
updateChannel.setOnPreferenceChangeListener { _, value ->
val channel = Integer.parseInt(value as String)
val previous = Config.updateChannel
val previousUpdateChannel = KConfig.updateChannel
val updateChannel = getChannelCompat(channel)
KConfig.updateChannel = updateChannel
if (updateChannel === UpdateChannel.CUSTOM) {
val v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null)
if (channel == Config.Value.CUSTOM_CHANNEL) {
val v = LayoutInflater.from(requireActivity())
.inflate(R.layout.custom_channel_dialog, null)
val url = v.findViewById<EditText>(R.id.custom_url)
url.setText(KConfig.customUpdateChannel)
url.setText(Config.customChannelUrl)
AlertDialog.Builder(requireActivity())
.setTitle(R.string.settings_update_custom)
.setView(v)
.setPositiveButton(R.string.ok) { _, _ ->
setCustomUpdateChannel(url.text.toString()) }
Config.customChannelUrl = url.text.toString() }
.setNegativeButton(R.string.close) { _, _ ->
KConfig.updateChannel = previousUpdateChannel }
.setOnCancelListener { KConfig.updateChannel = previousUpdateChannel }
Config.updateChannel = previous }
.setOnCancelListener { Config.updateChannel = previous }
.show()
}
true
......@@ -114,7 +109,7 @@ class SettingsFragment : BasePreferenceFragment() {
/* We only show canary channels if user is already on 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
val entries = updateChannel.entries
updateChannel.entries = entries.copyOf(entries.size - 2)
......@@ -168,8 +163,9 @@ class SettingsFragment : BasePreferenceFragment() {
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
when (key) {
Config.Key.ROOT_ACCESS, Config.Key.SU_MULTIUSER_MODE, Config.Key.SU_MNT_NS ->
settingRepo.put(key, Utils.getPrefsInt(prefs, key)).subscribe()
Config.Key.ROOT_ACCESS -> Config.rootMode = Utils.getPrefsInt(prefs, key)
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.COREONLY -> {
if (prefs.getBoolean(key, false)) {
......@@ -196,14 +192,13 @@ class SettingsFragment : BasePreferenceFragment() {
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val key = preference.key
when (key) {
when (preference.key) {
Config.Key.SU_FINGERPRINT -> {
val checked = (preference as SwitchPreferenceCompat).isChecked
preference.isChecked = !checked
FingerprintAuthDialog(requireActivity()) {
preference.isChecked = checked
Config.set(key, checked)
Config.suFingerprint = checked
}.show()
}
}
......@@ -237,34 +232,34 @@ class SettingsFragment : BasePreferenceFragment() {
private fun setSummary(key: String) {
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 -> {
var ch = Config.get<Int>(key)
var ch = Config.updateChannel
ch = if (ch < 0) Config.Value.STABLE_CHANNEL else ch
updateChannel.summary = resources
.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
.getStringArray(R.array.auto_response)[Config.get<Int>(key)]
.getStringArray(R.array.auto_response)[Config.suAutoReponse]
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 =
getString(R.string.request_timeout_summary, Config.get<Int>(key))
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)]
getString(R.string.request_timeout_summary, Config.suDefaultTimeout)
}
}
private fun setSummary() {
setSummary(Config.Key.UPDATE_CHANNEL)
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_NOTIFICATION)
setSummary(Config.Key.SU_REQUEST_TIMEOUT)
setSummary(Config.Key.SU_MULTIUSER_MODE)
setSummary(Config.Key.SU_MNT_NS)
}
}
......@@ -173,7 +173,7 @@ class SuRequestViewModel(
return true
}
when (Config.get<Int>(Config.Key.SU_AUTO_RESPONSE)) {
when (Config.suAutoReponse) {
Config.Value.SU_AUTO_DENY -> {
handler?.handleAction(Policy.DENY, 0)
return true
......@@ -190,8 +190,7 @@ class SuRequestViewModel(
@SuppressLint("ClickableViewAccessibility")
private fun showUI() {
val seconds = Config.get<Int>(Config.Key.SU_REQUEST_TIMEOUT).toLong()
val millis = SECONDS.toMillis(seconds)
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
timer = object : CountDownTimer(millis, 1000) {
override fun onTick(remains: Long) {
denyText.value = "%s (%d)"
......
......@@ -6,6 +6,7 @@ import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.SplashActivity;
import com.topjohnwu.magisk.view.ProgressNotification;
......@@ -24,8 +25,8 @@ public class DownloadApp {
}
public static void restore() {
String name = Utils.fmt("MagiskManager v%s(%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode);
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
dlInstall(name, new RestoreManager());
}
......@@ -33,7 +34,7 @@ public class DownloadApp {
File apk = new File(App.self.getCacheDir(), "manager.apk");
ProgressNotification progress = new ProgressNotification(name);
listener.progress = progress;
Networking.get(Config.managerLink)
Networking.get(Info.managerLink)
.setExecutor(App.THREAD_POOL)
.setDownloadProgressListener(progress)
.setErrorHandler((conn, e) -> progress.dlFail())
......
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();
}
}
}
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()
}
}
}
......@@ -107,9 +107,9 @@ object LocaleManager {
@JvmStatic
fun setLocale(wrapper: ContextWrapper) {
val localeConfig = Config.get<String>(Config.Key.LOCALE)
val localeConfig = Config.locale
locale = when {
localeConfig.isNullOrEmpty() -> defaultLocale
localeConfig.isEmpty() -> defaultLocale
else -> forLanguageTag(localeConfig)
}
Locale.setDefault(locale)
......
......@@ -13,7 +13,7 @@ public class Logger {
}
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) {
......@@ -21,6 +21,6 @@ public class Logger {
}
public static void error(String fmt, Object... args) {
error(Utils.fmt(fmt, args));
error(Utils.INSTANCE.fmt(fmt, args));
}
}
......@@ -3,6 +3,8 @@ package com.topjohnwu.magisk.utils;
import android.content.ComponentName;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.ClassMap;
......@@ -27,8 +29,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import androidx.core.app.NotificationCompat;
public class PatchAPK {
public static final String LOWERALPHA = "abcdefghijklmnopqrstuvwxyz";
......@@ -110,7 +110,7 @@ public class PatchAPK {
if (!Shell.su("pm install " + repack).exec().isSuccess())
return false;
Config.set(Config.Key.SU_MANAGER, pkg);
Config.setSuManager(pkg);
Config.export();
RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID,
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.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build());
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);
});
}
......
......@@ -4,8 +4,8 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
......@@ -132,7 +132,7 @@ class RootUtils : Shell.Initializer() {
job.add(context.rawResource(R.raw.util_functions))
.add(context.rawResource(R.raw.utils))
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
Config.loadMagiskInfo()
Info.loadMagiskInfo()
} else {
job.add(context.rawResource(R.raw.nonroot_utils))
}
......@@ -143,9 +143,9 @@ class RootUtils : Shell.Initializer() {
"export BOOTMODE=true")
.exec()
Config.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
Config.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
Config.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
return true
}
......@@ -158,7 +158,7 @@ class RootUtils : Shell.Initializer() {
@JvmStatic
fun reboot() {
Shell.su("/system/bin/reboot ${if (Config.recovery) "recovery" else ""}").submit()
Shell.su("/system/bin/reboot ${if (Info.recovery) "recovery" else ""}").submit()
}
}
}
package com.topjohnwu.magisk.utils
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Process
import android.widget.Toast
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.AppRepository
......@@ -17,6 +17,8 @@ import java.util.*
object SuLogger {
private val context: Context by inject()
@JvmStatic
fun handleLogs(intent: Intent) {
......@@ -66,9 +68,9 @@ object SuLogger {
}
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(
App.self.getString(
context.getString(
if (policy.policy == Policy.ALLOW)
R.string.su_allow_toast
else
......
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);
}
}
}
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)
}
}
}
package com.topjohnwu.magisk.utils
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 java.io.File
......@@ -19,36 +13,3 @@ fun ResponseBody.writeToFile(context: Context, fileName: String): File {
}
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)
}
\ No newline at end of file
......@@ -5,6 +5,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.R;
import com.topjohnwu.net.Networking;
import com.topjohnwu.net.ResponseListener;
......@@ -12,7 +14,6 @@ import com.topjohnwu.net.ResponseListener;
import java.io.InputStream;
import java.util.Scanner;
import androidx.appcompat.app.AlertDialog;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.html.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin;
......
......@@ -7,19 +7,19 @@ import android.content.Context;
import android.content.Intent;
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.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
import com.topjohnwu.magisk.ui.SplashActivity;
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 static NotificationManagerCompat mgr = NotificationManagerCompat.from(App.self);
......@@ -62,12 +62,12 @@ public class Notifications {
public static void managerUpdate() {
App app = App.self;
String name = Utils.fmt("MagiskManager v%s(%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode);
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
Intent intent = new Intent(app, ClassMap.get(GeneralReceiver.class));
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);
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
......
......@@ -5,13 +5,13 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.net.DownloadProgressListener;
import androidx.core.app.NotificationCompat;
public class ProgressNotification implements DownloadProgressListener {
private NotificationCompat.Builder builder;
......@@ -22,7 +22,7 @@ public class ProgressNotification implements DownloadProgressListener {
builder = Notifications.progress(title);
prevTime = System.currentTimeMillis();
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
......
package com.topjohnwu.magisk.view;
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 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
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;
object Shortcuts {
import java.util.ArrayList;
import androidx.annotation.RequiresApi;
public class Shortcuts {
public static void setup(Context context) {
fun setup(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
manager.setDynamicShortcuts(getShortCuts(context));
val manager = context.getSystemService(ShortcutManager::class.java)
manager?.dynamicShortcuts = 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();
private fun getShortCuts(context: Context): List<ShortcutInfo> {
val shortCuts = mutableListOf<ShortcutInfo>()
val root = Shell.rootAccess()
if (Utils.showSuperUser()) {
shortCuts.add(new ShortcutInfo.Builder(context, "superuser")
shortCuts.add(ShortcutInfo.Builder(context, "superuser")
.setShortLabel(context.getString(R.string.superuser))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.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());
.build())
}
if (root && (boolean) Config.get(Config.Key.MAGISKHIDE)) {
shortCuts.add(new ShortcutInfo.Builder(context, "magiskhide")
if (root && Config.magiskHide) {
shortCuts.add(ShortcutInfo.Builder(context, "magiskhide")
.setShortLabel(context.getString(R.string.magiskhide))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.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());
.build())
}
if (!(boolean) Config.get(Config.Key.COREONLY) && root && Config.magiskVersionCode >= 0) {
shortCuts.add(new ShortcutInfo.Builder(context, "modules")
if (!Config.coreOnly && root && Info.magiskVersionCode >= 0) {
shortCuts.add(ShortcutInfo.Builder(context, "modules")
.setShortLabel(context.getString(R.string.modules))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.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(new ShortcutInfo.Builder(context, "downloads")
.build())
shortCuts.add(ShortcutInfo.Builder(context, "downloads")
.setShortLabel(context.getString(R.string.downloads))
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
.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());
.build())
}
return shortCuts;
return shortCuts
}
}
......@@ -4,6 +4,8 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.tasks.MagiskInstaller;
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.io.SuFile;
import androidx.annotation.NonNull;
public class EnvFixDialog extends CustomAlertDialog {
public EnvFixDialog(@NonNull Activity activity) {
......@@ -36,7 +36,7 @@ public class EnvFixDialog extends CustomAlertDialog {
@Override
protected void onResult(boolean success) {
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)
UiThreadHandler.handler.postDelayed(RootUtils::reboot, 5000);
}
......
......@@ -11,14 +11,14 @@ import android.os.Build;
import android.view.Gravity;
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.Nullable;
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)
public class FingerprintAuthDialog extends CustomAlertDialog {
......@@ -31,13 +31,13 @@ public class FingerprintAuthDialog extends CustomAlertDialog {
super(activity);
callback = onSuccess;
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();
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
fingerprint.setTint(ta.getColor(0, Color.GRAY));
ta.recycle();
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);
setMessage(R.string.auth_fingerprint);
setNegativeButton(R.string.close, (d, w) -> {
......@@ -54,7 +54,9 @@ public class FingerprintAuthDialog extends CustomAlertDialog {
});
try {
helper = new DialogFingerprintHelper();
} catch (Exception ignored) {}
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess, @NonNull Runnable onFailure) {
......@@ -67,7 +69,7 @@ public class FingerprintAuthDialog extends CustomAlertDialog {
create();
if (helper == null) {
dialog.dismiss();
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
Utils.INSTANCE.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
} else {
helper.authenticate();
dialog.show();
......
......@@ -4,10 +4,12 @@ import android.app.Activity;
import android.content.Intent;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.IBaseLeanback;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
......@@ -19,8 +21,6 @@ import com.topjohnwu.net.Networking;
import java.io.File;
import java.util.List;
import androidx.appcompat.app.AlertDialog;
class InstallMethodDialog extends AlertDialog.Builder {
<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) {
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)
.setType("*/*")
.addCategory(Intent.CATEGORY_OPENABLE);
......@@ -68,11 +68,11 @@ class InstallMethodDialog extends AlertDialog.Builder {
private <Ctxt extends Activity & IBaseLeanback> void downloadOnly(Ctxt activity) {
activity.runWithExternalRW(() -> {
String filename = Utils.fmt("Magisk-v%s(%d).zip",
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode);
String filename = Utils.INSTANCE.fmt("Magisk-v%s(%d).zip",
Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode);
File zip = new File(Const.EXTERNAL_PATH, filename);
ProgressNotification progress = new ProgressNotification(filename);
Networking.get(Config.magiskLink)
Networking.get(Info.magiskLink)
.setDownloadProgressListener(progress)
.setErrorHandler((conn, e) -> progress.dlFail())
.getAsFile(zip, f -> {
......
......@@ -4,7 +4,7 @@ import android.app.Activity;
import android.net.Uri;
import android.text.TextUtils;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.IBaseLeanback;
import com.topjohnwu.magisk.utils.Utils;
......@@ -18,8 +18,8 @@ import java.util.List;
public class MagiskInstallDialog extends CustomAlertDialog {
public <Ctxt extends Activity & IBaseLeanback> MagiskInstallDialog(Ctxt a) {
super(a);
String filename = Utils.fmt("Magisk-v%s(%d).zip",
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode);
String filename = Utils.INSTANCE.fmt("Magisk-v%s(%d).zip",
Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode);
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)));
setMessage(a.getString(R.string.repo_install_msg, filename));
setCancelable(true);
......@@ -36,13 +36,13 @@ public class MagiskInstallDialog extends CustomAlertDialog {
}
new InstallMethodDialog(a, options).show();
});
if (!TextUtils.isEmpty(Config.magiskNoteLink)) {
if (!TextUtils.isEmpty(Info.magiskNoteLink)) {
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
Utils.openLink(a, Uri.parse(Config.magiskNoteLink));
Utils.INSTANCE.openLink(a, Uri.parse(Info.magiskNoteLink));
} else {
MarkDownWindow.show(a, null, Config.magiskNoteLink);
MarkDownWindow.show(a, null, Info.magiskNoteLink);
}
});
}
......
......@@ -5,7 +5,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.DownloadApp;
import com.topjohnwu.magisk.utils.Utils;
......@@ -15,14 +15,14 @@ public class ManagerInstallDialog extends CustomAlertDialog {
public ManagerInstallDialog(@NonNull Activity a) {
super(a);
String name = Utils.fmt("MagiskManager v%s(%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode);
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)));
setMessage(a.getString(R.string.repo_install_msg, name));
setCancelable(true);
setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name));
if (!TextUtils.isEmpty(Config.managerNoteLink)) {
setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Config.managerNoteLink));
if (!TextUtils.isEmpty(Info.managerNoteLink)) {
setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Info.managerNoteLink));
}
}
}
......@@ -7,9 +7,11 @@ import android.net.Uri;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
import com.topjohnwu.magisk.utils.Utils;
......@@ -19,8 +21,6 @@ import com.topjohnwu.superuser.Shell;
import java.io.File;
import androidx.annotation.NonNull;
public class UninstallDialog extends CustomAlertDialog {
public UninstallDialog(@NonNull Activity activity) {
......@@ -34,17 +34,17 @@ public class UninstallDialog extends CustomAlertDialog {
Shell.su("restore_imgs").submit(result -> {
dialog.cancel();
if (result.isSuccess()) {
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT);
Utils.INSTANCE.toast(R.string.restore_done, Toast.LENGTH_SHORT);
} 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) -> {
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
ProgressNotification progress = new ProgressNotification(zip.getName());
Networking.get(Config.uninstallerLink)
Networking.get(Info.uninstallerLink)
.setDownloadProgressListener(progress)
.setErrorHandler(((conn, e) -> progress.dlFail()))
.getAsFile(zip, f -> {
......
......@@ -6,6 +6,7 @@
<SwitchPreferenceCompat
android:key="dark_theme"
android:defaultValue="true"
android:title="@string/settings_dark_theme_title"
android:summary="@string/settings_dark_theme_summary" />
......@@ -95,28 +96,33 @@
<ListPreference
android:key="su_auto_response"
android:title="@string/auto_response"
android:defaultValue="0"
android:entries="@array/auto_response"
android:entryValues="@array/value_array" />
<ListPreference
android:key="su_request_timeout"
android:title="@string/request_timeout"
android:defaultValue="10"
android:entries="@array/request_timeout"
android:entryValues="@array/request_timeout_value" />
<ListPreference
android:key="su_notification"
android:title="@string/superuser_notification"
android:defaultValue="1"
android:entries="@array/su_notification"
android:entryValues="@array/value_array" />
<SwitchPreferenceCompat
android:key="su_fingerprint"
android:defaultValue="false"
android:title="@string/settings_su_fingerprint_title"
android:summary="@string/settings_su_fingerprint_summary"/>
<SwitchPreferenceCompat
android:key="su_reauth"
android:defaultValue="false"
android:title="@string/settings_su_reauth_title"
android:summary="@string/settings_su_reauth_summary"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment