mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-22 11:47:41 +00:00
Compare commits
113 Commits
manager-v7
...
v19.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c1602d2554 | ||
![]() |
9f8d4e1022 | ||
![]() |
d1dfda405f | ||
![]() |
28efded624 | ||
![]() |
06c86ee267 | ||
![]() |
5892780871 | ||
![]() |
4fcdcd9a8a | ||
![]() |
80d834fb55 | ||
![]() |
4122ebe18f | ||
![]() |
7d87777bf8 | ||
![]() |
4a73d634e0 | ||
![]() |
373dc10a40 | ||
![]() |
ed43ec8ea2 | ||
![]() |
7918fc3528 | ||
![]() |
bf58205b0a | ||
![]() |
c0d1ce96d1 | ||
![]() |
b31d3802eb | ||
![]() |
be1228c3b4 | ||
![]() |
15c94c6b34 | ||
![]() |
202d23426a | ||
![]() |
fc26de48b2 | ||
![]() |
76c88913f9 | ||
![]() |
a3a1aed723 | ||
![]() |
81aa56f60f | ||
![]() |
73bb850209 | ||
![]() |
8dfec12330 | ||
![]() |
ae24397793 | ||
![]() |
3b0f888407 | ||
![]() |
845d1e02b0 | ||
![]() |
5d357bc41f | ||
![]() |
6a54672b13 | ||
![]() |
3d9a15df44 | ||
![]() |
449c7fda2f | ||
![]() |
8b7b05da68 | ||
![]() |
92400ebcab | ||
![]() |
23d3e56967 | ||
![]() |
6785dc4967 | ||
![]() |
dad20f6a2d | ||
![]() |
bb15671046 | ||
![]() |
21984fac8b | ||
![]() |
f392afe87f | ||
![]() |
6a243ec7bc | ||
![]() |
8cd3b603df | ||
![]() |
6e1aefe6d8 | ||
![]() |
1c90b6eca3 | ||
![]() |
c33cf9f878 | ||
![]() |
27cb40eec9 | ||
![]() |
c06081b75d | ||
![]() |
a7eec2f0a0 | ||
![]() |
4fd0fe3194 | ||
![]() |
cc74593ddd | ||
![]() |
fdb7c5dba1 | ||
![]() |
77470c7cfa | ||
![]() |
f0a734fdab | ||
![]() |
75405b2b25 | ||
![]() |
90ed4b3c49 | ||
![]() |
290a17a764 | ||
![]() |
aaabd836e4 | ||
![]() |
076e5cea3b | ||
![]() |
8515971ccf | ||
![]() |
d86fb033ea | ||
![]() |
99d7d8ddbc | ||
![]() |
df78fd2d41 | ||
![]() |
dabe6267b9 | ||
![]() |
0119ebddbe | ||
![]() |
3216ef9f47 | ||
![]() |
b79d1bcded | ||
![]() |
17e234f9d5 | ||
![]() |
ea1f75f80e | ||
![]() |
8c40db5730 | ||
![]() |
80855e89ec | ||
![]() |
0850401dc4 | ||
![]() |
337fda2023 | ||
![]() |
64f238191e | ||
![]() |
eb169cb133 | ||
![]() |
92789c3113 | ||
![]() |
c1c677e161 | ||
![]() |
2fe917ff82 | ||
![]() |
0e6c205732 | ||
![]() |
125ae0a173 | ||
![]() |
0245e13591 | ||
![]() |
d546733287 | ||
![]() |
c275326d59 | ||
![]() |
d4561507b8 | ||
![]() |
2624706c69 | ||
![]() |
d39d885ec2 | ||
![]() |
d83c744725 | ||
![]() |
843995cdb9 | ||
![]() |
9491ba77e0 | ||
![]() |
58a449d437 | ||
![]() |
7f55e0f05b | ||
![]() |
4f9e8d2e8a | ||
![]() |
21be2f46f3 | ||
![]() |
a6e7680212 | ||
![]() |
e79e744e08 | ||
![]() |
7abdac72a4 | ||
![]() |
90d85eaf7d | ||
![]() |
e65f9740fb | ||
![]() |
7538f89b56 | ||
![]() |
7c755a3991 | ||
![]() |
10e903c9fc | ||
![]() |
b018124226 | ||
![]() |
5d632d0d90 | ||
![]() |
4eecaea601 | ||
![]() |
63055818ec | ||
![]() |
0beb08b687 | ||
![]() |
bbc9e60a12 | ||
![]() |
6c975ecc4c | ||
![]() |
23e8a4ce4b | ||
![]() |
50134a2f9b | ||
![]() |
628b37c4fa | ||
![]() |
1b4ae70a43 | ||
![]() |
065051a360 |
@@ -19,16 +19,14 @@ android {
|
|||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
versionName configProps['appVersion']
|
versionName configProps['appVersion']
|
||||||
versionCode configProps['appVersionCode'] as Integer
|
versionCode configProps['appVersionCode'] as Integer
|
||||||
javaCompileOptions {
|
|
||||||
annotationProcessorOptions {
|
|
||||||
argument('butterknife.debuggable', 'false')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
||||||
|
'proguard-rules.pro', 'proguard-kotlin.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +45,10 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation project(':net')
|
implementation project(':net')
|
||||||
@@ -58,26 +60,43 @@ dependencies {
|
|||||||
implementation 'com.github.skoumalcz:teanity:0.3.3'
|
implementation 'com.github.skoumalcz:teanity:0.3.3'
|
||||||
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
||||||
|
|
||||||
def markwonVersion = '3.0.1'
|
def vMarkwon = '3.0.1'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${vMarkwon}"
|
||||||
implementation "ru.noties.markwon:html:${markwonVersion}"
|
implementation "ru.noties.markwon:html:${vMarkwon}"
|
||||||
implementation "ru.noties.markwon:image-svg:${markwonVersion}"
|
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
|
||||||
|
|
||||||
def libsuVersion = '2.5.0'
|
def vLibsu = '2.5.0'
|
||||||
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
|
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
||||||
implementation "com.github.topjohnwu.libsu:io:${libsuVersion}"
|
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
|
||||||
|
|
||||||
def koin = "2.0.0-rc-2"
|
def vKoin = "2.0.1"
|
||||||
implementation "org.koin:koin-core:${koin}"
|
implementation "org.koin:koin-core:${vKoin}"
|
||||||
implementation "org.koin:koin-android:${koin}"
|
implementation "org.koin:koin-android:${vKoin}"
|
||||||
implementation "org.koin:koin-androidx-viewmodel:${koin}"
|
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||||
|
|
||||||
|
def vRetrofit = "2.5.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"
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||||
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
|
||||||
|
def vMoshi = "1.8.0"
|
||||||
|
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
||||||
|
|
||||||
|
def vKotshi = "2.0.1"
|
||||||
|
implementation "se.ansman.kotshi:api:${vKotshi}"
|
||||||
|
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'androidx.browser:browser:1.0.0'
|
||||||
implementation 'androidx.preference:preference: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-alpha05'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha06'
|
|
||||||
implementation 'androidx.work:work-runtime:2.0.1'
|
implementation 'androidx.work:work-runtime:2.0.1'
|
||||||
implementation 'androidx.transition:transition:1.2.0-alpha01'
|
implementation 'androidx.transition:transition:1.2.0-alpha01'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
implementation 'com.google.android.material:material:1.1.0-alpha06'
|
||||||
}
|
}
|
||||||
|
20
app/proguard-kotlin.pro
Normal file
20
app/proguard-kotlin.pro
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
## So every class is case insensitive to avoid some bizare problems
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
|
||||||
|
## If reflection issues come up uncomment this, that should temporarily fix it
|
||||||
|
#-keep class kotlin.** { *; }
|
||||||
|
#-keep class kotlin.Metadata { *; }
|
||||||
|
#-keepclassmembers class kotlin.Metadata {
|
||||||
|
# public <methods>;
|
||||||
|
#}
|
||||||
|
|
||||||
|
## Never warn about Kotlin, it should work as-is
|
||||||
|
-dontwarn kotlin.**
|
||||||
|
|
||||||
|
## Removes runtime null checks - doesn't really matter if it crashes on kotlin or java NPE
|
||||||
|
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||||
|
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
## Useless option for dex
|
||||||
|
-dontpreverify
|
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -16,6 +16,9 @@
|
|||||||
# public *;
|
# public *;
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
# Retrofit classes
|
||||||
|
-keep,allowobfuscation class com.topjohnwu.magisk.data.network.*
|
||||||
|
|
||||||
# Snet
|
# Snet
|
||||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
||||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
|
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
|
||||||
@@ -35,6 +38,7 @@
|
|||||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
||||||
|
|
||||||
# Strip logging
|
# Strip logging
|
||||||
|
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
||||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||||
public *** debug(...);
|
public *** debug(...);
|
||||||
}
|
}
|
||||||
|
@@ -4,22 +4,18 @@ import android.annotation.SuppressLint
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
import com.topjohnwu.magisk.utils.LocaleManager
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.RootUtils
|
||||||
import com.topjohnwu.magisk.utils.inject
|
import com.topjohnwu.magisk.utils.inject
|
||||||
import com.topjohnwu.net.Networking
|
import com.topjohnwu.net.Networking
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -29,13 +25,6 @@ open class App : Application(), Application.ActivityLifecycleCallbacks {
|
|||||||
|
|
||||||
lateinit var protectedContext: Context
|
lateinit var protectedContext: Context
|
||||||
|
|
||||||
@Deprecated("Use dependency injection")
|
|
||||||
val prefs: SharedPreferences by inject()
|
|
||||||
@Deprecated("Use dependency injection")
|
|
||||||
val DB: MagiskDB by inject()
|
|
||||||
@Deprecated("Use dependency injection")
|
|
||||||
val repoDB: RepoDatabaseHelper by inject()
|
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var foreground: Activity? = null
|
private var foreground: Activity? = null
|
||||||
|
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadModuleService;
|
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
|
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity;
|
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity;
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity;
|
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ClassMap {
|
|
||||||
private static Map<Class, Class> classMap = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
classMap.put(App.class, a.e.class);
|
|
||||||
classMap.put(MainActivity.class, a.b.class);
|
|
||||||
classMap.put(SplashActivity.class, a.c.class);
|
|
||||||
classMap.put(FlashActivity.class, a.f.class);
|
|
||||||
classMap.put(UpdateCheckService.class, a.g.class);
|
|
||||||
classMap.put(GeneralReceiver.class, a.h.class);
|
|
||||||
classMap.put(DownloadModuleService.class, a.j.class);
|
|
||||||
classMap.put(SuRequestActivity.class, a.m.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Class<T> get(Class c) {
|
|
||||||
return classMap.get(c);
|
|
||||||
}
|
|
||||||
}
|
|
27
app/src/main/java/com/topjohnwu/magisk/ClassMap.kt
Normal file
27
app/src/main/java/com/topjohnwu/magisk/ClassMap.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.download.DownloadModuleService
|
||||||
|
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
|
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||||
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
|
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||||
|
|
||||||
|
object ClassMap {
|
||||||
|
private val map = mapOf(
|
||||||
|
App::class.java to a.e::class.java,
|
||||||
|
MainActivity::class.java to a.b::class.java,
|
||||||
|
SplashActivity::class.java to a.c::class.java,
|
||||||
|
FlashActivity::class.java to a.f::class.java,
|
||||||
|
UpdateCheckService::class.java to a.g::class.java,
|
||||||
|
GeneralReceiver::class.java to a.h::class.java,
|
||||||
|
DownloadModuleService::class.java to a.j::class.java,
|
||||||
|
SuRequestActivity::class.java to a.m::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
operator fun get(c: Class<*>): Class<*>? {
|
||||||
|
return map.getOrElse(c) { null } //as? Class<T>
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
|
|
||||||
@@ -15,26 +16,29 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.collection.ArrayMap;
|
import androidx.collection.ArrayMap;
|
||||||
|
|
||||||
public class Config {
|
import static com.topjohnwu.magisk.ConfigLeanback.getPrefs;
|
||||||
|
import static com.topjohnwu.magisk.utils.XAndroidKt.getPackageName;
|
||||||
|
|
||||||
// Current status
|
public final class Config {
|
||||||
public static String magiskVersionString;
|
|
||||||
|
private static final ArrayMap<String, Object> defs = new ArrayMap<>();
|
||||||
public static int magiskVersionCode = -1;
|
public static int magiskVersionCode = -1;
|
||||||
private static boolean magiskHide;
|
// Current status
|
||||||
|
public static String magiskVersionString = "";
|
||||||
// Update Info
|
// Update Info
|
||||||
public static String remoteMagiskVersionString;
|
public static String remoteMagiskVersionString = "";
|
||||||
public static int remoteMagiskVersionCode = -1;
|
public static int remoteMagiskVersionCode = -1;
|
||||||
public static String magiskLink;
|
public static String magiskLink = "";
|
||||||
public static String magiskNoteLink;
|
public static String magiskNoteLink = "";
|
||||||
public static String magiskMD5;
|
public static String magiskMD5 = "";
|
||||||
public static String remoteManagerVersionString;
|
public static String remoteManagerVersionString = "";
|
||||||
public static int remoteManagerVersionCode = -1;
|
public static int remoteManagerVersionCode = -1;
|
||||||
public static String managerLink;
|
public static String managerLink = "";
|
||||||
public static String managerNoteLink;
|
public static String managerNoteLink = "";
|
||||||
public static String uninstallerLink;
|
public static String uninstallerLink = "";
|
||||||
|
|
||||||
// Install flags
|
// Install flags
|
||||||
public static boolean keepVerity = false;
|
public static boolean keepVerity = false;
|
||||||
@@ -98,25 +102,76 @@ public class Config {
|
|||||||
public static final int ORDER_DATE = 1;
|
public static final int ORDER_DATE = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean magiskHide = false;
|
||||||
|
|
||||||
public static void loadMagiskInfo() {
|
public static void loadMagiskInfo() {
|
||||||
try {
|
try {
|
||||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||||
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
||||||
} catch (NumberFormatException ignored) {}
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void export() {
|
public static void export() {
|
||||||
// Flush prefs to disk
|
// Flush prefs to disk
|
||||||
App app = App.self;
|
getPrefs().edit().apply();
|
||||||
app.getPrefs().edit().commit();
|
Context context = ConfigLeanback.getProtectedContext();
|
||||||
File xml = new File(App.deContext.getFilesDir().getParent() + "/shared_prefs",
|
File xml = new File(context.getFilesDir().getParent() + "/shared_prefs",
|
||||||
app.getPackageName() + "_preferences.xml");
|
getPackageName() + "_preferences.xml");
|
||||||
Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec();
|
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() {
|
public static void initialize() {
|
||||||
SharedPreferences pref = App.self.getPrefs();
|
SharedPreferences pref = getPrefs();
|
||||||
SharedPreferences.Editor editor = pref.edit();
|
SharedPreferences.Editor editor = pref.edit();
|
||||||
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
|
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
|
||||||
if (config.exists()) {
|
if (config.exists()) {
|
||||||
@@ -185,125 +240,72 @@ public class Config {
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> T get(String key) {
|
public static <T> T get(String key) {
|
||||||
App app = App.self;
|
|
||||||
switch (getConfigType(key)) {
|
switch (getConfigType(key)) {
|
||||||
case PREF_INT:
|
case PREF_INT:
|
||||||
return (T) (Integer) app.getPrefs().getInt(key, getDef(key));
|
return (T) (Integer) getPrefs().getInt(key, getDef(key));
|
||||||
case PREF_STR_INT:
|
case PREF_STR_INT:
|
||||||
return (T) (Integer) Utils.getPrefsInt(app.getPrefs(), key, getDef(key));
|
return (T) (Integer) Utils.getPrefsInt(getPrefs(), key, getDef(key));
|
||||||
case PREF_BOOL:
|
case PREF_BOOL:
|
||||||
return (T) (Boolean) app.getPrefs().getBoolean(key, getDef(key));
|
return (T) (Boolean) getPrefs().getBoolean(key, getDef(key));
|
||||||
case PREF_STR:
|
case PREF_STR:
|
||||||
return (T) app.getPrefs().getString(key, getDef(key));
|
return (T) getPrefs().getString(key, getDef(key));
|
||||||
case DB_INT:
|
case DB_INT:
|
||||||
return (T) (Integer) app.getDB().getSettings(key, getDef(key));
|
return (T) (Integer) ConfigLeanback.get(key, (Integer) getDef(key));
|
||||||
case DB_BOOL:
|
case DB_BOOL:
|
||||||
return (T) (Boolean) (app.getDB().getSettings(key, getDef(key) ? 1 : 0) != 0);
|
return (T) (Boolean) (ConfigLeanback.get(key, getDef(key) ? 1 : 0) != 0);
|
||||||
case DB_STR:
|
case DB_STR:
|
||||||
return (T) app.getDB().getStrings(key, getDef(key));
|
return (T) ConfigLeanback.get(key, getDef(key));
|
||||||
}
|
}
|
||||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
/* Will never get here (IllegalArgumentException in getConfigType) */
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void set(String key, Object val) {
|
public static void set(String key, Object val) {
|
||||||
App app = App.self;
|
|
||||||
switch (getConfigType(key)) {
|
switch (getConfigType(key)) {
|
||||||
case PREF_INT:
|
case PREF_INT:
|
||||||
app.getPrefs().edit().putInt(key, (int) val).apply();
|
getPrefs().edit().putInt(key, (int) val).apply();
|
||||||
break;
|
break;
|
||||||
case PREF_STR_INT:
|
case PREF_STR_INT:
|
||||||
app.getPrefs().edit().putString(key, String.valueOf(val)).apply();
|
getPrefs().edit().putString(key, String.valueOf(val)).apply();
|
||||||
break;
|
break;
|
||||||
case PREF_BOOL:
|
case PREF_BOOL:
|
||||||
app.getPrefs().edit().putBoolean(key, (boolean) val).apply();
|
getPrefs().edit().putBoolean(key, (boolean) val).apply();
|
||||||
break;
|
break;
|
||||||
case PREF_STR:
|
case PREF_STR:
|
||||||
app.getPrefs().edit().putString(key, (String) val).apply();
|
getPrefs().edit().putString(key, (String) val).apply();
|
||||||
break;
|
break;
|
||||||
case DB_INT:
|
case DB_INT:
|
||||||
app.getDB().setSettings(key, (int) val);
|
ConfigLeanback.put(key, (int) val);
|
||||||
break;
|
break;
|
||||||
case DB_BOOL:
|
case DB_BOOL:
|
||||||
app.getDB().setSettings(key, (boolean) val ? 1 : 0);
|
ConfigLeanback.put(key, (boolean) val ? 1 : 0);
|
||||||
break;
|
break;
|
||||||
case DB_STR:
|
case DB_STR:
|
||||||
app.getDB().setStrings(key, (String) val);
|
ConfigLeanback.put(key, (String) val);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void remove(String key) {
|
public static void remove(String key) {
|
||||||
App app = App.self;
|
|
||||||
switch (getConfigType(key)) {
|
switch (getConfigType(key)) {
|
||||||
case PREF_INT:
|
case PREF_INT:
|
||||||
case PREF_STR_INT:
|
case PREF_STR_INT:
|
||||||
case PREF_BOOL:
|
case PREF_BOOL:
|
||||||
case PREF_STR:
|
case PREF_STR:
|
||||||
app.getPrefs().edit().remove(key).apply();
|
getPrefs().edit().remove(key).apply();
|
||||||
break;
|
break;
|
||||||
case DB_BOOL:
|
case DB_BOOL:
|
||||||
case DB_INT:
|
case DB_INT:
|
||||||
app.getDB().rmSettings(key);
|
ConfigLeanback.delete(key);
|
||||||
break;
|
break;
|
||||||
case DB_STR:
|
case DB_STR:
|
||||||
app.getDB().setStrings(key, null);
|
ConfigLeanback.put(key, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ArrayMap<String, Object> defs = new ArrayMap<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
/* Set default configurations */
|
/* Set default configurations */
|
||||||
|
|
||||||
@@ -340,7 +342,9 @@ public class Config {
|
|||||||
//defs.put(Key.SU_MANAGER, null);
|
//defs.put(Key.SU_MANAGER, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T getDef(String key) {
|
@SuppressWarnings("unchecked")
|
||||||
|
@Nullable
|
||||||
|
private static <T> T getDef(String key) throws IllegalArgumentException {
|
||||||
Object val = defs.get(key);
|
Object val = defs.get(key);
|
||||||
switch (getConfigType(key)) {
|
switch (getConfigType(key)) {
|
||||||
case PREF_INT:
|
case PREF_INT:
|
||||||
@@ -359,36 +363,35 @@ public class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor) {
|
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor) {
|
||||||
App app = App.self;
|
|
||||||
for (String key : defs.keySet()) {
|
for (String key : defs.keySet()) {
|
||||||
|
Object value = defs.get(key);
|
||||||
int type = getConfigType(key);
|
int type = getConfigType(key);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DB_INT:
|
case DB_INT:
|
||||||
editor.putString(key, String.valueOf(
|
editor.putString(key, String.valueOf(ConfigLeanback.get(key, (Integer) value)));
|
||||||
app.getDB().getSettings(key, (Integer) defs.get(key))));
|
|
||||||
continue;
|
continue;
|
||||||
case DB_STR:
|
case DB_STR:
|
||||||
editor.putString(key, app.getDB().getStrings(key, (String) defs.get(key)));
|
editor.putString(key, ConfigLeanback.get(key, String.valueOf(value)));
|
||||||
continue;
|
continue;
|
||||||
case DB_BOOL:
|
case DB_BOOL:
|
||||||
int bs = app.getDB().getSettings(key, -1);
|
int bs = ConfigLeanback.get(key, -1);
|
||||||
editor.putBoolean(key, bs < 0 ? (Boolean) defs.get(key) : bs != 0);
|
editor.putBoolean(key, bs < 0 ? (Boolean) value : bs != 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (pref.contains(key))
|
if (pref.contains(key))
|
||||||
continue;
|
continue;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PREF_INT:
|
case PREF_INT:
|
||||||
editor.putInt(key, (Integer) defs.get(key));
|
editor.putInt(key, (Integer) value);
|
||||||
break;
|
break;
|
||||||
case PREF_STR_INT:
|
case PREF_STR_INT:
|
||||||
editor.putString(key, String.valueOf(defs.get(key)));
|
editor.putString(key, String.valueOf(value));
|
||||||
break;
|
break;
|
||||||
case PREF_STR:
|
case PREF_STR:
|
||||||
editor.putString(key, (String) defs.get(key));
|
editor.putString(key, (String) value);
|
||||||
break;
|
break;
|
||||||
case PREF_BOOL:
|
case PREF_BOOL:
|
||||||
editor.putBoolean(key, (Boolean) defs.get(key));
|
editor.putBoolean(key, (Boolean) value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
app/src/main/java/com/topjohnwu/magisk/ConfigLeanback.kt
Normal file
52
app/src/main/java/com/topjohnwu/magisk/ConfigLeanback.kt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,102 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Process;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class Const {
|
|
||||||
|
|
||||||
public static final String DEBUG_TAG = "MagiskManager";
|
|
||||||
|
|
||||||
// APK content
|
|
||||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
|
||||||
|
|
||||||
public static final String SU_KEYSTORE_KEY = "su_key";
|
|
||||||
|
|
||||||
// Paths
|
|
||||||
public static final String MAGISK_PATH = "/sbin/.magisk/img";
|
|
||||||
public static final File EXTERNAL_PATH;
|
|
||||||
public static File MAGISK_DISABLE_FILE;
|
|
||||||
|
|
||||||
static {
|
|
||||||
MAGISK_DISABLE_FILE = new File("xxx");
|
|
||||||
EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
|
||||||
EXTERNAL_PATH.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
|
||||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
|
||||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
|
||||||
|
|
||||||
// Versions
|
|
||||||
public static final int UPDATE_SERVICE_VER = 1;
|
|
||||||
public static final int SNET_EXT_VER = 12;
|
|
||||||
|
|
||||||
public static final int USER_ID = Process.myUid() / 100000;
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
public static final String MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log";
|
|
||||||
|
|
||||||
public static final class MAGISK_VER {
|
|
||||||
public static final int MIN_SUPPORT = 18000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ID {
|
|
||||||
public static final int FETCH_ZIP = 2;
|
|
||||||
public static final int SELECT_BOOT = 3;
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
|
||||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
|
||||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
|
||||||
public static final int HIDE_MANAGER_NOTIFICATION_ID = 8;
|
|
||||||
public static final String UPDATE_NOTIFICATION_CHANNEL = "update";
|
|
||||||
public static final String PROGRESS_NOTIFICATION_CHANNEL = "progress";
|
|
||||||
public static final String CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Url {
|
|
||||||
private static String getRaw(String where, String name) {
|
|
||||||
return String.format("https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s", where, name);
|
|
||||||
}
|
|
||||||
public static final String STABLE_URL = getRaw("master", "stable.json");
|
|
||||||
public static final String BETA_URL = getRaw("master", "beta.json");
|
|
||||||
public static final String CANARY_URL = getRaw("master", "canary_builds/release.json");
|
|
||||||
public static final String CANARY_DEBUG_URL = getRaw("master", "canary_builds/canary.json");
|
|
||||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
|
|
||||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
|
||||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
|
||||||
public static final String MODULE_INSTALLER = "https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh";
|
|
||||||
public static final String PAYPAL_URL = "https://www.paypal.me/topjohnwu";
|
|
||||||
public static final String PATREON_URL = "https://www.patreon.com/topjohnwu";
|
|
||||||
public static final String TWITTER_URL = "https://twitter.com/topjohnwu";
|
|
||||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
|
||||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk";
|
|
||||||
public static final String SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk");
|
|
||||||
public static final String BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Key {
|
|
||||||
// others
|
|
||||||
public static final String LINK_KEY = "Link";
|
|
||||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
|
||||||
// intents
|
|
||||||
public static final String OPEN_SECTION = "section";
|
|
||||||
public static final String INTENT_SET_NAME = "filename";
|
|
||||||
public static final String INTENT_SET_LINK = "link";
|
|
||||||
public static final String FLASH_ACTION = "action";
|
|
||||||
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
|
||||||
public static final String BROADCAST_REBOOT = "reboot";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Value {
|
|
||||||
public static final String FLASH_ZIP = "flash";
|
|
||||||
public static final String PATCH_FILE = "patch";
|
|
||||||
public static final String FLASH_MAGISK = "magisk";
|
|
||||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
|
||||||
public static final String UNINSTALL = "uninstall";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
105
app/src/main/java/com/topjohnwu/magisk/Const.kt
Normal file
105
app/src/main/java/com/topjohnwu/magisk/Const.kt
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Process
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
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)
|
||||||
|
@JvmField
|
||||||
|
var MAGISK_DISABLE_FILE: 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
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val USER_ID = Process.myUid() / 100000
|
||||||
|
|
||||||
|
// Generic
|
||||||
|
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
|
||||||
|
|
||||||
|
init {
|
||||||
|
EXTERNAL_PATH.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
object MagiskVersion {
|
||||||
|
const val MIN_SUPPORT = 18000
|
||||||
|
}
|
||||||
|
|
||||||
|
object ID {
|
||||||
|
const val FETCH_ZIP = 2
|
||||||
|
const val SELECT_BOOT = 3
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
|
||||||
|
const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||||
|
const val DTBO_NOTIFICATION_ID = 7
|
||||||
|
const val HIDE_MANAGER_NOTIFICATION_ID = 8
|
||||||
|
const val UPDATE_NOTIFICATION_CHANNEL = "update"
|
||||||
|
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
|
||||||
|
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Url {
|
||||||
|
@Deprecated("This shouldn't be used. There's literally no need for it")
|
||||||
|
const val REPO_URL =
|
||||||
|
"https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d"
|
||||||
|
const val FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s"
|
||||||
|
const val ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"
|
||||||
|
const val MODULE_INSTALLER =
|
||||||
|
"https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh"
|
||||||
|
const val PAYPAL_URL = "https://www.paypal.me/topjohnwu"
|
||||||
|
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
|
||||||
|
const val TWITTER_URL = "https://twitter.com/topjohnwu"
|
||||||
|
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")
|
||||||
|
|
||||||
|
private fun getRaw(where: String, name: String) =
|
||||||
|
"https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s".format(where, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Key {
|
||||||
|
// others
|
||||||
|
const val LINK_KEY = "Link"
|
||||||
|
const val IF_NONE_MATCH = "If-None-Match"
|
||||||
|
// intents
|
||||||
|
const val OPEN_SECTION = "section"
|
||||||
|
const val INTENT_SET_NAME = "filename"
|
||||||
|
const val INTENT_SET_LINK = "link"
|
||||||
|
const val FLASH_ACTION = "action"
|
||||||
|
const val BROADCAST_MANAGER_UPDATE = "manager_update"
|
||||||
|
const val BROADCAST_REBOOT = "reboot"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Value {
|
||||||
|
const val FLASH_ZIP = "flash"
|
||||||
|
const val PATCH_FILE = "patch"
|
||||||
|
const val FLASH_MAGISK = "magisk"
|
||||||
|
const val FLASH_INACTIVE_SLOT = "slot"
|
||||||
|
const val UNINSTALL = "uninstall"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
20
app/src/main/java/com/topjohnwu/magisk/Constants.kt
Normal file
20
app/src/main/java/com/topjohnwu/magisk/Constants.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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/"
|
||||||
|
|
||||||
|
}
|
47
app/src/main/java/com/topjohnwu/magisk/KConfig.kt
Normal file
47
app/src/main/java/com/topjohnwu/magisk/KConfig.kt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
|
import com.topjohnwu.magisk.model.entity.toMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class LogDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.LOG
|
||||||
|
|
||||||
|
fun deleteOutdated(
|
||||||
|
suTimeout: Long = Config.suLogTimeout * TimeUnit.DAYS.toMillis(1)
|
||||||
|
) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
lessThan("time", suTimeout.toString())
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun deleteAll() = query<Delete> {}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetchAll() = query<Select> {
|
||||||
|
orderBy("time", Order.DESC)
|
||||||
|
}.flattenAsFlowable { it }
|
||||||
|
.map { it.toLog() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun put(log: MagiskLog) = query<Insert> {
|
||||||
|
values(log.toMap())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
}
|
@@ -1,190 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.data.database;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.model.entity.Policy;
|
|
||||||
import com.topjohnwu.magisk.model.entity.SuLogEntry;
|
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class MagiskDB {
|
|
||||||
|
|
||||||
private static final String POLICY_TABLE = "policies";
|
|
||||||
private static final String LOG_TABLE = "logs";
|
|
||||||
private static final String SETTINGS_TABLE = "settings";
|
|
||||||
private static final String STRINGS_TABLE = "strings";
|
|
||||||
|
|
||||||
private PackageManager pm;
|
|
||||||
|
|
||||||
public MagiskDB(Context context) {
|
|
||||||
pm = context.getPackageManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePolicy(Policy policy) {
|
|
||||||
deletePolicy(policy.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> rawSQL(String fmt, Object... args) {
|
|
||||||
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ContentValues> SQL(String fmt, Object... args) {
|
|
||||||
List<ContentValues> list = new ArrayList<>();
|
|
||||||
for (String raw : rawSQL(fmt, args)) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
String[] cols = raw.split("\\|");
|
|
||||||
for (String col : cols) {
|
|
||||||
String[] pair = col.split("=", 2);
|
|
||||||
if (pair.length != 2)
|
|
||||||
continue;
|
|
||||||
values.put(pair[0], pair[1]);
|
|
||||||
}
|
|
||||||
list.add(values);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toSQL(ContentValues values) {
|
|
||||||
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
|
|
||||||
keys.append('(');
|
|
||||||
vals.append("VALUES(");
|
|
||||||
boolean first = true;
|
|
||||||
for (Map.Entry<String, Object> entry : values.valueSet()) {
|
|
||||||
if (!first) {
|
|
||||||
keys.append(',');
|
|
||||||
vals.append(',');
|
|
||||||
} else {
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
keys.append(entry.getKey());
|
|
||||||
vals.append('"');
|
|
||||||
vals.append(entry.getValue());
|
|
||||||
vals.append('"');
|
|
||||||
}
|
|
||||||
keys.append(')');
|
|
||||||
vals.append(')');
|
|
||||||
keys.append(vals);
|
|
||||||
return keys.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearOutdated() {
|
|
||||||
rawSQL(
|
|
||||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
|
||||||
"DELETE FROM %s WHERE time < %d",
|
|
||||||
POLICY_TABLE, System.currentTimeMillis() / 1000,
|
|
||||||
LOG_TABLE, System.currentTimeMillis() - Config.suLogTimeout * 86400000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePolicy(String pkg) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePolicy(int uid) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Policy getPolicy(int uid) {
|
|
||||||
List<ContentValues> res =
|
|
||||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
|
||||||
if (!res.isEmpty()) {
|
|
||||||
try {
|
|
||||||
return new Policy(res.get(0), pm);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
deletePolicy(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updatePolicy(Policy policy) {
|
|
||||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Policy> getPolicyList() {
|
|
||||||
List<Policy> list = new ArrayList<>();
|
|
||||||
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
|
||||||
try {
|
|
||||||
list.add(new Policy(values, pm));
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
deletePolicy(values.getAsInteger("uid"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(list);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<List<SuLogEntry>> getLogs() {
|
|
||||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
|
||||||
List<SuLogEntry> list = null;
|
|
||||||
String dateString = null, newString;
|
|
||||||
for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) {
|
|
||||||
Date date = new Date(values.getAsLong("time"));
|
|
||||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
|
||||||
if (!TextUtils.equals(dateString, newString)) {
|
|
||||||
dateString = newString;
|
|
||||||
list = new ArrayList<>();
|
|
||||||
ret.add(list);
|
|
||||||
}
|
|
||||||
list.add(new SuLogEntry(values));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLog(SuLogEntry log) {
|
|
||||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLogs() {
|
|
||||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rmSettings(String key) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSettings(String key, int value) {
|
|
||||||
ContentValues data = new ContentValues();
|
|
||||||
data.put("key", key);
|
|
||||||
data.put("value", value);
|
|
||||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSettings(String key, int defaultValue) {
|
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
|
||||||
if (res.isEmpty())
|
|
||||||
return defaultValue;
|
|
||||||
return res.get(0).getAsInteger("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrings(String key, String value) {
|
|
||||||
if (value == null) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ContentValues data = new ContentValues();
|
|
||||||
data.put("key", key);
|
|
||||||
data.put("value", value);
|
|
||||||
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStrings(String key, String defaultValue) {
|
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
|
||||||
if (res.isEmpty())
|
|
||||||
return defaultValue;
|
|
||||||
return res.get(0).getAsString("value");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,69 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
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 java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyDao(
|
||||||
|
private val context: Context
|
||||||
|
) : BaseDao() {
|
||||||
|
|
||||||
|
override val table: String = DatabaseDefinition.Table.POLICY
|
||||||
|
|
||||||
|
fun deleteOutdated(
|
||||||
|
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
|
||||||
|
) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
greaterThan("until", "0")
|
||||||
|
and {
|
||||||
|
lessThan("until", nowSeconds.toString())
|
||||||
|
}
|
||||||
|
or {
|
||||||
|
lessThan("until", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun delete(packageName: String) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
equals("package_name", packageName)
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun delete(uid: Int) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
equals("uid", uid)
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(uid: Int) = query<Select> {
|
||||||
|
condition {
|
||||||
|
equals("uid", uid)
|
||||||
|
}
|
||||||
|
}.map { it.firstOrNull()?.toPolicy(context.packageManager) }
|
||||||
|
.doOnError {
|
||||||
|
if (it is PackageManager.NameNotFoundException) {
|
||||||
|
delete(uid).subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(policy: MagiskPolicy) = query<Replace> {
|
||||||
|
values(policy.toMap())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetchAll() = query<Select> {
|
||||||
|
condition {
|
||||||
|
equals("uid/100000", Constants.USER_ID)
|
||||||
|
}
|
||||||
|
}.flattenAsFlowable { it }
|
||||||
|
.map { it.toPolicy(context.packageManager) }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
}
|
@@ -11,13 +11,15 @@ import com.topjohnwu.magisk.model.entity.Repo;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final int DATABASE_VER = 5;
|
private static final int DATABASE_VER = 5;
|
||||||
private static final String TABLE_NAME = "repos";
|
private static final String TABLE_NAME = "repos";
|
||||||
|
|
||||||
private SQLiteDatabase mDb;
|
private final SQLiteDatabase mDb;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public RepoDatabaseHelper(Context context) {
|
public RepoDatabaseHelper(Context context) {
|
||||||
super(context, "repo.db", null, DATABASE_VER);
|
super(context, "repo.db", null, DATABASE_VER);
|
||||||
mDb = getWritableDatabase();
|
mDb = getWritableDatabase();
|
||||||
@@ -46,32 +48,38 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
onUpgrade(db, 0, DATABASE_VER);
|
onUpgrade(db, 0, DATABASE_VER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void clearRepo() {
|
public void clearRepo() {
|
||||||
mDb.delete(TABLE_NAME, null, null);
|
mDb.delete(TABLE_NAME, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void removeRepo(String id) {
|
public void removeRepo(String id) {
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void removeRepo(Repo repo) {
|
public void removeRepo(Repo repo) {
|
||||||
removeRepo(repo.getId());
|
removeRepo(repo.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void removeRepo(Iterable<String> list) {
|
public void removeRepo(Iterable<String> list) {
|
||||||
for (String id : list) {
|
for (String id : list) {
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void addRepo(Repo repo) {
|
public void addRepo(Repo repo) {
|
||||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Repo getRepo(String id) {
|
public Repo getRepo(String id) {
|
||||||
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
|
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[]{id}, null, null, null)) {
|
||||||
if (c.moveToNext()) {
|
if (c.moveToNext()) {
|
||||||
return new Repo(c);
|
return new Repo(c);
|
||||||
}
|
}
|
||||||
@@ -79,10 +87,12 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Cursor getRawCursor() {
|
public Cursor getRawCursor() {
|
||||||
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Cursor getRepoCursor() {
|
public Cursor getRepoCursor() {
|
||||||
String orderBy = null;
|
String orderBy = null;
|
||||||
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||||
@@ -95,6 +105,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
|
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Set<String> getRepoIDSet() {
|
public Set<String> getRepoIDSet() {
|
||||||
HashSet<String> set = new HashSet<>(300);
|
HashSet<String> set = new HashSet<>(300);
|
||||||
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
|
||||||
|
class SettingsDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.SETTINGS
|
||||||
|
|
||||||
|
fun delete(key: String) = query<Delete> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun put(key: String, value: Int) = query<Insert> {
|
||||||
|
values(key to value.toString())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(key: String, default: Int = -1) = query<Select> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
|
||||||
|
class StringDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.STRINGS
|
||||||
|
|
||||||
|
fun delete(key: String) = query<Delete> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun put(key: String, value: String) = query<Insert> {
|
||||||
|
values(key to value)
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(key: String, default: String = "") = query<Select> {
|
||||||
|
fields("value")
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
abstract class BaseDao {
|
||||||
|
|
||||||
|
abstract val table: String
|
||||||
|
|
||||||
|
inline fun <reified Builder : MagiskQueryBuilder> query(builder: Builder.() -> Unit) =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply { table = this@BaseDao.table }
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let { MagiskQuery(it) }
|
||||||
|
.query()
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
object DatabaseDefinition {
|
||||||
|
|
||||||
|
object Table {
|
||||||
|
const val POLICY = "policies"
|
||||||
|
const val LOG = "logs"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
const val STRINGS = "strings"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun MagiskQuery.query() = query.su()
|
||||||
|
|
||||||
|
fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
|
||||||
|
fun String.su() = suRaw().map { it.toMap() }
|
||||||
|
|
||||||
|
fun List<String>.toMap() = map { it.split(Regex("\\|")) }
|
||||||
|
.map { it.toMapInternal() }
|
||||||
|
|
||||||
|
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
|
||||||
|
.filter { it.size == 2 }
|
||||||
|
.map { Pair(it[0], it[1]) }
|
||||||
|
.toMap()
|
@@ -0,0 +1,5 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
inline class MagiskQuery(private val _query: String) {
|
||||||
|
val query get() = "magisk --sqlite '$_query'"
|
||||||
|
}
|
@@ -0,0 +1,163 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
import androidx.annotation.StringDef
|
||||||
|
import com.topjohnwu.magisk.data.database.base.Order.Companion.ASC
|
||||||
|
import com.topjohnwu.magisk.data.database.base.Order.Companion.DESC
|
||||||
|
|
||||||
|
interface MagiskQueryBuilder {
|
||||||
|
|
||||||
|
val requestType: String
|
||||||
|
var table: String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Builder : MagiskQueryBuilder> invoke(builder: Builder.() -> Unit): MagiskQuery =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let {
|
||||||
|
MagiskQuery(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Delete : MagiskQueryBuilder {
|
||||||
|
override val requestType: String = "DELETE FROM"
|
||||||
|
override var table = ""
|
||||||
|
|
||||||
|
private var condition = ""
|
||||||
|
|
||||||
|
fun condition(builder: Condition.() -> Unit) {
|
||||||
|
condition = Condition().apply(builder).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return StringBuilder()
|
||||||
|
.appendln(requestType)
|
||||||
|
.appendln(table)
|
||||||
|
.appendln(condition)
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Select : MagiskQueryBuilder {
|
||||||
|
override val requestType: String get() = "SELECT $fields FROM"
|
||||||
|
override lateinit var table: String
|
||||||
|
|
||||||
|
private var fields = "*"
|
||||||
|
private var condition = ""
|
||||||
|
private var orderField = ""
|
||||||
|
|
||||||
|
fun fields(vararg newFields: String) {
|
||||||
|
if (newFields.isEmpty()) {
|
||||||
|
fields = "*"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fields = newFields.joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun condition(builder: Condition.() -> Unit) {
|
||||||
|
condition = Condition().apply(builder).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun orderBy(field: String, @OrderStrict order: String) {
|
||||||
|
orderField = "ORDER BY $field $order"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return StringBuilder()
|
||||||
|
.appendln(requestType)
|
||||||
|
.appendln(table)
|
||||||
|
.appendln(condition)
|
||||||
|
.appendln(orderField)
|
||||||
|
.toString()
|
||||||
|
.replace("\n", " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Replace : Insert() {
|
||||||
|
override val requestType: String = "REPLACE INTO"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Insert : MagiskQueryBuilder {
|
||||||
|
override val requestType: String = "INSERT INTO"
|
||||||
|
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()
|
||||||
|
|
||||||
|
fun values(vararg pairs: Pair<String, String>) {
|
||||||
|
_values = pairs.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun values(values: Map<String, String>) {
|
||||||
|
_values = values
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return StringBuilder()
|
||||||
|
.appendln(requestType)
|
||||||
|
.appendln(table)
|
||||||
|
.appendln("($keys) VALUES($values)")
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Condition {
|
||||||
|
|
||||||
|
private val conditionWord = "WHERE %s"
|
||||||
|
private var condition: String = ""
|
||||||
|
|
||||||
|
fun equals(field: String, value: Any) {
|
||||||
|
condition = when (value) {
|
||||||
|
is String -> "$field=\"$value\""
|
||||||
|
else -> "$field=$value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun greaterThan(field: String, value: String) {
|
||||||
|
condition = "$field > $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lessThan(field: String, value: String) {
|
||||||
|
condition = "$field < $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun greaterOrEqualTo(field: String, value: String) {
|
||||||
|
condition = "$field >= $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lessOrEqualTo(field: String, value: String) {
|
||||||
|
condition = "$field <= $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun and(builder: Condition.() -> Unit) {
|
||||||
|
condition = "($condition AND ${Condition().apply(builder).condition})"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun or(builder: Condition.() -> Unit) {
|
||||||
|
condition = "($condition OR ${Condition().apply(builder).condition})"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return conditionWord.format(condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Order {
|
||||||
|
|
||||||
|
@set:OrderStrict
|
||||||
|
var order = DESC
|
||||||
|
var field = ""
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ASC = "ASC"
|
||||||
|
const val DESC = "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringDef(ASC, DESC)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class OrderStrict
|
@@ -0,0 +1,63 @@
|
|||||||
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.KConfig
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskConfig
|
||||||
|
import io.reactivex.Single
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Streaming
|
||||||
|
import retrofit2.http.Url
|
||||||
|
|
||||||
|
|
||||||
|
interface GithubRawApiServices {
|
||||||
|
|
||||||
|
//region topjohnwu/magisk_files
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/stable.json")
|
||||||
|
fun fetchConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/beta.json")
|
||||||
|
fun fetchBetaConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/canary_builds/release.json")
|
||||||
|
fun fetchCanaryConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
|
||||||
|
fun fetchCanaryDebugConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET
|
||||||
|
fun fetchCustomConfig(@Url url: String): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/{$REVISION}/snet.apk")
|
||||||
|
@Streaming
|
||||||
|
fun fetchSafetynet(@Path(REVISION) revision: String = Constants.SNET_REVISION): Single<ResponseBody>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/{$REVISION}/bootctl")
|
||||||
|
@Streaming
|
||||||
|
fun fetchBootctl(@Path(REVISION) revision: String = Constants.BOOTCTL_REVISION): Single<ResponseBody>
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method shall be used exclusively for fetching files from urls from previous requests.
|
||||||
|
* Him, who uses it in a wrong way, shall die in an eternal flame.
|
||||||
|
* */
|
||||||
|
@GET
|
||||||
|
@Streaming
|
||||||
|
fun fetchFile(@Url url: String): Single<ResponseBody>
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REVISION = "revision"
|
||||||
|
private const val MODULE = "module"
|
||||||
|
private const val FILE = "file"
|
||||||
|
|
||||||
|
|
||||||
|
private const val MAGISK_FILES = KConfig.DEFAULT_CHANNEL
|
||||||
|
private const val MAGISK_MASTER = "topjohnwu/Magisk/master"
|
||||||
|
private const val MAGISK_MODULES = "Magisk-Modules-Repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
|
|
||||||
|
class AppRepository(private val policyDao: PolicyDao) {
|
||||||
|
|
||||||
|
fun deleteOutdated() = policyDao.deleteOutdated()
|
||||||
|
fun delete(packageName: String) = policyDao.delete(packageName)
|
||||||
|
fun delete(uid: Int) = policyDao.delete(uid)
|
||||||
|
fun fetch(uid: Int) = policyDao.fetch(uid)
|
||||||
|
fun fetchAll() = policyDao.fetchAll()
|
||||||
|
fun update(policy: MagiskPolicy) = policyDao.update(policy)
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class LogRepository(
|
||||||
|
private val logDao: LogDao
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun fetchLogs() = logDao.fetchAll()
|
||||||
|
.map { it.sortByDescending { it.date.time }; it }
|
||||||
|
.map { it.wrap() }
|
||||||
|
|
||||||
|
fun fetchMagiskLogs() = "tail -n 5000 ${Constants.MAGISK_LOG}".suRaw()
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.map { Timber.i(it.toString()); it }
|
||||||
|
|
||||||
|
fun clearLogs() = logDao.deleteAll()
|
||||||
|
fun clearOutdated() = logDao.deleteOutdated()
|
||||||
|
|
||||||
|
fun clearMagiskLogs() = Shell.su("echo -n > " + Const.MAGISK_LOG)
|
||||||
|
.toSingle()
|
||||||
|
.map { it.exec() }
|
||||||
|
|
||||||
|
fun put(log: MagiskLog) = logDao.put(log)
|
||||||
|
|
||||||
|
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
||||||
|
val day = TimeUnit.DAYS.toMillis(1)
|
||||||
|
return groupBy { it.date.time / day }
|
||||||
|
.map { WrappedMagiskLog(it.key * day, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,130 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
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.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,
|
||||||
|
private val apiRaw: GithubRawApiServices,
|
||||||
|
private val packageManager: PackageManager
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun fetchMagisk() = fetchConfig()
|
||||||
|
.flatMap { apiRaw.fetchFile(it.magisk.link) }
|
||||||
|
.map { it.writeToFile(context, FILE_MAGISK_ZIP) }
|
||||||
|
|
||||||
|
fun fetchManager() = fetchConfig()
|
||||||
|
.flatMap { apiRaw.fetchFile(it.app.link) }
|
||||||
|
.map { it.writeToFile(context, FILE_MAGISK_APK) }
|
||||||
|
|
||||||
|
fun fetchUninstaller() = fetchConfig()
|
||||||
|
.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 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 fetchApps() =
|
||||||
|
Single.fromCallable { packageManager.getInstalledApplications(0) }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||||
|
.map {
|
||||||
|
val label = Utils.getAppLabel(it, packageManager)
|
||||||
|
val icon = it.loadIcon(packageManager)
|
||||||
|
HideAppInfo(it, label, icon)
|
||||||
|
}
|
||||||
|
.filter { it.processes.isNotEmpty() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun fetchHideTargets() = Shell.su("magiskhide --ls").toSingle()
|
||||||
|
.map { it.exec().out }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.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()
|
||||||
|
|
||||||
|
private val Boolean.state get() = if (this) "add" else "rm"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FILE_MAGISK_ZIP = "magisk.zip"
|
||||||
|
const val FILE_MAGISK_APK = "magisk.apk"
|
||||||
|
const val FILE_UNINSTALLER_ZIP = "uninstaller.zip"
|
||||||
|
const val FILE_SAFETY_NET_APK = "safetynet.apk"
|
||||||
|
const val FILE_BOOTCTL_SH = "bootctl"
|
||||||
|
|
||||||
|
private val blacklist = listOf(
|
||||||
|
let { val app: App by inject(); app }.packageName,
|
||||||
|
"android",
|
||||||
|
"com.android.chrome",
|
||||||
|
"com.chrome.beta",
|
||||||
|
"com.chrome.dev",
|
||||||
|
"com.chrome.canary",
|
||||||
|
"com.android.webview",
|
||||||
|
"com.google.android.webview"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@@ -12,8 +12,7 @@ val applicationModule = module {
|
|||||||
factory { get<Context>().resources }
|
factory { get<Context>().resources }
|
||||||
factory { get<Context>() as App }
|
factory { get<Context>() as App }
|
||||||
factory { get<Context>().packageManager }
|
factory { get<Context>().packageManager }
|
||||||
single(SUTimeout) {
|
factory(Protected) { get<App>().protectedContext }
|
||||||
get<App>().protectedContext.getSharedPreferences("su_timeout", 0)
|
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
||||||
}
|
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
||||||
single { PreferenceManager.getDefaultSharedPreferences(get<App>().protectedContext) }
|
}
|
||||||
}
|
|
@@ -1,12 +1,15 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App
|
import com.topjohnwu.magisk.data.database.*
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import com.topjohnwu.magisk.tasks.UpdateRepos
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val databaseModule = module {
|
val databaseModule = module {
|
||||||
single { MagiskDB(get<App>().protectedContext) }
|
single { LogDao() }
|
||||||
|
single { PolicyDao(get()) }
|
||||||
|
single { SettingsDao() }
|
||||||
|
single { StringDao() }
|
||||||
single { RepoDatabaseHelper(get()) }
|
single { RepoDatabaseHelper(get()) }
|
||||||
|
single { UpdateRepos(get()) }
|
||||||
}
|
}
|
||||||
|
@@ -2,4 +2,5 @@ package com.topjohnwu.magisk.di
|
|||||||
|
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
|
|
||||||
val SUTimeout = named("su_timeout")
|
val SUTimeout = named("su_timeout")
|
||||||
|
val Protected = named("protected")
|
@@ -1,6 +1,69 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawApiServices
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
import retrofit2.CallAdapter
|
||||||
|
import retrofit2.Converter
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import se.ansman.kotshi.KotshiJsonAdapterFactory
|
||||||
|
|
||||||
|
val networkingModule = module {
|
||||||
|
single { createOkHttpClient() }
|
||||||
|
single { createConverterFactory() }
|
||||||
|
single { createCallAdapterFactory() }
|
||||||
|
single { createRetrofit(get(), get(), get()) }
|
||||||
|
single { createApiService<GithubRawApiServices>(get(), Constants.GITHUB_RAW_API_URL) }
|
||||||
|
}
|
||||||
|
|
||||||
val networkingModule = module {}
|
fun createOkHttpClient(): OkHttpClient {
|
||||||
|
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.addInterceptor(httpLoggingInterceptor)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createConverterFactory(): Converter.Factory {
|
||||||
|
val moshi = Moshi.Builder()
|
||||||
|
.add(JsonAdapterFactory.INSTANCE)
|
||||||
|
.build()
|
||||||
|
return MoshiConverterFactory.create(moshi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCallAdapterFactory(): CallAdapter.Factory {
|
||||||
|
return RxJava2CallAdapterFactory.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRetrofit(
|
||||||
|
okHttpClient: OkHttpClient,
|
||||||
|
converterFactory: Converter.Factory,
|
||||||
|
callAdapterFactory: CallAdapter.Factory
|
||||||
|
): Retrofit.Builder {
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.addConverterFactory(converterFactory)
|
||||||
|
.addCallAdapterFactory(callAdapterFactory)
|
||||||
|
.client(okHttpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@KotshiJsonAdapterFactory
|
||||||
|
abstract class JsonAdapterFactory : JsonAdapter.Factory {
|
||||||
|
companion object {
|
||||||
|
val INSTANCE: JsonAdapterFactory = KotshiJsonAdapterFactory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
|
||||||
|
return retrofitBuilder
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.build()
|
||||||
|
.create(T::class.java)
|
||||||
|
}
|
@@ -1,6 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.repository.*
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val repositoryModule = module {}
|
val repositoryModule = module {
|
||||||
|
single { MagiskRepository(get(), get(), get()) }
|
||||||
|
single { LogRepository(get()) }
|
||||||
|
single { AppRepository(get()) }
|
||||||
|
single { SettingRepository(get()) }
|
||||||
|
single { StringRepository(get()) }
|
||||||
|
}
|
||||||
|
@@ -18,7 +18,7 @@ val viewModelModules = module {
|
|||||||
viewModel { HomeViewModel(get()) }
|
viewModel { HomeViewModel(get()) }
|
||||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
||||||
viewModel { HideViewModel(get(), get()) }
|
viewModel { HideViewModel(get(), get()) }
|
||||||
viewModel { ModuleViewModel(get(), get()) }
|
viewModel { ModuleViewModel(get(), get(), get()) }
|
||||||
viewModel { LogViewModel(get(), get()) }
|
viewModel { LogViewModel(get(), get()) }
|
||||||
viewModel { (action: String, uri: Uri?) -> FlashViewModel(action, uri, get()) }
|
viewModel { (action: String, uri: Uri?) -> FlashViewModel(action, uri, get()) }
|
||||||
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
||||||
|
@@ -36,9 +36,18 @@ public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
|
|||||||
mVersionCode = p.readInt();
|
mVersionCode = p.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected BaseModule(MagiskModule m) {
|
||||||
|
mId = m.getId();
|
||||||
|
mName = m.getName();
|
||||||
|
mVersion = m.getVersion();
|
||||||
|
mAuthor = m.getAuthor();
|
||||||
|
mDescription = m.getDescription();
|
||||||
|
mVersionCode = Integer.parseInt(m.getVersionCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NonNull BaseModule module) {
|
public int compareTo(@NonNull BaseModule module) {
|
||||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
return getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,7 +80,9 @@ public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parseProps(List<String> props) { parseProps(props.toArray(new String[0])); }
|
protected void parseProps(List<String> props) {
|
||||||
|
parseProps(props.toArray(new String[0]));
|
||||||
|
}
|
||||||
|
|
||||||
protected void parseProps(String[] props) throws NumberFormatException {
|
protected void parseProps(String[] props) throws NumberFormatException {
|
||||||
for (String line : props) {
|
for (String line : props) {
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
)
|
@@ -0,0 +1,10 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import se.ansman.kotshi.JsonSerializable
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class MagiskConfig(
|
||||||
|
val app: MagiskApp,
|
||||||
|
val uninstaller: MagiskLink,
|
||||||
|
val magisk: MagiskFlashable
|
||||||
|
)
|
@@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
|
)
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import se.ansman.kotshi.JsonSerializable
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class MagiskLink(
|
||||||
|
val link: String
|
||||||
|
)
|
@@ -0,0 +1,58 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
|
||||||
|
import com.topjohnwu.magisk.utils.timeFormatTime
|
||||||
|
import com.topjohnwu.magisk.utils.toTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class MagiskLog(
|
||||||
|
val fromUid: Int,
|
||||||
|
val toUid: Int,
|
||||||
|
val fromPid: Int,
|
||||||
|
val packageName: String,
|
||||||
|
val appName: String,
|
||||||
|
val command: String,
|
||||||
|
val action: Boolean,
|
||||||
|
val date: Date
|
||||||
|
) {
|
||||||
|
val timeString = date.time.toTime(timeFormatTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WrappedMagiskLog(
|
||||||
|
val time: Long,
|
||||||
|
val items: List<MagiskLog>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Map<String, String>.toLog(): MagiskLog {
|
||||||
|
return MagiskLog(
|
||||||
|
fromUid = get("from_uid")?.toIntOrNull() ?: -1,
|
||||||
|
toUid = get("to_uid")?.toIntOrNull() ?: -1,
|
||||||
|
fromPid = get("from_pid")?.toIntOrNull() ?: -1,
|
||||||
|
packageName = get("package_name").orEmpty(),
|
||||||
|
appName = get("app_name").orEmpty(),
|
||||||
|
command = get("command").orEmpty(),
|
||||||
|
action = get("action")?.toIntOrNull() != 0,
|
||||||
|
date = get("time")?.toLongOrNull()?.toDate() ?: Date()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Long.toDate() = Date(this)
|
||||||
|
|
||||||
|
|
||||||
|
fun MagiskLog.toMap() = mapOf(
|
||||||
|
"from_uid" to fromUid,
|
||||||
|
"to_uid" to toUid,
|
||||||
|
"from_pid" to fromPid,
|
||||||
|
"package_name" to packageName,
|
||||||
|
"app_name" to appName,
|
||||||
|
"command" to command,
|
||||||
|
"action" to action,
|
||||||
|
"time" to date
|
||||||
|
).mapValues { it.toString() }
|
||||||
|
|
||||||
|
fun MagiskPolicy.toLog(
|
||||||
|
toUid: Int,
|
||||||
|
fromPid: Int,
|
||||||
|
command: String,
|
||||||
|
date: Date
|
||||||
|
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, date)
|
@@ -0,0 +1,86 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
|
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.data.database.base.su
|
||||||
|
import io.reactivex.Single
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface MagiskModule : Parcelable {
|
||||||
|
val id: String
|
||||||
|
val name: String
|
||||||
|
val author: String
|
||||||
|
val version: String
|
||||||
|
val versionCode: String
|
||||||
|
val description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(tableName = "repos")
|
||||||
|
@Parcelize
|
||||||
|
data class Repository(
|
||||||
|
@PrimaryKey @NonNull
|
||||||
|
override val id: String,
|
||||||
|
override val name: String,
|
||||||
|
override val author: String,
|
||||||
|
override val version: String,
|
||||||
|
override val versionCode: String,
|
||||||
|
override val description: String,
|
||||||
|
val lastUpdate: Long
|
||||||
|
) : MagiskModule
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Module(
|
||||||
|
override val id: String,
|
||||||
|
override val name: String,
|
||||||
|
override val author: String,
|
||||||
|
override val version: String,
|
||||||
|
override val versionCode: String,
|
||||||
|
override val description: String,
|
||||||
|
val path: String
|
||||||
|
) : MagiskModule
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun File.toModule(): Single<Module> {
|
||||||
|
val path = "${Constants.MAGISK_PATH}/$name"
|
||||||
|
return "dos2unix < $path/module.prop".su()
|
||||||
|
.map { it.first().toModule(path) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Map<String, String>.toModule(path: String): Module {
|
||||||
|
return Module(
|
||||||
|
id = get("id").orEmpty(),
|
||||||
|
name = get("name").orEmpty(),
|
||||||
|
author = get("author").orEmpty(),
|
||||||
|
version = get("version").orEmpty(),
|
||||||
|
versionCode = get("versionCode").orEmpty(),
|
||||||
|
description = get("description").orEmpty(),
|
||||||
|
path = path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun ResponseBody.toRepository(lastUpdate: Long) = string()
|
||||||
|
.split(Regex("\\n"))
|
||||||
|
.map { it.split("=", limit = 2) }
|
||||||
|
.filter { it.size == 2 }
|
||||||
|
.map { Pair(it[0], it[1]) }
|
||||||
|
.toMap()
|
||||||
|
.toRepository(lastUpdate)
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun Map<String, String>.toRepository(lastUpdate: Long) = Repository(
|
||||||
|
id = get("id").orEmpty(),
|
||||||
|
name = get("name").orEmpty(),
|
||||||
|
author = get("author").orEmpty(),
|
||||||
|
version = get("version").orEmpty(),
|
||||||
|
versionCode = get("versionCode").orEmpty(),
|
||||||
|
description = get("description").orEmpty(),
|
||||||
|
lastUpdate = lastUpdate
|
||||||
|
)
|
@@ -0,0 +1,96 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
|
||||||
|
|
||||||
|
|
||||||
|
data class MagiskPolicy(
|
||||||
|
val uid: Int,
|
||||||
|
val packageName: String,
|
||||||
|
val appName: String,
|
||||||
|
val policy: Int = INTERACTIVE,
|
||||||
|
val until: Long = -1L,
|
||||||
|
val logging: Boolean = true,
|
||||||
|
val notification: Boolean = true,
|
||||||
|
val applicationInfo: ApplicationInfo
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val INTERACTIVE = 0
|
||||||
|
const val DENY = 1
|
||||||
|
const val ALLOW = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@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() }
|
||||||
|
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
|
val uid = get("uid")?.toIntOrNull() ?: -1
|
||||||
|
val packageName = get("package_name").orEmpty()
|
||||||
|
val info = pm.getApplicationInfo(packageName, 0)
|
||||||
|
|
||||||
|
if (info.uid != uid)
|
||||||
|
throw PackageManager.NameNotFoundException()
|
||||||
|
|
||||||
|
return MagiskPolicy(
|
||||||
|
uid = uid,
|
||||||
|
packageName = packageName,
|
||||||
|
policy = get("policy")?.toIntOrNull() ?: INTERACTIVE,
|
||||||
|
until = get("until")?.toLongOrNull() ?: -1L,
|
||||||
|
logging = get("logging")?.toIntOrNull() != 0,
|
||||||
|
notification = get("notification")?.toIntOrNull() != 0,
|
||||||
|
applicationInfo = info,
|
||||||
|
appName = info.loadLabel(pm).toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun Int.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
|
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
|
||||||
|
?: throw PackageManager.NameNotFoundException()
|
||||||
|
val info = pm.getApplicationInfo(pkg, 0)
|
||||||
|
return MagiskPolicy(
|
||||||
|
uid = this,
|
||||||
|
packageName = pkg,
|
||||||
|
applicationInfo = info,
|
||||||
|
appName = info.loadLabel(pm).toString()
|
||||||
|
)
|
||||||
|
}
|
@@ -6,16 +6,33 @@ import android.os.Parcelable;
|
|||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
|
||||||
public class Module extends BaseModule {
|
public class OldModule extends BaseModule {
|
||||||
|
|
||||||
private SuFile mRemoveFile, mDisableFile, mUpdateFile;
|
public static final Parcelable.Creator<OldModule> CREATOR = new Creator<OldModule>() {
|
||||||
private boolean mEnable, mRemove, mUpdated;
|
/* It won't be used at any place */
|
||||||
|
@Override
|
||||||
|
public OldModule createFromParcel(Parcel source) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Module(String path) {
|
@Override
|
||||||
|
public OldModule[] newArray(int size) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final SuFile mRemoveFile;
|
||||||
|
private final SuFile mDisableFile;
|
||||||
|
private final SuFile mUpdateFile;
|
||||||
|
private final boolean mUpdated;
|
||||||
|
private boolean mEnable;
|
||||||
|
private boolean mRemove;
|
||||||
|
|
||||||
|
public OldModule(String path) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parseProps(Shell.su("dos2unix < " + path + "/module.prop").exec().getOut());
|
parseProps(Shell.su("dos2unix < " + path + "/module.prop").exec().getOut());
|
||||||
} catch (NumberFormatException ignored) {}
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
mRemoveFile = new SuFile(path, "remove");
|
mRemoveFile = new SuFile(path, "remove");
|
||||||
mDisableFile = new SuFile(path, "disable");
|
mDisableFile = new SuFile(path, "disable");
|
||||||
@@ -35,19 +52,6 @@ public class Module extends BaseModule {
|
|||||||
mUpdated = mUpdateFile.exists();
|
mUpdated = mUpdateFile.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator<Module> CREATOR = new Creator<Module>() {
|
|
||||||
/* It won't be used at any place */
|
|
||||||
@Override
|
|
||||||
public Module createFromParcel(Parcel source) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Module[] newArray(int size) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public void createDisableFile() {
|
public void createDisableFile() {
|
||||||
mEnable = !mDisableFile.createNewFile();
|
mEnable = !mDisableFile.createNewFile();
|
||||||
}
|
}
|
@@ -29,6 +29,11 @@ public class Repo extends BaseModule {
|
|||||||
mLastUpdate = new Date(p.readLong());
|
mLastUpdate = new Date(p.readLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Repo(Repository repo) {
|
||||||
|
super(repo);
|
||||||
|
mLastUpdate = new Date(repo.getLastUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
|
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,7 +54,7 @@ public class Repo extends BaseModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update() throws IllegalRepoException {
|
public void update() throws IllegalRepoException {
|
||||||
String props[] = Utils.dlString(getPropUrl()).split("\\n");
|
String[] props = Utils.dlString(getPropUrl()).split("\\n");
|
||||||
try {
|
try {
|
||||||
parseProps(props);
|
parseProps(props);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
|
@@ -15,11 +15,11 @@ public class SuLogEntry {
|
|||||||
public boolean action;
|
public boolean action;
|
||||||
public Date date;
|
public Date date;
|
||||||
|
|
||||||
public SuLogEntry(Policy policy) {
|
public SuLogEntry(MagiskPolicy policy) {
|
||||||
fromUid = policy.uid;
|
fromUid = policy.getUid();
|
||||||
packageName = policy.packageName;
|
packageName = policy.getPackageName();
|
||||||
appName = policy.appName;
|
appName = policy.getAppName();
|
||||||
action = policy.policy == Policy.ALLOW;
|
action = policy.getPolicy() == Policy.ALLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SuLogEntry(ContentValues values) {
|
public SuLogEntry(ContentValues values) {
|
||||||
@@ -47,10 +47,10 @@ public class SuLogEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDateString() {
|
public String getDateString() {
|
||||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.getLocale()).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTimeString() {
|
public String getTimeString() {
|
||||||
return new SimpleDateFormat("h:mm a", LocaleManager.locale).format(date);
|
return new SimpleDateFormat("h:mm a", LocaleManager.getLocale()).format(date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
data class Version(val version: String, val versionCode: Int)
|
@@ -1,11 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import androidx.databinding.ObservableList
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.model.entity.SuLogEntry
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||||
|
import com.topjohnwu.magisk.utils.timeFormatMedium
|
||||||
|
import com.topjohnwu.magisk.utils.toTime
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
import com.topjohnwu.magisk.utils.toggle
|
||||||
|
|
||||||
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
||||||
@@ -25,12 +27,12 @@ class LogRvItem : ComparableRvItem<LogRvItem>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LogItemRvItem(
|
class LogItemRvItem(
|
||||||
val items: ObservableList<ComparableRvItem<*>>
|
item: WrappedMagiskLog
|
||||||
) : ComparableRvItem<LogItemRvItem>() {
|
) : ComparableRvItem<LogItemRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_superuser_log
|
override val layoutRes: Int = R.layout.item_superuser_log
|
||||||
|
|
||||||
val date = items.filterIsInstance<LogItemEntryRvItem>().firstOrNull()
|
val date = item.time.toTime(timeFormatMedium)
|
||||||
?.item?.dateString.orEmpty()
|
val items: List<ComparableRvItem<*>> = item.items.map { LogItemEntryRvItem(it) }
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
fun toggle() = isExpanded.toggle()
|
||||||
@@ -41,7 +43,7 @@ class LogItemRvItem(
|
|||||||
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogItemEntryRvItem(val item: SuLogEntry) : ComparableRvItem<LogItemEntryRvItem>() {
|
class LogItemEntryRvItem(val item: MagiskLog) : ComparableRvItem<LogItemEntryRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_superuser_log_entry
|
override val layoutRes: Int = R.layout.item_superuser_log_entry
|
||||||
|
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
|
@@ -6,12 +6,13 @@ import com.skoumal.teanity.databinding.ComparableRvItem
|
|||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.model.entity.Module
|
import com.topjohnwu.magisk.model.entity.OldModule
|
||||||
import com.topjohnwu.magisk.model.entity.Repo
|
import com.topjohnwu.magisk.model.entity.Repo
|
||||||
|
import com.topjohnwu.magisk.model.entity.Repository
|
||||||
import com.topjohnwu.magisk.utils.get
|
import com.topjohnwu.magisk.utils.get
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
import com.topjohnwu.magisk.utils.toggle
|
||||||
|
|
||||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
class ModuleRvItem(val item: OldModule) : ComparableRvItem<ModuleRvItem>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_module
|
override val layoutRes: Int = R.layout.item_module
|
||||||
|
|
||||||
@@ -49,18 +50,22 @@ class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
|||||||
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
||||||
&& item.versionCode == other.item.versionCode
|
&& item.versionCode == other.item.versionCode
|
||||||
&& item.description == other.item.description
|
&& item.description == other.item.description
|
||||||
|
&& item.name == other.item.name
|
||||||
|
|
||||||
override fun itemSameAs(other: ModuleRvItem): Boolean = item.name == other.item.name
|
override fun itemSameAs(other: ModuleRvItem): Boolean = item.id == other.item.id
|
||||||
}
|
}
|
||||||
|
|
||||||
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
||||||
|
|
||||||
|
constructor(repo: Repository) : this(Repo(repo))
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_repo
|
override val layoutRes: Int = R.layout.item_repo
|
||||||
|
|
||||||
override fun contentSameAs(other: RepoRvItem): Boolean = item.version == other.item.version
|
override fun contentSameAs(other: RepoRvItem): Boolean = item.version == other.item.version
|
||||||
&& item.lastUpdate == other.item.lastUpdate
|
&& item.lastUpdate == other.item.lastUpdate
|
||||||
&& item.versionCode == other.item.versionCode
|
&& item.versionCode == other.item.versionCode
|
||||||
&& item.description == other.item.description
|
&& item.description == other.item.description
|
||||||
|
&& item.detailUrl == other.item.detailUrl
|
||||||
|
|
||||||
override fun itemSameAs(other: RepoRvItem): Boolean = item.detailUrl == other.item.detailUrl
|
override fun itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id
|
||||||
}
|
}
|
@@ -6,13 +6,14 @@ import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.Policy
|
import com.topjohnwu.magisk.model.entity.Policy
|
||||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||||
import com.topjohnwu.magisk.utils.inject
|
import com.topjohnwu.magisk.utils.inject
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
import com.topjohnwu.magisk.utils.toggle
|
||||||
|
|
||||||
class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_policy
|
override val layoutRes: Int = R.layout.item_policy
|
||||||
|
|
||||||
@@ -25,6 +26,13 @@ class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<Poli
|
|||||||
|
|
||||||
private val rxBus: RxBus by inject()
|
private val rxBus: RxBus by inject()
|
||||||
|
|
||||||
|
private val currentStateItem
|
||||||
|
get() = item.copy(
|
||||||
|
policy = if (isEnabled.value) Policy.ALLOW else Policy.DENY,
|
||||||
|
notification = shouldNotify.value,
|
||||||
|
logging = shouldLog.value
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isEnabled.addOnPropertyChangedCallback {
|
isEnabled.addOnPropertyChangedCallback {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
it ?: return@addOnPropertyChangedCallback
|
||||||
@@ -32,13 +40,11 @@ class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<Poli
|
|||||||
}
|
}
|
||||||
shouldNotify.addOnPropertyChangedCallback {
|
shouldNotify.addOnPropertyChangedCallback {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
it ?: return@addOnPropertyChangedCallback
|
||||||
item.notification = it
|
rxBus.post(PolicyUpdateEvent.Notification(currentStateItem))
|
||||||
rxBus.post(PolicyUpdateEvent.Notification(this@PolicyRvItem))
|
|
||||||
}
|
}
|
||||||
shouldLog.addOnPropertyChangedCallback {
|
shouldLog.addOnPropertyChangedCallback {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
it ?: return@addOnPropertyChangedCallback
|
||||||
item.logging = it
|
rxBus.post(PolicyUpdateEvent.Log(currentStateItem))
|
||||||
rxBus.post(PolicyUpdateEvent.Log(this@PolicyRvItem))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.model.events
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||||
@@ -8,9 +9,9 @@ import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
|||||||
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
||||||
|
|
||||||
class PolicyEnableEvent(val item: PolicyRvItem, val enable: Boolean) : RxBus.Event
|
class PolicyEnableEvent(val item: PolicyRvItem, val enable: Boolean) : RxBus.Event
|
||||||
sealed class PolicyUpdateEvent(val item: PolicyRvItem) : RxBus.Event {
|
sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event {
|
||||||
class Notification(item: PolicyRvItem) : PolicyUpdateEvent(item)
|
class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||||
class Log(item: PolicyRvItem) : PolicyUpdateEvent(item)
|
class Log(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
|
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
|
||||||
|
@@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import com.topjohnwu.magisk.tasks.FlashZip
|
import com.topjohnwu.magisk.tasks.FlashZip
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.utils.inject
|
import com.topjohnwu.magisk.utils.inject
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
@@ -33,7 +32,7 @@ sealed class Flashing(
|
|||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
override fun onResult(success: Boolean) {
|
||||||
if (success) {
|
if (success) {
|
||||||
Utils.loadModules()
|
//Utils.loadModules()
|
||||||
}
|
}
|
||||||
super.onResult(success)
|
super.onResult(success)
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class BooleanProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Boolean,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Boolean> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Boolean {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Boolean
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class FloatProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Float,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Float> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Float {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Float
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class IntProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Int,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Int> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Int {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Int
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class LongProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Long,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Long> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Long {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Long
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
|
||||||
|
abstract class PreferenceModel(
|
||||||
|
private val commitPrefs: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
protected abstract val fileName: String
|
||||||
|
protected abstract val context: Context
|
||||||
|
|
||||||
|
internal val prefs get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
protected fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Boolean,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
): ReadWriteProperty<PreferenceModel, Boolean> = BooleanProperty(name, default, commit)
|
||||||
|
|
||||||
|
protected fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Float,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
): ReadWriteProperty<PreferenceModel, Float> = FloatProperty(name, default, commit)
|
||||||
|
|
||||||
|
protected fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Int,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
): ReadWriteProperty<PreferenceModel, Int> = IntProperty(name, default, commit)
|
||||||
|
|
||||||
|
protected fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Long,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
): ReadWriteProperty<PreferenceModel, Long> = LongProperty(name, default, commit)
|
||||||
|
|
||||||
|
protected fun preference(
|
||||||
|
name: String,
|
||||||
|
default: String,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
): ReadWriteProperty<PreferenceModel, String> = StringProperty(name, default, commit)
|
||||||
|
|
||||||
|
protected fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Set<String>,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
): ReadWriteProperty<PreferenceModel, Set<String>> = StringSetProperty(name, default, commit)
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
abstract class Property {
|
||||||
|
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Boolean) = putBoolean(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Float) = putFloat(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Int) = putInt(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Long) = putLong(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: String) = putString(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Set<String>) = putStringSet(name, value)
|
||||||
|
|
||||||
|
fun SharedPreferences.get(name: String, value: Boolean) = getBoolean(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: Float) = getFloat(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: Int) = getInt(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: Long) = getLong(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: String) = getString(name, value) ?: value
|
||||||
|
fun SharedPreferences.get(name: String, value: Set<String>) = getStringSet(name, value) ?: value
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class StringProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: String,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, String> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): String {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.utils.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class StringSetProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Set<String>,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Set<String>> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Set<String> {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return runCatching { thisRef.prefs.get(prefName, default) }.getOrNull() ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Set<String>
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -6,11 +6,13 @@ import android.content.Intent
|
|||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
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.ui.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.utils.DownloadApp
|
import com.topjohnwu.magisk.utils.DownloadApp
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.RootUtils
|
||||||
import com.topjohnwu.magisk.utils.SuLogger
|
import com.topjohnwu.magisk.utils.SuLogger
|
||||||
|
import com.topjohnwu.magisk.utils.inject
|
||||||
import com.topjohnwu.magisk.utils.get
|
import com.topjohnwu.magisk.utils.get
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
@@ -18,6 +20,8 @@ import com.topjohnwu.superuser.Shell
|
|||||||
|
|
||||||
open class GeneralReceiver : BroadcastReceiver() {
|
open class GeneralReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
private val appRepo: AppRepository by inject()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST = "request"
|
const val REQUEST = "request"
|
||||||
const val LOG = "log"
|
const val LOG = "log"
|
||||||
@@ -32,7 +36,6 @@ open class GeneralReceiver : BroadcastReceiver() {
|
|||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
if (intent == null)
|
if (intent == null)
|
||||||
return
|
return
|
||||||
val mDB: MagiskDB = get()
|
|
||||||
var action: String? = intent.action ?: return
|
var action: String? = intent.action ?: return
|
||||||
when (action) {
|
when (action) {
|
||||||
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
||||||
@@ -47,7 +50,7 @@ open class GeneralReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
when (action) {
|
when (action) {
|
||||||
REQUEST -> {
|
REQUEST -> {
|
||||||
val i = Intent(context, ClassMap.get<Any>(SuRequestActivity::class.java))
|
val i = Intent(context, ClassMap[SuRequestActivity::class.java])
|
||||||
.setAction(action)
|
.setAction(action)
|
||||||
.putExtra("socket", intent.getStringExtra("socket"))
|
.putExtra("socket", intent.getStringExtra("socket"))
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
@@ -62,12 +65,12 @@ open class GeneralReceiver : BroadcastReceiver() {
|
|||||||
Intent.ACTION_PACKAGE_REPLACED ->
|
Intent.ACTION_PACKAGE_REPLACED ->
|
||||||
// This will only work pre-O
|
// This will only work pre-O
|
||||||
if (Config.get<Boolean>(Config.Key.SU_REAUTH)!!) {
|
if (Config.get<Boolean>(Config.Key.SU_REAUTH)!!) {
|
||||||
mDB.deletePolicy(getPkg(intent))
|
appRepo.delete(getPkg(intent)).blockingGet()
|
||||||
}
|
}
|
||||||
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
||||||
val pkg = getPkg(intent)
|
val pkg = getPkg(intent)
|
||||||
mDB.deletePolicy(pkg)
|
appRepo.delete(pkg).blockingGet()
|
||||||
Shell.su("magiskhide --rm $pkg").submit()
|
"magiskhide --rm $pkg".su().blockingGet()
|
||||||
}
|
}
|
||||||
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
||||||
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.update;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.work.ListenableWorker;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
import com.topjohnwu.magisk.BuildConfig;
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.model.worker.DelegateWorker;
|
|
||||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
|
||||||
import com.topjohnwu.magisk.view.Notifications;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
|
|
||||||
public class UpdateCheckService extends DelegateWorker {
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ListenableWorker.Result doWork() {
|
|
||||||
if (App.foreground() == null) {
|
|
||||||
Shell.getShell();
|
|
||||||
CheckUpdates.check(this::onCheckDone);
|
|
||||||
}
|
|
||||||
return ListenableWorker.Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCheckDone() {
|
|
||||||
if (BuildConfig.VERSION_CODE < Config.remoteManagerVersionCode) {
|
|
||||||
Notifications.managerUpdate();
|
|
||||||
} else if (Config.magiskVersionCode < Config.remoteMagiskVersionCode) {
|
|
||||||
Notifications.magiskUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,31 @@
|
|||||||
|
package com.topjohnwu.magisk.model.update
|
||||||
|
|
||||||
|
import androidx.work.ListenableWorker
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
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
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
app/src/main/java/com/topjohnwu/magisk/model/zip/Zip.kt
Normal file
75
app/src/main/java/com/topjohnwu/magisk/model/zip/Zip.kt
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package com.topjohnwu.magisk.model.zip
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.utils.forEach
|
||||||
|
import com.topjohnwu.magisk.utils.withStreams
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
import java.io.File
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
|
||||||
|
class Zip private constructor(private val values: Builder) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke(builder: Builder.() -> Unit): Zip {
|
||||||
|
return Zip(Builder().apply(builder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
lateinit var zip: File
|
||||||
|
lateinit var destination: File
|
||||||
|
var excludeDirs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Path(val path: String, val pullFromDir: Boolean = true)
|
||||||
|
|
||||||
|
fun unzip(vararg paths: Pair<String, Boolean>) =
|
||||||
|
unzip(*paths.map { Path(it.first, it.second) }.toTypedArray())
|
||||||
|
|
||||||
|
@Suppress("RedundantLambdaArrow")
|
||||||
|
fun unzip(vararg paths: Path) {
|
||||||
|
ensureRequiredParams()
|
||||||
|
|
||||||
|
values.zip.zipStream().use {
|
||||||
|
it.forEach { e ->
|
||||||
|
val currentPath = paths.firstOrNull { e.name.startsWith(it.path) }
|
||||||
|
val isDirectory = values.excludeDirs && e.isDirectory
|
||||||
|
if (currentPath == null || isDirectory) {
|
||||||
|
// Ignore directories, only create files
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = if (currentPath.pullFromDir) {
|
||||||
|
e.name.substring(e.name.lastIndexOf('/') + 1)
|
||||||
|
} else {
|
||||||
|
e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val out = File(values.destination, name)
|
||||||
|
.ensureExists()
|
||||||
|
.outputStream()
|
||||||
|
//.suOutputStream()
|
||||||
|
|
||||||
|
withStreams(it, out) { reader, writer ->
|
||||||
|
reader.copyTo(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureRequiredParams() {
|
||||||
|
if (!values.zip.exists()) {
|
||||||
|
throw RuntimeException("Zip file does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.ensureExists() =
|
||||||
|
if ((!parentFile.exists() && !parentFile.mkdirs()) || parentFile is SuFile) {
|
||||||
|
SuFile(parentFile, name).apply { parentFile.mkdirs() }
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.zipStream() = ZipInputStream(inputStream())
|
||||||
|
|
||||||
|
}
|
@@ -1,132 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.tasks;
|
|
||||||
|
|
||||||
import android.os.SystemClock;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.utils.Event;
|
|
||||||
import com.topjohnwu.net.Networking;
|
|
||||||
import com.topjohnwu.net.Request;
|
|
||||||
import com.topjohnwu.net.ResponseListener;
|
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class CheckUpdates {
|
|
||||||
|
|
||||||
private static Request getRequest() {
|
|
||||||
String url;
|
|
||||||
switch ((int) Config.get(Config.Key.UPDATE_CHANNEL)) {
|
|
||||||
case Config.Value.BETA_CHANNEL:
|
|
||||||
url = Const.Url.BETA_URL;
|
|
||||||
break;
|
|
||||||
case Config.Value.CUSTOM_CHANNEL:
|
|
||||||
url = Config.get(Config.Key.CUSTOM_CHANNEL);
|
|
||||||
break;
|
|
||||||
case Config.Value.CANARY_CHANNEL:
|
|
||||||
url = Const.Url.CANARY_URL;
|
|
||||||
break;
|
|
||||||
case Config.Value.CANARY_DEBUG_CHANNEL:
|
|
||||||
url = Const.Url.CANARY_DEBUG_URL;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
url = Const.Url.STABLE_URL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return Networking.get(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void check() {
|
|
||||||
check(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void check(Runnable cb) {
|
|
||||||
Request request = getRequest();
|
|
||||||
UpdateListener listener = new UpdateListener(cb);
|
|
||||||
if (ShellUtils.onMainThread()) {
|
|
||||||
request.getAsJSONObject(listener);
|
|
||||||
} else {
|
|
||||||
JSONObject json = request.execForJSONObject().getResult();
|
|
||||||
if (json != null)
|
|
||||||
listener.onResponse(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UpdateListener implements ResponseListener<JSONObject> {
|
|
||||||
|
|
||||||
private Runnable cb;
|
|
||||||
private long start;
|
|
||||||
|
|
||||||
UpdateListener(Runnable callback) {
|
|
||||||
cb = callback;
|
|
||||||
start = SystemClock.uptimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getInt(JSONObject json, String name, int defValue) {
|
|
||||||
if (json == null)
|
|
||||||
return defValue;
|
|
||||||
try {
|
|
||||||
return json.getInt(name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getString(JSONObject json, String name, String defValue) {
|
|
||||||
if (json == null)
|
|
||||||
return defValue;
|
|
||||||
try {
|
|
||||||
return json.getString(name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONObject getJson(JSONObject json, String name) {
|
|
||||||
try {
|
|
||||||
return json.getJSONObject(name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject json) {
|
|
||||||
JSONObject magisk = getJson(json, "magisk");
|
|
||||||
Config.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
|
||||||
|
|
||||||
if ((int) Config.get(Config.Key.UPDATE_CHANNEL) == Config.Value.DEFAULT_CHANNEL) {
|
|
||||||
if (Config.magiskVersionCode > Config.remoteMagiskVersionCode) {
|
|
||||||
// If we are newer than current stable channel, switch to beta
|
|
||||||
Config.set(Config.Key.UPDATE_CHANNEL, Config.Value.BETA_CHANNEL);
|
|
||||||
check(cb);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Config.set(Config.Key.UPDATE_CHANNEL, Config.Value.STABLE_CHANNEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.remoteMagiskVersionString = getString(magisk, "version", null);
|
|
||||||
Config.magiskLink = getString(magisk, "link", null);
|
|
||||||
Config.magiskNoteLink = getString(magisk, "note", null);
|
|
||||||
Config.magiskMD5 = getString(magisk, "md5", null);
|
|
||||||
|
|
||||||
JSONObject manager = getJson(json, "app");
|
|
||||||
Config.remoteManagerVersionString = getString(manager, "version", null);
|
|
||||||
Config.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
|
||||||
Config.managerLink = getString(manager, "link", null);
|
|
||||||
Config.managerNoteLink = getString(manager, "note", null);
|
|
||||||
|
|
||||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
|
||||||
Config.uninstallerLink = getString(uninstaller, "link", null);
|
|
||||||
|
|
||||||
UiThreadHandler.handler.postAtTime(() -> Event.trigger(Event.UPDATE_CHECK_DONE),
|
|
||||||
start + 1000 /* Add artificial delay to let UI behave correctly */);
|
|
||||||
|
|
||||||
if (cb != null)
|
|
||||||
cb.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -6,8 +6,8 @@ import android.util.Pair;
|
|||||||
import com.topjohnwu.magisk.App;
|
import com.topjohnwu.magisk.App;
|
||||||
import com.topjohnwu.magisk.Config;
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper;
|
||||||
import com.topjohnwu.magisk.model.entity.Repo;
|
import com.topjohnwu.magisk.model.entity.Repo;
|
||||||
import com.topjohnwu.magisk.utils.Event;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
import com.topjohnwu.magisk.utils.Logger;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
import com.topjohnwu.net.Networking;
|
import com.topjohnwu.net.Networking;
|
||||||
@@ -31,18 +31,27 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class UpdateRepos {
|
public class UpdateRepos {
|
||||||
private static final DateFormat DATE_FORMAT;
|
private static final DateFormat DATE_FORMAT;
|
||||||
|
|
||||||
private final App app = App.self;
|
|
||||||
private Set<String> cached;
|
|
||||||
private Queue<Pair<String, Date>> moduleQueue;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||||
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final RepoDatabaseHelper repoDB;
|
||||||
|
private Set<String> cached;
|
||||||
|
private Queue<Pair<String, Date>> moduleQueue;
|
||||||
|
|
||||||
|
public UpdateRepos(@NonNull RepoDatabaseHelper repoDatabase) {
|
||||||
|
repoDB = repoDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
private void runTasks(Runnable task) {
|
private void runTasks(Runnable task) {
|
||||||
Future[] futures = new Future[App.THREAD_POOL.getMaximumPoolSize() - 1];
|
Future[] futures = new Future[App.THREAD_POOL.getMaximumPoolSize() - 1];
|
||||||
for (int i = 0; i < futures.length; ++i) {
|
for (int i = 0; i < futures.length; ++i) {
|
||||||
@@ -54,7 +63,8 @@ public class UpdateRepos {
|
|||||||
f.get();
|
f.get();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
continue;
|
continue;
|
||||||
} catch (ExecutionException ignored) {}
|
} catch (ExecutionException ignored) {
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,17 +126,17 @@ public class UpdateRepos {
|
|||||||
Pair<String, Date> pair = moduleQueue.poll();
|
Pair<String, Date> pair = moduleQueue.poll();
|
||||||
if (pair == null)
|
if (pair == null)
|
||||||
return;
|
return;
|
||||||
Repo repo = app.getRepoDB().getRepo(pair.first);
|
Repo repo = repoDB.getRepo(pair.first);
|
||||||
try {
|
try {
|
||||||
if (repo == null)
|
if (repo == null)
|
||||||
repo = new Repo(pair.first);
|
repo = new Repo(pair.first);
|
||||||
else
|
else
|
||||||
cached.remove(pair.first);
|
cached.remove(pair.first);
|
||||||
repo.update(pair.second);
|
repo.update(pair.second);
|
||||||
app.getRepoDB().addRepo(repo);
|
repoDB.addRepo(repo);
|
||||||
} catch (Repo.IllegalRepoException e) {
|
} catch (Repo.IllegalRepoException e) {
|
||||||
Logger.debug(e.getMessage());
|
Logger.debug(e.getMessage());
|
||||||
app.getRepoDB().removeRepo(pair.first);
|
repoDB.removeRepo(pair.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -134,7 +144,7 @@ public class UpdateRepos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fullReload() {
|
private void fullReload() {
|
||||||
Cursor c = app.getRepoDB().getRawCursor();
|
Cursor c = repoDB.getRawCursor();
|
||||||
runTasks(() -> {
|
runTasks(() -> {
|
||||||
while (true) {
|
while (true) {
|
||||||
Repo repo;
|
Repo repo;
|
||||||
@@ -145,32 +155,31 @@ public class UpdateRepos {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
repo.update();
|
repo.update();
|
||||||
app.getRepoDB().addRepo(repo);
|
repoDB.addRepo(repo);
|
||||||
} catch (Repo.IllegalRepoException e) {
|
} catch (Repo.IllegalRepoException e) {
|
||||||
Logger.debug(e.getMessage());
|
Logger.debug(e.getMessage());
|
||||||
app.getRepoDB().removeRepo(repo);
|
repoDB.removeRepo(repo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void exec(boolean force) {
|
public Single<Boolean> exec(boolean force) {
|
||||||
Event.reset(Event.REPO_LOAD_DONE);
|
return Single.fromCallable(() -> {
|
||||||
App.THREAD_POOL.execute(() -> {
|
cached = Collections.synchronizedSet(repoDB.getRepoIDSet());
|
||||||
cached = Collections.synchronizedSet(app.getRepoDB().getRepoIDSet());
|
|
||||||
moduleQueue = new ConcurrentLinkedQueue<>();
|
moduleQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
if (loadPages()) {
|
if (loadPages()) {
|
||||||
// The leftover cached means they are removed from online repo
|
// The leftover cached means they are removed from online repo
|
||||||
app.getRepoDB().removeRepo(cached);
|
repoDB.removeRepo(cached);
|
||||||
} else if (force) {
|
} else if (force) {
|
||||||
fullReload();
|
fullReload();
|
||||||
}
|
}
|
||||||
Event.trigger(Event.REPO_LOAD_DONE);
|
return force; // not important
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void exec() {
|
public Single<Boolean> exec() {
|
||||||
exec(false);
|
return exec(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,7 +48,7 @@ open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
if (!SplashActivity.DONE) {
|
if (!SplashActivity.DONE) {
|
||||||
startActivity(Intent(this, ClassMap.get<Any>(SplashActivity::class.java)))
|
startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,15 +5,15 @@ import android.os.Bundle
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.tasks.CheckUpdates
|
|
||||||
import com.topjohnwu.magisk.tasks.UpdateRepos
|
import com.topjohnwu.magisk.tasks.UpdateRepos
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.net.Networking
|
import com.topjohnwu.net.Networking
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import org.koin.android.ext.android.get
|
||||||
|
|
||||||
open class SplashActivity : AppCompatActivity() {
|
open class SplashActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
Shell.getShell {
|
Shell.getShell {
|
||||||
if (Config.magiskVersionCode > 0 && Config.magiskVersionCode < Const.MAGISK_VER.MIN_SUPPORT) {
|
if (Config.magiskVersionCode > 0 && Config.magiskVersionCode < Const.MagiskVersion.MIN_SUPPORT) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.unsupport_magisk_title)
|
.setTitle(R.string.unsupport_magisk_title)
|
||||||
.setMessage(R.string.unsupport_magisk_message)
|
.setMessage(R.string.unsupport_magisk_message)
|
||||||
@@ -48,9 +48,6 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic detect all locales
|
|
||||||
LocaleManager.loadAvailableLocales(R.string.app_changelog)
|
|
||||||
|
|
||||||
// Set default configs
|
// Set default configs
|
||||||
Config.initialize()
|
Config.initialize()
|
||||||
|
|
||||||
@@ -59,7 +56,7 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// Schedule periodic update checks
|
// Schedule periodic update checks
|
||||||
Utils.scheduleUpdateCheck()
|
Utils.scheduleUpdateCheck()
|
||||||
CheckUpdates.check()
|
//CheckUpdates.check()
|
||||||
|
|
||||||
// Setup shortcuts
|
// Setup shortcuts
|
||||||
Shortcuts.setup(this)
|
Shortcuts.setup(this)
|
||||||
@@ -67,13 +64,14 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
// Magisk working as expected
|
// Magisk working as expected
|
||||||
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
|
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
|
||||||
// Load modules
|
// Load modules
|
||||||
Utils.loadModules(false)
|
//Utils.loadModules(false)
|
||||||
// Load repos
|
// Load repos
|
||||||
if (Networking.checkNetworkStatus(this))
|
if (Networking.checkNetworkStatus(this)) {
|
||||||
UpdateRepos().exec()
|
get<UpdateRepos>().exec().subscribeK()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = Intent(this, ClassMap.get<Any>(MainActivity::class.java))
|
val intent = Intent(this, ClassMap[MainActivity::class.java])
|
||||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
|
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
|
||||||
DONE = true
|
DONE = true
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
@@ -1,81 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.base;
|
|
||||||
|
|
||||||
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.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Event;
|
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceCategory;
|
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
|
||||||
import androidx.preference.PreferenceGroupAdapter;
|
|
||||||
import androidx.preference.PreferenceScreen;
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat
|
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Event.AutoListener {
|
|
||||||
|
|
||||||
public App app = App.self;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View v = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
app.getPrefs().registerOnSharedPreferenceChangeListener(this);
|
|
||||||
Event.register(this);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
app.getPrefs().unregisterOnSharedPreferenceChangeListener(this);
|
|
||||||
Event.unregister(this);
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] getListeningEvents() {
|
|
||||||
return new int[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
|
||||||
return new PreferenceGroupAdapter(preferenceScreen) {
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
|
|
||||||
super.onBindViewHolder(holder, position);
|
|
||||||
Preference preference = getItem(position);
|
|
||||||
if (preference instanceof PreferenceCategory)
|
|
||||||
setZeroPaddingToLayoutChildren(holder.itemView);
|
|
||||||
else {
|
|
||||||
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
|
|
||||||
if (iconFrame != null) {
|
|
||||||
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setZeroPaddingToLayoutChildren(View view) {
|
|
||||||
if (!(view instanceof ViewGroup))
|
|
||||||
return;
|
|
||||||
ViewGroup viewGroup = (ViewGroup) view;
|
|
||||||
int childCount = viewGroup.getChildCount();
|
|
||||||
for (int i = 0; i < childCount; i++) {
|
|
||||||
setZeroPaddingToLayoutChildren(viewGroup.getChildAt(i));
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
|
|
||||||
viewGroup.setPaddingRelative(0, viewGroup.getPaddingTop(), viewGroup.getPaddingEnd(), viewGroup.getPaddingBottom());
|
|
||||||
else
|
|
||||||
viewGroup.setPadding(0, viewGroup.getPaddingTop(), viewGroup.getPaddingRight(), viewGroup.getPaddingBottom());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,85 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.base
|
||||||
|
|
||||||
|
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.view.ViewGroup
|
||||||
|
import androidx.core.view.children
|
||||||
|
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,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val v = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> {
|
||||||
|
return object : PreferenceGroupAdapter(preferenceScreen) {
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
|
||||||
|
super.onBindViewHolder(holder, position)
|
||||||
|
when (val preference = getItem(position)) {
|
||||||
|
is PreferenceCategory -> setZeroPaddingToLayoutChildren(holder.itemView)
|
||||||
|
else -> holder.itemView.findViewById<View>(R.id.icon_frame)?.isVisible =
|
||||||
|
preference.icon != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setZeroPaddingToLayoutChildren(view: View) {
|
||||||
|
(view as? ViewGroup)?.children?.forEach {
|
||||||
|
setZeroPaddingToLayoutChildren(it)
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||||
|
view.setPaddingRelative(0, view.paddingTop, view.paddingEnd, view.paddingBottom)
|
||||||
|
else
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,16 +6,11 @@ import com.skoumal.teanity.viewmodel.LoadingViewModel
|
|||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.utils.Event
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
|
|
||||||
abstract class MagiskViewModel : LoadingViewModel(), Event.AutoListener {
|
abstract class MagiskViewModel : LoadingViewModel() {
|
||||||
|
|
||||||
override fun onEvent(event: Int) = Timber.i("Event of $event was not handled")
|
|
||||||
override fun getListeningEvents(): IntArray = intArrayOf()
|
|
||||||
|
|
||||||
fun withView(action: Activity.() -> Unit) {
|
fun withView(action: Activity.() -> Unit) {
|
||||||
ViewActionEvent(action).publish()
|
ViewActionEvent(action).publish()
|
||||||
|
@@ -1,32 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.ui.hide
|
package com.topjohnwu.magisk.ui.hide
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.App
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.utils.toSingle
|
import com.topjohnwu.magisk.utils.toSingle
|
||||||
import com.topjohnwu.magisk.utils.update
|
import com.topjohnwu.magisk.utils.update
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import io.reactivex.Single
|
|
||||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class HideViewModel(
|
class HideViewModel(
|
||||||
private val packageManager: PackageManager,
|
private val magiskRepo: MagiskRepository,
|
||||||
rxBus: RxBus
|
rxBus: RxBus
|
||||||
) : MagiskViewModel() {
|
) : MagiskViewModel() {
|
||||||
|
|
||||||
@@ -55,26 +49,19 @@ class HideViewModel(
|
|||||||
// fetching this for every item is nonsensical, so we add .cache() so the response is all
|
// fetching this for every item is nonsensical, so we add .cache() so the response is all
|
||||||
// the same for every single mapped item, it only actually executes the whole thing the
|
// the same for every single mapped item, it only actually executes the whole thing the
|
||||||
// first time around.
|
// first time around.
|
||||||
val hideTargets = Shell.su("magiskhide --ls").toSingle()
|
val hideTargets = magiskRepo.fetchHideTargets().cache()
|
||||||
.map { it.exec().out }
|
|
||||||
.flattenAsFlowable { it }
|
|
||||||
.map { HideTarget(it) }
|
|
||||||
.toList()
|
|
||||||
.cache()
|
|
||||||
|
|
||||||
Single.fromCallable { packageManager.getInstalledApplications(0) }
|
magiskRepo.fetchApps()
|
||||||
.flattenAsFlowable { it }
|
.flattenAsFlowable { it }
|
||||||
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
|
||||||
.map {
|
|
||||||
val label = Utils.getAppLabel(it, packageManager)
|
|
||||||
val icon = it.loadIcon(packageManager)
|
|
||||||
HideAppInfo(it, label, icon)
|
|
||||||
}
|
|
||||||
.filter { it.processes.isNotEmpty() }
|
|
||||||
.map { HideRvItem(it, hideTargets.blockingGet()) }
|
.map { HideRvItem(it, hideTargets.blockingGet()) }
|
||||||
.toList()
|
.toList()
|
||||||
.map { it.sortWith(compareBy(
|
.map {
|
||||||
{it.isHiddenState.value}, {it.item.name}, {it.packageName})); it }
|
it.sortedWith(compareBy(
|
||||||
|
{ it.isHiddenState.value },
|
||||||
|
{ it.item.name.toLowerCase() },
|
||||||
|
{ it.packageName }
|
||||||
|
))
|
||||||
|
}
|
||||||
.doOnSuccess { allItems.update(it) }
|
.doOnSuccess { allItems.update(it) }
|
||||||
.flatMap { queryRaw() }
|
.flatMap { queryRaw() }
|
||||||
.applyViewModel(this)
|
.applyViewModel(this)
|
||||||
@@ -103,26 +90,9 @@ class HideViewModel(
|
|||||||
.toList()
|
.toList()
|
||||||
.map { it to items.calculateDiff(it) }
|
.map { it to items.calculateDiff(it) }
|
||||||
|
|
||||||
private fun toggleItem(item: HideProcessRvItem) {
|
private fun toggleItem(item: HideProcessRvItem) = magiskRepo
|
||||||
val state = if (item.isHidden.value) "add" else "rm"
|
.toggleHide(item.isHidden.value, item.packageName, item.process)
|
||||||
"magiskhide --%s %s %s".format(state, item.packageName, item.process)
|
.subscribeK()
|
||||||
.let { Shell.su(it) }
|
.add()
|
||||||
.toSingle()
|
|
||||||
.map { it.submit() }
|
|
||||||
.subscribeK()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val blacklist = listOf(
|
|
||||||
App.self.packageName,
|
|
||||||
"android",
|
|
||||||
"com.android.chrome",
|
|
||||||
"com.chrome.beta",
|
|
||||||
"com.chrome.dev",
|
|
||||||
"com.chrome.canary",
|
|
||||||
"com.android.webview",
|
|
||||||
"com.google.android.webview"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@@ -1,23 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.ui.home
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
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.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.model.events.*
|
import com.topjohnwu.magisk.model.events.*
|
||||||
import com.topjohnwu.magisk.model.observer.Observer
|
import com.topjohnwu.magisk.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.tasks.CheckUpdates
|
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
import com.topjohnwu.magisk.utils.*
|
import com.topjohnwu.magisk.utils.ISafetyNetHelper
|
||||||
import com.topjohnwu.net.Networking
|
import com.topjohnwu.magisk.utils.packageName
|
||||||
|
import com.topjohnwu.magisk.utils.res
|
||||||
|
import com.topjohnwu.magisk.utils.toggle
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
|
|
||||||
class HomeViewModel(
|
class HomeViewModel(
|
||||||
private val context: Context
|
private val magiskRepo: MagiskRepository
|
||||||
) : MagiskViewModel() {
|
) : MagiskViewModel() {
|
||||||
|
|
||||||
val isAdvancedExpanded = KObservableField(false)
|
val isAdvancedExpanded = KObservableField(false)
|
||||||
@@ -83,8 +86,6 @@ class HomeViewModel(
|
|||||||
private var shownDialog = false
|
private var shownDialog = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Event.register(this)
|
|
||||||
|
|
||||||
isForceEncryption.addOnPropertyChangedCallback {
|
isForceEncryption.addOnPropertyChangedCallback {
|
||||||
Config.keepEnc = it ?: return@addOnPropertyChangedCallback
|
Config.keepEnc = it ?: return@addOnPropertyChangedCallback
|
||||||
}
|
}
|
||||||
@@ -95,13 +96,6 @@ class HomeViewModel(
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: Int) {
|
|
||||||
updateSelf()
|
|
||||||
ensureEnv()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getListeningEvents(): IntArray = intArrayOf(Event.UPDATE_CHECK_DONE)
|
|
||||||
|
|
||||||
fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish()
|
fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish()
|
||||||
fun patreonPressed() = OpenLinkEvent(Const.Url.PATREON_URL).publish()
|
fun patreonPressed() = OpenLinkEvent(Const.Url.PATREON_URL).publish()
|
||||||
fun twitterPressed() = OpenLinkEvent(Const.Url.TWITTER_URL).publish()
|
fun twitterPressed() = OpenLinkEvent(Const.Url.TWITTER_URL).publish()
|
||||||
@@ -160,23 +154,29 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
state = State.LOADING
|
magiskRepo.fetchConfig()
|
||||||
magiskState.value = MagiskState.LOADING
|
.applyViewModel(this)
|
||||||
managerState.value = MagiskState.LOADING
|
.doOnSubscribeUi {
|
||||||
ctsState.value = SafetyNetState.IDLE
|
magiskState.value = MagiskState.LOADING
|
||||||
basicIntegrityState.value = SafetyNetState.IDLE
|
managerState.value = MagiskState.LOADING
|
||||||
safetyNetTitle.value = R.string.safetyNet_check_text
|
ctsState.value = SafetyNetState.IDLE
|
||||||
Event.reset(this)
|
basicIntegrityState.value = SafetyNetState.IDLE
|
||||||
Config.remoteMagiskVersionString = null
|
safetyNetTitle.value = R.string.safetyNet_check_text
|
||||||
Config.remoteMagiskVersionCode = -1
|
}
|
||||||
|
.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()
|
||||||
|
}
|
||||||
|
|
||||||
hasRoot.value = Shell.rootAccess()
|
hasRoot.value = Shell.rootAccess()
|
||||||
|
|
||||||
if (Networking.checkNetworkStatus(context)) {
|
|
||||||
CheckUpdates.check()
|
|
||||||
} else {
|
|
||||||
state = State.LOADING_FAILED
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelf() {
|
private fun updateSelf() {
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.ui.log
|
package com.topjohnwu.magisk.ui.log
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.databinding.ObservableArrayList
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
import com.skoumal.teanity.extensions.doOnSuccessUi
|
import com.skoumal.teanity.extensions.doOnSubscribeUi
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
@@ -12,14 +11,14 @@ import com.skoumal.teanity.viewevents.SnackbarEvent
|
|||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.*
|
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem
|
||||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
import com.topjohnwu.magisk.utils.toSingle
|
|
||||||
import com.topjohnwu.magisk.utils.zip
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Single
|
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
|
||||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -28,7 +27,7 @@ import java.util.*
|
|||||||
|
|
||||||
class LogViewModel(
|
class LogViewModel(
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val database: MagiskDB
|
private val logRepo: LogRepository
|
||||||
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
||||||
|
|
||||||
val items = DiffObservableList(ComparableRvItem.callback)
|
val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
@@ -58,9 +57,10 @@ class LogViewModel(
|
|||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() = zip(updateLogs(), updateMagiskLog()) { _, _ -> true }
|
fun refresh() {
|
||||||
.subscribeK()
|
fetchLogs().subscribeK { logItem.update(it) }
|
||||||
.add()
|
fetchMagiskLog().subscribeK { magiskLogItem.update(it) }
|
||||||
|
}
|
||||||
|
|
||||||
fun saveLog() {
|
fun saveLog() {
|
||||||
val now = Calendar.getInstance()
|
val now = Calendar.getInstance()
|
||||||
@@ -88,35 +88,25 @@ class LogViewModel(
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearLogs(callback: () -> Unit) {
|
private fun clearLogs(callback: () -> Unit) = logRepo.clearLogs()
|
||||||
Single.fromCallable { database.clearLogs() }
|
.doOnSubscribeUi(callback)
|
||||||
.subscribeK {
|
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
|
||||||
SnackbarEvent(R.string.logs_cleared).publish()
|
.add()
|
||||||
callback()
|
|
||||||
}
|
|
||||||
.add()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearMagiskLogs(callback: () -> Unit) {
|
private fun clearMagiskLogs(callback: () -> Unit) = logRepo.clearMagiskLogs()
|
||||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit {
|
.ignoreElement()
|
||||||
SnackbarEvent(R.string.logs_cleared).publish()
|
.doOnComplete(callback)
|
||||||
callback()
|
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
|
||||||
}
|
.add()
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateLogs() = Single.fromCallable { database.logs }
|
private fun fetchLogs() = logRepo.fetchLogs()
|
||||||
.flattenAsFlowable { it }
|
.flattenAsFlowable { it }
|
||||||
.map { it.map { LogItemEntryRvItem(it) } }
|
.map { LogItemRvItem(it) }
|
||||||
.map { LogItemRvItem(ObservableArrayList<ComparableRvItem<*>>().apply { addAll(it) }) }
|
|
||||||
.toList()
|
.toList()
|
||||||
.doOnSuccessUi { logItem.update(it) }
|
|
||||||
|
|
||||||
private fun updateMagiskLog() = Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").toSingle()
|
private fun fetchMagiskLog() = logRepo.fetchMagiskLogs()
|
||||||
.map { it.exec() }
|
|
||||||
.map { it.out }
|
|
||||||
.flattenAsFlowable { it }
|
.flattenAsFlowable { it }
|
||||||
.map { ConsoleRvItem(it) }
|
.map { ConsoleRvItem(it) }
|
||||||
.toList()
|
.toList()
|
||||||
.doOnSuccessUi { magiskLogItem.update(it) }
|
|
||||||
|
|
||||||
}
|
}
|
@@ -2,16 +2,15 @@ package com.topjohnwu.magisk.ui.module
|
|||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
|
import com.skoumal.teanity.extensions.doOnSuccessUi
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
||||||
import com.topjohnwu.magisk.model.entity.Module
|
|
||||||
import com.topjohnwu.magisk.model.entity.Repo
|
import com.topjohnwu.magisk.model.entity.Repo
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
|
||||||
@@ -21,7 +20,6 @@ import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
|||||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||||
import com.topjohnwu.magisk.tasks.UpdateRepos
|
import com.topjohnwu.magisk.tasks.UpdateRepos
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
import com.topjohnwu.magisk.utils.Event
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.toSingle
|
import com.topjohnwu.magisk.utils.toSingle
|
||||||
import com.topjohnwu.magisk.utils.update
|
import com.topjohnwu.magisk.utils.update
|
||||||
@@ -30,8 +28,9 @@ import io.reactivex.disposables.Disposable
|
|||||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
|
|
||||||
class ModuleViewModel(
|
class ModuleViewModel(
|
||||||
private val repoDatabase: RepoDatabaseHelper,
|
private val resources: Resources,
|
||||||
private val resources: Resources
|
private val repoDatabase: RepoDatabaseHelper,
|
||||||
|
private val repoUpdater: UpdateRepos
|
||||||
) : MagiskViewModel() {
|
) : MagiskViewModel() {
|
||||||
|
|
||||||
val query = KObservableField("")
|
val query = KObservableField("")
|
||||||
@@ -52,36 +51,23 @@ class ModuleViewModel(
|
|||||||
queryDisposable?.dispose()
|
queryDisposable?.dispose()
|
||||||
queryDisposable = query()
|
queryDisposable = query()
|
||||||
}
|
}
|
||||||
Event.register(this)
|
refresh(false)
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getListeningEvents(): IntArray {
|
|
||||||
return intArrayOf(Event.MODULE_LOAD_DONE, Event.REPO_LOAD_DONE)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEvent(event: Int) = when (event) {
|
|
||||||
Event.MODULE_LOAD_DONE -> updateModules(Event.getResult(event))
|
|
||||||
Event.REPO_LOAD_DONE -> updateRepos()
|
|
||||||
else -> Unit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fabPressed() = OpenFilePickerEvent().publish()
|
fun fabPressed() = OpenFilePickerEvent().publish()
|
||||||
fun repoPressed(item: RepoRvItem) = OpenChangelogEvent(item.item).publish()
|
fun repoPressed(item: RepoRvItem) = OpenChangelogEvent(item.item).publish()
|
||||||
fun downloadPressed(item: RepoRvItem) = InstallModuleEvent(item.item).publish()
|
fun downloadPressed(item: RepoRvItem) = InstallModuleEvent(item.item).publish()
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh(force: Boolean) {
|
||||||
state = State.LOADING
|
Single.fromCallable { Utils.loadModulesLeanback() }
|
||||||
Utils.loadModules(true)
|
.map { it.values.toList() }
|
||||||
UpdateRepos().exec(true)
|
.flattenAsFlowable { it }
|
||||||
}
|
.map { ModuleRvItem(it) }
|
||||||
|
.toList()
|
||||||
private fun updateModules(result: Map<String, Module>) = result.values
|
.map { it to itemsInstalled.calculateDiff(it) }
|
||||||
.map { ModuleRvItem(it) }
|
.doOnSuccessUi { itemsInstalled.update(it.first, it.second) }
|
||||||
.let { itemsInstalled.update(it) }
|
.flatMap { repoUpdater.exec(force) }
|
||||||
|
.flatMap { Single.fromCallable { repoDatabase.repoCursor.toList { Repo(it) } } }
|
||||||
internal fun updateRepos() {
|
|
||||||
Single.fromCallable { repoDatabase.repoCursor.toList { Repo(it) } }
|
|
||||||
.flattenAsFlowable { it }
|
.flattenAsFlowable { it }
|
||||||
.map { RepoRvItem(it) }
|
.map { RepoRvItem(it) }
|
||||||
.toList()
|
.toList()
|
||||||
@@ -89,7 +75,6 @@ class ModuleViewModel(
|
|||||||
.flatMap { queryRaw() }
|
.flatMap { queryRaw() }
|
||||||
.applyViewModel(this)
|
.applyViewModel(this)
|
||||||
.subscribeK { itemsRemote.update(it.first, it.second) }
|
.subscribeK { itemsRemote.update(it.first, it.second) }
|
||||||
.add()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun query() = queryRaw()
|
private fun query() = queryRaw()
|
||||||
@@ -109,27 +94,20 @@ class ModuleViewModel(
|
|||||||
|
|
||||||
private fun List<RepoRvItem>.divide(): List<ComparableRvItem<*>> {
|
private fun List<RepoRvItem>.divide(): List<ComparableRvItem<*>> {
|
||||||
val installed = itemsInstalled.filterIsInstance<ModuleRvItem>()
|
val installed = itemsInstalled.filterIsInstance<ModuleRvItem>()
|
||||||
val installedModules = filter { installed.any { item -> it.item.id == item.item.id } }
|
|
||||||
|
|
||||||
fun installedByID(id: String) = installed.firstOrNull { it.item.id == id }
|
fun <T : ComparableRvItem<*>> List<T>.withTitle(text: Int) =
|
||||||
|
if (isEmpty()) this else listOf(SectionRvItem(resources.getString(text))) + this
|
||||||
|
|
||||||
fun List<RepoRvItem>.filterObsolete() = filter {
|
val groupedItems = groupBy { repo ->
|
||||||
val module = installedByID(it.item.id) ?: return@filter false
|
installed.firstOrNull { it.item.id == repo.item.id }?.let {
|
||||||
module.item.versionCode != it.item.versionCode
|
if (it.item.versionCode < repo.item.versionCode) MODULE_UPDATABLE
|
||||||
|
else MODULE_INSTALLED
|
||||||
|
} ?: MODULE_REMOTE
|
||||||
}
|
}
|
||||||
|
|
||||||
val resultObsolete = installedModules.filterObsolete()
|
return groupedItems.getOrElse(MODULE_UPDATABLE) { listOf() }.withTitle(R.string.update_available) +
|
||||||
val resultInstalled = installedModules - resultObsolete
|
groupedItems.getOrElse(MODULE_INSTALLED) { listOf() }.withTitle(R.string.installed) +
|
||||||
val resultRemote = toList() - installedModules
|
groupedItems.getOrElse(MODULE_REMOTE) { listOf() }.withTitle(R.string.not_installed)
|
||||||
|
|
||||||
fun buildList(@StringRes text: Int, list: List<RepoRvItem>): List<ComparableRvItem<*>> {
|
|
||||||
return if (list.isEmpty()) list
|
|
||||||
else listOf(SectionRvItem(resources.getString(text))) + list
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildList(R.string.update_available, resultObsolete) +
|
|
||||||
buildList(R.string.installed, resultInstalled) +
|
|
||||||
buildList(R.string.not_installed, resultRemote)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
|
private fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
|
||||||
@@ -138,4 +116,10 @@ class ModuleViewModel(
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected const val MODULE_INSTALLED = 0
|
||||||
|
protected const val MODULE_REMOTE = 1
|
||||||
|
protected const val MODULE_UPDATABLE = 2
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -28,7 +28,7 @@ class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>(
|
|||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||||
// Get the URI of the selected file
|
// Get the URI of the selected file
|
||||||
val intent = Intent(activity, ClassMap.get<Any>(FlashActivity::class.java))
|
val intent = Intent(activity, ClassMap[FlashActivity::class.java])
|
||||||
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
|
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@@ -56,7 +56,7 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
|
|||||||
Config.get<Int>(Config.Key.REPO_ORDER)!!
|
Config.get<Int>(Config.Key.REPO_ORDER)!!
|
||||||
) { d, which ->
|
) { d, which ->
|
||||||
Config.set(Config.Key.REPO_ORDER, which)
|
Config.set(Config.Key.REPO_ORDER, which)
|
||||||
viewModel.updateRepos()
|
viewModel.refresh(false)
|
||||||
d.dismiss()
|
d.dismiss()
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
|
|||||||
|
|
||||||
fun download(install: Boolean) {
|
fun download(install: Boolean) {
|
||||||
context.runWithExternalRW {
|
context.runWithExternalRW {
|
||||||
val intent = Intent(activity, ClassMap.get<Any>(DownloadModuleService::class.java))
|
val intent = Intent(activity, ClassMap[DownloadModuleService::class.java])
|
||||||
.putExtra("repo", item).putExtra("install", install)
|
.putExtra("repo", item).putExtra("install", install)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(intent) //hmm, service starts itself in foreground, this seems unnecessary
|
context.startForegroundService(intent) //hmm, service starts itself in foreground, this seems unnecessary
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.ui.settings;
|
package com.topjohnwu.magisk.ui.settings;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -11,11 +12,11 @@ import android.widget.Toast;
|
|||||||
import com.topjohnwu.magisk.BuildConfig;
|
import com.topjohnwu.magisk.BuildConfig;
|
||||||
import com.topjohnwu.magisk.Config;
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
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.R;
|
||||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
|
||||||
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment;
|
import com.topjohnwu.magisk.ui.base.BasePreferenceFragment;
|
||||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||||
import com.topjohnwu.magisk.utils.Event;
|
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||||
@@ -26,7 +27,6 @@ import com.topjohnwu.superuser.Shell;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
@@ -34,26 +34,61 @@ import androidx.preference.Preference;
|
|||||||
import androidx.preference.PreferenceCategory;
|
import androidx.preference.PreferenceCategory;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
import androidx.preference.SwitchPreferenceCompat;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import kotlin.Pair;
|
||||||
|
|
||||||
public class SettingsFragment extends BasePreferenceFragment {
|
public final class SettingsFragment extends BasePreferenceFragment {
|
||||||
|
|
||||||
private ListPreference updateChannel, autoRes, suNotification,
|
private ListPreference updateChannel, autoRes, suNotification,
|
||||||
requestTimeout, rootConfig, multiuserConfig, nsConfig;
|
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
|
@Override
|
||||||
public void onStart() {
|
public final void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
requireActivity().setTitle(R.string.settings);
|
requireActivity().setTitle(R.string.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public final void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
getPreferenceManager().setStorageDeviceProtected();
|
getPreferenceManager().setStorageDeviceProtected();
|
||||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||||
|
|
||||||
boolean showSuperuser = Utils.showSuperUser();
|
boolean showSuperuser = Utils.showSuperUser();
|
||||||
app.getPrefs().edit()
|
getPrefs().edit()
|
||||||
.putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
.putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
@@ -73,14 +108,17 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
||||||
app.getPrefs().edit().remove(Config.Key.ETAG_KEY).apply();
|
getPrefs().edit().remove(Config.Key.ETAG_KEY).apply();
|
||||||
app.getRepoDB().clearRepo();
|
getRepoDatabase().clearRepo();
|
||||||
|
//getModuleRepo().deleteAllCached().subscribeOn(Schedulers.io()).subscribe(() -> {
|
||||||
|
//}, throwable -> {
|
||||||
|
//});
|
||||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
findPreference("hosts").setOnPreferenceClickListener(pref -> {
|
findPreference("hosts").setOnPreferenceClickListener(pref -> {
|
||||||
Shell.su("add_hosts_module").exec();
|
Shell.su("add_hosts_module").exec();
|
||||||
Utils.loadModules();
|
//Utils.loadModules();
|
||||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT);
|
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -96,26 +134,30 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
SwitchPreferenceCompat fingerprint = (SwitchPreferenceCompat) findPreference(Config.Key.SU_FINGERPRINT);
|
SwitchPreferenceCompat fingerprint = (SwitchPreferenceCompat) findPreference(Config.Key.SU_FINGERPRINT);
|
||||||
|
|
||||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||||
int prev = Config.get(Config.Key.UPDATE_CHANNEL);
|
|
||||||
int channel = Integer.parseInt((String) o);
|
int channel = Integer.parseInt((String) o);
|
||||||
if (channel == Config.Value.CUSTOM_CHANNEL) {
|
|
||||||
|
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);
|
View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||||
EditText url = v.findViewById(R.id.custom_url);
|
EditText url = v.findViewById(R.id.custom_url);
|
||||||
url.setText(app.getPrefs().getString(Config.Key.CUSTOM_CHANNEL, ""));
|
url.setText(KConfig.getCustomUpdateChannel());
|
||||||
new AlertDialog.Builder(requireActivity())
|
new AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.settings_update_custom)
|
.setTitle(R.string.settings_update_custom)
|
||||||
.setView(v)
|
.setView(v)
|
||||||
.setPositiveButton(R.string.ok, (d, i) ->
|
.setPositiveButton(R.string.ok, (d, i) -> setCustomUpdateChannel(url.getText().toString()))
|
||||||
Config.set(Config.Key.CUSTOM_CHANNEL, url.getText().toString()))
|
.setNegativeButton(R.string.close, (d, i) -> KConfig.setUpdateChannel(previousUpdateChannel))
|
||||||
.setNegativeButton(R.string.close, (d, i) ->
|
.setOnCancelListener(d -> KConfig.setUpdateChannel(previousUpdateChannel))
|
||||||
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
|
||||||
.setOnCancelListener(d ->
|
|
||||||
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE));
|
||||||
|
|
||||||
/* We only show canary channels if user is already on canary channel
|
/* We only show canary channels if user is already on canary channel
|
||||||
* or the user have already chosen canary channel */
|
* or the user have already chosen canary channel */
|
||||||
if (!Utils.isCanary() &&
|
if (!Utils.isCanary() &&
|
||||||
@@ -147,11 +189,12 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
||||||
if (app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
if (getApp().getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||||
generalCatagory.removePreference(restoreManager);
|
generalCatagory.removePreference(restoreManager);
|
||||||
} else {
|
} else {
|
||||||
if (!Networking.checkNetworkStatus(app))
|
if (!Networking.checkNetworkStatus(requireContext())) {
|
||||||
generalCatagory.removePreference(restoreManager);
|
generalCatagory.removePreference(restoreManager);
|
||||||
|
}
|
||||||
generalCatagory.removePreference(hideManager);
|
generalCatagory.removePreference(hideManager);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -169,28 +212,15 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLocalePreference(ListPreference lp) {
|
|
||||||
CharSequence[] entries = new CharSequence[LocaleManager.locales.size() + 1];
|
|
||||||
CharSequence[] entryValues = new CharSequence[LocaleManager.locales.size() + 1];
|
|
||||||
entries[0] = LocaleManager.getString(LocaleManager.defaultLocale, R.string.system_default);
|
|
||||||
entryValues[0] = "";
|
|
||||||
int i = 1;
|
|
||||||
for (Locale locale : LocaleManager.locales) {
|
|
||||||
entries[i] = locale.getDisplayName(locale);
|
|
||||||
entryValues[i++] = LocaleManager.toLanguageTag(locale);
|
|
||||||
}
|
|
||||||
lp.setEntries(entries);
|
|
||||||
lp.setEntryValues(entryValues);
|
|
||||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
public final void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Config.Key.ROOT_ACCESS:
|
case Config.Key.ROOT_ACCESS:
|
||||||
case Config.Key.SU_MULTIUSER_MODE:
|
case Config.Key.SU_MULTIUSER_MODE:
|
||||||
case Config.Key.SU_MNT_NS:
|
case Config.Key.SU_MNT_NS:
|
||||||
app.getDB().setSettings(key, Utils.getPrefsInt(prefs, key));
|
getSettingRepo().put(key, Utils.getPrefsInt(prefs, key))
|
||||||
|
.subscribe(() -> {
|
||||||
|
}, Throwable::printStackTrace);
|
||||||
break;
|
break;
|
||||||
case Config.Key.DARK_THEME:
|
case Config.Key.DARK_THEME:
|
||||||
requireActivity().recreate();
|
requireActivity().recreate();
|
||||||
@@ -199,7 +229,8 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
if (prefs.getBoolean(key, false)) {
|
if (prefs.getBoolean(key, false)) {
|
||||||
try {
|
try {
|
||||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||||
} catch (IOException ignored) {}
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Const.MAGISK_DISABLE_FILE.delete();
|
Const.MAGISK_DISABLE_FILE.delete();
|
||||||
}
|
}
|
||||||
@@ -213,12 +244,12 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Config.Key.LOCALE:
|
case Config.Key.LOCALE:
|
||||||
LocaleManager.setLocale(app);
|
LocaleManager.setLocale(getApp());
|
||||||
requireActivity().recreate();
|
requireActivity().recreate();
|
||||||
break;
|
break;
|
||||||
case Config.Key.UPDATE_CHANNEL:
|
case Config.Key.UPDATE_CHANNEL:
|
||||||
case Config.Key.CUSTOM_CHANNEL:
|
case Config.Key.CUSTOM_CHANNEL:
|
||||||
CheckUpdates.check();
|
//CheckUpdates.check();
|
||||||
break;
|
break;
|
||||||
case Config.Key.CHECK_UPDATES:
|
case Config.Key.CHECK_UPDATES:
|
||||||
Utils.scheduleUpdateCheck();
|
Utils.scheduleUpdateCheck();
|
||||||
@@ -228,7 +259,7 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceTreeClick(Preference preference) {
|
public final boolean onPreferenceTreeClick(Preference preference) {
|
||||||
String key = preference.getKey();
|
String key = preference.getKey();
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Config.Key.SU_FINGERPRINT:
|
case Config.Key.SU_FINGERPRINT:
|
||||||
@@ -253,27 +284,27 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
break;
|
break;
|
||||||
case Config.Key.ROOT_ACCESS:
|
case Config.Key.ROOT_ACCESS:
|
||||||
rootConfig.setSummary(getResources()
|
rootConfig.setSummary(getResources()
|
||||||
.getStringArray(R.array.su_access)[(int)Config.get(key)]);
|
.getStringArray(R.array.su_access)[(int) Config.get(key)]);
|
||||||
break;
|
break;
|
||||||
case Config.Key.SU_AUTO_RESPONSE:
|
case Config.Key.SU_AUTO_RESPONSE:
|
||||||
autoRes.setSummary(getResources()
|
autoRes.setSummary(getResources()
|
||||||
.getStringArray(R.array.auto_response)[(int)Config.get(key)]);
|
.getStringArray(R.array.auto_response)[(int) Config.get(key)]);
|
||||||
break;
|
break;
|
||||||
case Config.Key.SU_NOTIFICATION:
|
case Config.Key.SU_NOTIFICATION:
|
||||||
suNotification.setSummary(getResources()
|
suNotification.setSummary(getResources()
|
||||||
.getStringArray(R.array.su_notification)[(int)Config.get(key)]);
|
.getStringArray(R.array.su_notification)[(int) Config.get(key)]);
|
||||||
break;
|
break;
|
||||||
case Config.Key.SU_REQUEST_TIMEOUT:
|
case Config.Key.SU_REQUEST_TIMEOUT:
|
||||||
requestTimeout.setSummary(
|
requestTimeout.setSummary(
|
||||||
getString(R.string.request_timeout_summary, (int)Config.get(key)));
|
getString(R.string.request_timeout_summary, (int) Config.get(key)));
|
||||||
break;
|
break;
|
||||||
case Config.Key.SU_MULTIUSER_MODE:
|
case Config.Key.SU_MULTIUSER_MODE:
|
||||||
multiuserConfig.setSummary(getResources()
|
multiuserConfig.setSummary(getResources()
|
||||||
.getStringArray(R.array.multiuser_summary)[(int)Config.get(key)]);
|
.getStringArray(R.array.multiuser_summary)[(int) Config.get(key)]);
|
||||||
break;
|
break;
|
||||||
case Config.Key.SU_MNT_NS:
|
case Config.Key.SU_MNT_NS:
|
||||||
nsConfig.setSummary(getResources()
|
nsConfig.setSummary(getResources()
|
||||||
.getStringArray(R.array.namespace_summary)[(int)Config.get(key)]);
|
.getStringArray(R.array.namespace_summary)[(int) Config.get(key)]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,14 +318,4 @@ public class SettingsFragment extends BasePreferenceFragment {
|
|||||||
setSummary(Config.Key.SU_MULTIUSER_MODE);
|
setSummary(Config.Key.SU_MULTIUSER_MODE);
|
||||||
setSummary(Config.Key.SU_MNT_NS);
|
setSummary(Config.Key.SU_MNT_NS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEvent(int event) {
|
|
||||||
setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] getListeningEvents() {
|
|
||||||
return new int[] {Event.LOCALE_FETCH_DONE};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,8 @@ import com.skoumal.teanity.util.DiffObservableList
|
|||||||
import com.skoumal.teanity.viewevents.SnackbarEvent
|
import com.skoumal.teanity.viewevents.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import com.topjohnwu.magisk.data.repository.AppRepository
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.Policy
|
import com.topjohnwu.magisk.model.entity.Policy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||||
@@ -24,7 +25,7 @@ import io.reactivex.Single
|
|||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
|
||||||
class SuperuserViewModel(
|
class SuperuserViewModel(
|
||||||
private val database: MagiskDB,
|
private val appRepo: AppRepository,
|
||||||
private val packageManager: PackageManager,
|
private val packageManager: PackageManager,
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
rxBus: RxBus
|
rxBus: RxBus
|
||||||
@@ -50,9 +51,9 @@ class SuperuserViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updatePolicies() {
|
fun updatePolicies() {
|
||||||
Single.fromCallable { database.policyList }
|
appRepo.fetchAll()
|
||||||
.flattenAsFlowable { it }
|
.flattenAsFlowable { it }
|
||||||
.map { PolicyRvItem(it, it.info.loadIcon(packageManager)) }
|
.map { PolicyRvItem(it, it.applicationInfo.loadIcon(packageManager)) }
|
||||||
.toList()
|
.toList()
|
||||||
.applySchedulers()
|
.applySchedulers()
|
||||||
.applyViewModel(this)
|
.applyViewModel(this)
|
||||||
@@ -85,28 +86,29 @@ class SuperuserViewModel(
|
|||||||
|
|
||||||
private fun updatePolicy(it: PolicyUpdateEvent) = when (it) {
|
private fun updatePolicy(it: PolicyUpdateEvent) = when (it) {
|
||||||
is PolicyUpdateEvent.Notification -> updatePolicy(it.item) {
|
is PolicyUpdateEvent.Notification -> updatePolicy(it.item) {
|
||||||
val textId = if (it.logging) R.string.su_snack_notif_on else R.string.su_snack_notif_off
|
val textId =
|
||||||
|
if (it.notification) R.string.su_snack_notif_on else R.string.su_snack_notif_off
|
||||||
val text = resources.getString(textId).format(it.appName)
|
val text = resources.getString(textId).format(it.appName)
|
||||||
SnackbarEvent(text).publish()
|
SnackbarEvent(text).publish()
|
||||||
}
|
}
|
||||||
is PolicyUpdateEvent.Log -> updatePolicy(it.item) {
|
is PolicyUpdateEvent.Log -> updatePolicy(it.item) {
|
||||||
val textId =
|
val textId =
|
||||||
if (it.notification) R.string.su_snack_log_on else R.string.su_snack_log_off
|
if (it.logging) R.string.su_snack_log_on else R.string.su_snack_log_off
|
||||||
val text = resources.getString(textId).format(it.appName)
|
val text = resources.getString(textId).format(it.appName)
|
||||||
SnackbarEvent(text).publish()
|
SnackbarEvent(text).publish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePolicy(item: PolicyRvItem, onSuccess: (Policy) -> Unit) =
|
private fun updatePolicy(item: MagiskPolicy, onSuccess: (MagiskPolicy) -> Unit) =
|
||||||
updatePolicy(item.item)
|
updatePolicy(item)
|
||||||
.subscribeK { onSuccess(it) }
|
.subscribeK { onSuccess(it) }
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
private fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
private fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||||
fun updateState() {
|
fun updateState() {
|
||||||
item.item.policy = if (enable) Policy.ALLOW else Policy.DENY
|
val app = item.item.copy(policy = if (enable) MagiskPolicy.ALLOW else MagiskPolicy.DENY)
|
||||||
|
|
||||||
updatePolicy(item.item)
|
updatePolicy(app)
|
||||||
.map { it.policy == Policy.ALLOW }
|
.map { it.policy == Policy.ALLOW }
|
||||||
.subscribeK {
|
.subscribeK {
|
||||||
val textId = if (it) R.string.su_snack_grant else R.string.su_snack_deny
|
val textId = if (it) R.string.su_snack_grant else R.string.su_snack_deny
|
||||||
@@ -128,12 +130,10 @@ class SuperuserViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePolicy(policy: Policy) =
|
private fun updatePolicy(policy: MagiskPolicy) =
|
||||||
Single.fromCallable { database.updatePolicy(policy); policy }
|
appRepo.update(policy).andThen(Single.just(policy))
|
||||||
.applySchedulers()
|
|
||||||
|
|
||||||
private fun deletePolicy(policy: Policy) =
|
private fun deletePolicy(policy: MagiskPolicy) =
|
||||||
Single.fromCallable { database.deletePolicy(policy); policy }
|
appRepo.delete(policy.uid).andThen(Single.just(policy))
|
||||||
.applySchedulers()
|
|
||||||
|
|
||||||
}
|
}
|
@@ -33,7 +33,7 @@ open class SuRequestActivity : MagiskActivity<SuRequestViewModel, ActivityReques
|
|||||||
val action = intent.action
|
val action = intent.action
|
||||||
|
|
||||||
if (TextUtils.equals(action, GeneralReceiver.REQUEST)) {
|
if (TextUtils.equals(action, GeneralReceiver.REQUEST)) {
|
||||||
if (!viewModel.handleRequest(intent) {})
|
if (!viewModel.handleRequest(intent))
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -10,26 +10,31 @@ import android.hardware.fingerprint.FingerprintManager
|
|||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import com.topjohnwu.magisk.data.repository.AppRepository
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.Policy
|
import com.topjohnwu.magisk.model.entity.Policy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
import com.topjohnwu.magisk.utils.FingerprintHelper
|
||||||
import com.topjohnwu.magisk.utils.SuConnector
|
import com.topjohnwu.magisk.utils.SuConnector
|
||||||
import com.topjohnwu.magisk.utils.now
|
import com.topjohnwu.magisk.utils.now
|
||||||
|
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit.*
|
import java.util.concurrent.TimeUnit.*
|
||||||
|
|
||||||
class SuRequestViewModel(
|
class SuRequestViewModel(
|
||||||
private val packageManager: PackageManager,
|
private val packageManager: PackageManager,
|
||||||
private val database: MagiskDB,
|
private val appRepo: AppRepository,
|
||||||
private val timeoutPrefs: SharedPreferences,
|
private val timeoutPrefs: SharedPreferences,
|
||||||
private val resources: Resources
|
private val resources: Resources
|
||||||
) : MagiskViewModel() {
|
) : MagiskViewModel() {
|
||||||
@@ -44,15 +49,20 @@ class SuRequestViewModel(
|
|||||||
val canUseFingerprint = KObservableField(FingerprintHelper.useFingerprint())
|
val canUseFingerprint = KObservableField(FingerprintHelper.useFingerprint())
|
||||||
val selectedItemPosition = KObservableField(0)
|
val selectedItemPosition = KObservableField(0)
|
||||||
|
|
||||||
val items = DiffObservableList(ComparableRvItem.callback)
|
private val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { binding, _, item ->
|
private val itemBinding = ItemBinding.of<ComparableRvItem<*>> { binding, _, item ->
|
||||||
item.bind(binding)
|
item.bind(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val adapter = BindingListViewAdapter<ComparableRvItem<*>>(1).apply {
|
||||||
|
itemBinding = this@SuRequestViewModel.itemBinding
|
||||||
|
setItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var handler: ActionHandler? = null
|
var handler: ActionHandler? = null
|
||||||
private var timer: CountDownTimer? = null
|
private var timer: CountDownTimer? = null
|
||||||
private var policy: Policy? = null
|
private var policy: MagiskPolicy? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
updatePolicy(value)
|
updatePolicy(value)
|
||||||
@@ -62,12 +72,16 @@ class SuRequestViewModel(
|
|||||||
resources.getStringArray(R.array.allow_timeout)
|
resources.getStringArray(R.array.allow_timeout)
|
||||||
.map { SpinnerRvItem(it) }
|
.map { SpinnerRvItem(it) }
|
||||||
.let { items.update(it) }
|
.let { items.update(it) }
|
||||||
|
|
||||||
|
selectedItemPosition.addOnPropertyChangedCallback {
|
||||||
|
Timber.e("Changed position to $it")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePolicy(policy: Policy?) {
|
private fun updatePolicy(policy: MagiskPolicy?) {
|
||||||
policy ?: return
|
policy ?: return
|
||||||
|
|
||||||
icon.value = policy.info.loadIcon(packageManager)
|
icon.value = policy.applicationInfo.loadIcon(packageManager)
|
||||||
title.value = policy.appName
|
title.value = policy.appName
|
||||||
packageName.value = policy.packageName
|
packageName.value = policy.packageName
|
||||||
|
|
||||||
@@ -94,7 +108,7 @@ class SuRequestViewModel(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleRequest(intent: Intent, createUICallback: () -> Unit): Boolean {
|
fun handleRequest(intent: Intent): Boolean {
|
||||||
val socketName = intent.getStringExtra("socket") ?: return false
|
val socketName = intent.getStringExtra("socket") ?: return false
|
||||||
|
|
||||||
val connector: SuConnector
|
val connector: SuConnector
|
||||||
@@ -107,8 +121,9 @@ class SuRequestViewModel(
|
|||||||
}
|
}
|
||||||
val bundle = connector.readSocketInput()
|
val bundle = connector.readSocketInput()
|
||||||
val uid = bundle.getString("uid")?.toIntOrNull() ?: return false
|
val uid = bundle.getString("uid")?.toIntOrNull() ?: return false
|
||||||
database.clearOutdated()
|
appRepo.deleteOutdated().blockingGet() // wrong!
|
||||||
policy = database.getPolicy(uid) ?: Policy(uid, packageManager)
|
policy = runCatching { appRepo.fetch(uid).blockingGet() }
|
||||||
|
.getOrDefault(uid.toPolicy(packageManager))
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
@@ -123,25 +138,26 @@ class SuRequestViewModel(
|
|||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ApplySharedPref")
|
||||||
override fun handleAction(action: Int) {
|
override fun handleAction(action: Int) {
|
||||||
val pos = selectedItemPosition.value
|
val pos = selectedItemPosition.value
|
||||||
timeoutPrefs.edit().putInt(policy?.packageName, pos).apply()
|
timeoutPrefs.edit().putInt(policy?.packageName, pos).commit()
|
||||||
handleAction(action, Config.Value.TIMEOUT_LIST[pos])
|
handleAction(action, Config.Value.TIMEOUT_LIST[pos])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: Int, time: Int) {
|
override fun handleAction(action: Int, time: Int) {
|
||||||
policy?.apply {
|
val until = if (time >= 0) {
|
||||||
policy = action
|
if (time == 0) {
|
||||||
if (time >= 0) {
|
0
|
||||||
until = if (time == 0) {
|
} else {
|
||||||
0
|
MILLISECONDS.toSeconds(now) + MINUTES.toSeconds(time.toLong())
|
||||||
} else {
|
|
||||||
MILLISECONDS.toSeconds(now) + MINUTES.toSeconds(time.toLong())
|
|
||||||
}
|
|
||||||
database.updatePolicy(this)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
policy?.until ?: 0
|
||||||
|
}
|
||||||
|
policy = policy?.copy(policy = action, until = until)?.apply {
|
||||||
|
appRepo.update(this).blockingGet()
|
||||||
}
|
}
|
||||||
policy?.policy = action
|
|
||||||
|
|
||||||
handleAction()
|
handleAction()
|
||||||
}
|
}
|
||||||
@@ -157,7 +173,7 @@ class SuRequestViewModel(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
when (Config.get<Any>(Config.Key.SU_AUTO_RESPONSE) as Int) {
|
when (Config.get<Int>(Config.Key.SU_AUTO_RESPONSE)) {
|
||||||
Config.Value.SU_AUTO_DENY -> {
|
Config.Value.SU_AUTO_DENY -> {
|
||||||
handler?.handleAction(Policy.DENY, 0)
|
handler?.handleAction(Policy.DENY, 0)
|
||||||
return true
|
return true
|
||||||
@@ -168,7 +184,6 @@ class SuRequestViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createUICallback()
|
|
||||||
showUI()
|
showUI()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
|
||||||
import android.widget.Spinner
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
@@ -115,7 +113,7 @@ fun setMovieBehavior(view: TextView, isMovieBehavior: Boolean, text: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("android:selectedItemPosition")
|
/*@BindingAdapter("selection"*//*, "selectionAttrChanged", "adapter"*//*)
|
||||||
fun setSelectedItemPosition(view: Spinner, position: Int) {
|
fun setSelectedItemPosition(view: Spinner, position: Int) {
|
||||||
view.setSelection(position)
|
view.setSelection(position)
|
||||||
}
|
}
|
||||||
@@ -126,7 +124,7 @@ fun setSelectedItemPosition(view: Spinner, position: Int) {
|
|||||||
)
|
)
|
||||||
fun getSelectedItemPosition(view: Spinner) = view.selectedItemPosition
|
fun getSelectedItemPosition(view: Spinner) = view.selectedItemPosition
|
||||||
|
|
||||||
@BindingAdapter("android:selectedItemPositionAttrChanged")
|
@BindingAdapter("selectedItemPositionAttrChanged")
|
||||||
fun setSelectedItemPositionListener(view: Spinner, listener: InverseBindingListener) {
|
fun setSelectedItemPositionListener(view: Spinner, listener: InverseBindingListener) {
|
||||||
view.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
view.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onNothingSelected(p0: AdapterView<*>?) {
|
override fun onNothingSelected(p0: AdapterView<*>?) {
|
||||||
@@ -137,7 +135,7 @@ fun setSelectedItemPositionListener(view: Spinner, listener: InverseBindingListe
|
|||||||
listener.onChange()
|
listener.onChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
@BindingAdapter("onTouch")
|
@BindingAdapter("onTouch")
|
||||||
fun setOnTouchListener(view: View, listener: View.OnTouchListener) {
|
fun setOnTouchListener(view: View, listener: View.OnTouchListener) {
|
||||||
@@ -155,8 +153,6 @@ fun setScrollToLast(view: RecyclerView, shouldScrollToLast: Boolean) {
|
|||||||
Observable.timer(1, TimeUnit.SECONDS).subscribeK { callback() }
|
Observable.timer(1, TimeUnit.SECONDS).subscribeK { callback() }
|
||||||
}
|
}
|
||||||
|
|
||||||
val tag = RecyclerView::class.java.name.sumBy { it.toInt() }
|
|
||||||
|
|
||||||
fun RecyclerView.Adapter<*>.setListener() {
|
fun RecyclerView.Adapter<*>.setListener() {
|
||||||
val observer = object : RecyclerView.AdapterDataObserver() {
|
val observer = object : RecyclerView.AdapterDataObserver() {
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
@@ -164,11 +160,12 @@ fun setScrollToLast(view: RecyclerView, shouldScrollToLast: Boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
registerAdapterDataObserver(observer)
|
registerAdapterDataObserver(observer)
|
||||||
view.setTag(tag, observer)
|
view.setTag(R.id.recyclerScrollListener, observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RecyclerView.Adapter<*>.removeListener() {
|
fun RecyclerView.Adapter<*>.removeListener() {
|
||||||
val observer = view.getTag(tag) as? RecyclerView.AdapterDataObserver ?: return
|
val observer =
|
||||||
|
view.getTag(R.id.recyclerScrollListener) as? RecyclerView.AdapterDataObserver ?: return
|
||||||
unregisterAdapterDataObserver(observer)
|
unregisterAdapterDataObserver(observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,123 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.collection.ArraySet;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class Event {
|
|
||||||
|
|
||||||
public static final int MAGISK_HIDE_DONE = 0;
|
|
||||||
public static final int MODULE_LOAD_DONE = 1;
|
|
||||||
public static final int REPO_LOAD_DONE = 2;
|
|
||||||
public static final int UPDATE_CHECK_DONE = 3;
|
|
||||||
public static final int LOCALE_FETCH_DONE = 4;
|
|
||||||
|
|
||||||
@IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
|
||||||
UPDATE_CHECK_DONE, LOCALE_FETCH_DONE})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface EventID {}
|
|
||||||
|
|
||||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
|
||||||
private static Store[] eventList = new Store[5];
|
|
||||||
|
|
||||||
public static void register(Listener listener, @EventID int... events) {
|
|
||||||
for (int event : events) {
|
|
||||||
if (eventList[event] == null)
|
|
||||||
eventList[event] = new Store();
|
|
||||||
eventList[event].listeners.add(listener);
|
|
||||||
if (eventList[event].triggered) {
|
|
||||||
listener.onEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void register(AutoListener listener) {
|
|
||||||
register(listener, listener.getListeningEvents());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unregister(Listener listener, @EventID int... events) {
|
|
||||||
for (int event : events) {
|
|
||||||
if (eventList[event] == null)
|
|
||||||
continue;
|
|
||||||
eventList[event].listeners.remove(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unregister(AutoListener listener) {
|
|
||||||
unregister(listener, listener.getListeningEvents());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void trigger(@EventID int event) {
|
|
||||||
trigger(true, event, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void trigger(@EventID int event, Object result) {
|
|
||||||
trigger(true, event, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void trigger(boolean perm, @EventID int event) {
|
|
||||||
trigger(perm, event, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void trigger(boolean perm, @EventID int event, Object result) {
|
|
||||||
if (eventList[event] == null)
|
|
||||||
eventList[event] = new Store();
|
|
||||||
if (perm) {
|
|
||||||
eventList[event].result = result;
|
|
||||||
eventList[event].triggered = true;
|
|
||||||
}
|
|
||||||
for (Listener sub : eventList[event].listeners) {
|
|
||||||
UiThreadHandler.run(() -> sub.onEvent(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reset(@EventID int event) {
|
|
||||||
if (eventList[event] == null)
|
|
||||||
return;
|
|
||||||
eventList[event].triggered = false;
|
|
||||||
eventList[event].result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reset(AutoListener listener) {
|
|
||||||
for (int event : listener.getListeningEvents())
|
|
||||||
reset(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTriggered(@EventID int event) {
|
|
||||||
if (eventList[event] == null)
|
|
||||||
return false;
|
|
||||||
return eventList[event].triggered;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTriggered(AutoListener listener) {
|
|
||||||
for (int event : listener.getListeningEvents()) {
|
|
||||||
if (!isTriggered(event))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T getResult(@EventID int event) {
|
|
||||||
return (T) eventList[event].result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Store {
|
|
||||||
boolean triggered = false;
|
|
||||||
Set<Listener> listeners = new ArraySet<>();
|
|
||||||
Object result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void onEvent(int event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AutoListener extends Listener {
|
|
||||||
@EventID
|
|
||||||
int[] getListeningEvents();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,147 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.ContextWrapper;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
import com.topjohnwu.superuser.internal.InternalUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class LocaleManager {
|
|
||||||
public static Locale locale = Locale.getDefault();
|
|
||||||
public final static Locale defaultLocale = Locale.getDefault();
|
|
||||||
public static List<Locale> locales;
|
|
||||||
|
|
||||||
public static Locale forLanguageTag(String tag) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
return Locale.forLanguageTag(tag);
|
|
||||||
} else {
|
|
||||||
String[] tok = tag.split("-");
|
|
||||||
if (tok.length == 0) {
|
|
||||||
return new Locale("");
|
|
||||||
}
|
|
||||||
String language;
|
|
||||||
switch (tok[0]) {
|
|
||||||
case "und":
|
|
||||||
language = ""; // Undefined
|
|
||||||
break;
|
|
||||||
case "fil":
|
|
||||||
language = "tl"; // Filipino
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
language = tok[0];
|
|
||||||
}
|
|
||||||
if ((language.length() != 2 && language.length() != 3))
|
|
||||||
return new Locale("");
|
|
||||||
if (tok.length == 1)
|
|
||||||
return new Locale(language);
|
|
||||||
String country = tok[1];
|
|
||||||
if (country.length() != 2 && country.length() != 3)
|
|
||||||
return new Locale(language);
|
|
||||||
return new Locale(language, country);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toLanguageTag(Locale loc) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
return loc.toLanguageTag();
|
|
||||||
} else {
|
|
||||||
String language = loc.getLanguage();
|
|
||||||
String country = loc.getCountry();
|
|
||||||
String variant = loc.getVariant();
|
|
||||||
if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) {
|
|
||||||
language = "und"; // Follow the Locale#toLanguageTag() implementation
|
|
||||||
} else if (language.equals("iw")) {
|
|
||||||
language = "he"; // correct deprecated "Hebrew"
|
|
||||||
} else if (language.equals("in")) {
|
|
||||||
language = "id"; // correct deprecated "Indonesian"
|
|
||||||
} else if (language.equals("ji")) {
|
|
||||||
language = "yi"; // correct deprecated "Yiddish"
|
|
||||||
}
|
|
||||||
// ensure valid country code, if not well formed, it's omitted
|
|
||||||
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) {
|
|
||||||
country = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// variant subtags that begin with a letter must be at least 5 characters long
|
|
||||||
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) {
|
|
||||||
variant = "";
|
|
||||||
}
|
|
||||||
StringBuilder tag = new StringBuilder(language);
|
|
||||||
if (!country.isEmpty())
|
|
||||||
tag.append('-').append(country);
|
|
||||||
if (!variant.isEmpty())
|
|
||||||
tag.append('-').append(variant);
|
|
||||||
return tag.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setLocale(ContextWrapper wrapper) {
|
|
||||||
String localeConfig = Config.get(Config.Key.LOCALE);
|
|
||||||
if (TextUtils.isEmpty(localeConfig)) {
|
|
||||||
locale = defaultLocale;
|
|
||||||
} else {
|
|
||||||
locale = forLanguageTag(localeConfig);
|
|
||||||
}
|
|
||||||
Locale.setDefault(locale);
|
|
||||||
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Context getLocaleContext(Context context, Locale locale) {
|
|
||||||
Configuration config = new Configuration(context.getResources().getConfiguration());
|
|
||||||
config.setLocale(locale);
|
|
||||||
return context.createConfigurationContext(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Context getLocaleContext(Locale locale) {
|
|
||||||
return getLocaleContext(App.self.getBaseContext(), locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getString(Locale locale, @StringRes int id) {
|
|
||||||
return getLocaleContext(locale).getString(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadAvailableLocales(@StringRes int compareId) {
|
|
||||||
Shell.EXECUTOR.execute(() -> {
|
|
||||||
locales = new ArrayList<>();
|
|
||||||
HashSet<String> set = new HashSet<>();
|
|
||||||
Resources res = App.self.getResources();
|
|
||||||
Locale locale;
|
|
||||||
|
|
||||||
// Add default locale
|
|
||||||
locales.add(Locale.ENGLISH);
|
|
||||||
set.add(getString(Locale.ENGLISH, compareId));
|
|
||||||
|
|
||||||
// Add some special locales
|
|
||||||
locales.add(Locale.TAIWAN);
|
|
||||||
set.add(getString(Locale.TAIWAN, compareId));
|
|
||||||
locale = new Locale("pt", "BR");
|
|
||||||
locales.add(locale);
|
|
||||||
set.add(getString(locale, compareId));
|
|
||||||
|
|
||||||
// Other locales
|
|
||||||
for (String s : res.getAssets().getLocales()) {
|
|
||||||
locale = forLanguageTag(s);
|
|
||||||
if (set.add(getString(locale, compareId))) {
|
|
||||||
locales.add(locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(locales, (a, b) -> a.getDisplayName(a).compareTo(b.getDisplayName(b)));
|
|
||||||
Event.trigger(Event.LOCALE_FETCH_DONE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
135
app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt
Normal file
135
app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.topjohnwu.magisk.App
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.superuser.internal.InternalUtils
|
||||||
|
import io.reactivex.Single
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object LocaleManager {
|
||||||
|
@JvmStatic
|
||||||
|
var locale = Locale.getDefault()
|
||||||
|
@JvmStatic
|
||||||
|
val defaultLocale = Locale.getDefault()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val availableLocales = Single.fromCallable {
|
||||||
|
val compareId = R.string.app_changelog
|
||||||
|
val res: Resources by inject()
|
||||||
|
mutableListOf<Locale>().apply {
|
||||||
|
// Add default locale
|
||||||
|
add(Locale.ENGLISH)
|
||||||
|
|
||||||
|
// Add some special locales
|
||||||
|
add(Locale.TAIWAN)
|
||||||
|
add(Locale("pt", "BR"))
|
||||||
|
|
||||||
|
// Other locales
|
||||||
|
val otherLocales = res.assets.locales
|
||||||
|
.map { forLanguageTag(it) }
|
||||||
|
.distinctBy { getString(it, compareId) }
|
||||||
|
|
||||||
|
listOf("", "").toTypedArray()
|
||||||
|
|
||||||
|
addAll(otherLocales)
|
||||||
|
}.sortedWith(Comparator { a, b ->
|
||||||
|
a.getDisplayName(a).toLowerCase(a)
|
||||||
|
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
||||||
|
})
|
||||||
|
}.cache()
|
||||||
|
|
||||||
|
private fun forLanguageTag(tag: String): Locale {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
return Locale.forLanguageTag(tag)
|
||||||
|
} else {
|
||||||
|
val tok = tag.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
if (tok.isEmpty()) {
|
||||||
|
return Locale("")
|
||||||
|
}
|
||||||
|
val language = when (tok[0]) {
|
||||||
|
"und" -> "" // Undefined
|
||||||
|
"fil" -> "tl" // Filipino
|
||||||
|
else -> tok[0]
|
||||||
|
}
|
||||||
|
if (language.length != 2 && language.length != 3)
|
||||||
|
return Locale("")
|
||||||
|
if (tok.size == 1)
|
||||||
|
return Locale(language)
|
||||||
|
val country = tok[1]
|
||||||
|
|
||||||
|
return if (country.length != 2 && country.length != 3) Locale(language)
|
||||||
|
else Locale(language, country)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun toLanguageTag(loc: Locale): String {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
return loc.toLanguageTag()
|
||||||
|
} else {
|
||||||
|
var language = loc.language
|
||||||
|
var country = loc.country
|
||||||
|
var variant = loc.variant
|
||||||
|
when {
|
||||||
|
language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) ->
|
||||||
|
language = "und" // Follow the Locale#toLanguageTag() implementation
|
||||||
|
language == "iw" -> language = "he" // correct deprecated "Hebrew"
|
||||||
|
language == "in" -> language = "id" // correct deprecated "Indonesian"
|
||||||
|
language == "ji" -> language = "yi" // correct deprecated "Yiddish"
|
||||||
|
}
|
||||||
|
// ensure valid country code, if not well formed, it's omitted
|
||||||
|
|
||||||
|
// variant subtags that begin with a letter must be at least 5 characters long
|
||||||
|
// ensure valid country code, if not well formed, it's omitted
|
||||||
|
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) {
|
||||||
|
country = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// variant subtags that begin with a letter must be at least 5 characters long
|
||||||
|
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) {
|
||||||
|
variant = ""
|
||||||
|
}
|
||||||
|
val tag = StringBuilder(language)
|
||||||
|
if (country.isNotEmpty())
|
||||||
|
tag.append('-').append(country)
|
||||||
|
if (variant.isNotEmpty())
|
||||||
|
tag.append('-').append(variant)
|
||||||
|
return tag.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setLocale(wrapper: ContextWrapper) {
|
||||||
|
val localeConfig = Config.get<String>(Config.Key.LOCALE)
|
||||||
|
locale = when {
|
||||||
|
localeConfig.isNullOrEmpty() -> defaultLocale
|
||||||
|
else -> forLanguageTag(localeConfig)
|
||||||
|
}
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale))
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getLocaleContext(context: Context, locale: Locale): Context {
|
||||||
|
val config = Configuration(context.resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
return context.createConfigurationContext(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getLocaleContext(locale: Locale): Context {
|
||||||
|
return getLocaleContext(App.self.baseContext, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getString(locale: Locale, @StringRes id: Int): String {
|
||||||
|
return getLocaleContext(locale).getString(id)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,88 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
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.model.entity.Policy;
|
|
||||||
import com.topjohnwu.magisk.model.entity.SuLogEntry;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class SuLogger {
|
|
||||||
|
|
||||||
public static void handleLogs(Intent intent) {
|
|
||||||
|
|
||||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
|
||||||
if (fromUid < 0) return;
|
|
||||||
if (fromUid == Process.myUid()) return;
|
|
||||||
|
|
||||||
App app = App.self;
|
|
||||||
PackageManager pm = app.getPackageManager();
|
|
||||||
Policy policy;
|
|
||||||
|
|
||||||
boolean notify;
|
|
||||||
Bundle data = intent.getExtras();
|
|
||||||
if (data.containsKey("notify")) {
|
|
||||||
notify = data.getBoolean("notify");
|
|
||||||
try {
|
|
||||||
policy = new Policy(fromUid, pm);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Doesn't report whether notify or not, check database ourselves
|
|
||||||
policy = app.getDB().getPolicy(fromUid);
|
|
||||||
if (policy == null)
|
|
||||||
return;
|
|
||||||
notify = policy.notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
policy.policy = data.getInt("policy", -1);
|
|
||||||
if (policy.policy < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (notify)
|
|
||||||
handleNotify(policy);
|
|
||||||
|
|
||||||
SuLogEntry log = new SuLogEntry(policy);
|
|
||||||
|
|
||||||
int toUid = intent.getIntExtra("to.uid", -1);
|
|
||||||
if (toUid < 0) return;
|
|
||||||
int pid = intent.getIntExtra("pid", -1);
|
|
||||||
if (pid < 0) return;
|
|
||||||
String command = intent.getStringExtra("command");
|
|
||||||
if (command == null) return;
|
|
||||||
log.toUid = toUid;
|
|
||||||
log.fromPid = pid;
|
|
||||||
log.command = command;
|
|
||||||
log.date = new Date();
|
|
||||||
app.getDB().addLog(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void handleNotify(Policy policy) {
|
|
||||||
if (policy.notification &&
|
|
||||||
(int) Config.get(Config.Key.SU_NOTIFICATION) == Config.Value.NOTIFICATION_TOAST) {
|
|
||||||
Utils.toast(App.self.getString(policy.policy == Policy.ALLOW ?
|
|
||||||
R.string.su_allow_toast : R.string.su_deny_toast, policy.appName),
|
|
||||||
Toast.LENGTH_SHORT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void handleNotify(Intent intent) {
|
|
||||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
|
||||||
if (fromUid < 0) return;
|
|
||||||
if (fromUid == Process.myUid()) return;
|
|
||||||
try {
|
|
||||||
Policy policy = new Policy(fromUid, App.self.getPackageManager());
|
|
||||||
policy.policy = intent.getIntExtra("policy", -1);
|
|
||||||
if (policy.policy >= 0)
|
|
||||||
handleNotify(policy);
|
|
||||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
94
app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt
Normal file
94
app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
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
|
||||||
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.model.entity.Policy
|
||||||
|
import com.topjohnwu.magisk.model.entity.toLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object SuLogger {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun handleLogs(intent: Intent) {
|
||||||
|
|
||||||
|
val fromUid = intent.getIntExtra("from.uid", -1)
|
||||||
|
if (fromUid < 0) return
|
||||||
|
if (fromUid == Process.myUid()) return
|
||||||
|
|
||||||
|
val pm: PackageManager by inject()
|
||||||
|
|
||||||
|
val notify: Boolean
|
||||||
|
val data = intent.extras
|
||||||
|
val policy: MagiskPolicy = if (data!!.containsKey("notify")) {
|
||||||
|
notify = data.getBoolean("notify")
|
||||||
|
runCatching {
|
||||||
|
fromUid.toPolicy(pm)
|
||||||
|
}.getOrElse { return }
|
||||||
|
} else {
|
||||||
|
// Doesn't report whether notify or not, check database ourselves
|
||||||
|
val appRepo: AppRepository by inject()
|
||||||
|
val policy = appRepo.fetch(fromUid).blockingGet() ?: return
|
||||||
|
notify = policy.notification
|
||||||
|
policy
|
||||||
|
}.copy(policy = data.getInt("policy", -1))
|
||||||
|
|
||||||
|
if (policy.policy < 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (notify)
|
||||||
|
handleNotify(policy)
|
||||||
|
|
||||||
|
val toUid = intent.getIntExtra("to.uid", -1)
|
||||||
|
if (toUid < 0) return
|
||||||
|
|
||||||
|
val pid = intent.getIntExtra("pid", -1)
|
||||||
|
if (pid < 0) return
|
||||||
|
|
||||||
|
val command = intent.getStringExtra("command") ?: return
|
||||||
|
val log = policy.toLog(
|
||||||
|
toUid = toUid,
|
||||||
|
fromPid = pid,
|
||||||
|
command = command,
|
||||||
|
date = Date()
|
||||||
|
)
|
||||||
|
|
||||||
|
val logRepo: LogRepository by inject()
|
||||||
|
logRepo.put(log).blockingGet()?.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleNotify(policy: MagiskPolicy) {
|
||||||
|
if (policy.notification && Config.get<Any>(Config.Key.SU_NOTIFICATION) as Int == Config.Value.NOTIFICATION_TOAST) {
|
||||||
|
Utils.toast(
|
||||||
|
App.self.getString(
|
||||||
|
if (policy.policy == Policy.ALLOW)
|
||||||
|
R.string.su_allow_toast
|
||||||
|
else
|
||||||
|
R.string.su_deny_toast, policy.appName
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleNotify(intent: Intent) {
|
||||||
|
val fromUid = intent.getIntExtra("from.uid", -1)
|
||||||
|
if (fromUid < 0) return
|
||||||
|
if (fromUid == Process.myUid()) return
|
||||||
|
runCatching {
|
||||||
|
val packageManager: PackageManager by inject()
|
||||||
|
val policy = fromUid.toPolicy(packageManager)
|
||||||
|
.copy(policy = intent.getIntExtra("policy", -1))
|
||||||
|
if (policy.policy >= 0)
|
||||||
|
handleNotify(policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,24 +7,16 @@ import android.content.pm.ApplicationInfo;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.work.Constraints;
|
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy;
|
|
||||||
import androidx.work.NetworkType;
|
|
||||||
import androidx.work.PeriodicWorkRequest;
|
|
||||||
import androidx.work.WorkManager;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
import com.topjohnwu.magisk.App;
|
||||||
import com.topjohnwu.magisk.BuildConfig;
|
import com.topjohnwu.magisk.BuildConfig;
|
||||||
import com.topjohnwu.magisk.ClassMap;
|
import com.topjohnwu.magisk.ClassMap;
|
||||||
import com.topjohnwu.magisk.Config;
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.model.entity.Module;
|
import com.topjohnwu.magisk.model.entity.OldModule;
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
||||||
import com.topjohnwu.net.Networking;
|
import com.topjohnwu.net.Networking;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
@@ -35,6 +27,14 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
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 class Utils {
|
||||||
|
|
||||||
public static void toast(CharSequence msg, int duration) {
|
public static void toast(CharSequence msg, int duration) {
|
||||||
@@ -72,7 +72,7 @@ public class Utils {
|
|||||||
if (info.labelRes > 0) {
|
if (info.labelRes > 0) {
|
||||||
Resources res = pm.getResourcesForApplication(info);
|
Resources res = pm.getResourcesForApplication(info);
|
||||||
Configuration config = new Configuration();
|
Configuration config = new Configuration();
|
||||||
config.setLocale(LocaleManager.locale);
|
config.setLocale(LocaleManager.getLocale());
|
||||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||||
return res.getString(info.labelRes);
|
return res.getString(info.labelRes);
|
||||||
}
|
}
|
||||||
@@ -86,28 +86,19 @@ public class Utils {
|
|||||||
.replace("#", "").replace("@", "").replace("\\", "_");
|
.replace("#", "").replace("@", "").replace("\\", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadModules() {
|
@WorkerThread
|
||||||
loadModules(true);
|
public static Map<String, OldModule> loadModulesLeanback() {
|
||||||
}
|
final Map<String, OldModule> moduleMap = new ValueSortedMap<>();
|
||||||
|
final SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||||
public static void loadModules(boolean async) {
|
final SuFile[] modules = path.listFiles((file, name) ->
|
||||||
Event.reset(Event.MODULE_LOAD_DONE);
|
!name.equals("lost+found") && !name.equals(".core")
|
||||||
Runnable run = () -> {
|
);
|
||||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
for (SuFile file : modules) {
|
||||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
if (file.isFile()) continue;
|
||||||
SuFile[] modules = path.listFiles(
|
OldModule module = new OldModule(Const.MAGISK_PATH + "/" + file.getName());
|
||||||
(file, name) -> !name.equals("lost+found") && !name.equals(".core"));
|
moduleMap.put(module.getId(), module);
|
||||||
for (SuFile file : modules) {
|
}
|
||||||
if (file.isFile()) continue;
|
return moduleMap;
|
||||||
Module module = new Module(Const.MAGISK_PATH + "/" + file.getName());
|
|
||||||
moduleMap.put(module.getId(), module);
|
|
||||||
}
|
|
||||||
Event.trigger(Event.MODULE_LOAD_DONE, moduleMap);
|
|
||||||
};
|
|
||||||
if (async)
|
|
||||||
App.THREAD_POOL.execute(run);
|
|
||||||
else
|
|
||||||
run.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean showSuperUser() {
|
public static boolean showSuperUser() {
|
||||||
@@ -120,13 +111,17 @@ public class Utils {
|
|||||||
return BuildConfig.VERSION_NAME.contains("-");
|
return BuildConfig.VERSION_NAME.contains("-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static void scheduleUpdateCheck() {
|
public static void scheduleUpdateCheck() {
|
||||||
if (Config.get(Config.Key.CHECK_UPDATES)) {
|
if (Config.get(Config.Key.CHECK_UPDATES)) {
|
||||||
Constraints constraints = new Constraints.Builder()
|
Constraints constraints = new Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
|
//ensures that notification doesn't pop up every time user starts the app
|
||||||
|
.setRequiresDeviceIdle(true)
|
||||||
.build();
|
.build();
|
||||||
|
Class<? extends ListenableWorker> service = (Class<? extends ListenableWorker>) ClassMap.get(UpdateCheckService.class);
|
||||||
PeriodicWorkRequest request = new PeriodicWorkRequest
|
PeriodicWorkRequest request = new PeriodicWorkRequest
|
||||||
.Builder(ClassMap.get(UpdateCheckService.class), 12, TimeUnit.HOURS)
|
.Builder(service, 12, TimeUnit.HOURS)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.build();
|
.build();
|
||||||
WorkManager.getInstance().enqueueUniquePeriodicWork(
|
WorkManager.getInstance().enqueueUniquePeriodicWork(
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.ComponentInfo
|
import android.content.pm.ComponentInfo
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
@@ -9,6 +10,7 @@ import android.content.pm.PackageManager.*
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import com.topjohnwu.magisk.App
|
import com.topjohnwu.magisk.App
|
||||||
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
val packageName: String
|
val packageName: String
|
||||||
@@ -80,3 +82,22 @@ fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
|||||||
|
|
||||||
fun Context.readUri(uri: Uri) = contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
fun Context.readUri(uri: Uri) = contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
||||||
|
|
||||||
|
fun ApplicationInfo.findAppLabel(pm: PackageManager): String {
|
||||||
|
return pm.getApplicationLabel(this)?.toString().orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
||||||
|
|
||||||
|
fun File.provide(): Uri {
|
||||||
|
val context: Context by inject()
|
||||||
|
return FileProvider.getUriForFile(context, "com.topjohnwu.magisk.fileprovider", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.mv(destination: File) {
|
||||||
|
inputStream().copyTo(destination)
|
||||||
|
deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toFile() = File(this)
|
||||||
|
|
||||||
|
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
|
38
app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt
Normal file
38
app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import org.kamranzafar.jtar.TarInputStream
|
||||||
|
import org.kamranzafar.jtar.TarOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||||
|
var entry: ZipEntry? = nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
callback(entry)
|
||||||
|
entry = nextEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Uri.copyTo(file: File) = toFile().copyTo(file)
|
||||||
|
fun InputStream.copyTo(file: File) =
|
||||||
|
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
||||||
|
|
||||||
|
fun File.tarInputStream() = TarInputStream(inputStream())
|
||||||
|
fun File.tarOutputStream() = TarOutputStream(this)
|
||||||
|
|
||||||
|
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||||
|
inStream: In,
|
||||||
|
outStream: Out,
|
||||||
|
withBoth: (In, Out) -> Unit
|
||||||
|
) {
|
||||||
|
inStream.use { reader ->
|
||||||
|
outStream.use { writer ->
|
||||||
|
withBoth(reader, writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,18 +3,15 @@ package com.topjohnwu.magisk.utils
|
|||||||
import org.koin.core.context.GlobalContext
|
import org.koin.core.context.GlobalContext
|
||||||
import org.koin.core.parameter.ParametersDefinition
|
import org.koin.core.parameter.ParametersDefinition
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
import org.koin.core.scope.Scope
|
|
||||||
|
|
||||||
fun getKoin() = GlobalContext.get().koin
|
fun getKoin() = GlobalContext.get().koin
|
||||||
|
|
||||||
inline fun <reified T : Any> inject(
|
inline fun <reified T : Any> inject(
|
||||||
qualifier: Qualifier? = null,
|
qualifier: Qualifier? = null,
|
||||||
scope: Scope? = null,
|
|
||||||
noinline parameters: ParametersDefinition? = null
|
noinline parameters: ParametersDefinition? = null
|
||||||
) = lazy { get<T>(qualifier, scope, parameters) }
|
) = lazy { get<T>(qualifier, parameters) }
|
||||||
|
|
||||||
inline fun <reified T : Any> get(
|
inline fun <reified T : Any> get(
|
||||||
qualifier: Qualifier? = null,
|
qualifier: Qualifier? = null,
|
||||||
scope: Scope? = null,
|
|
||||||
noinline parameters: ParametersDefinition? = null
|
noinline parameters: ParametersDefinition? = null
|
||||||
): T = getKoin().get(qualifier, scope, parameters)
|
): T = getKoin().get(qualifier, parameters)
|
54
app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
Normal file
54
app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
fun ResponseBody.writeToFile(context: Context, fileName: String): File {
|
||||||
|
val file = File(context.cacheDir, fileName)
|
||||||
|
withStreams(byteStream(), file.outputStream()) { inStream, outStream ->
|
||||||
|
inStream.copyTo(outStream)
|
||||||
|
}
|
||||||
|
return 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)
|
||||||
|
}
|
19
app/src/main/java/com/topjohnwu/magisk/utils/XSU.kt
Normal file
19
app/src/main/java/com/topjohnwu/magisk/utils/XSU.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
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 File.suOutputStream() = SuFileOutputStream(this)
|
||||||
|
fun File.suInputStream() = SuFileInputStream(this)
|
@@ -12,7 +12,12 @@ fun String.replaceRandomWithSpecial(): String {
|
|||||||
return replace(random, specialChars.random())
|
return replace(random, specialChars.random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
|
||||||
|
if (condition) apply(builder) else this
|
||||||
|
|
||||||
fun Int.res(vararg args: Any): String {
|
fun Int.res(vararg args: Any): String {
|
||||||
val resources: Resources by inject()
|
val resources: Resources by inject()
|
||||||
return resources.getString(this, *args)
|
return resources.getString(this, *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
|
@@ -1,18 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import java.text.DateFormat
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
val now get() = System.currentTimeMillis()
|
val now get() = System.currentTimeMillis()
|
||||||
|
|
||||||
fun Long.toTime(format: SimpleDateFormat) = format.format(this).orEmpty()
|
fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
|
||||||
fun String.toTime(format: SimpleDateFormat) = try {
|
fun String.toTime(format: DateFormat) = try {
|
||||||
format.parse(this)?.time ?: -1
|
format.parse(this)?.time ?: -1
|
||||||
} catch (e: ParseException) {
|
} catch (e: ParseException) {
|
||||||
-1L
|
-1L
|
||||||
}
|
}
|
||||||
|
|
||||||
private val locale get() = Locale.getDefault()
|
private val locale get() = LocaleManager.locale
|
||||||
val timeFormatFull by lazy { SimpleDateFormat("YYYY/MM/DD_HH:mm:ss", locale) }
|
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss", locale) }
|
||||||
val timeFormatStandard by lazy { SimpleDateFormat("YYYY-MM-DD'T'HH:mm:ss'Z'", locale) }
|
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", locale) }
|
||||||
|
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale) }
|
||||||
|
val timeFormatTime by lazy { SimpleDateFormat("h:mm a", LocaleManager.locale) }
|
14
app/src/main/java/com/topjohnwu/magisk/utils/XView.kt
Normal file
14
app/src/main/java/com/topjohnwu/magisk/utils/XView.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
|
||||||
|
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||||
|
|
||||||
|
fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Unit) =
|
||||||
|
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
@@ -1,40 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.view;
|
|
||||||
|
|
||||||
public abstract class Expandable {
|
|
||||||
|
|
||||||
private boolean mExpanded = false;
|
|
||||||
|
|
||||||
public final boolean isExpanded() {
|
|
||||||
return mExpanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setExpanded(boolean expanded) {
|
|
||||||
mExpanded = expanded;
|
|
||||||
onSetExpanded(expanded);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void expand() {
|
|
||||||
if (mExpanded)
|
|
||||||
return;
|
|
||||||
onExpand();
|
|
||||||
mExpanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void collapse() {
|
|
||||||
if (!mExpanded)
|
|
||||||
return;
|
|
||||||
onCollapse();
|
|
||||||
mExpanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void onExpand();
|
|
||||||
|
|
||||||
protected abstract void onCollapse();
|
|
||||||
|
|
||||||
protected void onSetExpanded(boolean expanded) {
|
|
||||||
if (expanded)
|
|
||||||
onExpand();
|
|
||||||
else
|
|
||||||
onCollapse();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,65 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.view;
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
|
|
||||||
public class ExpandableViewHolder extends Expandable {
|
|
||||||
|
|
||||||
private ViewGroup expandLayout;
|
|
||||||
private ValueAnimator expandAnimator, collapseAnimator;
|
|
||||||
private int expandHeight = 0;
|
|
||||||
|
|
||||||
public ExpandableViewHolder(ViewGroup viewGroup) {
|
|
||||||
expandLayout = viewGroup;
|
|
||||||
setExpanded(false);
|
|
||||||
expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
|
||||||
new ViewTreeObserver.OnPreDrawListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreDraw() {
|
|
||||||
if (expandHeight == 0) {
|
|
||||||
expandLayout.measure(0, 0);
|
|
||||||
expandHeight = expandLayout.getMeasuredHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
expandAnimator = slideAnimator(0, expandHeight);
|
|
||||||
collapseAnimator = slideAnimator(expandHeight, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onExpand() {
|
|
||||||
expandLayout.setVisibility(View.VISIBLE);
|
|
||||||
expandAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCollapse() {
|
|
||||||
collapseAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSetExpanded(boolean expanded) {
|
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
|
||||||
layoutParams.height = expanded ? expandHeight : 0;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueAnimator slideAnimator(int start, int end) {
|
|
||||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
|
||||||
|
|
||||||
animator.addUpdateListener(valueAnimator -> {
|
|
||||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
|
||||||
layoutParams.height = value;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
});
|
|
||||||
return animator;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.view;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.net.Networking;
|
import com.topjohnwu.net.Networking;
|
||||||
@@ -50,7 +51,13 @@ public class MarkDownWindow {
|
|||||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||||
alert.setTitle(title);
|
alert.setTitle(title);
|
||||||
View mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null);
|
View mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null);
|
||||||
markwon.setMarkdown(mv.findViewById(R.id.md_txt), md);
|
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.setView(mv);
|
||||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||||
alert.show();
|
alert.show();
|
||||||
|
@@ -89,15 +89,12 @@
|
|||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSpinner
|
<androidx.appcompat.widget.AppCompatSpinner
|
||||||
android:id="@+id/timeout"
|
android:id="@+id/timeout"
|
||||||
itemBinding="@{viewModel.itemBinding}"
|
|
||||||
items="@{viewModel.items}"
|
|
||||||
onTouch="@{() -> viewModel.spinnerTouched()}"
|
onTouch="@{() -> viewModel.spinnerTouched()}"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:selectedItemPosition="@={viewModel.selectedItemPosition}" />
|
android:adapter="@{viewModel.adapter}"
|
||||||
itemDropDownLayout="@{android.R.layout.simple_spinner_dropdown_item}"
|
android:selection="@={viewModel.selectedItemPosition}" />
|
||||||
android:onClick="@{() -> viewModel.spinnerPressed()}"
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/warning"
|
android:id="@+id/warning"
|
||||||
|
@@ -1,25 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="15dp"
|
android:padding="@dimen/margin_generic">
|
||||||
android:paddingTop="5dp"
|
|
||||||
android:paddingEnd="15dp"
|
|
||||||
android:paddingBottom="5dp">
|
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="5dp"
|
android:hint="@string/settings_update_custom_msg"
|
||||||
android:labelFor="@id/custom_url"
|
app:hintEnabled="true">
|
||||||
android:text="@string/settings_update_custom_msg"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:textColor="?android:textColorPrimary" />
|
android:id="@+id/custom_url"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textUri" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/custom_url"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textUri" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user