mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-20 03:37:31 +00:00
Compare commits
42 Commits
v19.3
...
manager-v7
Author | SHA1 | Date | |
---|---|---|---|
![]() |
12bbc7fd6b | ||
![]() |
bf9ac8252b | ||
![]() |
4a3f5dc619 | ||
![]() |
ca156befbd | ||
![]() |
4db41e2ac4 | ||
![]() |
982a43fce1 | ||
![]() |
dd76a74e1c | ||
![]() |
70cb52b2c7 | ||
![]() |
5c7f69acaa | ||
![]() |
f1d9015e5f | ||
![]() |
e8d900c58e | ||
![]() |
a6241ae912 | ||
![]() |
4a697ca2ec | ||
![]() |
58bec7f2c9 | ||
![]() |
213f84985c | ||
![]() |
074b1f8c61 | ||
![]() |
326eee8c83 | ||
![]() |
00bff4912e | ||
![]() |
0ce1720516 | ||
![]() |
ee407472cf | ||
![]() |
f341f3b2dd | ||
![]() |
8513946e09 | ||
![]() |
8ebd9c8927 | ||
![]() |
1d54c5144e | ||
![]() |
e40d4318fa | ||
![]() |
7756e10779 | ||
![]() |
3e58d502d0 | ||
![]() |
1c8846dc57 | ||
![]() |
2f320c7239 | ||
![]() |
e799918ab6 | ||
![]() |
86c4928e0f | ||
![]() |
0293eb5c51 | ||
![]() |
1ee75b6aa6 | ||
![]() |
4b30b224b5 | ||
![]() |
16b232d2a3 | ||
![]() |
3f3b1f5b1d | ||
![]() |
cec017b7bf | ||
![]() |
3123cc1059 | ||
![]() |
caa9df86bc | ||
![]() |
f417389a7a | ||
![]() |
662a5c8ea6 | ||
![]() |
7edfbfb764 |
@@ -43,6 +43,10 @@ android {
|
||||
exclude '/kotlin/**'
|
||||
exclude '/kotlinx/**'
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
@@ -74,12 +78,12 @@ dependencies {
|
||||
implementation "org.koin:koin-android:${vKoin}"
|
||||
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||
|
||||
def vRetrofit = "2.5.0"
|
||||
def vRetrofit = "2.6.0"
|
||||
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||
|
||||
def vOkHttp = "3.12.0"
|
||||
def vOkHttp = "3.12.3"
|
||||
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||
|
||||
@@ -90,13 +94,21 @@ dependencies {
|
||||
implementation "se.ansman.kotshi:api:${vKotshi}"
|
||||
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
||||
|
||||
modules {
|
||||
module('androidx.room:room-runtime') {
|
||||
replacedBy('com.github.topjohnwu:room-runtime')
|
||||
}
|
||||
}
|
||||
def vRoom = "2.1.0"
|
||||
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
||||
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.browser:browser:1.0.0'
|
||||
implementation 'androidx.preference:preference:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha05'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.work:work-runtime:2.0.1'
|
||||
implementation 'androidx.transition:transition:1.2.0-alpha01'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha06'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha07'
|
||||
}
|
||||
|
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@@ -16,9 +16,6 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Retrofit classes
|
||||
-keep,allowobfuscation class com.topjohnwu.magisk.data.network.*
|
||||
|
||||
# Snet
|
||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
|
||||
|
@@ -10,6 +10,9 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.room.Room
|
||||
import androidx.work.impl.WorkDatabase
|
||||
import androidx.work.impl.WorkDatabase_Impl
|
||||
import com.topjohnwu.magisk.di.koinModules
|
||||
import com.topjohnwu.magisk.utils.LocaleManager
|
||||
import com.topjohnwu.magisk.utils.RootUtils
|
||||
@@ -107,6 +110,12 @@ open class App : Application(), Application.ActivityLifecycleCallbacks {
|
||||
Shell.Config.addInitializers(RootUtils::class.java)
|
||||
Shell.Config.setTimeout(2)
|
||||
THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR as ThreadPoolExecutor
|
||||
Room.setFactory {
|
||||
when (it) {
|
||||
WorkDatabase::class.java -> WorkDatabase_Impl()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
|
@@ -21,7 +21,7 @@ object ClassMap {
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
operator fun get(c: Class<*>): Class<*>? {
|
||||
return map.getOrElse(c) { null } //as? Class<T>
|
||||
operator fun <T : Class<*>>get(c: Class<*>): T {
|
||||
return map.getOrElse(c) { throw IllegalArgumentException() } as T
|
||||
}
|
||||
}
|
||||
|
@@ -1,399 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArrayMap;
|
||||
|
||||
import static com.topjohnwu.magisk.ConfigLeanback.getPrefs;
|
||||
import static com.topjohnwu.magisk.utils.XAndroidKt.getPackageName;
|
||||
|
||||
public final class Config {
|
||||
|
||||
private static final ArrayMap<String, Object> defs = new ArrayMap<>();
|
||||
public static int magiskVersionCode = -1;
|
||||
// Current status
|
||||
public static String magiskVersionString = "";
|
||||
// Update Info
|
||||
public static String remoteMagiskVersionString = "";
|
||||
public static int remoteMagiskVersionCode = -1;
|
||||
public static String magiskLink = "";
|
||||
public static String magiskNoteLink = "";
|
||||
public static String magiskMD5 = "";
|
||||
public static String remoteManagerVersionString = "";
|
||||
public static int remoteManagerVersionCode = -1;
|
||||
public static String managerLink = "";
|
||||
public static String managerNoteLink = "";
|
||||
public static String uninstallerLink = "";
|
||||
|
||||
// Install flags
|
||||
public static boolean keepVerity = false;
|
||||
public static boolean keepEnc = false;
|
||||
public static boolean recovery = false;
|
||||
|
||||
public static int suLogTimeout = 14;
|
||||
|
||||
public static class Key {
|
||||
// su configs
|
||||
public static final String ROOT_ACCESS = "root_access";
|
||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
||||
public static final String SU_MNT_NS = "mnt_ns";
|
||||
public static final String SU_MANAGER = "requester";
|
||||
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
|
||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
||||
public static final String SU_NOTIFICATION = "su_notification";
|
||||
public static final String SU_REAUTH = "su_reauth";
|
||||
public static final String SU_FINGERPRINT = "su_fingerprint";
|
||||
|
||||
// prefs
|
||||
public static final String CHECK_UPDATES = "check_update";
|
||||
public static final String UPDATE_CHANNEL = "update_channel";
|
||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
||||
public static final String LOCALE = "locale";
|
||||
public static final String DARK_THEME = "dark_theme";
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
public static final String REPO_ORDER = "repo_order";
|
||||
public static final String SHOW_SYSTEM_APP = "show_system";
|
||||
|
||||
// system state
|
||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
||||
public static final String MAGISKHIDE = "magiskhide";
|
||||
public static final String COREONLY = "disable";
|
||||
}
|
||||
|
||||
public static class Value {
|
||||
public static final int DEFAULT_CHANNEL = -1;
|
||||
public static final int STABLE_CHANNEL = 0;
|
||||
public static final int BETA_CHANNEL = 1;
|
||||
public static final int CUSTOM_CHANNEL = 2;
|
||||
public static final int CANARY_CHANNEL = 3;
|
||||
public static final int CANARY_DEBUG_CHANNEL = 4;
|
||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||
public static final int MULTIUSER_MODE_USER = 2;
|
||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
public static final int NO_NOTIFICATION = 0;
|
||||
public static final int NOTIFICATION_TOAST = 1;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
public static final int[] TIMEOUT_LIST = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
||||
public static final int ORDER_DATE = 1;
|
||||
}
|
||||
|
||||
private static boolean magiskHide = false;
|
||||
|
||||
public static void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void export() {
|
||||
// Flush prefs to disk
|
||||
getPrefs().edit().apply();
|
||||
Context context = ConfigLeanback.getProtectedContext();
|
||||
File xml = new File(context.getFilesDir().getParent() + "/shared_prefs",
|
||||
getPackageName() + "_preferences.xml");
|
||||
Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
private static final int PREF_INT = 0;
|
||||
private static final int PREF_STR_INT = 1;
|
||||
private static final int PREF_BOOL = 2;
|
||||
private static final int PREF_STR = 3;
|
||||
private static final int DB_INT = 4;
|
||||
private static final int DB_BOOL = 5;
|
||||
private static final int DB_STR = 6;
|
||||
|
||||
private static int getConfigType(String key) {
|
||||
switch (key) {
|
||||
case Key.REPO_ORDER:
|
||||
return PREF_INT;
|
||||
|
||||
case Key.SU_REQUEST_TIMEOUT:
|
||||
case Key.SU_AUTO_RESPONSE:
|
||||
case Key.SU_NOTIFICATION:
|
||||
case Key.UPDATE_CHANNEL:
|
||||
return PREF_STR_INT;
|
||||
|
||||
case Key.DARK_THEME:
|
||||
case Key.SU_REAUTH:
|
||||
case Key.CHECK_UPDATES:
|
||||
case Key.MAGISKHIDE:
|
||||
case Key.COREONLY:
|
||||
case Key.SHOW_SYSTEM_APP:
|
||||
return PREF_BOOL;
|
||||
|
||||
case Key.CUSTOM_CHANNEL:
|
||||
case Key.LOCALE:
|
||||
case Key.ETAG_KEY:
|
||||
return PREF_STR;
|
||||
|
||||
case Key.ROOT_ACCESS:
|
||||
case Key.SU_MNT_NS:
|
||||
case Key.SU_MULTIUSER_MODE:
|
||||
return DB_INT;
|
||||
|
||||
case Key.SU_FINGERPRINT:
|
||||
return DB_BOOL;
|
||||
|
||||
case Key.SU_MANAGER:
|
||||
return DB_STR;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
SharedPreferences pref = getPrefs();
|
||||
SharedPreferences.Editor editor = pref.edit();
|
||||
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
|
||||
if (config.exists()) {
|
||||
try {
|
||||
SuFileInputStream is = new SuFileInputStream(config);
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(is, "UTF-8");
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
continue;
|
||||
String key = parser.getAttributeValue(null, "name");
|
||||
String value = parser.getAttributeValue(null, "value");
|
||||
switch (parser.getName()) {
|
||||
case "string":
|
||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
||||
editor.putString(key, parser.nextText());
|
||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
||||
break;
|
||||
case "boolean":
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
||||
break;
|
||||
case "int":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putInt(key, Integer.parseInt(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
case "long":
|
||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
||||
editor.putLong(key, Long.parseLong(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
||||
break;
|
||||
case "float":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putFloat(key, Float.parseFloat(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
default:
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
editor.remove(Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
editor = pref.edit();
|
||||
config.delete();
|
||||
}
|
||||
|
||||
// Set defaults if not set
|
||||
setDefs(pref, editor);
|
||||
|
||||
// These settings are from actual device state
|
||||
editor.putBoolean(Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putInt(Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.apply();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T get(String key) {
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
return (T) (Integer) getPrefs().getInt(key, getDef(key));
|
||||
case PREF_STR_INT:
|
||||
return (T) (Integer) Utils.getPrefsInt(getPrefs(), key, getDef(key));
|
||||
case PREF_BOOL:
|
||||
return (T) (Boolean) getPrefs().getBoolean(key, getDef(key));
|
||||
case PREF_STR:
|
||||
return (T) getPrefs().getString(key, getDef(key));
|
||||
case DB_INT:
|
||||
return (T) (Integer) ConfigLeanback.get(key, (Integer) getDef(key));
|
||||
case DB_BOOL:
|
||||
return (T) (Boolean) (ConfigLeanback.get(key, getDef(key) ? 1 : 0) != 0);
|
||||
case DB_STR:
|
||||
return (T) ConfigLeanback.get(key, getDef(key));
|
||||
}
|
||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void set(String key, Object val) {
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
getPrefs().edit().putInt(key, (int) val).apply();
|
||||
break;
|
||||
case PREF_STR_INT:
|
||||
getPrefs().edit().putString(key, String.valueOf(val)).apply();
|
||||
break;
|
||||
case PREF_BOOL:
|
||||
getPrefs().edit().putBoolean(key, (boolean) val).apply();
|
||||
break;
|
||||
case PREF_STR:
|
||||
getPrefs().edit().putString(key, (String) val).apply();
|
||||
break;
|
||||
case DB_INT:
|
||||
ConfigLeanback.put(key, (int) val);
|
||||
break;
|
||||
case DB_BOOL:
|
||||
ConfigLeanback.put(key, (boolean) val ? 1 : 0);
|
||||
break;
|
||||
case DB_STR:
|
||||
ConfigLeanback.put(key, (String) val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
case PREF_STR_INT:
|
||||
case PREF_BOOL:
|
||||
case PREF_STR:
|
||||
getPrefs().edit().remove(key).apply();
|
||||
break;
|
||||
case DB_BOOL:
|
||||
case DB_INT:
|
||||
ConfigLeanback.delete(key);
|
||||
break;
|
||||
case DB_STR:
|
||||
ConfigLeanback.put(key, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
/* Set default configurations */
|
||||
|
||||
// prefs int
|
||||
defs.put(Key.REPO_ORDER, Value.ORDER_DATE);
|
||||
|
||||
// prefs string int
|
||||
defs.put(Key.SU_REQUEST_TIMEOUT, 10);
|
||||
defs.put(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT);
|
||||
defs.put(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST);
|
||||
defs.put(Key.UPDATE_CHANNEL, Utils.isCanary() ?
|
||||
Value.CANARY_DEBUG_CHANNEL : Value.DEFAULT_CHANNEL);
|
||||
|
||||
// prefs bool
|
||||
defs.put(Key.CHECK_UPDATES, true);
|
||||
defs.put(Key.DARK_THEME, true);
|
||||
//defs.put(Key.SU_REAUTH, false);
|
||||
//defs.put(Key.SHOW_SYSTEM_APP, false);
|
||||
|
||||
// prefs string
|
||||
defs.put(Key.CUSTOM_CHANNEL, "");
|
||||
defs.put(Key.LOCALE, "");
|
||||
//defs.put(Key.ETAG_KEY, null);
|
||||
|
||||
// db int
|
||||
defs.put(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
defs.put(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER);
|
||||
defs.put(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
|
||||
// db bool
|
||||
//defs.put(Key.SU_FINGERPRINT, false);
|
||||
|
||||
// db strings
|
||||
//defs.put(Key.SU_MANAGER, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private static <T> T getDef(String key) throws IllegalArgumentException {
|
||||
Object val = defs.get(key);
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
case DB_INT:
|
||||
case PREF_STR_INT:
|
||||
return val != null ? (T) val : (T) (Integer) 0;
|
||||
case DB_BOOL:
|
||||
case PREF_BOOL:
|
||||
return val != null ? (T) val : (T) (Boolean) false;
|
||||
case DB_STR:
|
||||
case PREF_STR:
|
||||
return (T) val;
|
||||
}
|
||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor) {
|
||||
for (String key : defs.keySet()) {
|
||||
Object value = defs.get(key);
|
||||
int type = getConfigType(key);
|
||||
switch (type) {
|
||||
case DB_INT:
|
||||
editor.putString(key, String.valueOf(ConfigLeanback.get(key, (Integer) value)));
|
||||
continue;
|
||||
case DB_STR:
|
||||
editor.putString(key, ConfigLeanback.get(key, String.valueOf(value)));
|
||||
continue;
|
||||
case DB_BOOL:
|
||||
int bs = ConfigLeanback.get(key, -1);
|
||||
editor.putBoolean(key, bs < 0 ? (Boolean) value : bs != 0);
|
||||
continue;
|
||||
}
|
||||
if (pref.contains(key))
|
||||
continue;
|
||||
switch (type) {
|
||||
case PREF_INT:
|
||||
editor.putInt(key, (Integer) value);
|
||||
break;
|
||||
case PREF_STR_INT:
|
||||
editor.putString(key, String.valueOf(value));
|
||||
break;
|
||||
case PREF_STR:
|
||||
editor.putString(key, (String) value);
|
||||
break;
|
||||
case PREF_BOOL:
|
||||
editor.putBoolean(key, (Boolean) value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
196
app/src/main/java/com/topjohnwu/magisk/Config.kt
Normal file
196
app/src/main/java/com/topjohnwu/magisk/Config.kt
Normal file
@@ -0,0 +1,196 @@
|
||||
package com.topjohnwu.magisk
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Xml
|
||||
import androidx.core.content.edit
|
||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
||||
import com.topjohnwu.magisk.data.database.StringDao
|
||||
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
package com.topjohnwu.magisk
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.data.repository.SettingRepository
|
||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||
import com.topjohnwu.magisk.di.Protected
|
||||
import com.topjohnwu.magisk.utils.inject
|
||||
|
||||
object ConfigLeanback {
|
||||
|
||||
@JvmStatic
|
||||
val protectedContext: Context by inject(Protected)
|
||||
@JvmStatic
|
||||
val prefs: SharedPreferences by inject()
|
||||
|
||||
private val settingRepo: SettingRepository by inject()
|
||||
private val stringRepo: StringRepository by inject()
|
||||
|
||||
@JvmStatic
|
||||
@AnyThread
|
||||
fun put(key: String, value: Int) {
|
||||
settingRepo.put(key, value).subscribeK()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun get(key: String, defaultValue: Int): Int =
|
||||
settingRepo.fetch(key, defaultValue).blockingGet()
|
||||
|
||||
@JvmStatic
|
||||
@AnyThread
|
||||
fun put(key: String, value: String?) {
|
||||
val task = value?.let { stringRepo.put(key, it) } ?: stringRepo.delete(key)
|
||||
task.subscribeK()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun get(key: String, defaultValue: String?): String =
|
||||
stringRepo.fetch(key, defaultValue.orEmpty()).blockingGet()
|
||||
|
||||
@JvmStatic
|
||||
@AnyThread
|
||||
fun delete(key: String) {
|
||||
settingRepo.delete(key).subscribeK()
|
||||
}
|
||||
|
||||
}
|
@@ -9,33 +9,27 @@ object Const {
|
||||
|
||||
const val DEBUG_TAG = "MagiskManager"
|
||||
|
||||
// APK content
|
||||
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||
|
||||
const val SU_KEYSTORE_KEY = "su_key"
|
||||
|
||||
// Paths
|
||||
const val MAGISK_PATH = "/sbin/.magisk/img"
|
||||
@JvmField
|
||||
val EXTERNAL_PATH: File =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
val EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)!!
|
||||
@JvmField
|
||||
var MAGISK_DISABLE_FILE: File = File("xxx")
|
||||
|
||||
var MAGISK_DISABLE_FILE = File("xxx")
|
||||
const val TMP_FOLDER_PATH = "/dev/tmp"
|
||||
const val MAGISK_LOG = "/cache/magisk.log"
|
||||
const val MANAGER_CONFIGS = ".tmp.magisk.config"
|
||||
|
||||
// Versions
|
||||
const val UPDATE_SERVICE_VER = 1
|
||||
const val SNET_EXT_VER = 12
|
||||
const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
|
||||
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
|
||||
|
||||
// Misc
|
||||
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
|
||||
const val MANAGER_CONFIGS = ".tmp.magisk.config"
|
||||
@JvmField
|
||||
val USER_ID = Process.myUid() / 100000
|
||||
|
||||
// Generic
|
||||
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
|
||||
|
||||
init {
|
||||
EXTERNAL_PATH.mkdirs()
|
||||
}
|
||||
@@ -72,12 +66,11 @@ object Const {
|
||||
const val XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"
|
||||
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
|
||||
@JvmField
|
||||
val SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk")
|
||||
@JvmField
|
||||
val BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl")
|
||||
const val GITHUB_RAW_API_URL = "https://raw.githubusercontent.com/"
|
||||
|
||||
private fun getRaw(where: String, name: String) =
|
||||
"https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s".format(where, name)
|
||||
"${GITHUB_RAW_API_URL}topjohnwu/magisk_files/$where/$name"
|
||||
}
|
||||
|
||||
object Key {
|
||||
|
@@ -1,20 +0,0 @@
|
||||
package com.topjohnwu.magisk
|
||||
|
||||
import android.os.Process
|
||||
|
||||
object Constants {
|
||||
|
||||
// Paths
|
||||
val MAGISK_PATH = "/sbin/.magisk/img"
|
||||
val MAGISK_LOG = "/cache/magisk.log"
|
||||
|
||||
val USER_ID get() = Process.myUid() / 100000
|
||||
|
||||
const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
|
||||
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
|
||||
|
||||
const val GITHUB_URL = "https://github.com/"
|
||||
const val GITHUB_API_URL = "https://api.github.com/"
|
||||
const val GITHUB_RAW_API_URL = "https://raw.githubusercontent.com/"
|
||||
|
||||
}
|
30
app/src/main/java/com/topjohnwu/magisk/Info.java
Normal file
30
app/src/main/java/com/topjohnwu/magisk/Info.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.model.entity.UpdateInfo;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
public final class Info {
|
||||
|
||||
public static int magiskVersionCode = -1;
|
||||
|
||||
@NonNull
|
||||
public static String magiskVersionString = "";
|
||||
|
||||
public static UpdateInfo remote = new UpdateInfo();
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
package com.topjohnwu.magisk
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.KConfig.UpdateChannel.STABLE
|
||||
import com.topjohnwu.magisk.di.Protected
|
||||
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
||||
import com.topjohnwu.magisk.utils.inject
|
||||
|
||||
object KConfig : PreferenceModel() {
|
||||
|
||||
override val context: Context by inject(Protected)
|
||||
override val fileName: String = "${context.packageName}_preferences"
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
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())
|
||||
|
@@ -2,12 +2,13 @@ package com.topjohnwu.magisk.data.database
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import com.topjohnwu.magisk.Constants
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.data.database.base.*
|
||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||
import com.topjohnwu.magisk.model.entity.toMap
|
||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||
import com.topjohnwu.magisk.utils.now
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
@@ -47,12 +48,7 @@ class PolicyDao(
|
||||
condition {
|
||||
equals("uid", uid)
|
||||
}
|
||||
}.map { it.firstOrNull()?.toPolicy(context.packageManager) }
|
||||
.doOnError {
|
||||
if (it is PackageManager.NameNotFoundException) {
|
||||
delete(uid).subscribe()
|
||||
}
|
||||
}
|
||||
}.map { it.first().toPolicySafe() }
|
||||
|
||||
fun update(policy: MagiskPolicy) = query<Replace> {
|
||||
values(policy.toMap())
|
||||
@@ -60,10 +56,26 @@ class PolicyDao(
|
||||
|
||||
fun fetchAll() = query<Select> {
|
||||
condition {
|
||||
equals("uid/100000", Constants.USER_ID)
|
||||
equals("uid/100000", Const.USER_ID)
|
||||
}
|
||||
}.flattenAsFlowable { it }
|
||||
.map { it.toPolicy(context.packageManager) }
|
||||
.toList()
|
||||
}.map { it.mapNotNull { it.toPolicySafe() } }
|
||||
|
||||
|
||||
private fun Map<String, String>.toPolicySafe(): MagiskPolicy? {
|
||||
val taskResult = runCatching { toPolicy(context.packageManager) }
|
||||
val result = taskResult.getOrNull()
|
||||
val exception = taskResult.exceptionOrNull()
|
||||
|
||||
Timber.e(exception)
|
||||
|
||||
when (exception) {
|
||||
is PackageManager.NameNotFoundException -> {
|
||||
val uid = getOrElse("uid") { null } ?: return null
|
||||
delete(uid).subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
package com.topjohnwu.magisk.data.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.model.entity.Repo;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Deprecated
|
||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final int DATABASE_VER = 5;
|
||||
private static final String TABLE_NAME = "repos";
|
||||
|
||||
private final SQLiteDatabase mDb;
|
||||
|
||||
@Deprecated
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
mDb = getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
// Nuke old DB and create new table
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
|
||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
|
||||
Config.remove(Config.Key.ETAG_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void removeRepo(Repo repo) {
|
||||
removeRepo(repo.getId());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
for (String id : list) {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Repo getRepo(String id) {
|
||||
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[]{id}, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
return new Repo(c);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Cursor getRawCursor() {
|
||||
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||
case Config.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Config.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Set<String> getRepoIDSet() {
|
||||
HashSet<String> set = new HashSet<>(300);
|
||||
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||
while (c.moveToNext()) {
|
||||
set.add(c.getString(c.getColumnIndex("id")));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
package com.topjohnwu.magisk.data.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.content.edit
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.model.entity.Repo
|
||||
import java.util.*
|
||||
|
||||
@Deprecated("")
|
||||
class RepoDatabaseHelper
|
||||
constructor(context: Context) : SQLiteOpenHelper(context, "repo.db", null, DATABASE_VER) {
|
||||
|
||||
private val mDb: SQLiteDatabase = writableDatabase
|
||||
|
||||
val rawCursor: Cursor
|
||||
@Deprecated("")
|
||||
get() = mDb.query(TABLE_NAME, null, null, null, null, null, null)
|
||||
|
||||
val repoCursor: Cursor
|
||||
@Deprecated("")
|
||||
get() {
|
||||
var orderBy: String? = null
|
||||
when (Config.repoOrder) {
|
||||
Config.Value.ORDER_NAME -> orderBy = "name COLLATE NOCASE"
|
||||
Config.Value.ORDER_DATE -> orderBy = "last_update DESC"
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy)
|
||||
}
|
||||
|
||||
val repoIDSet: Set<String>
|
||||
@Deprecated("")
|
||||
get() {
|
||||
val set = HashSet<String>(300)
|
||||
mDb.query(TABLE_NAME, null, null, null, null, null, null).use { c ->
|
||||
while (c.moveToNext()) {
|
||||
set.add(c.getString(c.getColumnIndex("id")))
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
onUpgrade(db, 0, DATABASE_VER)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion != newVersion) {
|
||||
// Nuke old DB and create new table
|
||||
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
|
||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))")
|
||||
Config.prefs.edit {
|
||||
remove(Config.Key.ETAG_KEY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
onUpgrade(db, 0, DATABASE_VER)
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
fun clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null)
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("")
|
||||
fun removeRepo(id: String) {
|
||||
mDb.delete(TABLE_NAME, "id=?", arrayOf(id))
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
fun removeRepo(repo: Repo) {
|
||||
removeRepo(repo.id)
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
fun removeRepo(list: Iterable<String>) {
|
||||
list.forEach {
|
||||
mDb.delete(TABLE_NAME, "id=?", arrayOf(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
fun addRepo(repo: Repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.contentValues)
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
fun getRepo(id: String): Repo? {
|
||||
mDb.query(TABLE_NAME, null, "id=?", arrayOf(id), null, null, null).use { c ->
|
||||
if (c.moveToNext()) {
|
||||
return Repo(c)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DATABASE_VER = 5
|
||||
private val TABLE_NAME = "repos"
|
||||
}
|
||||
}
|
@@ -10,11 +10,12 @@ class SettingsDao : BaseDao() {
|
||||
condition { equals("key", key) }
|
||||
}.ignoreElement()
|
||||
|
||||
fun put(key: String, value: Int) = query<Insert> {
|
||||
values(key to value.toString())
|
||||
fun put(key: String, value: Int) = query<Replace> {
|
||||
values("key" to key, "value" to value)
|
||||
}.ignoreElement()
|
||||
|
||||
fun fetch(key: String, default: Int = -1) = query<Select> {
|
||||
fields("value")
|
||||
condition { equals("key", key) }
|
||||
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
|
||||
|
||||
|
@@ -10,8 +10,8 @@ class StringDao : BaseDao() {
|
||||
condition { equals("key", key) }
|
||||
}.ignoreElement()
|
||||
|
||||
fun put(key: String, value: String) = query<Insert> {
|
||||
values(key to value)
|
||||
fun put(key: String, value: String) = query<Replace> {
|
||||
values("key" to key, "value" to value)
|
||||
}.ignoreElement()
|
||||
|
||||
fun fetch(key: String, default: String = "") = query<Select> {
|
||||
|
@@ -32,11 +32,7 @@ class Delete : MagiskQueryBuilder {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder()
|
||||
.appendln(requestType)
|
||||
.appendln(table)
|
||||
.appendln(condition)
|
||||
.toString()
|
||||
return listOf(requestType, table, condition).joinToString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +61,7 @@ class Select : MagiskQueryBuilder {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder()
|
||||
.appendln(requestType)
|
||||
.appendln(table)
|
||||
.appendln(condition)
|
||||
.appendln(orderField)
|
||||
.toString()
|
||||
.replace("\n", " ")
|
||||
return listOf(requestType, table, condition, orderField).joinToString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,23 +74,25 @@ open class Insert : MagiskQueryBuilder {
|
||||
override lateinit var table: String
|
||||
|
||||
private val keys get() = _values.keys.joinToString(",")
|
||||
private val values get() = _values.values.joinToString(",") { "\"$it\"" }
|
||||
private var _values: Map<String, String> = mapOf()
|
||||
private val values get() = _values.values.joinToString(",") {
|
||||
when (it) {
|
||||
is Boolean -> if (it) "1" else "0"
|
||||
is Number -> it.toString()
|
||||
else -> "\"$it\""
|
||||
}
|
||||
}
|
||||
private var _values: Map<String, Any> = mapOf()
|
||||
|
||||
fun values(vararg pairs: Pair<String, String>) {
|
||||
fun values(vararg pairs: Pair<String, Any>) {
|
||||
_values = pairs.toMap()
|
||||
}
|
||||
|
||||
fun values(values: Map<String, String>) {
|
||||
fun values(values: Map<String, Any>) {
|
||||
_values = values
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder()
|
||||
.appendln(requestType)
|
||||
.appendln(table)
|
||||
.appendln("($keys) VALUES($values)")
|
||||
.toString()
|
||||
return listOf(requestType, table, "($keys) VALUES($values)").joinToString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,7 @@
|
||||
package com.topjohnwu.magisk.data.network
|
||||
|
||||
import com.topjohnwu.magisk.Constants
|
||||
import com.topjohnwu.magisk.KConfig
|
||||
import com.topjohnwu.magisk.model.entity.MagiskConfig
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
||||
import io.reactivex.Single
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
@@ -16,27 +15,27 @@ interface GithubRawApiServices {
|
||||
//region topjohnwu/magisk_files
|
||||
|
||||
@GET("$MAGISK_FILES/master/stable.json")
|
||||
fun fetchConfig(): Single<MagiskConfig>
|
||||
fun fetchStableUpdate(): Single<UpdateInfo>
|
||||
|
||||
@GET("$MAGISK_FILES/master/beta.json")
|
||||
fun fetchBetaConfig(): Single<MagiskConfig>
|
||||
fun fetchBetaUpdate(): Single<UpdateInfo>
|
||||
|
||||
@GET("$MAGISK_FILES/master/canary_builds/release.json")
|
||||
fun fetchCanaryConfig(): Single<MagiskConfig>
|
||||
fun fetchCanaryUpdate(): Single<UpdateInfo>
|
||||
|
||||
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
|
||||
fun fetchCanaryDebugConfig(): Single<MagiskConfig>
|
||||
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
|
||||
|
||||
@GET
|
||||
fun fetchCustomConfig(@Url url: String): Single<MagiskConfig>
|
||||
fun fetchCustomUpdate(@Url url: String): Single<UpdateInfo>
|
||||
|
||||
@GET("$MAGISK_FILES/{$REVISION}/snet.apk")
|
||||
@Streaming
|
||||
fun fetchSafetynet(@Path(REVISION) revision: String = Constants.SNET_REVISION): Single<ResponseBody>
|
||||
fun fetchSafetynet(@Path(REVISION) revision: String = Const.SNET_REVISION): Single<ResponseBody>
|
||||
|
||||
@GET("$MAGISK_FILES/{$REVISION}/bootctl")
|
||||
@Streaming
|
||||
fun fetchBootctl(@Path(REVISION) revision: String = Constants.BOOTCTL_REVISION): Single<ResponseBody>
|
||||
fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): Single<ResponseBody>
|
||||
|
||||
//endregion
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
@@ -0,0 +1,95 @@
|
||||
package com.topjohnwu.magisk.data.repository
|
||||
|
||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
||||
import com.topjohnwu.magisk.data.database.StringDao
|
||||
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
interface DBConfig {
|
||||
val settingsDao: SettingsDao
|
||||
val stringDao: StringDao
|
||||
|
||||
fun dbSettings(
|
||||
name: String,
|
||||
default: Int
|
||||
) = DBSettingsValue(name, default)
|
||||
|
||||
fun dbSettings(
|
||||
name: String,
|
||||
default: Boolean
|
||||
) = DBBoolSettings(name, default)
|
||||
|
||||
fun dbStrings(
|
||||
name: String,
|
||||
default: String
|
||||
) = DBStringsValue(name, default)
|
||||
|
||||
}
|
||||
|
||||
class DBSettingsValue(
|
||||
private val name: String,
|
||||
private val default: Int
|
||||
) : ReadWriteProperty<DBConfig, Int> {
|
||||
|
||||
private var value: Int? = null
|
||||
|
||||
private fun getKey(property: KProperty<*>) = name.trimEmptyToNull() ?: property.name
|
||||
|
||||
@Synchronized
|
||||
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
|
||||
if (value == null)
|
||||
value = thisRef.settingsDao.fetch(getKey(property), default).blockingGet()
|
||||
return value!!
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Int) {
|
||||
synchronized(this) {
|
||||
this.value = value
|
||||
}
|
||||
thisRef.settingsDao.put(getKey(property), value)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
class DBBoolSettings(
|
||||
name: String,
|
||||
default: Boolean
|
||||
) : ReadWriteProperty<DBConfig, Boolean> {
|
||||
|
||||
val base = DBSettingsValue(name, if (default) 1 else 0)
|
||||
|
||||
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean
|
||||
= base.getValue(thisRef, property) != 0
|
||||
|
||||
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
|
||||
base.setValue(thisRef, property, if (value) 1 else 0)
|
||||
}
|
||||
|
||||
class DBStringsValue(
|
||||
private val name: String,
|
||||
private val default: String
|
||||
) : ReadWriteProperty<DBConfig, String> {
|
||||
|
||||
private var value: String? = null
|
||||
|
||||
private fun getKey(property: KProperty<*>) = name.trimEmptyToNull() ?: property.name
|
||||
|
||||
@Synchronized
|
||||
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
|
||||
if (value == null)
|
||||
value = thisRef.stringDao.fetch(getKey(property), default).blockingGet()
|
||||
return value!!
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: String) {
|
||||
synchronized(this) {
|
||||
this.value = value
|
||||
}
|
||||
thisRef.stringDao.put(getKey(property), value)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
}
|
@@ -1,14 +1,12 @@
|
||||
package com.topjohnwu.magisk.data.repository
|
||||
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.Constants
|
||||
import com.topjohnwu.magisk.data.database.LogDao
|
||||
import com.topjohnwu.magisk.data.database.base.suRaw
|
||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||
import com.topjohnwu.magisk.utils.toSingle
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
@@ -20,9 +18,8 @@ class LogRepository(
|
||||
.map { it.sortByDescending { it.date.time }; it }
|
||||
.map { it.wrap() }
|
||||
|
||||
fun fetchMagiskLogs() = "tail -n 5000 ${Constants.MAGISK_LOG}".suRaw()
|
||||
fun fetchMagiskLogs() = "tail -n 5000 ${Const.MAGISK_LOG}".suRaw()
|
||||
.filter { it.isNotEmpty() }
|
||||
.map { Timber.i(it.toString()); it }
|
||||
|
||||
fun clearLogs() = logDao.deleteAll()
|
||||
fun clearOutdated() = logDao.deleteOutdated()
|
||||
|
@@ -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,55 +22,42 @@ 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) }
|
||||
|
||||
fun fetchSafetynet() = apiRaw
|
||||
.fetchSafetynet()
|
||||
.map { it.writeToFile(context, FILE_SAFETY_NET_APK) }
|
||||
fun fetchSafetynet() = apiRaw.fetchSafetynet()
|
||||
|
||||
fun fetchBootctl() = apiRaw
|
||||
.fetchBootctl()
|
||||
.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 fetchMagiskVersion(): Single<Version> = Single.zip(
|
||||
fetchMagiskVersionName(),
|
||||
fetchMagiskVersionCode(),
|
||||
BiFunction { versionName, versionCode ->
|
||||
Version(versionName, versionCode)
|
||||
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 < Info.magiskVersionCode
|
||||
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
|
||||
Config.updateChannel = Config.Value.BETA_CHANNEL
|
||||
apiRaw.fetchBetaUpdate()
|
||||
} else {
|
||||
Single.just(it)
|
||||
}
|
||||
)
|
||||
|
||||
}.map { Info.remote = it; it }
|
||||
|
||||
fun fetchApps() =
|
||||
Single.fromCallable { packageManager.getInstalledApplications(0) }
|
||||
@@ -93,16 +77,6 @@ class MagiskRepository(
|
||||
.map { HideTarget(it) }
|
||||
.toList()
|
||||
|
||||
private fun fetchMagiskVersionName() = "magisk -v".suRaw()
|
||||
.map { it.first() }
|
||||
.map { it.substring(0 until it.indexOf(":")) }
|
||||
.onErrorReturn { "Unknown" }
|
||||
|
||||
private fun fetchMagiskVersionCode() = "magisk -V".suRaw()
|
||||
.map { it.first() }
|
||||
.map { it.toIntOrNull() ?: -1 }
|
||||
.onErrorReturn { -1 }
|
||||
|
||||
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
|
||||
"magiskhide --%s %s %s".format(isEnabled.state, packageName, process).su().ignoreElement()
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
package com.topjohnwu.magisk.data.repository
|
||||
|
||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
||||
|
||||
class SettingRepository(private val settingsDao: SettingsDao) {
|
||||
|
||||
fun fetch(key: String, default: Int) = settingsDao.fetch(key, default)
|
||||
fun put(key: String, value: Int) = settingsDao.put(key, value)
|
||||
fun delete(key: String) = settingsDao.delete(key)
|
||||
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.topjohnwu.magisk.data.repository
|
||||
|
||||
import com.topjohnwu.magisk.data.database.StringDao
|
||||
|
||||
class StringRepository(private val stringDao: StringDao) {
|
||||
|
||||
fun fetch(key: String, default: String) = stringDao.fetch(key, default)
|
||||
fun put(key: String, value: String) = stringDao.put(key, value)
|
||||
fun delete(key: String) = stringDao.delete(key)
|
||||
|
||||
}
|
@@ -2,7 +2,8 @@ package com.topjohnwu.magisk.di
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.Constants
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.data.network.GithubRawApiServices
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
@@ -19,17 +20,20 @@ val networkingModule = module {
|
||||
single { createConverterFactory() }
|
||||
single { createCallAdapterFactory() }
|
||||
single { createRetrofit(get(), get(), get()) }
|
||||
single { createApiService<GithubRawApiServices>(get(), Constants.GITHUB_RAW_API_URL) }
|
||||
single { createApiService<GithubRawApiServices>(get(), Const.Url.GITHUB_RAW_API_URL) }
|
||||
}
|
||||
|
||||
fun createOkHttpClient(): OkHttpClient {
|
||||
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
val builder = OkHttpClient.Builder()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
builder.addInterceptor(httpLoggingInterceptor)
|
||||
}
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.build()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun createConverterFactory(): Converter.Factory {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import com.topjohnwu.magisk.data.repository.*
|
||||
import com.topjohnwu.magisk.data.repository.AppRepository
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
||||
@@ -8,6 +10,4 @@ val repositoryModule = module {
|
||||
single { MagiskRepository(get(), get(), get()) }
|
||||
single { LogRepository(get()) }
|
||||
single { AppRepository(get()) }
|
||||
single { SettingRepository(get()) }
|
||||
single { StringRepository(get()) }
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package com.topjohnwu.magisk.model.binding
|
||||
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
|
||||
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||
|
||||
class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
|
||||
|
||||
private var recyclerView: RecyclerView? = null
|
||||
|
||||
override fun onBindBinding(
|
||||
binding: ViewDataBinding,
|
||||
variableId: Int,
|
||||
layoutRes: Int,
|
||||
position: Int,
|
||||
item: ComparableRvItem<*>
|
||||
) {
|
||||
super.onBindBinding(binding, variableId, layoutRes, position, item)
|
||||
|
||||
when (item) {
|
||||
is LenientRvItem -> {
|
||||
val recycler = recyclerView ?: return
|
||||
item.onBindingBound(binding)
|
||||
item.onBindingBound(binding, recycler)
|
||||
}
|
||||
else -> item.onBindingBound(binding)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView)
|
||||
this.recyclerView = recyclerView
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
@@ -1,11 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import se.ansman.kotshi.JsonSerializable
|
||||
|
||||
@JsonSerializable
|
||||
data class MagiskApp(
|
||||
val version: String,
|
||||
val versionCode: String,
|
||||
val link: String,
|
||||
val note: String
|
||||
)
|
@@ -1,10 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import se.ansman.kotshi.JsonSerializable
|
||||
|
||||
@JsonSerializable
|
||||
data class MagiskConfig(
|
||||
val app: MagiskApp,
|
||||
val uninstaller: MagiskLink,
|
||||
val magisk: MagiskFlashable
|
||||
)
|
@@ -1,13 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import se.ansman.kotshi.JsonSerializable
|
||||
|
||||
@JsonSerializable
|
||||
data class MagiskFlashable(
|
||||
val version: String,
|
||||
val versionCode: String,
|
||||
val link: String,
|
||||
val note: String,
|
||||
@Json(name = "md5") val hash: String
|
||||
)
|
@@ -1,8 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import se.ansman.kotshi.JsonSerializable
|
||||
|
||||
@JsonSerializable
|
||||
data class MagiskLink(
|
||||
val link: String
|
||||
)
|
@@ -38,7 +38,6 @@ fun Map<String, String>.toLog(): MagiskLog {
|
||||
|
||||
fun Long.toDate() = Date(this)
|
||||
|
||||
|
||||
fun MagiskLog.toMap() = mapOf(
|
||||
"from_uid" to fromUid,
|
||||
"to_uid" to toUid,
|
||||
@@ -47,8 +46,8 @@ fun MagiskLog.toMap() = mapOf(
|
||||
"app_name" to appName,
|
||||
"command" to command,
|
||||
"action" to action,
|
||||
"time" to date
|
||||
).mapValues { it.toString() }
|
||||
"time" to date.time
|
||||
)
|
||||
|
||||
fun MagiskPolicy.toLog(
|
||||
toUid: Int,
|
||||
|
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.topjohnwu.magisk.Constants
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.data.database.base.su
|
||||
import io.reactivex.Single
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
@@ -48,7 +48,7 @@ data class Module(
|
||||
|
||||
@AnyThread
|
||||
fun File.toModule(): Single<Module> {
|
||||
val path = "${Constants.MAGISK_PATH}/$name"
|
||||
val path = "${Const.MAGISK_PATH}/$name"
|
||||
return "dos2unix < $path/module.prop".su()
|
||||
.map { it.first().toModule(path) }
|
||||
}
|
||||
|
@@ -24,42 +24,14 @@ data class MagiskPolicy(
|
||||
|
||||
}
|
||||
|
||||
/*@Throws(PackageManager.NameNotFoundException::class)
|
||||
fun ContentValues.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||
val uid = getAsInteger("uid")
|
||||
val packageName = getAsString("package_name")
|
||||
val info = pm.getApplicationInfo(packageName, 0)
|
||||
if (info.uid != uid)
|
||||
throw PackageManager.NameNotFoundException()
|
||||
return MagiskPolicy(
|
||||
uid = uid,
|
||||
packageName = packageName,
|
||||
policy = getAsInteger("policy"),
|
||||
until = getAsInteger("until").toLong(),
|
||||
logging = getAsInteger("logging") != 0,
|
||||
notification = getAsInteger("notification") != 0,
|
||||
applicationInfo = info,
|
||||
appName = info.loadLabel(pm).toString()
|
||||
)
|
||||
}
|
||||
fun MagiskPolicy.toContentValues() = ContentValues().apply {
|
||||
put("uid", uid)
|
||||
put("uid", uid)
|
||||
put("package_name", packageName)
|
||||
put("policy", policy)
|
||||
put("until", until)
|
||||
put("logging", if (logging) 1 else 0)
|
||||
put("notification", if (notification) 1 else 0)
|
||||
}*/
|
||||
|
||||
fun MagiskPolicy.toMap() = mapOf(
|
||||
"uid" to uid,
|
||||
"package_name" to packageName,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to if (logging) 1 else 0,
|
||||
"notification" to if (notification) 1 else 0
|
||||
).mapValues { it.value.toString() }
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
|
||||
@Throws(PackageManager.NameNotFoundException::class)
|
||||
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||
|
@@ -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 {
|
||||
|
@@ -0,0 +1,33 @@
|
||||
package com.topjohnwu.magisk.model.entity
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import se.ansman.kotshi.JsonSerializable
|
||||
|
||||
@JsonSerializable
|
||||
data class UpdateInfo(
|
||||
val app: ManagerJson = ManagerJson(),
|
||||
val uninstaller: UninstallerJson = UninstallerJson(),
|
||||
val magisk: MagiskJson = MagiskJson()
|
||||
)
|
||||
|
||||
@JsonSerializable
|
||||
data class UninstallerJson(
|
||||
val link: String = ""
|
||||
)
|
||||
|
||||
@JsonSerializable
|
||||
data class MagiskJson(
|
||||
val version: String = "",
|
||||
val versionCode: Int = -1,
|
||||
val link: String = "",
|
||||
val note: String = "",
|
||||
@Json(name = "md5") val hash: String = ""
|
||||
)
|
||||
|
||||
@JsonSerializable
|
||||
data class ManagerJson(
|
||||
val version: String = "",
|
||||
val versionCode: Int = -1,
|
||||
val link: String = "",
|
||||
val note: String = ""
|
||||
)
|
@@ -1,11 +1,26 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.topjohnwu.magisk.R
|
||||
|
||||
class ConsoleRvItem(val item: String) : ComparableRvItem<ConsoleRvItem>() {
|
||||
class ConsoleRvItem(val item: String) : LenientRvItem<ConsoleRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_console
|
||||
|
||||
override fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
||||
val view = binding.root as TextView
|
||||
view.measure(0, 0)
|
||||
val desiredWidth = view.measuredWidth
|
||||
|
||||
view.updateLayoutParams { width = desiredWidth }
|
||||
|
||||
if (recyclerView.width < desiredWidth) {
|
||||
recyclerView.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
|
||||
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
|
||||
/**
|
||||
* This item addresses issues where enclosing recycler has to be invalidated or generally
|
||||
* manipulated with. This shouldn't be however necessary for 99.9% of use-cases. Refrain from using
|
||||
* this item as it provides virtually no additional functionality. Stick with ComparableRvItem.
|
||||
* */
|
||||
abstract class LenientRvItem<in T> : ComparableRvItem<T>() {
|
||||
|
||||
open fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {}
|
||||
|
||||
}
|
@@ -16,7 +16,7 @@ class BooleanProperty(
|
||||
property: KProperty<*>
|
||||
): Boolean {
|
||||
val prefName = name.trimEmptyToNull() ?: property.name
|
||||
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||
return thisRef.prefs.get(prefName, default)
|
||||
}
|
||||
|
||||
override operator fun setValue(
|
||||
|
@@ -16,7 +16,7 @@ class FloatProperty(
|
||||
property: KProperty<*>
|
||||
): Float {
|
||||
val prefName = name.trimEmptyToNull() ?: property.name
|
||||
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||
return thisRef.prefs.get(prefName, default)
|
||||
}
|
||||
|
||||
override operator fun setValue(
|
||||
|
@@ -16,7 +16,7 @@ class IntProperty(
|
||||
property: KProperty<*>
|
||||
): Int {
|
||||
val prefName = name.trimEmptyToNull() ?: property.name
|
||||
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||
return thisRef.prefs.get(prefName, default)
|
||||
}
|
||||
|
||||
override operator fun setValue(
|
||||
|
@@ -16,7 +16,7 @@ class LongProperty(
|
||||
property: KProperty<*>
|
||||
): Long {
|
||||
val prefName = name.trimEmptyToNull() ?: property.name
|
||||
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||
return thisRef.prefs.get(prefName, default)
|
||||
}
|
||||
|
||||
override operator fun setValue(
|
||||
|
@@ -1,51 +1,69 @@
|
||||
package com.topjohnwu.magisk.model.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
abstract class PreferenceModel(
|
||||
private val commitPrefs: Boolean = false
|
||||
) {
|
||||
interface PreferenceModel {
|
||||
|
||||
protected abstract val fileName: String
|
||||
protected abstract val context: Context
|
||||
val context: Context
|
||||
|
||||
internal val prefs get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
||||
val fileName: String
|
||||
get() = "${context.packageName}_preferences"
|
||||
val commitPrefs: Boolean
|
||||
get() = false
|
||||
val prefs: SharedPreferences
|
||||
get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
||||
|
||||
protected fun preference(
|
||||
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,
|
||||
commit: Boolean = commitPrefs
|
||||
): ReadWriteProperty<PreferenceModel, Boolean> = BooleanProperty(name, default, commit)
|
||||
) = BooleanProperty(name, default, commit)
|
||||
|
||||
protected fun preference(
|
||||
fun preference(
|
||||
name: String,
|
||||
default: Float,
|
||||
commit: Boolean = commitPrefs
|
||||
): ReadWriteProperty<PreferenceModel, Float> = FloatProperty(name, default, commit)
|
||||
) = FloatProperty(name, default, commit)
|
||||
|
||||
protected fun preference(
|
||||
fun preference(
|
||||
name: String,
|
||||
default: Int,
|
||||
commit: Boolean = commitPrefs
|
||||
): ReadWriteProperty<PreferenceModel, Int> = IntProperty(name, default, commit)
|
||||
) = IntProperty(name, default, commit)
|
||||
|
||||
protected fun preference(
|
||||
fun preference(
|
||||
name: String,
|
||||
default: Long,
|
||||
commit: Boolean = commitPrefs
|
||||
): ReadWriteProperty<PreferenceModel, Long> = LongProperty(name, default, commit)
|
||||
) = LongProperty(name, default, commit)
|
||||
|
||||
protected fun preference(
|
||||
fun preference(
|
||||
name: String,
|
||||
default: String,
|
||||
commit: Boolean = commitPrefs
|
||||
): ReadWriteProperty<PreferenceModel, String> = StringProperty(name, default, commit)
|
||||
) = StringProperty(name, default, commit)
|
||||
|
||||
protected fun preference(
|
||||
fun preference(
|
||||
name: String,
|
||||
default: Set<String>,
|
||||
commit: Boolean = commitPrefs
|
||||
): ReadWriteProperty<PreferenceModel, Set<String>> = StringSetProperty(name, default, commit)
|
||||
) = StringSetProperty(name, default, commit)
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ class StringProperty(
|
||||
property: KProperty<*>
|
||||
): String {
|
||||
val prefName = name.trimEmptyToNull() ?: property.name
|
||||
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||
return thisRef.prefs.get(prefName, default)
|
||||
}
|
||||
|
||||
override operator fun setValue(
|
||||
|
@@ -16,7 +16,7 @@ class StringSetProperty(
|
||||
property: KProperty<*>
|
||||
): Set<String> {
|
||||
val prefName = name.trimEmptyToNull() ?: property.name
|
||||
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||
return thisRef.prefs.get(prefName, default)
|
||||
}
|
||||
|
||||
override operator fun setValue(
|
||||
|
@@ -6,14 +6,14 @@ 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
|
||||
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.utils.reboot
|
||||
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,10 +73,11 @@ 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.remote = Info.remote.copy(app = Info.remote.app.copy(
|
||||
link = intent.getStringExtra(Const.Key.INTENT_SET_LINK) ?: ""))
|
||||
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME))
|
||||
}
|
||||
Const.Key.BROADCAST_REBOOT -> RootUtils.reboot()
|
||||
Const.Key.BROADCAST_REBOOT -> reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,30 +2,29 @@ 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.entity.MagiskConfig
|
||||
import com.topjohnwu.magisk.model.worker.DelegateWorker
|
||||
import com.topjohnwu.magisk.utils.inject
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.superuser.Shell
|
||||
|
||||
class UpdateCheckService : DelegateWorker() {
|
||||
|
||||
private val magiskRepo: MagiskRepository by inject()
|
||||
|
||||
override fun doWork(): ListenableWorker.Result {
|
||||
val config = runCatching { magiskRepo.fetchConfig().blockingGet() }
|
||||
config.getOrNull()?.let { checkUpdates(it) }
|
||||
return when {
|
||||
config.isFailure -> ListenableWorker.Result.failure()
|
||||
else -> ListenableWorker.Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUpdates(config: MagiskConfig) {
|
||||
when {
|
||||
BuildConfig.VERSION_CODE < config.app.versionCode.toIntOrNull() ?: -1 -> Notifications.managerUpdate()
|
||||
Config.magiskVersionCode < config.magisk.versionCode.toIntOrNull() ?: -1 -> Notifications.magiskUpdate()
|
||||
// Make sure shell initializer was ran
|
||||
Shell.getShell()
|
||||
return runCatching {
|
||||
magiskRepo.fetchUpdate().blockingGet()
|
||||
if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
|
||||
Notifications.managerUpdate()
|
||||
else if (Info.magiskVersionCode < Info.remote.magisk.versionCode)
|
||||
Notifications.magiskUpdate()
|
||||
ListenableWorker.Result.success()
|
||||
}.getOrElse {
|
||||
ListenableWorker.Result.failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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.remote.getMagisk().getHash())) {
|
||||
console.add("- Downloading zip");
|
||||
Networking.get(Config.magiskLink)
|
||||
Networking.get(Info.remote.getMagisk().getLink())
|
||||
.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;
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
@@ -36,12 +35,6 @@ import io.reactivex.Single;
|
||||
|
||||
@Deprecated
|
||||
public class UpdateRepos {
|
||||
private static final DateFormat DATE_FORMAT;
|
||||
|
||||
static {
|
||||
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final RepoDatabaseHelper repoDB;
|
||||
@@ -70,14 +63,25 @@ public class UpdateRepos {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static instance of (Simple)DateFormat is not threadsafe so in order to make it safe it needs
|
||||
* to be created beforehand on the same thread where it'll be used.
|
||||
* See https://stackoverflow.com/a/18383395
|
||||
*/
|
||||
private static SimpleDateFormat getDateFormat() {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return format;
|
||||
}
|
||||
|
||||
/* We sort repos by last push, which means that we only need to check whether the
|
||||
* 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();
|
||||
@@ -94,10 +98,12 @@ public class UpdateRepos {
|
||||
return true;
|
||||
|
||||
try {
|
||||
SimpleDateFormat dateFormat = getDateFormat();
|
||||
|
||||
for (int i = 0; i < res.getResult().length(); i++) {
|
||||
JSONObject rawRepo = res.getResult().getJSONObject(i);
|
||||
String id = rawRepo.getString("name");
|
||||
Date date = DATE_FORMAT.parse(rawRepo.getString("pushed_at"));
|
||||
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
|
||||
moduleQueue.offer(new Pair<>(id, date));
|
||||
}
|
||||
} catch (JSONException | ParseException e) {
|
||||
@@ -110,7 +116,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 =
|
||||
|
@@ -5,13 +5,11 @@ import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.tasks.UpdateRepos
|
||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import com.topjohnwu.net.Networking
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
@@ -21,7 +19,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 +33,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,21 +54,10 @@ 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)
|
||||
// Load repos
|
||||
if (Networking.checkNetworkStatus(this)) {
|
||||
get<UpdateRepos>().exec().subscribeK()
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(this, ClassMap[MainActivity::class.java])
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
|
||||
DONE = true
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.base
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
interface ActivityResultListener {
|
||||
fun onActivityResult(resultCode: Int, data: Intent?)
|
||||
}
|
@@ -12,20 +12,14 @@ 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 com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
||||
import com.topjohnwu.magisk.data.repository.SettingRepository
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
protected val repoDatabase: RepoDatabaseHelper by inject()
|
||||
protected val prefs: SharedPreferences by inject()
|
||||
protected val app: App by inject()
|
||||
protected val settingRepo: SettingRepository by inject()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -67,19 +61,7 @@ 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
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.base
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
interface IBaseLeanback {
|
||||
|
||||
fun runWithExternalRW(callback: Runnable)
|
||||
fun runWithPermissions(vararg permissions: String, callback: Runnable)
|
||||
fun startActivityForResult(intent: Intent, requestCode: Int, listener: ActivityResultListener)
|
||||
|
||||
}
|
@@ -1,10 +1,13 @@
|
||||
package com.topjohnwu.magisk.ui.base
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
@@ -16,6 +19,7 @@ import com.karumi.dexter.listener.PermissionRequest
|
||||
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
|
||||
import com.ncapdevi.fragnav.FragNavController
|
||||
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
||||
import com.skoumal.teanity.view.TeanityActivity
|
||||
import com.skoumal.teanity.viewevents.ViewEvent
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||
@@ -27,16 +31,20 @@ import com.topjohnwu.magisk.model.navigation.Navigator
|
||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||
import com.topjohnwu.magisk.utils.LocaleManager
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import timber.log.Timber
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
typealias RequestCallback = MagiskActivity<*, *>.(Int, Intent?) -> Unit
|
||||
|
||||
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
|
||||
MagiskLeanbackActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
|
||||
Navigator, FragNavController.TransactionListener {
|
||||
TeanityActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
|
||||
Navigator, FragNavController.TransactionListener {
|
||||
|
||||
override val numberOfRootFragments: Int get() = baseFragments.size
|
||||
override val baseFragments: List<KClass<out Fragment>> = listOf()
|
||||
private val resultCallbacks = SparseArrayCompat<RequestCallback>()
|
||||
|
||||
|
||||
protected open val defaultPosition: Int = 0
|
||||
|
||||
@@ -50,14 +58,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?) {
|
||||
@@ -66,6 +72,10 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
||||
super.applyOverrideConfiguration(config)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(LocaleManager.getLocaleContext(base, LocaleManager.locale))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
navigationController?.apply {
|
||||
@@ -185,19 +195,23 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
||||
Dexter.withActivity(this)
|
||||
.withPermissions(*permissions)
|
||||
.withListener(object : MultiplePermissionsListener {
|
||||
override fun onPermissionsChecked(report: MultiplePermissionsReport?) =
|
||||
if (report?.areAllPermissionsGranted() == true) {
|
||||
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
|
||||
if (report.areAllPermissionsGranted()) {
|
||||
request.onSuccess()
|
||||
} else {
|
||||
request.onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionRationaleShouldBeShown(
|
||||
permissions: MutableList<PermissionRequest>?,
|
||||
token: PermissionToken?
|
||||
) = request.onShowRationale(permissions.orEmpty().map { it.name })
|
||||
})
|
||||
.check()
|
||||
permissions: MutableList<PermissionRequest>,
|
||||
token: PermissionToken
|
||||
) = token.continuePermissionRequest()
|
||||
}).check()
|
||||
}
|
||||
|
||||
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
|
||||
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
|
||||
}
|
||||
|
||||
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
||||
@@ -207,4 +221,21 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
resultCallbacks[requestCode]?.apply {
|
||||
resultCallbacks.remove(requestCode)
|
||||
invoke(this@MagiskActivity, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
fun startActivityForResult(
|
||||
intent: Intent,
|
||||
requestCode: Int,
|
||||
listener: RequestCallback
|
||||
) {
|
||||
resultCallbacks[requestCode] = listener
|
||||
startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,64 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.base
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.MultiplePermissionsReport
|
||||
import com.karumi.dexter.PermissionToken
|
||||
import com.karumi.dexter.listener.PermissionRequest
|
||||
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
|
||||
import com.skoumal.teanity.view.TeanityActivity
|
||||
import com.topjohnwu.magisk.Const
|
||||
|
||||
abstract class MagiskLeanbackActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
|
||||
TeanityActivity<ViewModel, Binding>(), IBaseLeanback {
|
||||
|
||||
private val resultListeners = SparseArrayCompat<ActivityResultListener>()
|
||||
|
||||
@Deprecated("Permissions will be checked in a different streamlined way")
|
||||
fun runWithExternalRW(callback: () -> Unit) = runWithExternalRW(Runnable { callback() })
|
||||
|
||||
@Deprecated("Permissions will be checked in a different streamlined way")
|
||||
override fun runWithExternalRW(callback: Runnable) {
|
||||
runWithPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, callback = callback)
|
||||
}
|
||||
|
||||
@Deprecated("Permissions will be checked in a different streamlined way")
|
||||
override fun runWithPermissions(vararg permissions: String, callback: Runnable) {
|
||||
Dexter.withActivity(this)
|
||||
.withPermissions(*permissions)
|
||||
.withListener(object : MultiplePermissionsListener {
|
||||
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
|
||||
if (report?.areAllPermissionsGranted() == true) {
|
||||
Const.EXTERNAL_PATH.mkdirs()
|
||||
callback.run()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionRationaleShouldBeShown(
|
||||
permissions: MutableList<PermissionRequest>?,
|
||||
token: PermissionToken?
|
||||
) = Unit
|
||||
})
|
||||
.check()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
resultListeners.get(requestCode)?.apply {
|
||||
resultListeners.remove(requestCode)
|
||||
onActivityResult(resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun startActivityForResult(
|
||||
intent: Intent,
|
||||
requestCode: Int,
|
||||
listener: ActivityResultListener
|
||||
) {
|
||||
resultListeners.put(requestCode, listener)
|
||||
startActivityForResult(intent, requestCode)
|
||||
}
|
||||
}
|
@@ -103,7 +103,7 @@ class FlashViewModel(
|
||||
.subscribeK { SnackbarEvent(it).publish() }
|
||||
.add()
|
||||
|
||||
fun restartPressed() = RootUtils.reboot()
|
||||
fun restartPressed() = reboot()
|
||||
|
||||
fun backPressed() = back()
|
||||
|
||||
|
@@ -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())
|
||||
}*/
|
||||
}
|
||||
|
@@ -1,16 +1,20 @@
|
||||
package com.topjohnwu.magisk.ui.home
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.skoumal.teanity.viewevents.ViewEvent
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
|
||||
import com.topjohnwu.magisk.model.events.*
|
||||
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper
|
||||
import com.topjohnwu.magisk.utils.copyTo
|
||||
import com.topjohnwu.magisk.utils.inject
|
||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||
import com.topjohnwu.magisk.view.dialogs.*
|
||||
import com.topjohnwu.net.Networking
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import dalvik.system.DexClassLoader
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
@@ -21,6 +25,8 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_magisk
|
||||
override val viewModel: HomeViewModel by viewModel()
|
||||
val magiskRepo: MagiskRepository by inject()
|
||||
lateinit var EXT_FILE: File
|
||||
|
||||
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
|
||||
|
||||
@@ -37,6 +43,11 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
EXT_FILE = File("${requireActivity().filesDir.parent}/snet", "snet.apk")
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
@@ -45,7 +56,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
||||
|
||||
private fun installMagisk() {
|
||||
// Show Manager update first
|
||||
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
if (Info.remote.app.versionCode > BuildConfig.VERSION_CODE) {
|
||||
installManager()
|
||||
return
|
||||
}
|
||||
@@ -61,9 +72,9 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
|
||||
.show(requireActivity(), null, resources.openRawResource(R.raw.changelog))
|
||||
|
||||
private fun downloadSafetyNet(requiresUserInput: Boolean = true) {
|
||||
fun download() = Networking
|
||||
.get(Const.Url.SNET_URL)
|
||||
.getAsFile(EXT_APK) { updateSafetyNet(true) }
|
||||
fun download() = magiskRepo.fetchSafetynet()
|
||||
.map { it.byteStream().copyTo(EXT_FILE) }
|
||||
.subscribeK { updateSafetyNet(true) }
|
||||
|
||||
if (!requiresUserInput) {
|
||||
download()
|
||||
|
@@ -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.remote.magisk.versionCode..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.remote.magisk.version, Info.remote.magisk.versionCode)
|
||||
|
||||
managerState.value = when (Config.remoteManagerVersionCode) {
|
||||
managerState.value = when (Info.remote.app.versionCode) {
|
||||
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.remote.app.version, Info.remote.app.versionCode)
|
||||
}
|
||||
|
||||
private fun ensureEnv() {
|
||||
|
@@ -12,6 +12,7 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.model.binding.BindingAdapter
|
||||
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
|
||||
@@ -30,6 +31,7 @@ class LogViewModel(
|
||||
private val logRepo: LogRepository
|
||||
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
||||
|
||||
val itemsAdapter = BindingAdapter()
|
||||
val items = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
@@ -41,6 +43,8 @@ class LogViewModel(
|
||||
private val logItem get() = items[0] as LogRvItem
|
||||
private val magiskLogItem get() = items[1] as MagiskLogRvItem
|
||||
|
||||
val scrollPosition = KObservableField(0)
|
||||
|
||||
init {
|
||||
currentPage.addOnPropertyChangedCallback {
|
||||
it ?: return@addOnPropertyChangedCallback
|
||||
@@ -57,6 +61,10 @@ class LogViewModel(
|
||||
else -> ""
|
||||
}
|
||||
|
||||
fun scrollDownPressed() {
|
||||
scrollPosition.value = magiskLogItem.items.size - 1
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
fetchLogs().subscribeK { logItem.update(it) }
|
||||
fetchMagiskLog().subscribeK { magiskLogItem.update(it) }
|
||||
|
@@ -16,7 +16,7 @@ import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||
import com.topjohnwu.magisk.utils.RootUtils
|
||||
import com.topjohnwu.magisk.utils.reboot
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
@@ -64,19 +64,20 @@ class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>(
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.reboot -> {
|
||||
RootUtils.reboot()
|
||||
reboot()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_recovery -> {
|
||||
Shell.su("/system/bin/reboot recovery").submit()
|
||||
reboot("recovery")
|
||||
return true
|
||||
}
|
||||
R.id.reboot_bootloader -> {
|
||||
reboot("booloader")
|
||||
Shell.su("/system/bin/reboot bootloader").submit()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_download -> {
|
||||
Shell.su("/system/bin/reboot download").submit()
|
||||
reboot("download")
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
@@ -84,32 +85,12 @@ class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>(
|
||||
}
|
||||
|
||||
private fun selectFile() {
|
||||
magiskActivity.runWithExternalRW {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "application/zip"
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP)
|
||||
magiskActivity.withExternalRW {
|
||||
onSuccess {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "application/zip"
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*override fun getListeningEvents(): IntArray {
|
||||
return intArrayOf(Event.MODULE_LOAD_DONE)
|
||||
}
|
||||
|
||||
override fun onEvent(event: Int) {
|
||||
updateUI(Event.getResult(event))
|
||||
}*/
|
||||
|
||||
/*private fun updateUI(moduleMap: Map<String, Module>) {
|
||||
listModules.clear()
|
||||
listModules.addAll(moduleMap.values)
|
||||
if (listModules.size == 0) {
|
||||
emptyRv!!.visibility = View.VISIBLE
|
||||
recyclerView!!.visibility = View.GONE
|
||||
} else {
|
||||
emptyRv!!.visibility = View.GONE
|
||||
recyclerView!!.visibility = View.VISIBLE
|
||||
recyclerView!!.adapter = ModulesAdapter(listModules)
|
||||
}
|
||||
mSwipeRefreshLayout!!.isRefreshing = false
|
||||
}*/
|
||||
}
|
||||
|
@@ -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()
|
||||
@@ -74,20 +74,22 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
|
||||
}
|
||||
|
||||
private fun openChangelog(item: Repo) {
|
||||
MarkDownWindow.show(context, null, item.detailUrl)
|
||||
MarkDownWindow.show(requireActivity(), null, item.detailUrl)
|
||||
}
|
||||
|
||||
private fun installModule(item: Repo) {
|
||||
val context = magiskActivity
|
||||
|
||||
fun download(install: Boolean) {
|
||||
context.runWithExternalRW {
|
||||
val intent = Intent(activity, ClassMap[DownloadModuleService::class.java])
|
||||
.putExtra("repo", item).putExtra("install", install)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent) //hmm, service starts itself in foreground, this seems unnecessary
|
||||
} else {
|
||||
context.startService(intent)
|
||||
context.withExternalRW {
|
||||
onSuccess {
|
||||
val intent = Intent(activity, ClassMap[DownloadModuleService::class.java])
|
||||
.putExtra("repo", item).putExtra("install", install)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,321 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.settings;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.KConfig;
|
||||
import com.topjohnwu.magisk.KConfig.UpdateChannel;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import kotlin.Pair;
|
||||
|
||||
public final class SettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
private ListPreference updateChannel, autoRes, suNotification,
|
||||
requestTimeout, rootConfig, multiuserConfig, nsConfig;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@SuppressLint("CheckResult")
|
||||
private static void setLocalePreference(ListPreference lp) {
|
||||
lp.setSummary("Loading");
|
||||
lp.setEnabled(false);
|
||||
LocaleManager.getAvailableLocales()
|
||||
.flattenAsFlowable(locales -> locales)
|
||||
.map(locale -> new Pair<>(locale.getDisplayName(locale), LocaleManager.toLanguageTag(locale)))
|
||||
.toList()
|
||||
.map(list -> {
|
||||
CharSequence[] names = new CharSequence[list.size() + 1];
|
||||
CharSequence[] values = new CharSequence[list.size() + 1];
|
||||
|
||||
names[0] = LocaleManager.getString(LocaleManager.getDefaultLocale(), R.string.system_default);
|
||||
values[0] = "";
|
||||
int i = 1;
|
||||
for (Pair<String, String> item : list) {
|
||||
names[i] = item.getFirst();
|
||||
values[i++] = item.getSecond();
|
||||
}
|
||||
return new Pair<>(names, values);
|
||||
})
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(it -> {
|
||||
lp.setEnabled(true);
|
||||
lp.setEntries(it.getFirst());
|
||||
lp.setEntryValues(it.getSecond());
|
||||
lp.setSummary(LocaleManager.getLocale().getDisplayName(LocaleManager.getLocale()));
|
||||
}, Throwable::printStackTrace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onStart() {
|
||||
super.onStart();
|
||||
setHasOptionsMenu(true);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
getPreferenceManager().setStorageDeviceProtected();
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
|
||||
boolean showSuperuser = Utils.showSuperUser();
|
||||
getPrefs().edit()
|
||||
.putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
||||
.apply();
|
||||
|
||||
PreferenceScreen prefScreen = getPreferenceScreen();
|
||||
|
||||
PreferenceCategory generalCatagory = (PreferenceCategory) findPreference("general");
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
hideManager.setOnPreferenceClickListener(pref -> {
|
||||
PatchAPK.hideManager();
|
||||
return true;
|
||||
});
|
||||
Preference restoreManager = findPreference("restore");
|
||||
restoreManager.setOnPreferenceClickListener(pref -> {
|
||||
DownloadApp.restore();
|
||||
return true;
|
||||
});
|
||||
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
||||
getPrefs().edit().remove(Config.Key.ETAG_KEY).apply();
|
||||
getRepoDatabase().clearRepo();
|
||||
//getModuleRepo().deleteAllCached().subscribeOn(Schedulers.io()).subscribe(() -> {
|
||||
//}, throwable -> {
|
||||
//});
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
findPreference("hosts").setOnPreferenceClickListener(pref -> {
|
||||
Shell.su("add_hosts_module").exec();
|
||||
//Utils.loadModules();
|
||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Config.Key.UPDATE_CHANNEL);
|
||||
rootConfig = (ListPreference) findPreference(Config.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Config.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Config.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Config.Key.SU_NOTIFICATION);
|
||||
multiuserConfig = (ListPreference) findPreference(Config.Key.SU_MULTIUSER_MODE);
|
||||
nsConfig = (ListPreference) findPreference(Config.Key.SU_MNT_NS);
|
||||
SwitchPreferenceCompat reauth = (SwitchPreferenceCompat) findPreference(Config.Key.SU_REAUTH);
|
||||
SwitchPreferenceCompat fingerprint = (SwitchPreferenceCompat) findPreference(Config.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||
int channel = Integer.parseInt((String) o);
|
||||
|
||||
final UpdateChannel previousUpdateChannel = KConfig.getUpdateChannel();
|
||||
final UpdateChannel updateChannel = getChannelCompat(channel);
|
||||
|
||||
KConfig.setUpdateChannel(updateChannel);
|
||||
|
||||
if (updateChannel == UpdateChannel.CUSTOM) {
|
||||
View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(KConfig.getCustomUpdateChannel());
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) -> setCustomUpdateChannel(url.getText().toString()))
|
||||
.setNegativeButton(R.string.close, (d, i) -> KConfig.setUpdateChannel(previousUpdateChannel))
|
||||
.setOnCancelListener(d -> KConfig.setUpdateChannel(previousUpdateChannel))
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE));
|
||||
|
||||
/* We only show canary channels if user is already on canary channel
|
||||
* or the user have already chosen canary channel */
|
||||
if (!Utils.isCanary() &&
|
||||
(int) Config.get(Config.Key.UPDATE_CHANNEL) < Config.Value.CANARY_CHANNEL) {
|
||||
// Remove the last 2 entries
|
||||
CharSequence[] entries = updateChannel.getEntries();
|
||||
updateChannel.setEntries(Arrays.copyOf(entries, entries.length - 2));
|
||||
}
|
||||
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserConfig);
|
||||
}
|
||||
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reauth.setEnabled(false);
|
||||
reauth.setChecked(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setChecked(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
||||
if (getApp().getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (!Networking.checkNetworkStatus(requireContext())) {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!showSuperuser) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
switch (key) {
|
||||
case Config.Key.ROOT_ACCESS:
|
||||
case Config.Key.SU_MULTIUSER_MODE:
|
||||
case Config.Key.SU_MNT_NS:
|
||||
getSettingRepo().put(key, Utils.getPrefsInt(prefs, key))
|
||||
.subscribe(() -> {
|
||||
}, Throwable::printStackTrace);
|
||||
break;
|
||||
case Config.Key.DARK_THEME:
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Config.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
||||
break;
|
||||
case Config.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit();
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit();
|
||||
}
|
||||
break;
|
||||
case Config.Key.LOCALE:
|
||||
LocaleManager.setLocale(getApp());
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Config.Key.UPDATE_CHANNEL:
|
||||
case Config.Key.CUSTOM_CHANNEL:
|
||||
//CheckUpdates.check();
|
||||
break;
|
||||
case Config.Key.CHECK_UPDATES:
|
||||
Utils.scheduleUpdateCheck();
|
||||
break;
|
||||
}
|
||||
setSummary(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onPreferenceTreeClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
switch (key) {
|
||||
case Config.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreferenceCompat) preference).isChecked();
|
||||
((SwitchPreferenceCompat) preference).setChecked(!checked);
|
||||
new FingerprintAuthDialog(requireActivity(), () -> {
|
||||
((SwitchPreferenceCompat) preference).setChecked(checked);
|
||||
Config.set(key, checked);
|
||||
}).show();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSummary(String key) {
|
||||
switch (key) {
|
||||
case Config.Key.UPDATE_CHANNEL:
|
||||
int ch = Config.get(key);
|
||||
ch = ch < 0 ? Config.Value.STABLE_CHANNEL : ch;
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[ch]);
|
||||
break;
|
||||
case Config.Key.ROOT_ACCESS:
|
||||
rootConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)[(int) Config.get(key)]);
|
||||
break;
|
||||
case Config.Key.SU_AUTO_RESPONSE:
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)[(int) Config.get(key)]);
|
||||
break;
|
||||
case Config.Key.SU_NOTIFICATION:
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[(int) Config.get(key)]);
|
||||
break;
|
||||
case Config.Key.SU_REQUEST_TIMEOUT:
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary, (int) Config.get(key)));
|
||||
break;
|
||||
case Config.Key.SU_MULTIUSER_MODE:
|
||||
multiuserConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[(int) Config.get(key)]);
|
||||
break;
|
||||
case Config.Key.SU_MNT_NS:
|
||||
nsConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)[(int) Config.get(key)]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
setSummary(Config.Key.UPDATE_CHANNEL);
|
||||
setSummary(Config.Key.ROOT_ACCESS);
|
||||
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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,265 @@
|
||||
package com.topjohnwu.magisk.ui.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
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.ui.base.BasePreferenceFragment
|
||||
import com.topjohnwu.magisk.utils.*
|
||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
||||
import com.topjohnwu.net.Networking
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class SettingsFragment : BasePreferenceFragment() {
|
||||
|
||||
private val repoDatabase: RepoDatabaseHelper by inject()
|
||||
|
||||
private lateinit var updateChannel: ListPreference
|
||||
private lateinit var autoRes: ListPreference
|
||||
private lateinit var suNotification: ListPreference
|
||||
private lateinit var requestTimeout: ListPreference
|
||||
private lateinit var rootConfig: ListPreference
|
||||
private lateinit var multiuserConfig: ListPreference
|
||||
private lateinit var nsConfig: ListPreference
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.settings)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.setStorageDeviceProtected()
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey)
|
||||
|
||||
updateChannel = findPref(Config.Key.UPDATE_CHANNEL)
|
||||
rootConfig = findPref(Config.Key.ROOT_ACCESS)
|
||||
autoRes = findPref(Config.Key.SU_AUTO_RESPONSE)
|
||||
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)
|
||||
val reauth = findPreference(Config.Key.SU_REAUTH) as SwitchPreferenceCompat
|
||||
val fingerprint = findPreference(Config.Key.SU_FINGERPRINT) as SwitchPreferenceCompat
|
||||
val generalCatagory = findPreference("general") as PreferenceCategory
|
||||
val magiskCategory = findPreference("magisk") as PreferenceCategory
|
||||
val suCategory = findPreference("superuser") as PreferenceCategory
|
||||
val hideManager = findPreference("hide")
|
||||
hideManager.setOnPreferenceClickListener {
|
||||
PatchAPK.hideManager()
|
||||
true
|
||||
}
|
||||
val restoreManager = findPreference("restore")
|
||||
restoreManager.setOnPreferenceClickListener {
|
||||
DownloadApp.restore()
|
||||
true
|
||||
}
|
||||
findPreference("clear").setOnPreferenceClickListener {
|
||||
prefs.edit {
|
||||
remove(Config.Key.ETAG_KEY)
|
||||
}
|
||||
repoDatabase.clearRepo()
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
|
||||
true
|
||||
}
|
||||
findPreference("hosts").setOnPreferenceClickListener {
|
||||
Shell.su("add_hosts_module").exec()
|
||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||
true
|
||||
}
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener { _, value ->
|
||||
val channel = Integer.parseInt(value as String)
|
||||
val previous = Config.updateChannel
|
||||
|
||||
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(Config.customChannelUrl)
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
Config.customChannelUrl = url.text.toString() }
|
||||
.setNegativeButton(R.string.close) { _, _ ->
|
||||
Config.updateChannel = previous }
|
||||
.setOnCancelListener { Config.updateChannel = previous }
|
||||
.show()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
setLocalePreference(findPreference(Config.Key.LOCALE) as ListPreference)
|
||||
|
||||
/* We only show canary channels if user is already on canary channel
|
||||
* or the user have already chosen 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)
|
||||
|
||||
}
|
||||
|
||||
setSummary()
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserConfig)
|
||||
}
|
||||
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reauth.isEnabled = false
|
||||
reauth.isChecked = false
|
||||
reauth.setSummary(R.string.android_o_not_support)
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.isEnabled = false
|
||||
fingerprint.isChecked = false
|
||||
fingerprint.setSummary(R.string.disable_fingerprint)
|
||||
}
|
||||
|
||||
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
||||
if (app.packageName == BuildConfig.APPLICATION_ID) {
|
||||
generalCatagory.removePreference(restoreManager)
|
||||
} else {
|
||||
if (!Networking.checkNetworkStatus(requireContext())) {
|
||||
generalCatagory.removePreference(restoreManager)
|
||||
}
|
||||
generalCatagory.removePreference(hideManager)
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager)
|
||||
generalCatagory.removePreference(hideManager)
|
||||
}
|
||||
|
||||
if (!Utils.showSuperUser()) {
|
||||
preferenceScreen.removePreference(suCategory)
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
preferenceScreen.removePreference(magiskCategory)
|
||||
generalCatagory.removePreference(hideManager)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
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)) {
|
||||
runCatching {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile()
|
||||
}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete()
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG)
|
||||
}
|
||||
Config.Key.MAGISKHIDE -> if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit()
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit()
|
||||
}
|
||||
Config.Key.LOCALE -> {
|
||||
LocaleManager.setLocale(app)
|
||||
requireActivity().recreate()
|
||||
}
|
||||
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck()
|
||||
}
|
||||
setSummary(key)
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
when (preference.key) {
|
||||
Config.Key.SU_FINGERPRINT -> {
|
||||
val checked = (preference as SwitchPreferenceCompat).isChecked
|
||||
preference.isChecked = !checked
|
||||
FingerprintAuthDialog(requireActivity()) {
|
||||
preference.isChecked = checked
|
||||
Config.suFingerprint = checked
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setLocalePreference(lp: ListPreference) {
|
||||
lp.isEnabled = false
|
||||
LocaleManager.availableLocales
|
||||
.map {
|
||||
val names = mutableListOf<String>()
|
||||
val values = mutableListOf<String>()
|
||||
|
||||
names.add(LocaleManager.getString(
|
||||
LocaleManager.defaultLocale, R.string.system_default))
|
||||
values.add("")
|
||||
|
||||
it.forEach { locale ->
|
||||
names.add(locale.getDisplayName(locale))
|
||||
values.add(LocaleManager.toLanguageTag(locale))
|
||||
}
|
||||
|
||||
Pair(names.toTypedArray(), values.toTypedArray())
|
||||
}.subscribeK { (names, values) ->
|
||||
lp.isEnabled = true
|
||||
lp.entries = names
|
||||
lp.entryValues = values
|
||||
lp.summary = LocaleManager.locale.getDisplayName(LocaleManager.locale)
|
||||
}
|
||||
}
|
||||
|
||||
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.updateChannel
|
||||
ch = if (ch < 0) Config.Value.STABLE_CHANNEL else ch
|
||||
updateChannel.summary = resources
|
||||
.getStringArray(R.array.update_channel)[ch]
|
||||
}
|
||||
Config.Key.SU_AUTO_RESPONSE -> autoRes.summary = resources
|
||||
.getStringArray(R.array.auto_response)[Config.suAutoReponse]
|
||||
Config.Key.SU_NOTIFICATION -> suNotification.summary = resources
|
||||
.getStringArray(R.array.su_notification)[Config.suNotification]
|
||||
Config.Key.SU_REQUEST_TIMEOUT -> requestTimeout.summary =
|
||||
getString(R.string.request_timeout_summary, Config.suDefaultTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSummary() {
|
||||
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)
|
||||
}
|
||||
}
|
@@ -55,9 +55,16 @@ class SuperuserViewModel(
|
||||
.flattenAsFlowable { it }
|
||||
.map { PolicyRvItem(it, it.applicationInfo.loadIcon(packageManager)) }
|
||||
.toList()
|
||||
.map {
|
||||
it.sortedWith(compareBy(
|
||||
{ it.item.appName.toLowerCase() },
|
||||
{ it.item.packageName }
|
||||
))
|
||||
}
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.applySchedulers()
|
||||
.applyViewModel(this)
|
||||
.subscribeK { items.update(it) }
|
||||
.subscribeK { items.update(it.first, it.second) }
|
||||
.add()
|
||||
}
|
||||
|
||||
|
@@ -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)"
|
||||
|
@@ -11,8 +11,10 @@ import androidx.databinding.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.R
|
||||
@@ -176,4 +178,40 @@ fun setScrollToLast(view: RecyclerView, shouldScrollToLast: Boolean) {
|
||||
} else {
|
||||
view.adapter?.removeListener()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("hide")
|
||||
fun setHidden(view: FloatingActionButton, hide: Boolean) {
|
||||
if (hide) view.hide() else view.show()
|
||||
}
|
||||
|
||||
@BindingAdapter("scrollPosition", "scrollPositionSmooth", requireAll = false)
|
||||
fun setScrollPosition(view: RecyclerView, position: Int, smoothScroll: Boolean) {
|
||||
val adapterItemCount = view.adapter?.itemCount ?: -1
|
||||
if (position !in 0 until adapterItemCount) {
|
||||
// the position is not in adapter bounds, adapter will throw exception for invalid positions
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
smoothScroll -> view.smoothScrollToPosition(position)
|
||||
else -> view.scrollToPosition(position)
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("recyclerScrollEvent")
|
||||
fun setScrollListener(view: RecyclerView, listener: InverseBindingListener) {
|
||||
view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
// don't change this or the recycler will stop at every line, effectively disabling smooth scroll
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
listener.onChange()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "scrollPosition", event = "recyclerScrollEvent")
|
||||
fun getScrollPosition(view: RecyclerView) = (view.layoutManager as? LinearLayoutManager)
|
||||
?.findLastCompletelyVisibleItemPosition()
|
||||
?: -1
|
@@ -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.remote.getApp().getVersion(), Info.remote.getApp().getVersionCode());
|
||||
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.remote.getApp().getLink())
|
||||
.setExecutor(App.THREAD_POOL)
|
||||
.setDownloadProgressListener(progress)
|
||||
.setErrorHandler((conn, e) -> progress.dlFail())
|
||||
|
@@ -1,123 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.KeyguardManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public abstract class FingerprintHelper {
|
||||
|
||||
private FingerprintManager manager;
|
||||
private Cipher cipher;
|
||||
private CancellationSignal cancel;
|
||||
|
||||
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(Const.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(
|
||||
Const.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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import com.topjohnwu.magisk.Config
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
abstract class FingerprintHelper @Throws(Exception::class)
|
||||
protected constructor() {
|
||||
|
||||
private val manager: FingerprintManager?
|
||||
private val cipher: Cipher
|
||||
private var cancel: CancellationSignal? = null
|
||||
private val context: Context by inject()
|
||||
|
||||
init {
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||
manager = context.getSystemService(FingerprintManager::class.java)
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||
keyStore.load(null)
|
||||
var key = keyStore.getKey(SU_KEYSTORE_KEY, null) as SecretKey? ?: generateKey()
|
||||
runCatching {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
||||
}.onFailure {
|
||||
// Only happens on Marshmallow
|
||||
key = generateKey()
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onAuthenticationError(errorCode: Int, errString: CharSequence)
|
||||
|
||||
abstract fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence)
|
||||
|
||||
abstract fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult)
|
||||
|
||||
abstract fun onAuthenticationFailed()
|
||||
|
||||
fun authenticate() {
|
||||
cancel = CancellationSignal()
|
||||
val cryptoObject = FingerprintManager.CryptoObject(cipher)
|
||||
manager!!.authenticate(cryptoObject, cancel, 0, Callback(), null)
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
if (cancel != null)
|
||||
cancel!!.cancel()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun generateKey(): SecretKey {
|
||||
val keygen = KeyGenerator
|
||||
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
|
||||
val builder = KeyGenParameterSpec.Builder(
|
||||
SU_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
builder.setInvalidatedByBiometricEnrollment(false)
|
||||
}
|
||||
keygen.init(builder.build())
|
||||
return keygen.generateKey()
|
||||
}
|
||||
|
||||
private inner class Callback : FingerprintManager.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
this@FingerprintHelper.onAuthenticationError(errorCode, errString)
|
||||
}
|
||||
|
||||
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
|
||||
this@FingerprintHelper.onAuthenticationHelp(helpCode, helpString)
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
||||
this@FingerprintHelper.onAuthenticationSucceeded(result)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
this@FingerprintHelper.onAuthenticationFailed()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SU_KEYSTORE_KEY = "su_key"
|
||||
|
||||
fun useFingerprint(): Boolean {
|
||||
var fp = Config.suFingerprint
|
||||
if (fp && !canUseFingerprint()) {
|
||||
Config.suFingerprint = false
|
||||
fp = false
|
||||
}
|
||||
return fp
|
||||
}
|
||||
|
||||
fun canUseFingerprint(context: Context = get()): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false
|
||||
val km = context.getSystemService(KeyguardManager::class.java)
|
||||
val fm = context.getSystemService(FingerprintManager::class.java)
|
||||
return km?.isKeyguardSecure ?: false &&
|
||||
fm != null && fm.isHardwareDetected && fm.hasEnrolledFingerprints()
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
||||
@@ -155,10 +155,5 @@ class RootUtils : Shell.Initializer() {
|
||||
fun rmAndLaunch(rm: String, component: ComponentName) {
|
||||
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun reboot() {
|
||||
Shell.su("/system/bin/reboot ${if (Config.recovery) "recovery" else ""}").submit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
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
|
||||
|
@@ -1,145 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.entity.OldModule;
|
||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.ExistingPeriodicWorkPolicy;
|
||||
import androidx.work.ListenableWorker;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, resId, duration).show());
|
||||
}
|
||||
|
||||
public static String dlString(String url) {
|
||||
String s = Networking.get(url).execForString().getResult();
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||
return getPrefsInt(prefs, key, 0);
|
||||
}
|
||||
|
||||
public static int dpInPx(int dp) {
|
||||
float scale = App.self.getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5);
|
||||
}
|
||||
|
||||
public static String fmt(String fmt, Object... args) {
|
||||
return String.format(Locale.US, fmt, args);
|
||||
}
|
||||
|
||||
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
|
||||
try {
|
||||
if (info.labelRes > 0) {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.getLocale());
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
public static String getLegalFilename(CharSequence filename) {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||
.replace("#", "").replace("@", "").replace("\\", "_");
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Map<String, OldModule> loadModulesLeanback() {
|
||||
final Map<String, OldModule> moduleMap = new ValueSortedMap<>();
|
||||
final SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||
final SuFile[] modules = path.listFiles((file, name) ->
|
||||
!name.equals("lost+found") && !name.equals(".core")
|
||||
);
|
||||
for (SuFile file : modules) {
|
||||
if (file.isFile()) continue;
|
||||
OldModule module = new OldModule(Const.MAGISK_PATH + "/" + file.getName());
|
||||
moduleMap.put(module.getId(), module);
|
||||
}
|
||||
return moduleMap;
|
||||
}
|
||||
|
||||
public static boolean showSuperUser() {
|
||||
return Shell.rootAccess() && (Const.USER_ID == 0 ||
|
||||
(int) Config.get(Config.Key.SU_MULTIUSER_MODE) !=
|
||||
Config.Value.MULTIUSER_MODE_OWNER_MANAGED);
|
||||
}
|
||||
|
||||
public static boolean isCanary() {
|
||||
return BuildConfig.VERSION_NAME.contains("-");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void scheduleUpdateCheck() {
|
||||
if (Config.get(Config.Key.CHECK_UPDATES)) {
|
||||
Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
//ensures that notification doesn't pop up every time user starts the app
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build();
|
||||
Class<? extends ListenableWorker> service = (Class<? extends ListenableWorker>) ClassMap.get(UpdateCheckService.class);
|
||||
PeriodicWorkRequest request = new PeriodicWorkRequest
|
||||
.Builder(service, 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
123
app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt
Normal file
123
app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.work.*
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.OldModule
|
||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||
import com.topjohnwu.net.Networking
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object Utils {
|
||||
|
||||
val isCanary: Boolean
|
||||
get() = BuildConfig.VERSION_NAME.contains("-")
|
||||
|
||||
fun toast(msg: CharSequence, duration: Int) {
|
||||
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
|
||||
}
|
||||
|
||||
fun toast(resId: Int, duration: Int) {
|
||||
UiThreadHandler.run { Toast.makeText(get(), resId, duration).show() }
|
||||
}
|
||||
|
||||
fun dlString(url: String): String {
|
||||
val s = Networking.get(url).execForString().result
|
||||
return s ?: ""
|
||||
}
|
||||
|
||||
fun getPrefsInt(prefs: SharedPreferences, key: String, def: Int = 0): Int {
|
||||
return prefs.getString(key, def.toString())!!.toInt()
|
||||
}
|
||||
|
||||
fun dpInPx(dp: Int): Int {
|
||||
val scale = get<Resources>().displayMetrics.density
|
||||
return (dp * scale + 0.5).toInt()
|
||||
}
|
||||
|
||||
fun fmt(fmt: String, vararg args: Any): String {
|
||||
return String.format(Locale.US, fmt, *args)
|
||||
}
|
||||
|
||||
fun getAppLabel(info: ApplicationInfo, pm: PackageManager): String {
|
||||
try {
|
||||
if (info.labelRes > 0) {
|
||||
val res = pm.getResourcesForApplication(info)
|
||||
val config = Configuration()
|
||||
config.setLocale(LocaleManager.locale)
|
||||
res.updateConfiguration(config, res.displayMetrics)
|
||||
return res.getString(info.labelRes)
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
return info.loadLabel(pm).toString()
|
||||
}
|
||||
|
||||
fun getLegalFilename(filename: CharSequence): String {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||
.replace("#", "").replace("@", "").replace("\\", "_")
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun loadModulesLeanback(): Map<String, OldModule> {
|
||||
val moduleMap = ValueSortedMap<String, OldModule>()
|
||||
val path = SuFile(Const.MAGISK_PATH)
|
||||
val modules = path.listFiles { _, name -> name != "lost+found" && name != ".core" }
|
||||
for (file in modules!!) {
|
||||
if (file.isFile) continue
|
||||
val module = OldModule(Const.MAGISK_PATH + "/" + file.name)
|
||||
moduleMap[module.id] = module
|
||||
}
|
||||
return moduleMap
|
||||
}
|
||||
|
||||
fun showSuperUser(): Boolean {
|
||||
return Shell.rootAccess() && (Const.USER_ID == 0
|
||||
|| Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED)
|
||||
}
|
||||
|
||||
fun scheduleUpdateCheck() {
|
||||
if (Config.checkUpdate) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
val request = PeriodicWorkRequest
|
||||
.Builder(ClassMap[UpdateCheckService::class.java], 12, TimeUnit.HOURS)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork(
|
||||
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
|
||||
ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||
} else {
|
||||
WorkManager.getInstance().cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
|
||||
}
|
||||
}
|
||||
|
||||
fun openLink(context: Context, link: Uri) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, link)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import androidx.databinding.ObservableList
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.skoumal.teanity.util.DiffObservableList
|
||||
@@ -76,4 +77,8 @@ fun <T1> ObservableList<T1>.copyNewInputInto(
|
||||
val addedValues = sender?.slice(positionStart until positionEnd).orEmpty()
|
||||
target.addAll(addedValues)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
|
||||
put(key, value)
|
||||
}
|
||||
|
@@ -1,12 +1,6 @@
|
||||
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)
|
||||
}
|
@@ -1,18 +1,13 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import com.topjohnwu.magisk.Info
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||
import java.io.File
|
||||
|
||||
fun reboot(recovery: Boolean = false): Shell.Result {
|
||||
val command = StringBuilder("/system/bin/reboot")
|
||||
.appendIf(recovery) {
|
||||
append(" recovery")
|
||||
}
|
||||
.toString()
|
||||
|
||||
return Shell.su(command).exec()
|
||||
fun reboot(reason: String = if (Info.recovery) "recovery" else "") {
|
||||
Shell.su("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
|
||||
}
|
||||
|
||||
fun File.suOutputStream() = SuFileOutputStream(this)
|
||||
|
@@ -1,66 +0,0 @@
|
||||
package com.topjohnwu.magisk.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.net.Networking;
|
||||
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;
|
||||
import ru.noties.markwon.image.svg.SvgPlugin;
|
||||
|
||||
public class MarkDownWindow {
|
||||
|
||||
public static void show(Context activity, String title, String url) {
|
||||
Networking.get(url).getAsString(new Listener(activity, title));
|
||||
}
|
||||
|
||||
public static void show(Context activity, String title, InputStream is) {
|
||||
try (Scanner s = new Scanner(is, "UTF-8")) {
|
||||
s.useDelimiter("\\A");
|
||||
new Listener(activity, title).onResponse(s.next());
|
||||
}
|
||||
}
|
||||
|
||||
static class Listener implements ResponseListener<String> {
|
||||
|
||||
Context activity;
|
||||
String title;
|
||||
|
||||
Listener(Context a, String t) {
|
||||
activity = a;
|
||||
title = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(String md) {
|
||||
Markwon markwon = Markwon.builder(activity)
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.usePlugin(ImagesPlugin.create(activity))
|
||||
.usePlugin(SvgPlugin.create(activity.getResources()))
|
||||
.build();
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
alert.setTitle(title);
|
||||
View mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null);
|
||||
TextView tv = mv.findViewById(R.id.md_txt);
|
||||
try {
|
||||
markwon.setMarkdown(tv, md);
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
//Nothing we can do about this error other than show error message
|
||||
tv.setText(R.string.download_file_error);
|
||||
}
|
||||
alert.setView(mv);
|
||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package com.topjohnwu.magisk.view
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.net.Networking
|
||||
import com.topjohnwu.net.ResponseListener
|
||||
import ru.noties.markwon.Markwon
|
||||
import ru.noties.markwon.html.HtmlPlugin
|
||||
import ru.noties.markwon.image.ImagesPlugin
|
||||
import ru.noties.markwon.image.svg.SvgPlugin
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
object MarkDownWindow {
|
||||
|
||||
fun show(activity: Context, title: String?, url: String) {
|
||||
Networking.get(url).getAsString(Listener(activity, title))
|
||||
}
|
||||
|
||||
fun show(activity: Context, title: String?, input: InputStream) {
|
||||
Scanner(input, "UTF-8").use {
|
||||
it.useDelimiter("\\A")
|
||||
Listener(activity, title).onResponse(it.next())
|
||||
}
|
||||
}
|
||||
|
||||
internal class Listener(var activity: Context, var title: String?) : ResponseListener<String> {
|
||||
|
||||
override fun onResponse(md: String) {
|
||||
val markwon = Markwon.builder(activity)
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.usePlugin(ImagesPlugin.create(activity))
|
||||
.usePlugin(SvgPlugin.create(activity.resources))
|
||||
.build()
|
||||
val alert = AlertDialog.Builder(activity)
|
||||
alert.setTitle(title)
|
||||
val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null)
|
||||
val tv = mv.findViewById<TextView>(R.id.md_txt)
|
||||
try {
|
||||
markwon.setMarkdown(tv, md)
|
||||
} catch (e: ExceptionInInitializerError) {
|
||||
//Nothing we can do about this error other than show error message
|
||||
tv.setText(R.string.download_file_error)
|
||||
}
|
||||
|
||||
alert.setView(mv)
|
||||
alert.setNegativeButton(R.string.close) { dialog, _ -> dialog.dismiss() }
|
||||
alert.show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.remote.getApp().getVersion(), Info.remote.getApp().getVersionCode());
|
||||
|
||||
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.remote.getApp().getLink());
|
||||
intent.putExtra(Const.Key.INTENT_SET_NAME, name);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
@@ -1,85 +0,0 @@
|
||||
package com.topjohnwu.magisk.view;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
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;
|
||||
private Notification notification;
|
||||
private long prevTime;
|
||||
|
||||
public ProgressNotification(String title) {
|
||||
builder = Notifications.progress(title);
|
||||
prevTime = System.currentTimeMillis();
|
||||
update();
|
||||
Utils.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long bytesDownloaded, long totalBytes) {
|
||||
long cur = System.currentTimeMillis();
|
||||
if (cur - prevTime >= 1000) {
|
||||
prevTime = cur;
|
||||
int progress = (int) (bytesDownloaded * 100 / totalBytes);
|
||||
builder.setProgress(100, progress, false);
|
||||
builder.setContentText(progress + "%");
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
public NotificationCompat.Builder getNotificationBuilder() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
public Notification getNotification() {
|
||||
return notification;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
notification = builder.build();
|
||||
Notifications.mgr.notify(hashCode(), notification);
|
||||
}
|
||||
|
||||
private void lastUpdate() {
|
||||
notification = builder.build();
|
||||
Notifications.mgr.cancel(hashCode());
|
||||
Notifications.mgr.notify(notification.hashCode(), notification);
|
||||
}
|
||||
|
||||
public void dlDone() {
|
||||
dlDone(PendingIntent.getActivity(App.self, hashCode(),
|
||||
new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
}
|
||||
|
||||
public void dlDone(PendingIntent intent) {
|
||||
builder.setProgress(0, 0, false)
|
||||
.setContentText(App.self.getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentIntent(intent)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true);
|
||||
lastUpdate();
|
||||
}
|
||||
|
||||
public void dlFail() {
|
||||
builder.setProgress(0, 0, false)
|
||||
.setContentText(App.self.getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false);
|
||||
lastUpdate();
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
Notifications.mgr.cancel(hashCode());
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package com.topjohnwu.magisk.view
|
||||
|
||||
import android.app.Notification
|
||||
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
|
||||
|
||||
class ProgressNotification(title: String) : DownloadProgressListener {
|
||||
|
||||
val notificationBuilder: NotificationCompat.Builder = Notifications.progress(title)
|
||||
lateinit var notification: Notification
|
||||
private set
|
||||
private var prevTime: Long = 0
|
||||
|
||||
init {
|
||||
prevTime = System.currentTimeMillis()
|
||||
update()
|
||||
Utils.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT)
|
||||
}
|
||||
|
||||
override fun onProgress(bytesDownloaded: Long, totalBytes: Long) {
|
||||
val cur = System.currentTimeMillis()
|
||||
if (cur - prevTime >= 1000) {
|
||||
prevTime = cur
|
||||
val progress = (bytesDownloaded * 100 / totalBytes).toInt()
|
||||
notificationBuilder.setProgress(100, progress, false)
|
||||
notificationBuilder.setContentText("$progress%")
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
fun update() {
|
||||
notification = notificationBuilder.build()
|
||||
Notifications.mgr.notify(hashCode(), notification)
|
||||
}
|
||||
|
||||
private fun lastUpdate() {
|
||||
Notifications.mgr.cancel(hashCode())
|
||||
notification = notificationBuilder.build().apply {
|
||||
Notifications.mgr.notify(hashCode(), this)
|
||||
}
|
||||
}
|
||||
|
||||
fun dlDone(intent: PendingIntent = PendingIntent.getActivity(App.self, hashCode(),
|
||||
Intent(), PendingIntent.FLAG_UPDATE_CURRENT)) {
|
||||
notificationBuilder.setProgress(0, 0, false)
|
||||
.setContentText(App.self.getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentIntent(intent)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
lastUpdate()
|
||||
}
|
||||
|
||||
fun dlFail() {
|
||||
notificationBuilder.setProgress(0, 0, false)
|
||||
.setContentText(App.self.getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false)
|
||||
lastUpdate()
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
Notifications.mgr.cancel(hashCode())
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
package com.topjohnwu.magisk.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ui.SplashActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class Shortcuts {
|
||||
|
||||
public static void setup(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
|
||||
manager.setDynamicShortcuts(getShortCuts(context));
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||
private static ArrayList<ShortcutInfo> getShortCuts(Context context) {
|
||||
ArrayList<ShortcutInfo> shortCuts = new ArrayList<>();
|
||||
boolean root = Shell.rootAccess();
|
||||
if (Utils.showSuperUser()) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "superuser")
|
||||
.setShortLabel(context.getString(R.string.superuser))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_superuser))
|
||||
.setRank(0)
|
||||
.build());
|
||||
}
|
||||
if (root && (boolean) Config.get(Config.Key.MAGISKHIDE)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "magiskhide")
|
||||
.setShortLabel(context.getString(R.string.magiskhide))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide))
|
||||
.setRank(1)
|
||||
.build());
|
||||
}
|
||||
if (!(boolean) Config.get(Config.Key.COREONLY) && root && Config.magiskVersionCode >= 0) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "modules")
|
||||
.setShortLabel(context.getString(R.string.modules))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_extension))
|
||||
.setRank(3)
|
||||
.build());
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "downloads")
|
||||
.setShortLabel(context.getString(R.string.downloads))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download))
|
||||
.setRank(2)
|
||||
.build());
|
||||
}
|
||||
return shortCuts;
|
||||
}
|
||||
}
|
72
app/src/main/java/com/topjohnwu/magisk/view/Shortcuts.kt
Normal file
72
app/src/main/java/com/topjohnwu/magisk/view/Shortcuts.kt
Normal file
@@ -0,0 +1,72 @@
|
||||
package com.topjohnwu.magisk.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.ui.SplashActivity
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
|
||||
object Shortcuts {
|
||||
|
||||
fun setup(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
val manager = context.getSystemService(ShortcutManager::class.java)
|
||||
manager?.dynamicShortcuts = getShortCuts(context)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||
private fun getShortCuts(context: Context): List<ShortcutInfo> {
|
||||
val shortCuts = mutableListOf<ShortcutInfo>()
|
||||
val root = Shell.rootAccess()
|
||||
if (Utils.showSuperUser()) {
|
||||
shortCuts.add(ShortcutInfo.Builder(context, "superuser")
|
||||
.setShortLabel(context.getString(R.string.superuser))
|
||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
||||
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_superuser))
|
||||
.setRank(0)
|
||||
.build())
|
||||
}
|
||||
if (root && Config.magiskHide) {
|
||||
shortCuts.add(ShortcutInfo.Builder(context, "magiskhide")
|
||||
.setShortLabel(context.getString(R.string.magiskhide))
|
||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
||||
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide))
|
||||
.setRank(1)
|
||||
.build())
|
||||
}
|
||||
if (!Config.coreOnly && root && Info.magiskVersionCode >= 0) {
|
||||
shortCuts.add(ShortcutInfo.Builder(context, "modules")
|
||||
.setShortLabel(context.getString(R.string.modules))
|
||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
||||
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_extension))
|
||||
.setRank(3)
|
||||
.build())
|
||||
shortCuts.add(ShortcutInfo.Builder(context, "downloads")
|
||||
.setShortLabel(context.getString(R.string.downloads))
|
||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download))
|
||||
.setRank(2)
|
||||
.build())
|
||||
}
|
||||
return shortCuts
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
package com.topjohnwu.magisk.view;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.XAndroidKt;
|
||||
|
||||
public class SnackbarMaker {
|
||||
|
||||
public static Snackbar make(Activity activity, CharSequence text, int duration) {
|
||||
View view = activity.findViewById(android.R.id.content);
|
||||
return make(view, text, duration);
|
||||
}
|
||||
|
||||
public static Snackbar make(Activity activity, @StringRes int resId, int duration) {
|
||||
return make(activity, activity.getString(resId), duration);
|
||||
}
|
||||
|
||||
public static Snackbar make(View view, CharSequence text, int duration) {
|
||||
Snackbar snack = Snackbar.make(view, text, duration);
|
||||
setup(snack);
|
||||
return snack;
|
||||
}
|
||||
|
||||
public static Snackbar make(View view, @StringRes int resId, int duration) {
|
||||
Snackbar snack = Snackbar.make(view, resId, duration);
|
||||
setup(snack);
|
||||
return snack;
|
||||
}
|
||||
|
||||
private static void setup(Snackbar snack) {
|
||||
TextView text = snack.getView().findViewById(com.google.android.material.R.id.snackbar_text);
|
||||
text.setMaxLines(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static void showUri(Activity activity, Uri uri) {
|
||||
make(activity, activity.getString(R.string.internal_storage,
|
||||
"/Download/" + XAndroidKt.getFileName(uri)),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok, (v)->{}).show();
|
||||
}
|
||||
}
|
46
app/src/main/java/com/topjohnwu/magisk/view/SnackbarMaker.kt
Normal file
46
app/src/main/java/com/topjohnwu/magisk/view/SnackbarMaker.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.topjohnwu.magisk.view
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.utils.fileName
|
||||
|
||||
object SnackbarMaker {
|
||||
|
||||
fun make(activity: Activity, text: CharSequence, duration: Int): Snackbar {
|
||||
val view = activity.findViewById<View>(android.R.id.content)
|
||||
return make(view, text, duration)
|
||||
}
|
||||
|
||||
fun make(activity: Activity, @StringRes resId: Int, duration: Int): Snackbar {
|
||||
return make(activity, activity.getString(resId), duration)
|
||||
}
|
||||
|
||||
fun make(view: View, text: CharSequence, duration: Int): Snackbar {
|
||||
val snack = Snackbar.make(view, text, duration)
|
||||
setup(snack)
|
||||
return snack
|
||||
}
|
||||
|
||||
fun make(view: View, @StringRes resId: Int, duration: Int): Snackbar {
|
||||
val snack = Snackbar.make(view, resId, duration)
|
||||
setup(snack)
|
||||
return snack
|
||||
}
|
||||
|
||||
private fun setup(snack: Snackbar) {
|
||||
val text = snack.view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
|
||||
text.maxLines = Integer.MAX_VALUE
|
||||
}
|
||||
|
||||
fun showUri(activity: Activity, uri: Uri) {
|
||||
make(activity, activity.getString(R.string.internal_storage,
|
||||
"/Download/" + uri.fileName),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok) { }.show()
|
||||
}
|
||||
}
|
@@ -1,137 +0,0 @@
|
||||
package com.topjohnwu.magisk.view.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.topjohnwu.magisk.databinding.AlertDialogBinding;
|
||||
|
||||
public class CustomAlertDialog extends AlertDialog.Builder {
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
|
||||
protected AlertDialog dialog;
|
||||
protected AlertDialogBinding binding;
|
||||
|
||||
{
|
||||
binding = AlertDialogBinding.inflate(LayoutInflater.from(getContext()));
|
||||
super.setView(binding.getRoot());
|
||||
binding.message.setVisibility(View.GONE);
|
||||
binding.negative.setVisibility(View.GONE);
|
||||
binding.positive.setVisibility(View.GONE);
|
||||
binding.neutral.setVisibility(View.GONE);
|
||||
binding.buttonPanel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Context context, @StyleRes int themeResId) {
|
||||
super(context, themeResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(int layoutResId) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(View view) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@Nullable CharSequence message) {
|
||||
binding.message.setVisibility(View.VISIBLE);
|
||||
binding.message.setText(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@StringRes int messageId) {
|
||||
return setMessage(getContext().getString(messageId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
binding.buttonPanel.setVisibility(View.VISIBLE);
|
||||
binding.positive.setVisibility(View.VISIBLE);
|
||||
binding.positive.setText(text);
|
||||
positiveListener = listener;
|
||||
binding.positive.setOnClickListener(v -> {
|
||||
if (positiveListener != null) {
|
||||
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setPositiveButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
binding.buttonPanel.setVisibility(View.VISIBLE);
|
||||
binding.negative.setVisibility(View.VISIBLE);
|
||||
binding.negative.setText(text);
|
||||
negativeListener = listener;
|
||||
binding.negative.setOnClickListener(v -> {
|
||||
if (negativeListener != null) {
|
||||
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNegativeButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
binding.buttonPanel.setVisibility(View.VISIBLE);
|
||||
binding.neutral.setVisibility(View.VISIBLE);
|
||||
binding.neutral.setText(text);
|
||||
neutralListener = listener;
|
||||
binding.neutral.setOnClickListener(v -> {
|
||||
if (neutralListener != null) {
|
||||
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNeutralButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
dialog = super.create();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
package com.topjohnwu.magisk.view.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
import com.topjohnwu.magisk.databinding.AlertDialogBinding
|
||||
|
||||
open class CustomAlertDialog : AlertDialog.Builder {
|
||||
|
||||
private var positiveListener: DialogInterface.OnClickListener? = null
|
||||
private var negativeListener: DialogInterface.OnClickListener? = null
|
||||
private var neutralListener: DialogInterface.OnClickListener? = null
|
||||
|
||||
protected var dialog: AlertDialog? = null
|
||||
protected var binding: AlertDialogBinding =
|
||||
AlertDialogBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
init {
|
||||
super.setView(binding.root)
|
||||
binding.message.visibility = View.GONE
|
||||
binding.negative.visibility = View.GONE
|
||||
binding.positive.visibility = View.GONE
|
||||
binding.neutral.visibility = View.GONE
|
||||
binding.buttonPanel.visibility = View.GONE
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, @StyleRes themeResId: Int) : super(context, themeResId)
|
||||
|
||||
override fun setView(layoutResId: Int): CustomAlertDialog {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setView(view: View): CustomAlertDialog {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setMessage(message: CharSequence?): CustomAlertDialog {
|
||||
binding.message.visibility = View.VISIBLE
|
||||
binding.message.text = message
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setMessage(@StringRes messageId: Int): CustomAlertDialog {
|
||||
return setMessage(context.getString(messageId))
|
||||
}
|
||||
|
||||
override fun setPositiveButton(text: CharSequence, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
|
||||
binding.buttonPanel.visibility = View.VISIBLE
|
||||
binding.positive.visibility = View.VISIBLE
|
||||
binding.positive.text = text
|
||||
positiveListener = listener
|
||||
binding.positive.setOnClickListener {
|
||||
positiveListener?.onClick(dialog, DialogInterface.BUTTON_POSITIVE)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setPositiveButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
|
||||
return setPositiveButton(context.getString(textId), listener)
|
||||
}
|
||||
|
||||
override fun setNegativeButton(text: CharSequence, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
|
||||
binding.buttonPanel.visibility = View.VISIBLE
|
||||
binding.negative.visibility = View.VISIBLE
|
||||
binding.negative.text = text
|
||||
negativeListener = listener
|
||||
binding.negative.setOnClickListener {
|
||||
negativeListener?.onClick(dialog, DialogInterface.BUTTON_NEGATIVE)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setNegativeButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
|
||||
return setNegativeButton(context.getString(textId), listener)
|
||||
}
|
||||
|
||||
override fun setNeutralButton(text: CharSequence, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
|
||||
binding.buttonPanel.visibility = View.VISIBLE
|
||||
binding.neutral.visibility = View.VISIBLE
|
||||
binding.neutral.text = text
|
||||
neutralListener = listener
|
||||
binding.neutral.setOnClickListener {
|
||||
neutralListener?.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setNeutralButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
|
||||
return setNeutralButton(context.getString(textId), listener)
|
||||
}
|
||||
|
||||
override fun create(): AlertDialog {
|
||||
return super.create().apply { dialog = this }
|
||||
}
|
||||
|
||||
override fun show(): AlertDialog {
|
||||
return create().apply { show() }
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
dialog?.dismiss()
|
||||
}
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
package com.topjohnwu.magisk.view.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.tasks.MagiskInstaller;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
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) {
|
||||
super(activity);
|
||||
setTitle(R.string.env_fix_title);
|
||||
setMessage(R.string.env_fix_msg);
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.yes, (d, i) -> {
|
||||
ProgressDialog pd = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.setup_title),
|
||||
activity.getString(R.string.setup_msg));
|
||||
new MagiskInstaller() {
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
installDir = new SuFile("/data/adb/magisk");
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec();
|
||||
return extractZip() && Shell.su("fix_env").exec().isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
pd.dismiss();
|
||||
Utils.toast(success ? R.string.reboot_delay_toast : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(RootUtils::reboot, 5000);
|
||||
}
|
||||
}.exec();
|
||||
});
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.topjohnwu.magisk.view.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ProgressDialog
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.reboot
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
|
||||
class EnvFixDialog(activity: Activity) : CustomAlertDialog(activity) {
|
||||
|
||||
init {
|
||||
setTitle(R.string.env_fix_title)
|
||||
setMessage(R.string.env_fix_msg)
|
||||
setCancelable(true)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
val pd = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.setup_title),
|
||||
activity.getString(R.string.setup_msg))
|
||||
object : MagiskInstaller() {
|
||||
override fun operations(): Boolean {
|
||||
installDir = SuFile("/data/adb/magisk")
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
||||
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
||||
}
|
||||
|
||||
override fun onResult(success: Boolean) {
|
||||
pd.dismiss()
|
||||
Utils.toast(if (success) R.string.reboot_delay_toast else R.string.setup_fail, Toast.LENGTH_LONG)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed({ reboot() }, 5000)
|
||||
}
|
||||
}.exec()
|
||||
}
|
||||
setNegativeButton(R.string.no_thanks, null)
|
||||
}
|
||||
}
|
@@ -1,106 +0,0 @@
|
||||
package com.topjohnwu.magisk.view.dialogs;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
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;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public class FingerprintAuthDialog extends CustomAlertDialog {
|
||||
|
||||
private final Runnable callback;
|
||||
@Nullable
|
||||
private Runnable failureCallback;
|
||||
private DialogFingerprintHelper helper;
|
||||
|
||||
public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess) {
|
||||
super(activity);
|
||||
callback = onSuccess;
|
||||
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.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.setGravity(Gravity.CENTER);
|
||||
setMessage(R.string.auth_fingerprint);
|
||||
setNegativeButton(R.string.close, (d, w) -> {
|
||||
helper.cancel();
|
||||
if (failureCallback != null) {
|
||||
failureCallback.run();
|
||||
}
|
||||
});
|
||||
setOnCancelListener(d -> {
|
||||
helper.cancel();
|
||||
if (failureCallback != null) {
|
||||
failureCallback.run();
|
||||
}
|
||||
});
|
||||
try {
|
||||
helper = new DialogFingerprintHelper();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess, @NonNull Runnable onFailure) {
|
||||
this(activity, onSuccess);
|
||||
failureCallback = onFailure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
if (helper == null) {
|
||||
dialog.dismiss();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
helper.authenticate();
|
||||
dialog.show();
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
class DialogFingerprintHelper extends FingerprintHelper {
|
||||
|
||||
DialogFingerprintHelper() throws Exception {}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
binding.message.setTextColor(Color.RED);
|
||||
binding.message.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
binding.message.setTextColor(Color.RED);
|
||||
binding.message.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
binding.message.setTextColor(Color.RED);
|
||||
binding.message.setText(R.string.auth_fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
dismiss();
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.view.dialogs
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Build
|
||||
import android.view.Gravity
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
class FingerprintAuthDialog(activity: Activity, private val callback: () -> Unit)
|
||||
: CustomAlertDialog(activity) {
|
||||
|
||||
private var failureCallback: (() -> Unit)? = null
|
||||
private var helper: DialogFingerprintHelper? = null
|
||||
|
||||
init {
|
||||
val fingerprint = ContextCompat.getDrawable(activity, R.drawable.ic_fingerprint)
|
||||
fingerprint?.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50))
|
||||
val theme = activity.theme
|
||||
val ta = theme.obtainStyledAttributes(intArrayOf(R.attr.imageColorTint))
|
||||
fingerprint?.setTint(ta.getColor(0, Color.GRAY))
|
||||
ta.recycle()
|
||||
binding.message.setCompoundDrawables(null, null, null, fingerprint)
|
||||
binding.message.compoundDrawablePadding = Utils.dpInPx(20)
|
||||
binding.message.gravity = Gravity.CENTER
|
||||
setMessage(R.string.auth_fingerprint)
|
||||
setNegativeButton(R.string.close) { _, _ ->
|
||||
helper?.cancel()
|
||||
failureCallback?.invoke()
|
||||
}
|
||||
setOnCancelListener {
|
||||
helper?.cancel()
|
||||
failureCallback?.invoke()
|
||||
}
|
||||
runCatching {
|
||||
helper = DialogFingerprintHelper()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
constructor(activity: Activity, onSuccess: () -> Unit, onFailure: () -> Unit)
|
||||
: this(activity, onSuccess) {
|
||||
failureCallback = onFailure
|
||||
}
|
||||
|
||||
override fun show(): AlertDialog {
|
||||
return create().apply {
|
||||
if (helper == null) {
|
||||
dismiss()
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
helper?.authenticate()
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class DialogFingerprintHelper @Throws(Exception::class)
|
||||
constructor() : FingerprintHelper() {
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
binding.message.setTextColor(Color.RED)
|
||||
binding.message.text = errString
|
||||
}
|
||||
|
||||
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
|
||||
binding.message.setTextColor(Color.RED)
|
||||
binding.message.text = helpString
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
binding.message.setTextColor(Color.RED)
|
||||
binding.message.setText(R.string.auth_fail)
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
||||
dismiss()
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user