mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 07:07:27 +00:00
Compare commits
173 Commits
manager-v6
...
v18.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b31402766e | ||
![]() |
9ab3143bf0 | ||
![]() |
81a0cddb9e | ||
![]() |
f620ac769f | ||
![]() |
dc91041edd | ||
![]() |
6ee08b6717 | ||
![]() |
5a2cd2ac84 | ||
![]() |
2bd8448aaa | ||
![]() |
2360adb592 | ||
![]() |
c7301a5161 | ||
![]() |
72270825c1 | ||
![]() |
1e94f0a094 | ||
![]() |
e39d2567ea | ||
![]() |
949136c92a | ||
![]() |
9f456a9b19 | ||
![]() |
4cf6ba25ca | ||
![]() |
093f971896 | ||
![]() |
df38a9da71 | ||
![]() |
813814c54a | ||
![]() |
68cb32f375 | ||
![]() |
93c9590b0f | ||
![]() |
619d979c39 | ||
![]() |
c30faad838 | ||
![]() |
bea0de4980 | ||
![]() |
ef5a490415 | ||
![]() |
fa404285be | ||
![]() |
0e526258ff | ||
![]() |
56d2fb9a3b | ||
![]() |
7c82690852 | ||
![]() |
62acc17e42 | ||
![]() |
9fbe5895b7 | ||
![]() |
6bbe0f07d4 | ||
![]() |
bd3e0b9336 | ||
![]() |
699debdaca | ||
![]() |
70eba568af | ||
![]() |
bb7560e441 | ||
![]() |
43c0cac52f | ||
![]() |
4b4aa148a9 | ||
![]() |
c9c90c4e7f | ||
![]() |
99093e9a4c | ||
![]() |
2cf33d635d | ||
![]() |
d6abaf846e | ||
![]() |
4b88131977 | ||
![]() |
4520f46a57 | ||
![]() |
348d47076a | ||
![]() |
6e7b90a184 | ||
![]() |
28d7a7a6d2 | ||
![]() |
da13b5dbf2 | ||
![]() |
a60710e3bb | ||
![]() |
7d2a2b9983 | ||
![]() |
749df5dacd | ||
![]() |
af88b7c807 | ||
![]() |
4091687733 | ||
![]() |
cfb0a3ba2a | ||
![]() |
6c4d082f35 | ||
![]() |
262185046a | ||
![]() |
da9d00be7d | ||
![]() |
454abc388b | ||
![]() |
3e9174deed | ||
![]() |
4df1047b07 | ||
![]() |
60f69feaff | ||
![]() |
5df426380d | ||
![]() |
976c299657 | ||
![]() |
18ab6b51fd | ||
![]() |
4be8bd4d18 | ||
![]() |
075bc4a6d5 | ||
![]() |
1c61feb368 | ||
![]() |
d32b788988 | ||
![]() |
7565ea2787 | ||
![]() |
9275975b2c | ||
![]() |
b3e0d5ba58 | ||
![]() |
841dee94c6 | ||
![]() |
71638191ee | ||
![]() |
9d6851cbbd | ||
![]() |
d633d05803 | ||
![]() |
45d7879d7b | ||
![]() |
4a8375355c | ||
![]() |
d3ebd763a2 | ||
![]() |
b7f69238a1 | ||
![]() |
118a9f224e | ||
![]() |
a44dc8df37 | ||
![]() |
abf19aad74 | ||
![]() |
d73127b175 | ||
![]() |
00f4242fa4 | ||
![]() |
f6a4510659 | ||
![]() |
33215424d8 | ||
![]() |
6094bc9210 | ||
![]() |
a8cd9b3aa9 | ||
![]() |
a189dec1c8 | ||
![]() |
858216796a | ||
![]() |
f24342f117 | ||
![]() |
50b55a77de | ||
![]() |
fdf167db11 | ||
![]() |
a4f8bd4ee0 | ||
![]() |
3e4c12cf56 | ||
![]() |
03c39e692a | ||
![]() |
ab63b0e970 | ||
![]() |
6ea42a35a9 | ||
![]() |
d366dfc72b | ||
![]() |
85042fbe25 | ||
![]() |
23e5188422 | ||
![]() |
93ee0c8798 | ||
![]() |
aa88486f59 | ||
![]() |
1d9c441038 | ||
![]() |
928c56bda2 | ||
![]() |
bc6f37eecc | ||
![]() |
ffebff8cab | ||
![]() |
6d6bd89d6b | ||
![]() |
0a64a7e5d4 | ||
![]() |
586488af48 | ||
![]() |
7b9a45f1a8 | ||
![]() |
4ff70aefac | ||
![]() |
d63b5d7014 | ||
![]() |
ab5f6bf901 | ||
![]() |
04088b34a2 | ||
![]() |
3edcd2004e | ||
![]() |
7bd52d0245 | ||
![]() |
1df65940b9 | ||
![]() |
d9ace35c3e | ||
![]() |
1fe92cee6f | ||
![]() |
267868c3b0 | ||
![]() |
6d27eb7f64 | ||
![]() |
2e10fa494f | ||
![]() |
039be65a89 | ||
![]() |
570ecd9987 | ||
![]() |
a575180475 | ||
![]() |
07d1a20f3d | ||
![]() |
76491cbb31 | ||
![]() |
bf7d6ddcb2 | ||
![]() |
44b969e0b6 | ||
![]() |
176e470497 | ||
![]() |
646a10d9bf | ||
![]() |
52137fd64f | ||
![]() |
3ccac8c3b8 | ||
![]() |
0be158afa1 | ||
![]() |
e6942e0122 | ||
![]() |
496b22026f | ||
![]() |
bb2df02dff | ||
![]() |
4c850ecc31 | ||
![]() |
da9c6f6e23 | ||
![]() |
58ba0b0b4e | ||
![]() |
1d0b87246a | ||
![]() |
920b60da19 | ||
![]() |
523e66294b | ||
![]() |
23f8f35098 | ||
![]() |
8d210b5e37 | ||
![]() |
3c6c0e6700 | ||
![]() |
01344c451f | ||
![]() |
2c42c79482 | ||
![]() |
75c2cfe7bf | ||
![]() |
6c6eeb3f28 | ||
![]() |
31053e0cd0 | ||
![]() |
aad9aced18 | ||
![]() |
dd2c9eeafe | ||
![]() |
740d76bc42 | ||
![]() |
45f4f5afd9 | ||
![]() |
e875de3e98 | ||
![]() |
fd7786633d | ||
![]() |
bce9cfa39a | ||
![]() |
ff3d66a661 | ||
![]() |
006d28abd5 | ||
![]() |
59b1e63bdf | ||
![]() |
eab74ef06b | ||
![]() |
89837de9b0 | ||
![]() |
b245931c79 | ||
![]() |
fd5e42698c | ||
![]() |
c75512ba6e | ||
![]() |
a22e7aa0b1 | ||
![]() |
020dd97f99 | ||
![]() |
e9882d9702 | ||
![]() |
fd4a27dbf2 | ||
![]() |
9c63e31da6 | ||
![]() |
c91f809eba |
@@ -2,7 +2,7 @@
|
||||
[Downloads](https://github.com/topjohnwu/Magisk/releases) | [Documentation](https://topjohnwu.github.io/Magisk/) | [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
|
||||
|
||||
## Introduction
|
||||
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 5.0 (API 21). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
|
||||
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2 (API 17). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
|
||||
|
||||
Furthermore, Magisk provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact. With its systemless nature along with several other hacks, Magisk can hide modifications from nearly any system integrity verifications used in banking apps, corporation monitoring apps, game cheat detections, and most importantly [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html).
|
||||
|
||||
@@ -10,7 +10,7 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
|
||||
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that is already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
|
||||
|
||||
## Building Environment Requirements
|
||||
1. Python 3.5+: run `build.py` script
|
||||
1. Python 3: run `build.py` script
|
||||
2. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips
|
||||
3. Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK
|
||||
4. Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME`
|
||||
|
2
app-core/.gitignore
vendored
Normal file
2
app-core/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
src/main/res/raw/util_functions.sh
|
21
app-core/build.gradle
Normal file
21
app-core/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api project(':net')
|
||||
api project(':signing')
|
||||
api 'org.kamranzafar:jtar:2.3'
|
||||
|
||||
def libsuVersion = '2.3.0'
|
||||
api "com.github.topjohnwu.libsu:core:${libsuVersion}"
|
||||
api "com.github.topjohnwu.libsu:io:${libsuVersion}"
|
||||
}
|
21
app-core/proguard-rules.pro
vendored
Normal file
21
app-core/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
4
app-core/src/main/AndroidManifest.xml
Normal file
4
app-core/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.topjohnwu.magisk.core" >
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
</manifest>
|
61
app-core/src/main/java/com/topjohnwu/magisk/App.java
Normal file
61
app-core/src/main/java/com/topjohnwu/magisk/App.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.topjohnwu.magisk.core.BuildConfig;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
public static App self;
|
||||
public static ThreadPoolExecutor THREAD_POOL;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDB mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
static {
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER | Shell.FLAG_USE_MAGISK_BUSYBOX);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.addInitializers(RootUtils.class);
|
||||
Shell.Config.setTimeout(2);
|
||||
THREAD_POOL = (ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
self = this;
|
||||
|
||||
Context de = this;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
de = createDeviceProtectedStorageContext();
|
||||
de.moveSharedPreferencesFrom(this, PreferenceManager.getDefaultSharedPreferencesName(base));
|
||||
}
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(de);
|
||||
mDB = new MagiskDB(this);
|
||||
|
||||
Networking.init(this);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
372
app-core/src/main/java/com/topjohnwu/magisk/Config.java
Normal file
372
app-core/src/main/java/com/topjohnwu/magisk/Config.java
Normal file
@@ -0,0 +1,372 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Config {
|
||||
|
||||
// Current status
|
||||
public static String magiskVersionString;
|
||||
public static int magiskVersionCode = -1;
|
||||
private static boolean magiskHide;
|
||||
|
||||
// Update Info
|
||||
public static String remoteMagiskVersionString;
|
||||
public static int remoteMagiskVersionCode = -1;
|
||||
public static String magiskLink;
|
||||
public static String magiskNoteLink;
|
||||
public static String magiskMD5;
|
||||
public static String remoteManagerVersionString;
|
||||
public static int remoteManagerVersionCode = -1;
|
||||
public static String managerLink;
|
||||
public static String managerNoteLink;
|
||||
public static String uninstallerLink;
|
||||
|
||||
// Install flags
|
||||
public static boolean keepVerity = false;
|
||||
public static boolean keepEnc = false;
|
||||
public static boolean recovery = false;
|
||||
|
||||
public static int suLogTimeout = 14;
|
||||
|
||||
public static class Key {
|
||||
// su configs
|
||||
public static final String ROOT_ACCESS = "root_access";
|
||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
||||
public static final String SU_MNT_NS = "mnt_ns";
|
||||
public static final String SU_MANAGER = "requester";
|
||||
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
|
||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
||||
public static final String SU_NOTIFICATION = "su_notification";
|
||||
public static final String SU_REAUTH = "su_reauth";
|
||||
public static final String SU_FINGERPRINT = "su_fingerprint";
|
||||
|
||||
// prefs
|
||||
public static final String CHECK_UPDATES = "check_update";
|
||||
public static final String UPDATE_CHANNEL = "update_channel";
|
||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
||||
public static final String BOOT_FORMAT = "boot_format";
|
||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
||||
public static final String MAGISKHIDE = "magiskhide";
|
||||
public static final String COREONLY = "disable";
|
||||
public static final String LOCALE = "locale";
|
||||
public static final String DARK_THEME = "dark_theme";
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
public static final String REPO_ORDER = "repo_order";
|
||||
public static final String SHOW_SYSTEM_APP = "show_system";
|
||||
}
|
||||
|
||||
public static class Value {
|
||||
public static final int STABLE_CHANNEL = 0;
|
||||
public static final int BETA_CHANNEL = 1;
|
||||
public static final int CUSTOM_CHANNEL = 2;
|
||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||
public static final int MULTIUSER_MODE_USER = 2;
|
||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
public static final int NO_NOTIFICATION = 0;
|
||||
public static final int NOTIFICATION_TOAST = 1;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
public static final int[] TIMEOUT_LIST = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
||||
public static final int ORDER_DATE = 1;
|
||||
}
|
||||
|
||||
private static Bundle defs = new Bundle();
|
||||
|
||||
static {
|
||||
/* Set default configurations */
|
||||
|
||||
// prefs int
|
||||
defs.putInt(Key.REPO_ORDER, Value.ORDER_DATE);
|
||||
|
||||
// prefs string int
|
||||
defs.putInt(Key.SU_REQUEST_TIMEOUT, 10);
|
||||
defs.putInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT);
|
||||
defs.putInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST);
|
||||
defs.putInt(Key.UPDATE_CHANNEL, Value.STABLE_CHANNEL);
|
||||
|
||||
// prefs bool
|
||||
defs.putBoolean(Key.CHECK_UPDATES, true);
|
||||
// defs.putBoolean(Key.DARK_THEME, false);
|
||||
// defs.putBoolean(Key.SU_REAUTH, false);
|
||||
// defs.putBoolean(Key.MAGISKHIDE, false);
|
||||
// defs.putBoolean(Key.COREONLY, false);
|
||||
// defs.putBoolean(Key.SHOW_SYSTEM_APP, false);
|
||||
|
||||
// prefs string
|
||||
defs.putString(Key.CUSTOM_CHANNEL, "");
|
||||
defs.putString(Key.BOOT_FORMAT, ".img");
|
||||
defs.putString(Key.LOCALE, "");
|
||||
// defs.putString(Key.ETAG_KEY, null);
|
||||
|
||||
// db int
|
||||
defs.putInt(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
defs.putInt(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER);
|
||||
defs.putInt(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
|
||||
// db bool
|
||||
// defs.putBoolean(Key.SU_FINGERPRINT, false);
|
||||
|
||||
// db strings
|
||||
// defs.putString(Key.SU_MANAGER, null);
|
||||
}
|
||||
|
||||
public static void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public static void export() {
|
||||
// Flush prefs to disk
|
||||
App app = App.self;
|
||||
app.prefs.edit().commit();
|
||||
File xml = new File(app.getFilesDir().getParent() + "/shared_prefs",
|
||||
app.getPackageName() + "_preferences.xml");
|
||||
Shell.su(Utils.fmt("cat %s > /data/user/0/%s", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
SharedPreferences pref = App.self.prefs;
|
||||
SharedPreferences.Editor editor = pref.edit();
|
||||
SuFile config = new SuFile("/data/user/0/" + Const.MANAGER_CONFIGS);
|
||||
if (config.exists()) {
|
||||
try {
|
||||
SuFileInputStream is = new SuFileInputStream(config);
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(is, "UTF-8");
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
continue;
|
||||
String key = parser.getAttributeValue(null, "name");
|
||||
String value = parser.getAttributeValue(null, "value");
|
||||
switch (parser.getName()) {
|
||||
case "string":
|
||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
||||
editor.putString(key, parser.nextText());
|
||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
||||
break;
|
||||
case "boolean":
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
||||
break;
|
||||
case "int":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putInt(key, Integer.parseInt(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
case "long":
|
||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
||||
editor.putLong(key, Long.parseLong(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
||||
break;
|
||||
case "float":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putFloat(key, Float.parseFloat(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
default:
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
editor.remove(Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
editor = pref.edit();
|
||||
config.delete();
|
||||
}
|
||||
|
||||
// Set to defaults if not set
|
||||
setDefs(pref, editor,
|
||||
Key.SU_REQUEST_TIMEOUT, Key.SU_AUTO_RESPONSE, Key.ROOT_ACCESS,
|
||||
Key.SU_MNT_NS, Key.SU_NOTIFICATION, Key.DARK_THEME,
|
||||
Key.CHECK_UPDATES, Key.UPDATE_CHANNEL, Key.REPO_ORDER);
|
||||
|
||||
// These settings are from actual device state
|
||||
editor.putBoolean(Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putInt(Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.apply();
|
||||
}
|
||||
|
||||
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.BOOT_FORMAT:
|
||||
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")
|
||||
public static <T> T get(String key) {
|
||||
App app = App.self;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
return (T) (Integer) app.prefs.getInt(key, defs.getInt(key));
|
||||
case PREF_STR_INT:
|
||||
return (T) (Integer) Utils.getPrefsInt(app.prefs, key, defs.getInt(key));
|
||||
case PREF_BOOL:
|
||||
return (T) (Boolean) app.prefs.getBoolean(key, defs.getBoolean(key));
|
||||
case PREF_STR:
|
||||
return (T) app.prefs.getString(key, defs.getString(key));
|
||||
case DB_INT:
|
||||
return (T) (Integer) app.mDB.getSettings(key, defs.getInt(key));
|
||||
case DB_BOOL:
|
||||
return (T) (Boolean) (app.mDB.getSettings(key, defs.getBoolean(key) ? 1 : 0) != 0);
|
||||
case DB_STR:
|
||||
return (T) app.mDB.getStrings(key, defs.getString(key));
|
||||
}
|
||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
||||
return (T) new Object();
|
||||
}
|
||||
|
||||
public static void set(String key, Object val) {
|
||||
App app = App.self;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
app.prefs.edit().putInt(key, (int) val).apply();
|
||||
break;
|
||||
case PREF_STR_INT:
|
||||
app.prefs.edit().putString(key, String.valueOf(val)).apply();
|
||||
break;
|
||||
case PREF_BOOL:
|
||||
app.prefs.edit().putBoolean(key, (boolean) val).apply();
|
||||
break;
|
||||
case PREF_STR:
|
||||
app.prefs.edit().putString(key, (String) val).apply();
|
||||
break;
|
||||
case DB_INT:
|
||||
app.mDB.setSettings(key, (int) val);
|
||||
break;
|
||||
case DB_BOOL:
|
||||
app.mDB.setSettings(key, (boolean) val ? 1 : 0);
|
||||
break;
|
||||
case DB_STR:
|
||||
app.mDB.setStrings(key, (String) val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
App app = App.self;
|
||||
int def;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
case PREF_STR_INT:
|
||||
case PREF_BOOL:
|
||||
case PREF_STR:
|
||||
app.prefs.edit().remove(key).apply();
|
||||
break;
|
||||
case DB_INT:
|
||||
def = defs.getInt(key);
|
||||
app.mDB.setSettings(key, def);
|
||||
break;
|
||||
case DB_BOOL:
|
||||
def = defs.getBoolean(key) ? 1 : 0;
|
||||
app.mDB.setSettings(key, def);
|
||||
break;
|
||||
case DB_STR:
|
||||
app.mDB.setStrings(key, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor, String... keys) {
|
||||
for (String key : keys) {
|
||||
if (pref.contains(key))
|
||||
continue;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
editor.putInt(key, defs.getInt(key));
|
||||
break;
|
||||
case DB_INT:
|
||||
case PREF_STR_INT:
|
||||
editor.putString(key, String.valueOf(defs.getInt(key)));
|
||||
break;
|
||||
case PREF_STR:
|
||||
case DB_STR:
|
||||
editor.putString(key, defs.getString(key));
|
||||
break;
|
||||
case PREF_BOOL:
|
||||
case DB_BOOL:
|
||||
editor.putBoolean(key, defs.getBoolean(key));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,6 @@ import java.util.List;
|
||||
public class Const {
|
||||
|
||||
public static final String DEBUG_TAG = "MagiskManager";
|
||||
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
|
||||
|
||||
// APK content
|
||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
@@ -40,18 +39,14 @@ public class Const {
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
Data.MM().getPackageName(),
|
||||
App.self.getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
public static final int USER_ID = Process.myUid() / 100000;
|
||||
|
||||
public static final class MAGISK_VER {
|
||||
public static final int SEPOL_REFACTOR = 1640;
|
||||
public static final int FIX_ENV = 1650;
|
||||
public static final int DBVER_SIX = 17000;
|
||||
public static final int CMDLINE_DB = 17305;
|
||||
public static final int HIDE_STATUS = 17315;
|
||||
public static final int MIN_SUPPORT = 18000;
|
||||
}
|
||||
|
||||
public static class ID {
|
||||
@@ -67,12 +62,16 @@ public class Const {
|
||||
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 {
|
||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json";
|
||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed";
|
||||
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 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 PAYPAL_URL = "https://www.paypal.me/topjohnwu";
|
||||
@@ -80,23 +79,16 @@ public class Const {
|
||||
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 = "https://raw.githubusercontent.com/topjohnwu/magisk_files/b66b1a914978e5f4c4bbfd74a59f4ad371bac107/snet.apk";
|
||||
public static final String SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk");
|
||||
public static final String BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl");
|
||||
}
|
||||
|
||||
|
||||
public static class Key {
|
||||
// su
|
||||
public static final String ROOT_ACCESS = "root_access";
|
||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
||||
public static final String SU_MNT_NS = "mnt_ns";
|
||||
public static final String SU_MANAGER = "requester";
|
||||
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
|
||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
||||
public static final String SU_NOTIFICATION = "su_notification";
|
||||
public static final String SU_REAUTH = "su_reauth";
|
||||
public static final String SU_FINGERPRINT = "su_fingerprint";
|
||||
|
||||
// others
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
// intents
|
||||
public static final String FROM_SPLASH = "splash";
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_NAME = "filename";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
@@ -104,52 +96,15 @@ public class Const {
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
||||
public static final String BROADCAST_REBOOT = "reboot";
|
||||
|
||||
// others
|
||||
public static final String CHECK_UPDATES = "check_update";
|
||||
public static final String UPDATE_CHANNEL = "update_channel";
|
||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
||||
public static final String BOOT_FORMAT = "boot_format";
|
||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
||||
public static final String APP_VER = "app_version";
|
||||
public static final String MAGISKHIDE = "magiskhide";
|
||||
public static final String HOSTS = "hosts";
|
||||
public static final String COREONLY = "disable";
|
||||
public static final String LOCALE = "locale";
|
||||
public static final String DARK_THEME = "dark_theme";
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
public static final String REPO_ORDER = "repo_order";
|
||||
}
|
||||
|
||||
|
||||
public static class Value {
|
||||
public static final int STABLE_CHANNEL = 0;
|
||||
public static final int BETA_CHANNEL = 1;
|
||||
public static final int CUSTOM_CHANNEL = 2;
|
||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||
public static final int MULTIUSER_MODE_USER = 2;
|
||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
public static final int NO_NOTIFICATION = 0;
|
||||
public static final int NOTIFICATION_TOAST = 1;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
||||
public static final String UNINSTALL = "uninstall";
|
||||
public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
||||
public static final int ORDER_DATE = 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -2,12 +2,14 @@ package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
|
||||
|
||||
private String mId, mName, mVersion, mAuthor, mDescription;
|
||||
private int mVersionCode = -1, minMagiskVersion = -1;
|
||||
@@ -26,6 +28,37 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
|
||||
}
|
||||
|
||||
protected BaseModule(Parcel p) {
|
||||
mId = p.readString();
|
||||
mName = p.readString();
|
||||
mVersion = p.readString();
|
||||
mAuthor = p.readString();
|
||||
mDescription = p.readString();
|
||||
mVersionCode = p.readInt();
|
||||
minMagiskVersion = p.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BaseModule module) {
|
||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeString(mName);
|
||||
dest.writeString(mVersion);
|
||||
dest.writeString(mAuthor);
|
||||
dest.writeString(mDescription);
|
||||
dest.writeInt(mVersionCode);
|
||||
dest.writeInt(minMagiskVersion);
|
||||
}
|
||||
|
||||
private String nonNull(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
@@ -119,9 +152,4 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
public int getMinMagiskVersion() {
|
||||
return minMagiskVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BaseModule module) {
|
||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||
}
|
||||
}
|
@@ -1,5 +1,8 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
@@ -32,6 +35,19 @@ public class Module extends BaseModule {
|
||||
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() {
|
||||
mEnable = !mDisableFile.createNewFile();
|
||||
}
|
@@ -2,9 +2,10 @@ package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
@@ -24,6 +25,30 @@ public class Repo extends BaseModule {
|
||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
||||
}
|
||||
|
||||
public Repo(Parcel p) {
|
||||
super(p);
|
||||
mLastUpdate = new Date(p.readLong());
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
|
||||
|
||||
@Override
|
||||
public Repo createFromParcel(Parcel source) {
|
||||
return new Repo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repo[] newArray(int size) {
|
||||
return new Repo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mLastUpdate.getTime());
|
||||
}
|
||||
|
||||
public void update() throws IllegalRepoException {
|
||||
String props[] = Utils.dlString(getPropUrl()).split("\\n");
|
||||
try {
|
||||
@@ -73,7 +98,7 @@ public class Repo extends BaseModule {
|
||||
}
|
||||
|
||||
public String getDownloadFilename() {
|
||||
return Download.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
}
|
||||
|
||||
public class IllegalRepoException extends Exception {
|
@@ -1,11 +1,12 @@
|
||||
package com.topjohnwu.magisk.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.Data;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
@@ -19,12 +20,21 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskDBCmdline extends MagiskDB {
|
||||
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 MagiskDBCmdline() {
|
||||
pm = Data.MM().getPackageManager();
|
||||
public MagiskDB(Context context) {
|
||||
pm = context.getPackageManager();
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.uid);
|
||||
}
|
||||
|
||||
private List<String> rawSQL(String fmt, Object... args) {
|
||||
@@ -70,27 +80,23 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
return keys.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearOutdated() {
|
||||
rawSQL(
|
||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
||||
"DELETE FROM %s WHERE time < %d",
|
||||
"DELETE FROM %s WHERE time < %d",
|
||||
POLICY_TABLE, System.currentTimeMillis() / 1000,
|
||||
LOG_TABLE, System.currentTimeMillis() - Data.suLogTimeout * 86400000
|
||||
LOG_TABLE, System.currentTimeMillis() - Config.suLogTimeout * 86400000
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePolicy(String pkg) {
|
||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePolicy(int uid) {
|
||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Policy getPolicy(int uid) {
|
||||
List<ContentValues> res =
|
||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||
@@ -104,12 +110,10 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePolicy(Policy policy) {
|
||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
||||
}
|
||||
|
||||
@Override
|
||||
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)) {
|
||||
@@ -123,7 +127,6 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<SuLogEntry>> getLogs() {
|
||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
||||
List<SuLogEntry> list = null;
|
||||
@@ -141,17 +144,14 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLog(SuLogEntry log) {
|
||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLogs() {
|
||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
@@ -159,7 +159,6 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
||||
if (res.isEmpty())
|
||||
@@ -167,7 +166,6 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
return res.get(0).getAsInteger("value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||
@@ -179,7 +177,6 @@ public class MagiskDBCmdline extends MagiskDB {
|
||||
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||
if (res.isEmpty())
|
@@ -5,10 +5,8 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -20,12 +18,9 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String TABLE_NAME = "repos";
|
||||
|
||||
private SQLiteDatabase mDb;
|
||||
private MagiskManager mm;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
mm = Data.MM();
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Remove outdated repos
|
||||
@@ -46,7 +41,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
Config.remove(Config.Key.ETAG_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +52,11 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Repo repo) {
|
||||
@@ -75,12 +68,10 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public Repo getRepo(String id) {
|
||||
@@ -98,15 +89,15 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (Data.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||
case Config.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Const.Value.ORDER_DATE:
|
||||
case Config.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(Data.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER) },
|
||||
new String[] { String.valueOf(Config.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER) },
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
@@ -119,18 +110,4 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void registerAdapter(ReposAdapter a) {
|
||||
adapter = a;
|
||||
}
|
||||
|
||||
public void unregisterAdapter() {
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
private void notifyAdapter() {
|
||||
if (adapter != null) {
|
||||
Data.mainHandler.post(adapter::notifyDBChanged);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.Request;
|
||||
import com.topjohnwu.net.ResponseListener;
|
||||
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.STABLE_CHANNEL:
|
||||
default:
|
||||
url = Const.Url.STABLE_URL;
|
||||
break;
|
||||
}
|
||||
return Networking.get(url);
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
getRequest().getAsJSONObject(new UpdateListener(null));
|
||||
}
|
||||
|
||||
public static void checkNow(Runnable cb) {
|
||||
JSONObject json = getRequest().execForJSONObject().getResult();
|
||||
if (json != null)
|
||||
new UpdateListener(cb).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.remoteMagiskVersionString = getString(magisk, "version", null);
|
||||
Config.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
||||
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(() -> Topic.publish(Topic.UPDATE_CHECK_DONE),
|
||||
start + 1000 /* Add artificial delay to let UI behave correctly */);
|
||||
|
||||
if (cb != null)
|
||||
cb.run();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class FlashZip {
|
||||
|
||||
private Uri mUri;
|
||||
private File tmpFile;
|
||||
private List<String> console, logs;
|
||||
|
||||
public FlashZip(Uri uri, List<String> out, List<String> err) {
|
||||
mUri = uri;
|
||||
console = out;
|
||||
logs = err;
|
||||
tmpFile = new File(App.self.getCacheDir(), "install.zip");
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws IOException {
|
||||
ZipUtils.unzip(tmpFile, tmpFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
return Shell.su("grep -q '#MAGISK' " + new File(tmpFile.getParentFile(), "updater-script"))
|
||||
.exec().isSuccess();
|
||||
}
|
||||
|
||||
private boolean flash() throws IOException {
|
||||
console.add("- Copying zip to temp directory");
|
||||
try (InputStream in = App.self.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(tmpFile))) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot copy to cache");
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
if (!unzipAndCheck()) {
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Unzip error");
|
||||
throw e;
|
||||
}
|
||||
console.add("- Installing " + Utils.getNameFromUri(App.self, mUri));
|
||||
return Shell.su("cd " + tmpFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + tmpFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess();
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
boolean success = false;
|
||||
try {
|
||||
success = flash();
|
||||
} catch (IOException ignored) {}
|
||||
Shell.su("cd /", "rm -rf " + tmpFile.getParent() + " " + Const.TMP_FOLDER_PATH).submit();
|
||||
boolean finalSuccess = success;
|
||||
UiThreadHandler.run(() -> onResult(finalSuccess));
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void onResult(boolean success);
|
||||
}
|
@@ -0,0 +1,315 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.container.TarEntry;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.DownloadProgressListener;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.signing.SignBoot;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
import org.kamranzafar.jtar.TarOutputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
public abstract class MagiskInstaller {
|
||||
|
||||
private List<String> console, logs;
|
||||
protected String srcBoot;
|
||||
protected File installDir;
|
||||
|
||||
private class ProgressLog implements DownloadProgressListener {
|
||||
|
||||
private int prev = -1;
|
||||
private int location;
|
||||
|
||||
@Override
|
||||
public void onProgress(long bytesDownloaded, long totalBytes) {
|
||||
if (prev < 0) {
|
||||
location = console.size();
|
||||
console.add("... 0%");
|
||||
}
|
||||
int curr = (int) (100 * bytesDownloaded / totalBytes);
|
||||
if (prev != curr) {
|
||||
prev = curr;
|
||||
console.set(location, "... " + prev + "%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected MagiskInstaller() {
|
||||
console = NOPList.getInstance();
|
||||
logs = NOPList.getInstance();
|
||||
}
|
||||
|
||||
public MagiskInstaller(List<String> out, List<String> err) {
|
||||
console = out;
|
||||
logs = err;
|
||||
installDir = new File(Utils.getDEContext().getFilesDir().getParent(), "install");
|
||||
Shell.sh("rm -rf " + installDir).exec();
|
||||
installDir.mkdirs();
|
||||
}
|
||||
|
||||
protected boolean findImage() {
|
||||
console.add("- Detecting target image");
|
||||
srcBoot = ShellUtils.fastCmd("find_boot_image", "echo \"$BOOTIMAGE\"");
|
||||
if (srcBoot.isEmpty()) {
|
||||
console.add("! Unable to detect target image");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean findSecondaryImage() {
|
||||
String slot = ShellUtils.fastCmd("echo $SLOT");
|
||||
String target = (TextUtils.equals(slot, "_a") ? "_b" : "_a");
|
||||
console.add("- Target slot: " + target);
|
||||
console.add("- Detecting target image");
|
||||
srcBoot = ShellUtils.fastCmd(
|
||||
"SLOT=" + target,
|
||||
"find_boot_image",
|
||||
"SLOT=" + slot,
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
if (srcBoot.isEmpty()) {
|
||||
console.add("! Unable to detect target image");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean extractZip() {
|
||||
String arch;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
arch = abis.contains("x86") ? "x86" : "arm";
|
||||
} else {
|
||||
arch = TextUtils.equals(Build.CPU_ABI, "x86") ? "x86" : "arm";
|
||||
}
|
||||
|
||||
console.add("- Device platform: " + Build.CPU_ABI);
|
||||
|
||||
File zip = new File(App.self.getCacheDir(), "magisk.zip");
|
||||
|
||||
if (!ShellUtils.checkSum("MD5", zip, Config.magiskMD5)) {
|
||||
console.add("- Downloading zip");
|
||||
Networking.get(Config.magiskLink)
|
||||
.setDownloadProgressListener(new ProgressLog())
|
||||
.execForFile(zip);
|
||||
} else {
|
||||
console.add("- Existing zip found");
|
||||
}
|
||||
|
||||
try {
|
||||
ZipInputStream zi = new ZipInputStream(new BufferedInputStream(
|
||||
new FileInputStream(zip), (int) zip.length()));
|
||||
ZipEntry ze;
|
||||
while ((ze = zi.getNextEntry()) != null) {
|
||||
if (ze.isDirectory())
|
||||
continue;
|
||||
String name = null;
|
||||
String[] names = { arch + "/", "common/", "META-INF/com/google/android/update-binary" };
|
||||
for (String n : names) {
|
||||
if (ze.getName().startsWith(n)) {
|
||||
name = ze.getName().substring(ze.getName().lastIndexOf('/') + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (name == null && ze.getName().startsWith("chromeos/"))
|
||||
name = ze.getName();
|
||||
if (name == null)
|
||||
continue;
|
||||
File dest;
|
||||
if (installDir instanceof SuFile) {
|
||||
dest = new SuFile(installDir, name);
|
||||
} else {
|
||||
dest = new File(installDir, name);
|
||||
}
|
||||
dest.getParentFile().mkdirs();
|
||||
try (OutputStream out = new SuFileOutputStream(dest)) {
|
||||
ShellUtils.pump(zi, out);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot unzip zip");
|
||||
return false;
|
||||
}
|
||||
Shell.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
|
||||
installDir, installDir, installDir)).exec();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean copyBoot(Uri bootUri) {
|
||||
srcBoot = new File(installDir, "boot.img").getPath();
|
||||
console.add("- Copying image to cache");
|
||||
// Copy boot image to local
|
||||
try (InputStream in = App.self.getContentResolver().openInputStream(bootUri);
|
||||
OutputStream out = new FileOutputStream(srcBoot)) {
|
||||
if (in == null)
|
||||
throw new FileNotFoundException();
|
||||
|
||||
InputStream src;
|
||||
if (Utils.getNameFromUri(App.self, bootUri).endsWith(".tar")) {
|
||||
// Extract boot.img from tar
|
||||
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
|
||||
org.kamranzafar.jtar.TarEntry entry;
|
||||
while ((entry = tar.getNextEntry()) != null) {
|
||||
if (entry.getName().equals("boot.img"))
|
||||
break;
|
||||
}
|
||||
src = tar;
|
||||
} else {
|
||||
// Direct copy raw image
|
||||
src = new BufferedInputStream(in);
|
||||
}
|
||||
ShellUtils.pump(src, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
console.add("! Copy failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean patchBoot() {
|
||||
boolean isSigned;
|
||||
try (InputStream in = new SuFileInputStream(srcBoot)) {
|
||||
isSigned = SignBoot.verifySignature(in, null);
|
||||
if (isSigned) {
|
||||
console.add("- Boot image is signed with AVB 1.0");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Unable to check signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Patch boot image
|
||||
if (!Shell.sh(Utils.fmt("cd %s; KEEPFORCEENCRYPT=%b KEEPVERITY=%b " +
|
||||
"sh update-binary indep boot_patch.sh %s",
|
||||
installDir, Config.keepEnc, Config.keepVerity, srcBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return false;
|
||||
|
||||
Shell.Job job = Shell.sh("mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /");
|
||||
|
||||
File patched = new File(installDir, "new-boot.img");
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with test keys");
|
||||
File signed = new File(installDir, "signed.img");
|
||||
try (InputStream in = new SuFileInputStream(patched);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))) {
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
job.add("mv -f " + signed + " " + patched);
|
||||
}
|
||||
job.exec();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean flashBoot() {
|
||||
if (!Shell.su(Utils.fmt("direct_install %s %s", installDir, srcBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return false;
|
||||
if (!Config.keepVerity)
|
||||
Shell.su("find_dtbo_image", "patch_dtbo_image").to(console, logs).exec();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean storeBoot() {
|
||||
File patched = new File(installDir, "new-boot.img");
|
||||
String fmt = Config.get(Config.Key.BOOT_FORMAT);
|
||||
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + fmt);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream os;
|
||||
try {
|
||||
switch (fmt) {
|
||||
case ".img.tar":
|
||||
os = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
((TarOutputStream) os).putNextEntry(new TarEntry(patched, "boot.img"));
|
||||
break;
|
||||
default:
|
||||
case ".img":
|
||||
os = new BufferedOutputStream(new FileOutputStream(dest));
|
||||
break;
|
||||
}
|
||||
try (InputStream in = new SuFileInputStream(patched)) {
|
||||
ShellUtils.pump(in, os);
|
||||
os.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Failed to store boot to " + dest);
|
||||
return false;
|
||||
}
|
||||
Shell.sh("rm -f " + patched).exec();
|
||||
console.add("");
|
||||
console.add("****************************");
|
||||
console.add(" Patched image is placed in ");
|
||||
console.add(" " + dest + " ");
|
||||
console.add("****************************");
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean postOTA() {
|
||||
SuFile bootctl = new SuFile("/data/adb/bootctl");
|
||||
try (InputStream in = Networking.get(Const.Url.BOOTCTL_URL).execForInputStream().getResult();
|
||||
OutputStream out = new SuFileOutputStream(bootctl)) {
|
||||
ShellUtils.pump(in, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
Shell.su("post_ota " + bootctl.getParent()).exec();
|
||||
console.add("***************************************");
|
||||
console.add(" Next reboot will boot to second slot!");
|
||||
console.add("***************************************");
|
||||
return true;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
protected abstract boolean operations();
|
||||
|
||||
@MainThread
|
||||
protected abstract void onResult(boolean success);
|
||||
|
||||
public void exec() {
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
boolean b = operations();
|
||||
UiThreadHandler.run(() -> onResult(b));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,176 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.Request;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class UpdateRepos {
|
||||
private static final DateFormat DATE_FORMAT;
|
||||
|
||||
private App app = App.self;
|
||||
private Set<String> cached;
|
||||
private Queue<Pair<String, Date>> moduleQueue;
|
||||
|
||||
static {
|
||||
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private void runTasks(Runnable task) {
|
||||
Future[] futures = new Future[App.THREAD_POOL.getMaximumPoolSize() - 1];
|
||||
for (int i = 0; i < futures.length; ++i) {
|
||||
futures[i] = App.THREAD_POOL.submit(task);
|
||||
}
|
||||
for (Future f : futures) {
|
||||
while (true) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
} catch (ExecutionException ignored) {}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* We sort repos by last push, which means that we only need to check whether the
|
||||
* first page is updated to determine whether the online repo database is changed
|
||||
*/
|
||||
private boolean parsePage(int page) {
|
||||
Request req = Networking.get(Utils.fmt(Const.Url.REPO_URL, page + 1));
|
||||
if (page == 0) {
|
||||
String etag = Config.get(Config.Key.ETAG_KEY);
|
||||
if (etag != null)
|
||||
req.addHeaders(Const.Key.IF_NONE_MATCH, etag);
|
||||
}
|
||||
Request.Result<JSONArray> res = req.execForJSONArray();
|
||||
// JSON not updated
|
||||
if (res.getCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||
return false;
|
||||
// Network error
|
||||
if (res.getResult() == null) {
|
||||
cached.clear();
|
||||
return true;
|
||||
}
|
||||
// Current page is the last page
|
||||
if (res.getResult().length() == 0)
|
||||
return true;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < res.getResult().length(); i++) {
|
||||
JSONObject rawRepo = res.getResult().getJSONObject(i);
|
||||
String id = rawRepo.getString("name");
|
||||
Date date = DATE_FORMAT.parse(rawRepo.getString("pushed_at"));
|
||||
moduleQueue.offer(new Pair<>(id, date));
|
||||
}
|
||||
} catch (JSONException | ParseException e) {
|
||||
// Should not happen, but if exception occurs, page load fails
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update ETAG
|
||||
if (page == 0) {
|
||||
String etag = res.getConnection().getHeaderField(Config.Key.ETAG_KEY);
|
||||
if (etag != null) {
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
Config.set(Config.Key.ETAG_KEY, etag);
|
||||
}
|
||||
}
|
||||
|
||||
String links = res.getConnection().getHeaderField(Const.Key.LINK_KEY);
|
||||
return links == null || !links.contains("next") || parsePage(page + 1);
|
||||
}
|
||||
|
||||
private boolean loadPages() {
|
||||
if (!parsePage(0))
|
||||
return false;
|
||||
runTasks(() -> {
|
||||
while (true) {
|
||||
Pair<String, Date> pair = moduleQueue.poll();
|
||||
if (pair == null)
|
||||
return;
|
||||
Repo repo = app.repoDB.getRepo(pair.first);
|
||||
try {
|
||||
if (repo == null)
|
||||
repo = new Repo(pair.first);
|
||||
else
|
||||
cached.remove(pair.first);
|
||||
repo.update(pair.second);
|
||||
app.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
app.repoDB.removeRepo(pair.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void fullReload() {
|
||||
Cursor c = app.repoDB.getRawCursor();
|
||||
runTasks(() -> {
|
||||
while (true) {
|
||||
Repo repo;
|
||||
synchronized (c) {
|
||||
if (!c.moveToNext())
|
||||
return;
|
||||
repo = new Repo(c);
|
||||
}
|
||||
try {
|
||||
repo.update();
|
||||
app.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
app.repoDB.removeRepo(repo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void exec(boolean force) {
|
||||
Topic.reset(Topic.REPO_LOAD_DONE);
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
cached = Collections.synchronizedSet(app.repoDB.getRepoIDSet());
|
||||
moduleQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
if (loadPages()) {
|
||||
// The leftover cached means they are removed from online repo
|
||||
app.repoDB.removeRepo(cached);
|
||||
} else if (force) {
|
||||
fullReload();
|
||||
}
|
||||
Topic.publish(Topic.REPO_LOAD_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
exec(false);
|
||||
}
|
||||
}
|
@@ -1,26 +1,17 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
@@ -35,11 +26,10 @@ public abstract class FingerprintHelper {
|
||||
private Cipher cipher;
|
||||
private CancellationSignal cancel;
|
||||
|
||||
public static boolean useFingerPrint() {
|
||||
MagiskManager mm = Data.MM();
|
||||
boolean fp = mm.mDB.getSettings(Const.Key.SU_FINGERPRINT, 0) != 0;
|
||||
public static boolean useFingerprint() {
|
||||
boolean fp = Config.get(Config.Key.SU_FINGERPRINT);
|
||||
if (fp && !canUseFingerprint()) {
|
||||
mm.mDB.setSettings(Const.Key.SU_FINGERPRINT, 0);
|
||||
Config.set(Config.Key.SU_FINGERPRINT, false);
|
||||
fp = false;
|
||||
}
|
||||
return fp;
|
||||
@@ -48,65 +38,14 @@ public abstract class FingerprintHelper {
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
MagiskManager mm = Data.MM();
|
||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
||||
KeyguardManager km = App.self.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = App.self.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
public static void showAuthDialog(Activity activity, Runnable onSuccess) {
|
||||
CustomAlertDialog dialog = new CustomAlertDialog(activity);
|
||||
CustomAlertDialog.ViewHolder vh = dialog.getViewHolder();
|
||||
try {
|
||||
FingerprintHelper helper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(R.string.auth_fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
dialog.dismiss();
|
||||
onSuccess.run();
|
||||
}
|
||||
};
|
||||
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
|
||||
Resources.Theme theme = activity.getTheme();
|
||||
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
|
||||
fingerprint.setTint(ta.getColor(0, Color.GRAY));
|
||||
ta.recycle();
|
||||
vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
|
||||
vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
|
||||
vh.messageView.setGravity(Gravity.CENTER);
|
||||
dialog.setMessage(R.string.auth_fingerprint)
|
||||
.setNegativeButton(R.string.close, (d, w) -> helper.cancel())
|
||||
.setOnCancelListener(d -> helper.cancel())
|
||||
.show();
|
||||
helper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = Data.MM().getSystemService(FingerprintManager.class);
|
||||
manager = App.self.getSystemService(FingerprintManager.class);
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
@@ -0,0 +1,152 @@
|
||||
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 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;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
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 (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = forLanguageTag(localeConfig);
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale));
|
||||
}
|
||||
|
||||
public static Context getLocaleContext(Context context, Locale locale) {
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
Configuration config = new Configuration(context.getResources().getConfiguration());
|
||||
config.setLocale(locale);
|
||||
return context.createConfigurationContext(config);
|
||||
} else {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (Build.VERSION.SDK_INT < 17)
|
||||
return;
|
||||
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)));
|
||||
Topic.publish(Topic.LOCALE_FETCH_DONE);
|
||||
});
|
||||
}
|
||||
}
|
@@ -2,8 +2,8 @@ package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.core.BuildConfig;
|
||||
|
||||
public class Logger {
|
||||
|
@@ -2,29 +2,19 @@ package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.magisk.core.R;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class RootUtils extends Shell.Initializer {
|
||||
|
||||
static {
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
}
|
||||
|
||||
public static void uninstallPkg(String pkg) {
|
||||
Shell.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg).exec();
|
||||
}
|
||||
|
||||
public static void rmAndLaunch(String rm, String launch) {
|
||||
Shell.su(Utils.fmt("(rm_launch %d %s %s)&", Const.USER_ID, rm, launch)).exec();
|
||||
}
|
||||
@@ -33,13 +23,10 @@ public class RootUtils extends Shell.Initializer {
|
||||
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||
Shell.Job job = shell.newJob();
|
||||
if (shell.isRoot()) {
|
||||
if (!new SuFile("/sbin/.magisk").exists())
|
||||
job.add("ln -s /sbin/.core /sbin/.magisk");
|
||||
|
||||
job.add(context.getResources().openRawResource(R.raw.util_functions))
|
||||
.add(context.getResources().openRawResource(R.raw.utils));
|
||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||
Data.loadMagiskInfo();
|
||||
Config.loadMagiskInfo();
|
||||
} else {
|
||||
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
||||
job.add(nonroot);
|
||||
@@ -47,8 +34,9 @@ public class RootUtils extends Shell.Initializer {
|
||||
|
||||
job.add("mount_partitions", "get_flags", "run_migrations", "export BOOTMODE=true").exec();
|
||||
|
||||
Data.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Data.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
Config.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Config.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
Config.recovery = Boolean.parseBoolean(ShellUtils.fastCmd("echo $RECOVERYMODE"));
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class SuConnector {
|
||||
|
||||
private LocalSocket socket;
|
||||
protected DataOutputStream out;
|
||||
protected DataInputStream in;
|
||||
|
||||
protected SuConnector(String name) throws IOException {
|
||||
socket = new LocalSocket();
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
||||
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
|
||||
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
|
||||
}
|
||||
|
||||
private String readString() throws IOException {
|
||||
int len = in.readInt();
|
||||
byte[] buf = new byte[len];
|
||||
in.readFully(buf);
|
||||
return new String(buf, "UTF-8");
|
||||
}
|
||||
|
||||
public Bundle readSocketInput() throws IOException {
|
||||
Bundle bundle = new Bundle();
|
||||
while (true) {
|
||||
String name = readString();
|
||||
if (TextUtils.equals(name, "eof"))
|
||||
break;
|
||||
bundle.putString(name, readString());
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public void response() {
|
||||
try {
|
||||
onResponse();
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
|
||||
protected abstract void onResponse() throws IOException;
|
||||
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
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.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class SuLogger {
|
||||
|
||||
public 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.mDB.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.mDB.addLog(log);
|
||||
}
|
||||
|
||||
private void handleNotify(Policy policy) {
|
||||
if (policy.notification &&
|
||||
(int) Config.get(Config.Key.SU_NOTIFICATION) == Config.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(getMessage(policy), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
public 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) {}
|
||||
}
|
||||
|
||||
public abstract String getMessage(Policy policy);
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -15,16 +15,15 @@ public class Topic {
|
||||
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 SNET_CHECK_DONE = 4;
|
||||
public static final int LOCALE_FETCH_DONE = 5;
|
||||
public static final int LOCALE_FETCH_DONE = 4;
|
||||
|
||||
@IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
||||
UPDATE_CHECK_DONE, SNET_CHECK_DONE, LOCALE_FETCH_DONE})
|
||||
UPDATE_CHECK_DONE, LOCALE_FETCH_DONE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TopicID {}
|
||||
|
||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
||||
private static Store[] topicList = new Store[6];
|
||||
private static Store[] topicList = new Store[5];
|
||||
|
||||
public static void subscribe(Subscriber sub, @TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
@@ -38,8 +37,7 @@ public class Topic {
|
||||
}
|
||||
|
||||
public static void subscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
subscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
subscribe(sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void unsubscribe(Subscriber sub, @TopicID int... topics) {
|
||||
@@ -51,8 +49,7 @@ public class Topic {
|
||||
}
|
||||
|
||||
public static void unsubscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
unsubscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
unsubscribe(sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void publish(@TopicID int topic, Object... results) {
|
||||
@@ -67,7 +64,7 @@ public class Topic {
|
||||
topicList[topic].published = true;
|
||||
}
|
||||
for (Subscriber sub : topicList[topic].subscribers) {
|
||||
Data.mainHandler.post(() -> sub.onPublish(topic, results));
|
||||
UiThreadHandler.run(() -> sub.onPublish(topic, results));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +87,10 @@ public class Topic {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isPublished(AutoSubscriber sub) {
|
||||
return isPublished(sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
private static class Store {
|
||||
boolean published = false;
|
||||
Set<Subscriber> subscribers = new HashSet<>();
|
||||
@@ -100,7 +101,7 @@ public class Topic {
|
||||
void onPublish(int topic, Object[] result);
|
||||
}
|
||||
|
||||
public interface AutoSubscriber {
|
||||
public interface AutoSubscriber extends Subscriber {
|
||||
@TopicID
|
||||
int[] getSubscribedTopics();
|
||||
}
|
@@ -1,10 +1,6 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -12,19 +8,18 @@ import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.androidnetworking.AndroidNetworking;
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.util.Locale;
|
||||
@@ -32,6 +27,19 @@ import java.util.Map;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, resId, duration).show());
|
||||
}
|
||||
|
||||
public static String dlString(String url) {
|
||||
String s = Networking.get(url).execForString().getResult();
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
@@ -59,7 +67,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static int dpInPx(int dp) {
|
||||
float scale = Data.MM().getResources().getDisplayMetrics().density;
|
||||
float scale = App.self.getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5);
|
||||
}
|
||||
|
||||
@@ -67,56 +75,28 @@ public class Utils {
|
||||
return String.format(Locale.US, fmt, args);
|
||||
}
|
||||
|
||||
public static String dos2unix(String s) {
|
||||
String newString = s.replace("\r\n", "\n");
|
||||
if(!newString.endsWith("\n")) {
|
||||
return newString + "\n";
|
||||
} else {
|
||||
return newString;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupUpdateCheck() {
|
||||
MagiskManager mm = Data.MM();
|
||||
JobScheduler scheduler = (JobScheduler) mm.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (mm.prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(mm, Data.classMap.get(UpdateCheckService.class));
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
scheduler.schedule(info);
|
||||
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
|
||||
try {
|
||||
if (info.labelRes > 0 && Build.VERSION.SDK_INT >= 17) {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
}
|
||||
} else {
|
||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
public static void openLink(Context context, Uri link) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, link);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
Data.mainHandler.post(() -> Toast.makeText(Data.MM(), msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
Data.mainHandler.post(() -> Toast.makeText(Data.MM(), resId, duration).show());
|
||||
public static String getLegalFilename(CharSequence filename) {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||
.replace("#", "").replace("@", "").replace("\\", "_");
|
||||
}
|
||||
|
||||
public static void loadModules() {
|
||||
Topic.reset(Topic.MODULE_LOAD_DONE);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||
SuFile[] modules = path.listFiles(
|
||||
@@ -130,29 +110,18 @@ public class Utils {
|
||||
});
|
||||
}
|
||||
|
||||
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
|
||||
try {
|
||||
if (info.labelRes > 0) {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
public static boolean showSuperUser() {
|
||||
if (Data.multiuserState < 0)
|
||||
Data.multiuserState = Data.MM().mDB.getSettings(Const.Key.SU_MULTIUSER_MODE,
|
||||
Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
return Shell.rootAccess() && (Const.USER_ID == 0 ||
|
||||
Data.multiuserState != Const.Value.MULTIUSER_MODE_OWNER_MANAGED);
|
||||
(int) Config.get(Config.Key.SU_MULTIUSER_MODE) !=
|
||||
Config.Value.MULTIUSER_MODE_OWNER_MANAGED);
|
||||
}
|
||||
|
||||
public static String dlString(String url) {
|
||||
String s = (String) AndroidNetworking.get(url).build().executeForString().getResult();
|
||||
return s == null ? "" : s;
|
||||
public static Context getDEContext() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
App.self.createDeviceProtectedStorageContext() : App.self;
|
||||
}
|
||||
}
|
||||
|
||||
public static void reboot() {
|
||||
Shell.su("/system/bin/reboot" + (Config.recovery ? " recovery" : "")).submit();
|
||||
}
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.signing.JarMap;
|
||||
import com.topjohnwu.signing.SignAPK;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
@@ -1,42 +1,3 @@
|
||||
db_sepatch() {
|
||||
magiskpolicy --live 'create magisk_file' 'attradd magisk_file mlstrustedobject' \
|
||||
'allow * magisk_file file *' 'allow * magisk_file dir *' \
|
||||
'allow magisk_file * filesystem associate'
|
||||
}
|
||||
|
||||
db_clean() {
|
||||
local USERID=$1
|
||||
local DIR="/sbin/.magisk/db-${USERID}"
|
||||
umount -l /data/user*/*/*/databases/su.db $DIR $DIR/*
|
||||
rm -rf $DIR
|
||||
[ "$USERID" = "*" ] && rm -fv /data/adb/magisk.db*
|
||||
}
|
||||
|
||||
db_init() {
|
||||
# Temporary let the folder rw by anyone
|
||||
chcon u:object_r:magisk_file:s0 /data/adb
|
||||
chmod 777 /data/adb
|
||||
}
|
||||
|
||||
db_restore() {
|
||||
chmod 700 /data/adb
|
||||
magisk --restorecon
|
||||
}
|
||||
|
||||
db_setup() {
|
||||
local USER=$1
|
||||
local USERID=$(($USER / 100000))
|
||||
local DIR=/sbin/.magisk/db-${USERID}
|
||||
mkdir -p $DIR
|
||||
touch $DIR/magisk.db
|
||||
mount -o bind /data/adb/magisk.db $DIR/magisk.db
|
||||
rm -f /data/adb/magisk.db-*
|
||||
chcon u:object_r:magisk_file:s0 $DIR $DIR/*
|
||||
chmod 700 $DIR
|
||||
chown $USER.$USER $DIR
|
||||
chmod 666 $DIR/*
|
||||
}
|
||||
|
||||
env_check() {
|
||||
for file in busybox magisk magiskboot magiskinit util_functions.sh boot_patch.sh; do
|
||||
[ -f /data/adb/magisk/$file ] || return 1
|
||||
@@ -46,11 +7,14 @@ env_check() {
|
||||
|
||||
fix_env() {
|
||||
cd /data/adb/magisk
|
||||
local OLDPATH="$PATH"
|
||||
PATH=/sbin:/system/bin:/vendor/bin
|
||||
sh update-binary extract
|
||||
PATH="$OLDPATH"
|
||||
./busybox rm -f /sbin/.magisk/busybox/*
|
||||
/sbin/.magisk/mirror/bin/busybox --install -s /sbin/.magisk/busybox
|
||||
rm -f update-binary magisk.apk
|
||||
cd /
|
||||
rm -rf /sbin/.magisk/busybox/*
|
||||
/sbin/.magisk/mirror/bin/busybox --install -s /sbin/.magisk/busybox
|
||||
}
|
||||
|
||||
direct_install() {
|
||||
@@ -105,7 +69,7 @@ post_ota() {
|
||||
./bootctl hal-info || return
|
||||
[ `./bootctl get-current-slot` -eq 0 ] && SLOT_NUM=1 || SLOT_NUM=0
|
||||
./bootctl set-active-boot-slot $SLOT_NUM
|
||||
echo '${0%/*}/../bootctl mark-boot-successful;rm -f ${0%/*}/../bootctl $0' > post-fs-data.d/post_ota.sh
|
||||
echo "BCTRL=${1}/bootctl;\$BCTRL mark-boot-successful;rm -f \$BCTRL \$0" > post-fs-data.d/post_ota.sh
|
||||
chmod 755 post-fs-data.d/post_ota.sh
|
||||
cd /
|
||||
}
|
||||
@@ -139,6 +103,6 @@ EOF
|
||||
rm_launch() {
|
||||
db_clean $1
|
||||
pm uninstall $2
|
||||
monkey -p $3 1
|
||||
am start -n ${3}/a.c
|
||||
exit
|
||||
}
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -6,7 +6,6 @@
|
||||
app/release
|
||||
*.hprof
|
||||
.externalNativeBuild/
|
||||
src/full/res/raw/util_functions.sh
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
||||
|
@@ -5,13 +5,9 @@ def configPath = project.hasProperty('configPath') ? project.configPath : rootPr
|
||||
configProps.load(new FileInputStream(configPath))
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
applicationId 'com.topjohnwu.magisk'
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -37,7 +33,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "mode"
|
||||
flavorDimensions 'mode'
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
@@ -51,18 +47,10 @@ android {
|
||||
}
|
||||
stub {
|
||||
versionCode 1
|
||||
versionName "stub"
|
||||
versionName 'stub'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
@@ -70,23 +58,24 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'androidx.core:core:1.0.1'
|
||||
fullImplementation project(':utils')
|
||||
fullImplementation 'com.amitshekhar.android:android-networking:1.0.2'
|
||||
fullImplementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
fullImplementation "androidx.preference:preference:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "androidx.recyclerview:recyclerview:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "androidx.cardview:cardview:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "com.google.android.material:material:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation 'com.github.topjohnwu:libsu:2.1.2'
|
||||
fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
|
||||
fullImplementation 'org.kamranzafar:jtar:2.3'
|
||||
implementation project(':net')
|
||||
fullImplementation project(':app-core')
|
||||
fullImplementation 'ru.noties:markwon:2.0.1'
|
||||
fullImplementation 'com.caverock:androidsvg-aar:1.3'
|
||||
|
||||
def butterKnifeVersion = '9.0.0-rc2'
|
||||
if (properties.containsKey('android.injected.invoked.from.ide')) {
|
||||
fullImplementation "com.jakewharton:butterknife-reflect:${butterKnifeVersion}"
|
||||
} else {
|
||||
fullImplementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}"
|
||||
fullAnnotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
|
||||
}
|
||||
def androidXVersion = "1.0.0"
|
||||
implementation 'androidx.core:core:1.0.1'
|
||||
fullImplementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
fullImplementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
fullImplementation "androidx.preference:preference:${androidXVersion}"
|
||||
fullImplementation "androidx.recyclerview:recyclerview:${androidXVersion}"
|
||||
fullImplementation "androidx.cardview:cardview:${androidXVersion}"
|
||||
fullImplementation "com.google.android.material:material:${androidXVersion}"
|
||||
fullImplementation 'android.arch.work:work-runtime:1.0.0-beta03'
|
||||
fullImplementation 'androidx.room:room-runtime:2.0.0'
|
||||
fullImplementation 'androidx.transition:transition:1.0.1'
|
||||
|
||||
def butterKnifeVersion = '10.0.0'
|
||||
fullImplementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}"
|
||||
fullAnnotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
|
||||
}
|
||||
|
13
app/proguard-rules.pro
vendored
13
app/proguard-rules.pro
vendored
@@ -22,11 +22,18 @@
|
||||
-keep,allowoptimization class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Snet extention
|
||||
# Snet
|
||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
|
||||
-keepclassmembers class * implements com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback {
|
||||
void onResponse(int);
|
||||
}
|
||||
|
||||
# Fast Android Networking Library
|
||||
-dontwarn okhttp3.**
|
||||
# BootSigner
|
||||
-keepclassmembers class com.topjohnwu.signer.BootSigner { *; }
|
||||
|
||||
# SVG
|
||||
-dontwarn com.caverock.androidsvg.SVGAndroidRenderer
|
||||
|
||||
# Strip logging
|
||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||
|
@@ -5,13 +5,12 @@
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name="a.q"
|
||||
android:name="a.e"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||
|
||||
<!-- Activities -->
|
||||
|
||||
@@ -29,67 +28,42 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="a.d"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name="a.e"
|
||||
android:theme="@style/AppTheme.StatusBar"/>
|
||||
<activity
|
||||
android:name="a.f"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:screenOrientation="nosensor"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name="a.g"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
android:theme="@style/AppTheme.NoDrawer" />
|
||||
|
||||
<!-- Superuser -->
|
||||
|
||||
<activity
|
||||
android:name="a.m"
|
||||
android:directBootAware="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:taskAffinity="a.m"
|
||||
android:theme="@style/SuRequest" />
|
||||
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
|
||||
<receiver android:name=".superuser.SuReceiver" />
|
||||
|
||||
<!-- Receiver -->
|
||||
|
||||
<receiver android:name="a.h">
|
||||
<receiver
|
||||
android:name="a.h"
|
||||
android:directBootAware="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="a.i">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Service -->
|
||||
|
||||
<service
|
||||
android:name="a.j"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service
|
||||
android:name="a.k"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service android:name="a.j" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
|
@@ -1,10 +1,13 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.utils.BootSigner;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.signing.BootSigner;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class a extends BootSigner {
|
||||
/* stub */
|
||||
public static boolean patchAPK(String in, String out, String pkg) {
|
||||
return PatchAPK.patch(in, out, pkg);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.AboutActivity;
|
||||
|
||||
public class d extends AboutActivity {
|
||||
/* stub */
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.DonationActivity;
|
||||
import com.topjohnwu.magisk.App;
|
||||
|
||||
public class e extends DonationActivity {
|
||||
public class e extends App {
|
||||
/* stub */
|
||||
}
|
||||
|
@@ -1,7 +1,15 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.NoUIActivity;
|
||||
import android.content.Context;
|
||||
|
||||
public class g extends NoUIActivity {
|
||||
/* stub */
|
||||
import com.topjohnwu.magisk.components.UpdateCheckService;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
public class g extends w<UpdateCheckService> {
|
||||
/* Stub */
|
||||
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.GeneralReceiver;
|
||||
import com.topjohnwu.magisk.components.GeneralReceiver;
|
||||
|
||||
public class h extends GeneralReceiver {
|
||||
/* stub */
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
|
||||
public class i extends ShortcutReceiver {
|
||||
/* stub */
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.components.DownloadModuleService;
|
||||
|
||||
public class j extends OnBootService {
|
||||
public class j extends DownloadModuleService {
|
||||
/* stub */
|
||||
}
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
|
||||
public class k extends UpdateCheckService {
|
||||
/* stub */
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
package a;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
|
||||
public class l extends AboutCardRow {
|
||||
/* stub */
|
||||
|
||||
public l(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public l(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public l(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
public class q extends MagiskManager {
|
||||
/* stub */
|
||||
}
|
42
app/src/full/java/a/w.java
Normal file
42
app/src/full/java/a/w.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package a;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.components.DelegateWorker;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
public abstract class w<T extends DelegateWorker> extends Worker {
|
||||
|
||||
/* Wrapper class to workaround Proguard -keep class * extends Worker */
|
||||
|
||||
private T base;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
try {
|
||||
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
||||
.getActualTypeArguments()[0]).newInstance();
|
||||
base.setActualWorker(this);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
if (base == null)
|
||||
return Result.failure();
|
||||
return base.doWork();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped() {
|
||||
if (base != null)
|
||||
base.onStopped();
|
||||
}
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class AboutActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
||||
@BindView(R.id.app_changelog) AboutCardRow appChangelog;
|
||||
@BindView(R.id.app_translators) AboutCardRow appTranslators;
|
||||
@BindView(R.id.app_source_code) AboutCardRow appSourceCode;
|
||||
@BindView(R.id.support_thread) AboutCardRow supportThread;
|
||||
@BindView(R.id.follow_twitter) AboutCardRow twitter;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
new AboutActivity_ViewBinding(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.about);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d) (%s)",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getPackageName()));
|
||||
|
||||
appChangelog.setOnClickListener(v -> {
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
});
|
||||
|
||||
String translators = getString(R.string.translators);
|
||||
if (TextUtils.isEmpty(translators)) {
|
||||
appTranslators.setVisibility(View.GONE);
|
||||
} else {
|
||||
appTranslators.setSummary(translators);
|
||||
}
|
||||
|
||||
appSourceCode.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.SOURCE_CODE_URL)));
|
||||
supportThread.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.XDA_THREAD)));
|
||||
twitter.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.TWITTER_URL)));
|
||||
|
||||
setFloating();
|
||||
}
|
||||
|
||||
}
|
27
app/src/full/java/com/topjohnwu/magisk/ClassMap.java
Normal file
27
app/src/full/java/com/topjohnwu/magisk/ClassMap.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import com.topjohnwu.magisk.components.DownloadModuleService;
|
||||
import com.topjohnwu.magisk.components.GeneralReceiver;
|
||||
import com.topjohnwu.magisk.components.UpdateCheckService;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,195 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.receivers.GeneralReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Data {
|
||||
// Global app instance
|
||||
public static WeakReference<MagiskManager> weakApp;
|
||||
public static Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
public static Map<Class, Class> classMap = new HashMap<>();
|
||||
|
||||
// Current status
|
||||
public static String magiskVersionString;
|
||||
public static int magiskVersionCode = -1;
|
||||
public static boolean magiskHide;
|
||||
|
||||
// Update Info
|
||||
public static String remoteMagiskVersionString;
|
||||
public static int remoteMagiskVersionCode = -1;
|
||||
public static String magiskLink;
|
||||
public static String magiskNoteLink;
|
||||
public static String magiskMD5;
|
||||
public static String remoteManagerVersionString;
|
||||
public static int remoteManagerVersionCode = -1;
|
||||
public static String managerLink;
|
||||
public static String managerNoteLink;
|
||||
public static String uninstallerLink;
|
||||
|
||||
// Install flags
|
||||
public static boolean keepVerity = false;
|
||||
public static boolean keepEnc = false;
|
||||
|
||||
// Configs
|
||||
public static boolean isDarkTheme;
|
||||
public static int suRequestTimeout;
|
||||
public static int suLogTimeout = 14;
|
||||
public static int multiuserState = -1;
|
||||
public static int suResponseType;
|
||||
public static int suNotificationType;
|
||||
public static int updateChannel;
|
||||
public static int repoOrder;
|
||||
|
||||
static {
|
||||
classMap.put(MagiskManager.class, a.q.class);
|
||||
classMap.put(MainActivity.class, a.b.class);
|
||||
classMap.put(SplashActivity.class, a.c.class);
|
||||
classMap.put(AboutActivity.class, a.d.class);
|
||||
classMap.put(DonationActivity.class, a.e.class);
|
||||
classMap.put(FlashActivity.class, a.f.class);
|
||||
classMap.put(NoUIActivity.class, a.g.class);
|
||||
classMap.put(GeneralReceiver.class, a.h.class);
|
||||
classMap.put(ShortcutReceiver.class, a.i.class);
|
||||
classMap.put(OnBootService.class, a.j.class);
|
||||
classMap.put(UpdateCheckService.class, a.k.class);
|
||||
classMap.put(AboutCardRow.class, a.l.class);
|
||||
classMap.put(SuRequestActivity.class, a.m.class);
|
||||
|
||||
}
|
||||
|
||||
public static void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
if (magiskVersionCode >= Const.MAGISK_VER.HIDE_STATUS) {
|
||||
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
||||
} else {
|
||||
String s = ShellUtils.fastCmd(("resetprop -p ") + Const.MAGISKHIDE_PROP);
|
||||
magiskHide = s.isEmpty() || Integer.parseInt(s) != 0;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public static MagiskManager MM() {
|
||||
return weakApp.get();
|
||||
}
|
||||
|
||||
public static void exportPrefs() {
|
||||
// Flush prefs to disk
|
||||
MagiskManager mm = MM();
|
||||
mm.prefs.edit().commit();
|
||||
File xml = new File(mm.getFilesDir().getParent() + "/shared_prefs",
|
||||
mm.getPackageName() + "_preferences.xml");
|
||||
Shell.su(Utils.fmt("cat %s > /data/user/0/%s", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
public static void importPrefs() {
|
||||
MagiskManager mm = MM();
|
||||
SuFile config = new SuFile("/data/user/0/" + Const.MANAGER_CONFIGS);
|
||||
if (config.exists()) {
|
||||
SharedPreferences.Editor editor = mm.prefs.edit();
|
||||
try {
|
||||
SuFileInputStream is = new SuFileInputStream(config);
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(is, "UTF-8");
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
continue;
|
||||
String key = parser.getAttributeValue(null, "name");
|
||||
String value = parser.getAttributeValue(null, "value");
|
||||
switch (parser.getName()) {
|
||||
case "string":
|
||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
||||
editor.putString(key, parser.nextText());
|
||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
||||
break;
|
||||
case "boolean":
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
||||
break;
|
||||
case "int":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putInt(key, Integer.parseInt(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
case "long":
|
||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
||||
editor.putLong(key, Long.parseLong(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
||||
break;
|
||||
case "float":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putFloat(key, Float.parseFloat(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
default:
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
editor.remove(Const.Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
loadConfig();
|
||||
config.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadConfig() {
|
||||
MagiskManager mm = MM();
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(mm.prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(mm.prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(mm.prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
|
||||
// config
|
||||
isDarkTheme = mm.prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateChannel = Utils.getPrefsInt(mm.prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
repoOrder = mm.prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_DATE);
|
||||
}
|
||||
|
||||
public static void writeConfig() {
|
||||
MM().prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class DonationActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.paypal) AboutCardRow paypal;
|
||||
@BindView(R.id.patreon) AboutCardRow patreon;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_donation);
|
||||
new DonationActivity_ViewBinding(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.donation);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
paypal.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.PAYPAL_URL)));
|
||||
patreon.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.PATREON_URL)));
|
||||
}
|
||||
}
|
@@ -1,56 +1,57 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.adapters.StringListAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.tasks.FlashZip;
|
||||
import com.topjohnwu.magisk.tasks.MagiskInstaller;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class FlashActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.txtLog) TextView flashLogs;
|
||||
@BindView(R.id.button_panel) public LinearLayout buttonPanel;
|
||||
@BindView(R.id.reboot) public Button reboot;
|
||||
@BindView(R.id.scrollView) ScrollView sv;
|
||||
@BindView(R.id.button_panel) LinearLayout buttonPanel;
|
||||
@BindView(R.id.reboot) Button reboot;
|
||||
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||
@BindColor(android.R.color.white) int white;
|
||||
|
||||
private List<String> logs;
|
||||
private List<String> console, logs;
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void reboot() {
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
Utils.reboot();
|
||||
}
|
||||
|
||||
@OnClick(R.id.save_logs)
|
||||
void saveLogs() {
|
||||
runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
|
||||
runWithExternalRW(() -> {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
@@ -72,9 +73,19 @@ public class FlashActivity extends BaseActivity {
|
||||
});
|
||||
}
|
||||
|
||||
@OnClick(R.id.close)
|
||||
public void close() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
return R.style.AppTheme_NoDrawer_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,77 +104,167 @@ public class FlashActivity extends BaseActivity {
|
||||
if (!Shell.rootAccess())
|
||||
reboot.setVisibility(View.GONE);
|
||||
|
||||
logs = new ArrayList<>();
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
logs = Collections.synchronizedList(new ArrayList<>());
|
||||
console = new ConsoleList();
|
||||
rv.setAdapter(new ConsoleAdapter());
|
||||
|
||||
private void updateUI() {
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String set(int i, String s) {
|
||||
String ret = super.set(i, s);
|
||||
Data.mainHandler.post(this::updateUI);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
// We must receive a Uri of the target zip
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
||||
case Const.Value.FLASH_ZIP:
|
||||
new FlashZip(this, uri, console, logs).exec();
|
||||
new FlashModule(uri).exec();
|
||||
break;
|
||||
case Const.Value.UNINSTALL:
|
||||
new UninstallMagisk(this, uri, console, logs).exec();
|
||||
new Uninstall(uri).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.DIRECT_MODE).exec();
|
||||
new DirectInstall().exec();
|
||||
break;
|
||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
new SecondSlot().exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs,
|
||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
|
||||
new PatchBoot(uri).exec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.close)
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
}
|
||||
private class ConsoleAdapter extends StringListAdapter<ConsoleAdapter.ViewHolder> {
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
private static class UninstallMagisk extends FlashZip {
|
||||
|
||||
private UninstallMagisk(BaseActivity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context, uri, console, logs);
|
||||
ConsoleAdapter() {
|
||||
super(console, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
if (result == 1) {
|
||||
Data.mainHandler.postDelayed(() ->
|
||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
||||
} else {
|
||||
super.onPostExecute(result);
|
||||
protected int itemLayoutRes() {
|
||||
return R.layout.list_item_console;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder createViewHolder(@NonNull View v) {
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
class ViewHolder extends StringListAdapter.ViewHolder {
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
txt.setTextColor(white);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int textViewResId() {
|
||||
return R.id.txt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ConsoleList extends CallbackList<String> {
|
||||
|
||||
ConsoleList() {
|
||||
super(new ArrayList<>());
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
rv.getAdapter().notifyItemChanged(size() - 1);
|
||||
rv.postDelayed(() -> rv.smoothScrollToPosition(size() - 1), 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String set(int i, String s) {
|
||||
String ret = super.set(i, s);
|
||||
UiThreadHandler.run(this::updateUI);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlashModule extends FlashZip {
|
||||
|
||||
FlashModule(Uri uri) {
|
||||
super(uri, console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
if (success) {
|
||||
Utils.loadModules();
|
||||
} else {
|
||||
console.add("! Installation failed");
|
||||
reboot.setVisibility(View.GONE);
|
||||
}
|
||||
buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private class Uninstall extends FlashModule {
|
||||
|
||||
Uninstall(Uri uri) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(Shell.su("pm uninstall " + getPackageName())::exec, 3000);
|
||||
else
|
||||
super.onResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class BaseInstaller extends MagiskInstaller {
|
||||
BaseInstaller() {
|
||||
super(console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
if (success) {
|
||||
console.add("- All done!");
|
||||
} else {
|
||||
Shell.sh("rm -rf " + installDir).submit();
|
||||
console.add("! Installation failed");
|
||||
reboot.setVisibility(View.GONE);
|
||||
}
|
||||
buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private class DirectInstall extends BaseInstaller {
|
||||
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
return findImage() && extractZip() && patchBoot() && flashBoot();
|
||||
}
|
||||
}
|
||||
|
||||
private class SecondSlot extends BaseInstaller {
|
||||
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
return findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA();
|
||||
}
|
||||
}
|
||||
|
||||
private class PatchBoot extends BaseInstaller {
|
||||
|
||||
private Uri uri;
|
||||
|
||||
PatchBoot(Uri u) {
|
||||
uri = u;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
return copyBoot(uri) && extractZip() && patchBoot() && storeBoot();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,52 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.ContainerApp;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MagiskManager extends ContainerApp {
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDB mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
public MagiskManager() {
|
||||
Data.weakApp = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.setInitializer(RootUtils.class);
|
||||
Shell.Config.setTimeout(2);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mDB = MagiskDB.getInstance();
|
||||
repoDB = new RepoDatabaseHelper(this);
|
||||
|
||||
LocaleManager.setLocale(this);
|
||||
Data.loadConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.Menu;
|
||||
@@ -16,9 +17,9 @@ import com.topjohnwu.magisk.fragments.ModulesFragment;
|
||||
import com.topjohnwu.magisk.fragments.ReposFragment;
|
||||
import com.topjohnwu.magisk.fragments.SettingsFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -49,8 +50,8 @@ public class MainActivity extends BaseActivity
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
if (!mm.hasInit) {
|
||||
startActivity(new Intent(this, Data.classMap.get(SplashActivity.class)));
|
||||
if (!getIntent().getBooleanExtra(Const.Key.FROM_SPLASH, false)) {
|
||||
startActivity(new Intent(this, ClassMap.get(SplashActivity.class)));
|
||||
finish();
|
||||
}
|
||||
|
||||
@@ -73,7 +74,9 @@ public class MainActivity extends BaseActivity
|
||||
}
|
||||
};
|
||||
|
||||
toolbarElevation = toolbar.getElevation();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
toolbarElevation = toolbar.getElevation();
|
||||
}
|
||||
|
||||
drawer.addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
@@ -120,10 +123,10 @@ public class MainActivity extends BaseActivity
|
||||
public void checkHideSection() {
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() &&
|
||||
mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Download.checkNetworkStatus(this)
|
||||
&& Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
(boolean) Config.get(Config.Key.MAGISKHIDE));
|
||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Config.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Networking.checkNetworkStatus(this)
|
||||
&& Shell.rootAccess() && Config.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||
menu.findItem(R.id.superuser).setVisible(Utils.showSuperUser());
|
||||
}
|
||||
@@ -150,19 +153,12 @@ public class MainActivity extends BaseActivity
|
||||
case "settings":
|
||||
itemId = R.id.settings;
|
||||
break;
|
||||
case "about":
|
||||
itemId = R.id.app_about;
|
||||
break;
|
||||
case "donation":
|
||||
itemId = R.id.donation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
navigate(itemId);
|
||||
}
|
||||
|
||||
public void navigate(int itemId) {
|
||||
int bak = mDrawerItem;
|
||||
mDrawerItem = itemId;
|
||||
navigationView.setCheckedItem(itemId);
|
||||
switch (itemId) {
|
||||
@@ -188,14 +184,6 @@ public class MainActivity extends BaseActivity
|
||||
case R.id.settings:
|
||||
displayFragment(new SettingsFragment(), true);
|
||||
break;
|
||||
case R.id.app_about:
|
||||
startActivity(new Intent(this, Data.classMap.get(AboutActivity.class)));
|
||||
mDrawerItem = bak;
|
||||
break;
|
||||
case R.id.donation:
|
||||
startActivity(new Intent(this, Data.classMap.get(DonationActivity.class)));
|
||||
mDrawerItem = bak;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +194,8 @@ public class MainActivity extends BaseActivity
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(R.id.content_frame, navFragment)
|
||||
.commitNow();
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class NoUIActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -5,69 +5,84 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.Notifications;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||
import com.topjohnwu.magisk.uicomponents.Notifications;
|
||||
import com.topjohnwu.magisk.uicomponents.Shortcuts;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class SplashActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String pkg = mm.mDB.getStrings(Const.Key.SU_MANAGER, null);
|
||||
Shell.getShell(shell -> {
|
||||
if (Config.magiskVersionCode > 0 &&
|
||||
Config.magiskVersionCode < Const.MAGISK_VER.MIN_SUPPORT) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unsupport_magisk_title)
|
||||
.setMessage(R.string.unsupport_magisk_message)
|
||||
.setNegativeButton(R.string.ok, null)
|
||||
.setOnDismissListener(dialog -> finish())
|
||||
.show();
|
||||
} else {
|
||||
initAndStart();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initAndStart() {
|
||||
String pkg = Config.get(Config.Key.SU_MANAGER);
|
||||
if (pkg != null && getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, null);
|
||||
Shell.su("pm uninstall " + pkg).exec();
|
||||
Config.remove(Config.Key.SU_MANAGER);
|
||||
Shell.su("pm uninstall " + pkg).submit();
|
||||
}
|
||||
if (TextUtils.equals(pkg, getPackageName())) {
|
||||
try {
|
||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||
getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
|
||||
RootUtils.uninstallPkg(BuildConfig.APPLICATION_ID);
|
||||
Shell.su("pm uninstall " + BuildConfig.APPLICATION_ID).submit();
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && Data.magiskVersionCode > 0) {
|
||||
// Update check service
|
||||
Utils.setupUpdateCheck();
|
||||
// Load modules
|
||||
Utils.loadModules();
|
||||
}
|
||||
|
||||
Data.importPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
LocaleManager.loadAvailableLocales();
|
||||
LocaleManager.loadAvailableLocales(R.string.app_changelog);
|
||||
|
||||
// Set default configs
|
||||
Config.initialize();
|
||||
|
||||
// Create notification channel on Android O
|
||||
Notifications.setup(this);
|
||||
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, Data.classMap.get(ShortcutReceiver.class)));
|
||||
// Schedule periodic update checks
|
||||
AppUtils.scheduleUpdateCheck();
|
||||
|
||||
if (Download.checkNetworkStatus(this)) {
|
||||
// Fire update check
|
||||
CheckUpdates.check();
|
||||
// Repo update check
|
||||
new UpdateRepos().exec();
|
||||
// Setup shortcuts
|
||||
Shortcuts.setup(this);
|
||||
|
||||
// Create repo database
|
||||
app.repoDB = new RepoDatabaseHelper(this);
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
|
||||
// Load modules
|
||||
Utils.loadModules();
|
||||
// Load repos
|
||||
if (Networking.checkNetworkStatus(this))
|
||||
new UpdateRepos().exec();
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
Data.writeConfig();
|
||||
|
||||
mm.hasInit = true;
|
||||
|
||||
Intent intent = new Intent(this, Data.classMap.get(MainActivity.class));
|
||||
Intent intent = new Intent(this, ClassMap.get(MainActivity.class));
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
intent.putExtra(Const.Key.FROM_SPLASH, true);
|
||||
intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.INTENT_PERM));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
@@ -21,10 +21,12 @@ import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuRequestActivity extends BaseActivity {
|
||||
@@ -42,48 +44,7 @@ public class SuRequestActivity extends BaseActivity {
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
class SuConnectorV1 extends SuConnector {
|
||||
|
||||
SuConnectorV1(String name) throws IOException {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
new FileObserver(name) {
|
||||
@Override
|
||||
public void onEvent(int fileEvent, String path) {
|
||||
if (fileEvent == FileObserver.DELETE_SELF) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}.startWatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse() throws IOException {
|
||||
out.write((policy.policy == Policy.ALLOW ? "socket:ALLOW" : "socket:DENY").getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
class SuConnectorV2 extends SuConnector {
|
||||
|
||||
SuConnectorV2(String name) throws IOException {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse() throws IOException {
|
||||
out.writeInt(policy.policy);
|
||||
}
|
||||
}
|
||||
private SharedPreferences timeoutPrefs;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
@@ -114,17 +75,22 @@ public class SuRequestActivity extends BaseActivity {
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
mm.mDB.clearOutdated();
|
||||
app.mDB.clearOutdated();
|
||||
timeoutPrefs = Utils.getDEContext().getSharedPreferences("su_timeout", 0);
|
||||
|
||||
// Get policy
|
||||
Intent intent = getIntent();
|
||||
try {
|
||||
String socketName = intent.getStringExtra("socket");
|
||||
connector = intent.getIntExtra("version", 1) == 1 ?
|
||||
new SuConnectorV1(socketName) : new SuConnectorV2(socketName);
|
||||
connector = new SuConnector(socketName) {
|
||||
@Override
|
||||
protected void onResponse() throws IOException {
|
||||
out.writeInt(policy.policy);
|
||||
}
|
||||
};
|
||||
Bundle bundle = connector.readSocketInput();
|
||||
int uid = Integer.parseInt(bundle.getString("uid"));
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
policy = app.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
@@ -140,14 +106,14 @@ public class SuRequestActivity extends BaseActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Data.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
switch ((int) Config.get(Config.Key.SU_AUTO_RESPONSE)) {
|
||||
case Config.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Const.Value.SU_AUTO_ALLOW:
|
||||
case Config.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Const.Value.SU_PROMPT:
|
||||
case Config.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -163,13 +129,21 @@ public class SuRequestActivity extends BaseActivity {
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
warning.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
AppCompatResources.getDrawable(this, R.drawable.ic_warning), null, null, null);
|
||||
} else {
|
||||
warning.setCompoundDrawablesWithIntrinsicBounds(
|
||||
AppCompatResources.getDrawable(this, R.drawable.ic_warning), null, null, null);
|
||||
}
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
timeout.setSelection(timeoutPrefs.getInt(policy.packageName, 0));
|
||||
|
||||
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
||||
timer = new CountDownTimer((int) Config.get(Config.Key.SU_REQUEST_TIMEOUT) * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
@@ -181,7 +155,7 @@ public class SuRequestActivity extends BaseActivity {
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFP = FingerprintHelper.useFingerPrint();
|
||||
boolean useFP = FingerprintHelper.useFingerprint();
|
||||
|
||||
if (useFP) {
|
||||
try {
|
||||
@@ -245,14 +219,16 @@ public class SuRequestActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void handleAction(int action) {
|
||||
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
||||
int pos = timeout.getSelectedItemPosition();
|
||||
timeoutPrefs.edit().putInt(policy.packageName, pos).apply();
|
||||
handleAction(action, Config.Value.TIMEOUT_LIST[pos]);
|
||||
}
|
||||
|
||||
private void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.mDB.updatePolicy(policy);
|
||||
app.mDB.updatePolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
|
@@ -2,14 +2,13 @@ package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -18,6 +17,7 @@ import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -25,22 +25,30 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
private List<ApplicationInfo> fullList, showList;
|
||||
private static PackageInfo PLATFORM;
|
||||
|
||||
private List<PackageInfo> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
private boolean showSystem;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
filter = new ApplicationFilter();
|
||||
pm = context.getPackageManager();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
showSystem = false;
|
||||
if (PLATFORM == null) {
|
||||
try {
|
||||
PLATFORM = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -50,12 +58,17 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledApplications(0);
|
||||
fullList = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
for (Iterator<ApplicationInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
ApplicationInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled || info.uid == 1000) {
|
||||
for (Iterator<PackageInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
PackageInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) ||
|
||||
/* Do not show disabled apps */
|
||||
!info.applicationInfo.enabled ||
|
||||
/* Never show platform apps */
|
||||
PLATFORM.signatures[0].equals(info.signatures[0])) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
@@ -63,8 +76,8 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return Utils.getAppLabel(a, pm).toLowerCase()
|
||||
.compareTo(Utils.getAppLabel(b, pm).toLowerCase());
|
||||
return Utils.getAppLabel(a.applicationInfo, pm)
|
||||
.compareToIgnoreCase(Utils.getAppLabel(b.applicationInfo, pm));
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
@@ -74,9 +87,13 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
public void setShowSystem(boolean b) {
|
||||
showSystem = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position);
|
||||
ApplicationInfo info = showList.get(position).applicationInfo;
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(Utils.getAppLabel(info, pm));
|
||||
@@ -100,12 +117,41 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
return showList.size();
|
||||
}
|
||||
|
||||
private boolean contains(String s, String filter) {
|
||||
return s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
// Show if have launch intent or not system app
|
||||
private boolean systemFilter(PackageInfo info) {
|
||||
if (showSystem)
|
||||
return true;
|
||||
return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
|
||||
pm.getLaunchIntentForPackage(info.packageName) != null;
|
||||
}
|
||||
|
||||
public void filter(String constraint) {
|
||||
filter.filter(constraint);
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
showList = new ArrayList<>();
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
for (PackageInfo info : fullList) {
|
||||
if (systemFilter(info))
|
||||
showList.add(info);
|
||||
}
|
||||
} else {
|
||||
String filter = constraint.toLowerCase();
|
||||
for (PackageInfo info : fullList) {
|
||||
if ((contains(Utils.getAppLabel(info.applicationInfo, pm), filter) ||
|
||||
contains(info.packageName, filter)) && systemFilter(info)) {
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
UiThreadHandler.run(this::notifyDataSetChanged);
|
||||
});
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
@@ -120,33 +166,4 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationFilter extends Filter {
|
||||
|
||||
private boolean lowercaseContains(String s, CharSequence filter) {
|
||||
return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
showList = fullList;
|
||||
} else {
|
||||
showList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : fullList) {
|
||||
if (lowercaseContains(Utils.getAppLabel(info, pm), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@ import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
@@ -6,23 +6,23 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.dialogs.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.dialogs.FingerprintAuthDialog;
|
||||
import com.topjohnwu.magisk.uicomponents.ArrowExpandedViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
@@ -31,10 +31,11 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
private List<Policy> policyList;
|
||||
private MagiskDB dbHelper;
|
||||
private PackageManager pm;
|
||||
private Set<Policy> expandList = new HashSet<>();
|
||||
private boolean[] expandList;
|
||||
|
||||
public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) {
|
||||
policyList = list;
|
||||
expandList = new boolean[policyList.size()];
|
||||
dbHelper = db;
|
||||
this.pm = pm;
|
||||
}
|
||||
@@ -50,15 +51,14 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Policy policy = policyList.get(position);
|
||||
|
||||
holder.setExpanded(expandList.contains(policy));
|
||||
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (holder.isExpanded()) {
|
||||
holder.collapse();
|
||||
expandList.remove(policy);
|
||||
holder.settings.setExpanded(expandList[position]);
|
||||
holder.trigger.setOnClickListener(view -> {
|
||||
if (holder.settings.isExpanded()) {
|
||||
holder.settings.collapse();
|
||||
expandList[position] = false;
|
||||
} else {
|
||||
holder.expand();
|
||||
expandList.add(policy);
|
||||
holder.settings.expand();
|
||||
expandList[position] = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,12 +85,12 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
};
|
||||
if (FingerprintHelper.useFingerPrint()) {
|
||||
if (FingerprintHelper.useFingerprint()) {
|
||||
holder.masterSwitch.setChecked(!isChecked);
|
||||
FingerprintHelper.showAuthDialog((Activity) v.getContext(), () -> {
|
||||
new FingerprintAuthDialog((Activity) v.getContext(), () -> {
|
||||
holder.masterSwitch.setChecked(isChecked);
|
||||
r.run();
|
||||
});
|
||||
}).show();
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
@@ -129,9 +129,6 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.setCancelable(true)
|
||||
.show());
|
||||
|
||||
// Hide for now
|
||||
holder.moreInfo.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,31 +136,26 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
return policyList.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.package_name) TextView packageName;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.master_switch) Switch masterSwitch;
|
||||
@BindView(R.id.notification_switch) Switch notificationSwitch;
|
||||
@BindView(R.id.logging_switch) Switch loggingSwitch;
|
||||
@BindView(R.id.master_switch) SwitchCompat masterSwitch;
|
||||
@BindView(R.id.notification_switch) SwitchCompat notificationSwitch;
|
||||
@BindView(R.id.logging_switch) SwitchCompat loggingSwitch;
|
||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
@BindView(R.id.trigger) View trigger;
|
||||
@BindView(R.id.delete) ImageView delete;
|
||||
@BindView(R.id.more_info) ImageView moreInfo;
|
||||
|
||||
private Container container = new Container();
|
||||
ExpandableViewHolder settings;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
container.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return container;
|
||||
settings = new ArrowExpandedViewHolder(expandLayout, arrow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,26 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.DownloadModule;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.DownloadModuleService;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.dialogs.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -101,7 +103,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
new MarkDownWindow((BaseActivity) context, null, repo.getDetailUrl()).exec());
|
||||
MarkDownWindow.show((BaseActivity) context, null, repo.getDetailUrl()));
|
||||
|
||||
holder.downloadImage.setOnClickListener(v -> {
|
||||
new CustomAlertDialog((BaseActivity) context)
|
||||
@@ -109,14 +111,26 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) ->
|
||||
DownloadModule.exec((BaseActivity) context, repo, true))
|
||||
startDownload((BaseActivity) context, repo, true))
|
||||
.setNeutralButton(R.string.download, (d, i) ->
|
||||
DownloadModule.exec((BaseActivity) context, repo, false))
|
||||
startDownload((BaseActivity) context, repo, false))
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void startDownload(BaseActivity activity, Repo repo, Boolean install) {
|
||||
activity.runWithExternalRW(() -> {
|
||||
Intent intent = new Intent(activity, ClassMap.get(DownloadModuleService.class))
|
||||
.putExtra("repo", repo).putExtra("install", install);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
activity.startForegroundService(intent);
|
||||
} else {
|
||||
activity.startService(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void notifyDBChanged() {
|
||||
if (repoCursor != null)
|
||||
repoCursor.close();
|
||||
@@ -178,7 +192,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
@BindView(R.id.version_name) TextView versionName;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.info_layout) LinearLayout infoLayout;
|
||||
@BindView(R.id.info_layout) View infoLayout;
|
||||
@BindView(R.id.download) ImageView downloadImage;
|
||||
@BindView(R.id.update_time) TextView updateTime;
|
||||
|
||||
|
@@ -0,0 +1,103 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class StringListAdapter<VH extends StringListAdapter.ViewHolder>
|
||||
extends RecyclerView.Adapter<VH> {
|
||||
|
||||
private RecyclerView rv;
|
||||
private boolean dynamic;
|
||||
private int screenWidth;
|
||||
private int txtWidth = -1;
|
||||
private int padding;
|
||||
|
||||
protected List<String> mList;
|
||||
|
||||
public StringListAdapter(List<String> list) {
|
||||
this(list, false);
|
||||
}
|
||||
|
||||
public StringListAdapter(List<String> list, boolean isDynamic) {
|
||||
mList = list;
|
||||
dynamic = isDynamic;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayoutRes(), parent, false);
|
||||
VH vh = createViewHolder(v);
|
||||
if (txtWidth < 0)
|
||||
onUpdateTextWidth(vh);
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
holder.txt.setText(mList.get(position));
|
||||
holder.txt.getLayoutParams().width = txtWidth;
|
||||
if (dynamic)
|
||||
onUpdateTextWidth(holder);
|
||||
}
|
||||
|
||||
protected void onUpdateTextWidth(VH vh) {
|
||||
if (txtWidth < 0) {
|
||||
txtWidth = screenWidth - padding;
|
||||
} else {
|
||||
vh.txt.measure(0, 0);
|
||||
int width = vh.txt.getMeasuredWidth();
|
||||
if (width > txtWidth) {
|
||||
txtWidth = width;
|
||||
vh.txt.getLayoutParams().width = txtWidth;
|
||||
}
|
||||
}
|
||||
if (rv.getWidth() != txtWidth + padding)
|
||||
rv.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView rv) {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
((Activity) rv.getContext()).getWindowManager()
|
||||
.getDefaultDisplay().getMetrics(displayMetrics);
|
||||
screenWidth = displayMetrics.widthPixels;
|
||||
padding = rv.getPaddingLeft() + rv.getPaddingRight();
|
||||
this.rv = rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getItemCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract int itemLayoutRes();
|
||||
|
||||
@NonNull
|
||||
public abstract VH createViewHolder(@NonNull View v);
|
||||
|
||||
public static abstract class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView txt;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
txt = itemView.findViewById(textViewResId());
|
||||
}
|
||||
|
||||
@IdRes
|
||||
protected abstract int textViewResId();
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -9,9 +10,9 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -83,21 +84,22 @@ public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, S
|
||||
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
|
||||
SuLogEntry entry = logEntries.get(section).get(position);
|
||||
int realIdx = getItemPosition(section, position);
|
||||
holder.setExpanded(itemExpanded.contains(realIdx));
|
||||
holder.expandable.setExpanded(itemExpanded.contains(realIdx));
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (holder.isExpanded()) {
|
||||
holder.collapse();
|
||||
if (holder.expandable.isExpanded()) {
|
||||
holder.expandable.collapse();
|
||||
itemExpanded.remove(realIdx);
|
||||
} else {
|
||||
holder.expand();
|
||||
holder.expandable.expand();
|
||||
itemExpanded.add(realIdx);
|
||||
}
|
||||
});
|
||||
Context context = holder.itemView.getContext();
|
||||
holder.appName.setText(entry.appName);
|
||||
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||
holder.command.setText(entry.command);
|
||||
holder.fromPid.setText(String.valueOf(entry.fromPid));
|
||||
holder.toUid.setText(String.valueOf(entry.toUid));
|
||||
holder.pid.setText(context.getString(R.string.pid, entry.fromPid));
|
||||
holder.uid.setText(context.getString(R.string.target_uid, entry.toUid));
|
||||
holder.command.setText(context.getString(R.string.command, entry.command));
|
||||
holder.time.setText(entry.getTimeString());
|
||||
}
|
||||
|
||||
@@ -120,28 +122,22 @@ public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, S
|
||||
}
|
||||
}
|
||||
|
||||
static class LogViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||
static class LogViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.action) TextView action;
|
||||
@BindView(R.id.time) TextView time;
|
||||
@BindView(R.id.fromPid) TextView fromPid;
|
||||
@BindView(R.id.toUid) TextView toUid;
|
||||
@BindView(R.id.command) TextView command;
|
||||
@BindView(R.id.pid) TextView pid;
|
||||
@BindView(R.id.uid) TextView uid;
|
||||
@BindView(R.id.cmd) TextView command;
|
||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||
|
||||
private Container container = new Container();
|
||||
ExpandableViewHolder expandable;
|
||||
|
||||
LogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
|
||||
container.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return container;
|
||||
expandable = new ExpandableViewHolder(expandLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,78 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
public static final File dexPath =
|
||||
new File(Data.MM().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private ISafetyNetHelper helper;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
private void dlSnet() throws Exception {
|
||||
Shell.sh("rm -rf " + dexPath.getParent()).exec();
|
||||
dexPath.getParentFile().mkdir();
|
||||
HttpURLConnection conn = WebService.mustRequest(Const.Url.SNET_URL);
|
||||
try (
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||
ShellUtils.pump(in, out);
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void dyload() throws Exception {
|
||||
DexClassLoader loader = new DexClassLoader(dexPath.getPath(), dexPath.getParent(),
|
||||
null, ISafetyNetHelper.class.getClassLoader());
|
||||
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.Snet");
|
||||
helper = (ISafetyNetHelper) clazz.getMethod("newHelper",
|
||||
Class.class, String.class, Activity.class, Object.class)
|
||||
.invoke(null, ISafetyNetHelper.class, dexPath.getPath(), getActivity(),
|
||||
(ISafetyNetHelper.Callback) code ->
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, code));
|
||||
if (helper.getVersion() < Const.SNET_EXT_VER) {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
try {
|
||||
dyload();
|
||||
} catch (Exception e) {
|
||||
// If dynamic load failed, try re-downloading and reload
|
||||
dlSnet();
|
||||
dyload();
|
||||
}
|
||||
// Run attestation
|
||||
helper.attest();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, -1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -1,107 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import com.androidnetworking.AndroidNetworking;
|
||||
import com.androidnetworking.error.ANError;
|
||||
import com.androidnetworking.interfaces.JSONObjectRequestListener;
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.components.Notifications;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates {
|
||||
|
||||
private static int getInt(JSONObject json, String name, int defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getInt(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getString(JSONObject json, String name, String defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getString(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject getJson(JSONObject json, String name) {
|
||||
try {
|
||||
return json.getJSONObject(name);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void check(Runnable cb) {
|
||||
String url;
|
||||
switch (Data.updateChannel) {
|
||||
case Const.Value.STABLE_CHANNEL:
|
||||
url = Const.Url.STABLE_URL;
|
||||
break;
|
||||
case Const.Value.BETA_CHANNEL:
|
||||
url = Const.Url.BETA_URL;
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
url = Data.MM().prefs.getString(Const.Key.CUSTOM_CHANNEL, "");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
AndroidNetworking.get(url).build().getAsJSONObject(new UpdateListener(cb));
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
check(null);
|
||||
}
|
||||
|
||||
private static class UpdateListener implements JSONObjectRequestListener {
|
||||
|
||||
private Runnable cb;
|
||||
|
||||
UpdateListener(Runnable callback) {
|
||||
cb = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(JSONObject json) {
|
||||
JSONObject magisk = getJson(json, "magisk");
|
||||
Data.remoteMagiskVersionString = getString(magisk, "version", null);
|
||||
Data.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
||||
Data.magiskLink = getString(magisk, "link", null);
|
||||
Data.magiskNoteLink = getString(magisk, "note", null);
|
||||
Data.magiskMD5 = getString(magisk, "md5", null);
|
||||
|
||||
JSONObject manager = getJson(json, "app");
|
||||
Data.remoteManagerVersionString = getString(manager, "version", null);
|
||||
Data.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
||||
Data.managerLink = getString(manager, "link", null);
|
||||
Data.managerNoteLink = getString(manager, "note", null);
|
||||
|
||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
||||
Data.uninstallerLink = getString(uninstaller, "link", null);
|
||||
|
||||
if (cb != null) {
|
||||
if (BuildConfig.VERSION_CODE < Data.remoteManagerVersionCode) {
|
||||
Notifications.managerUpdate();
|
||||
} else if (Data.magiskVersionCode < Data.remoteMagiskVersionCode) {
|
||||
Notifications.magiskUpdate();
|
||||
}
|
||||
cb.run();
|
||||
}
|
||||
Topic.publish(Topic.UPDATE_CHECK_DONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ANError anError) {}
|
||||
}
|
||||
}
|
@@ -1,125 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.ProgressNotification;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class DownloadModule {
|
||||
|
||||
public static void exec(BaseActivity activity, Repo repo, boolean install) {
|
||||
activity.runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
|
||||
() -> AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> dlProcessInstall(repo, install)));
|
||||
}
|
||||
|
||||
private static void dlProcessInstall(Repo repo, boolean install) {
|
||||
File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename());
|
||||
ProgressNotification progress = new ProgressNotification(output.getName());
|
||||
try {
|
||||
MagiskManager mm = Data.MM();
|
||||
HttpURLConnection conn = WebService.mustRequest(repo.getZipUrl());
|
||||
ProgressInputStream pis = new ProgressInputStream(conn.getInputStream(),
|
||||
conn.getContentLength(), progress);
|
||||
removeTopFolder(new BufferedInputStream(pis),
|
||||
new BufferedOutputStream(new FileOutputStream(output)));
|
||||
conn.disconnect();
|
||||
if (install) {
|
||||
progress.dismiss();
|
||||
Intent intent = new Intent(mm, Data.classMap.get(FlashActivity.class));
|
||||
intent.setData(Uri.fromFile(output))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
mm.startActivity(intent);
|
||||
} else {
|
||||
progress.getNotification().setContentTitle(output.getName());
|
||||
progress.dlDone();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
progress.dlFail();
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeTopFolder(InputStream in, OutputStream out) throws IOException {
|
||||
try (ZipInputStream zin = new ZipInputStream(in);
|
||||
ZipOutputStream zout = new ZipOutputStream(out)) {
|
||||
ZipEntry entry;
|
||||
int off = -1;
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
if (off < 0)
|
||||
off = entry.getName().indexOf('/') + 1;
|
||||
String path = entry.getName().substring(off);
|
||||
if (path.isEmpty())
|
||||
continue;
|
||||
zout.putNextEntry(new ZipEntry(path));
|
||||
if (!entry.isDirectory())
|
||||
ShellUtils.pump(zin, zout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProgressInputStream extends FilterInputStream {
|
||||
|
||||
private long totalBytes;
|
||||
private long bytesDownloaded;
|
||||
private ProgressNotification progress;
|
||||
|
||||
protected ProgressInputStream(InputStream in, long size, ProgressNotification p) {
|
||||
super(in);
|
||||
totalBytes = size;
|
||||
progress = p;
|
||||
}
|
||||
|
||||
private void updateProgress() {
|
||||
progress.onProgress(bytesDownloaded, totalBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b >= 0) {
|
||||
bytesDownloaded++;
|
||||
updateProgress();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int sz = super.read(b, off, len);
|
||||
if (sz > 0) {
|
||||
bytesDownloaded += sz;
|
||||
updateProgress();
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,104 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
private Uri mUri;
|
||||
private File mCachedFile;
|
||||
private List<String> console, logs;
|
||||
|
||||
public FlashZip(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context);
|
||||
mUri = uri;
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
mCachedFile = new File(context.getCacheDir(), "install.zip");
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws Exception {
|
||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
return ShellUtils.fastCmdResult("grep -q '#MAGISK' " + new File(mCachedFile.getParentFile(), "updater-script"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
MagiskManager mm = Data.MM();
|
||||
try {
|
||||
console.add("- Copying zip to temp directory");
|
||||
|
||||
mCachedFile.delete();
|
||||
try (
|
||||
InputStream in = mm.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
|
||||
) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot copy to cache");
|
||||
throw e;
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
if (!Shell.su("cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess())
|
||||
return -1;
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
console.add("- All done!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.su("rm -rf " + mCachedFile.getParent(), "rm -rf " + Const.TMP_FOLDER_PATH).submit();
|
||||
switch (result) {
|
||||
case -1:
|
||||
console.add("! Installation failed");
|
||||
SnackbarMaker.showUri(getActivity(), mUri);
|
||||
break;
|
||||
case 0:
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
break;
|
||||
case 1:
|
||||
// Reload modules
|
||||
Utils.loadModules();
|
||||
break;
|
||||
}
|
||||
activity.reboot.setVisibility(result > 0 ? View.VISIBLE : View.GONE);
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
@@ -1,397 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.TarEntry;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
import org.kamranzafar.jtar.TarOutputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private static final int PATCH_MODE = 0;
|
||||
public static final int DIRECT_MODE = 1;
|
||||
private static final int FIX_ENV_MODE = 2;
|
||||
public static final int SECOND_SLOT_MODE = 3;
|
||||
|
||||
private Uri bootUri;
|
||||
private List<String> console, logs;
|
||||
private String mBoot;
|
||||
private int mode;
|
||||
private File installDir;
|
||||
private ProgressDialog dialog;
|
||||
private MagiskManager mm;
|
||||
|
||||
public InstallMagisk(Activity context) {
|
||||
super(context);
|
||||
mm = Data.MM();
|
||||
mode = FIX_ENV_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, int mode) {
|
||||
this(context);
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri boot) {
|
||||
this(context, console, logs, PATCH_MODE);
|
||||
bootUri = boot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
Activity a = getActivity();
|
||||
dialog = ProgressDialog.show(a, a.getString(R.string.setup_title), a.getString(R.string.setup_msg));
|
||||
console = NOPList.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressStream extends FilterInputStream {
|
||||
|
||||
private int prev = -1;
|
||||
private int progress = 0;
|
||||
private int total;
|
||||
|
||||
private ProgressStream(HttpURLConnection conn) throws IOException {
|
||||
super(conn.getInputStream());
|
||||
total = conn.getContentLength();
|
||||
console.add("... 0%");
|
||||
}
|
||||
|
||||
private void update(int step) {
|
||||
progress += step;
|
||||
int curr = (int) (100 * (double) progress / total);
|
||||
if (prev != curr) {
|
||||
prev = curr;
|
||||
console.set(console.size() - 1, "... " + prev + "%");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0)
|
||||
update(1);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int step = super.read(b, off, len);
|
||||
if (step > 0)
|
||||
update(step);
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFiles(String arch) throws IOException {
|
||||
File zip = new File(mm.getFilesDir(), "magisk.zip");
|
||||
BufferedInputStream buf;
|
||||
|
||||
if (!ShellUtils.checkSum("MD5", zip, Data.magiskMD5)) {
|
||||
console.add("- Downloading zip");
|
||||
HttpURLConnection conn = WebService.mustRequest(Data.magiskLink);
|
||||
buf = new BufferedInputStream(new ProgressStream(conn), conn.getContentLength());
|
||||
buf.mark(conn.getContentLength() + 1);
|
||||
try (OutputStream out = new FileOutputStream(zip)) {
|
||||
ShellUtils.pump(buf, out);
|
||||
}
|
||||
buf.reset();
|
||||
conn.disconnect();
|
||||
} else {
|
||||
console.add("- Existing zip found");
|
||||
buf = new BufferedInputStream(new FileInputStream(zip), (int) zip.length());
|
||||
buf.mark((int) zip.length() + 1);
|
||||
}
|
||||
|
||||
console.add("- Extracting files");
|
||||
try (InputStream in = buf) {
|
||||
ZipUtils.unzip(in, installDir, arch + "/", true);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "common/", true);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "chromeos/", false);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "META-INF/com/google/android/update-binary", true);
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot unzip zip");
|
||||
throw e;
|
||||
}
|
||||
Shell.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
|
||||
installDir, installDir, installDir)).exec();
|
||||
}
|
||||
|
||||
private boolean dumpBoot() {
|
||||
console.add("- Copying image to cache");
|
||||
// Copy boot image to local
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(bootUri);
|
||||
OutputStream out = new FileOutputStream(mBoot)
|
||||
) {
|
||||
if (in == null)
|
||||
throw new FileNotFoundException();
|
||||
|
||||
InputStream src;
|
||||
if (Utils.getNameFromUri(mm, bootUri).endsWith(".tar")) {
|
||||
// Extract boot.img from tar
|
||||
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
|
||||
org.kamranzafar.jtar.TarEntry entry;
|
||||
while ((entry = tar.getNextEntry()) != null) {
|
||||
if (entry.getName().equals("boot.img"))
|
||||
break;
|
||||
}
|
||||
src = tar;
|
||||
} else {
|
||||
// Direct copy raw image
|
||||
src = new BufferedInputStream(in);
|
||||
}
|
||||
ShellUtils.pump(src, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
console.add("! Copy failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private File patchBoot() throws IOException {
|
||||
boolean isSigned;
|
||||
try (InputStream in = new SuFileInputStream(mBoot)) {
|
||||
isSigned = SignBoot.verifySignature(in, null);
|
||||
if (isSigned) {
|
||||
console.add("- Boot image is signed with AVB 1.0");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Unable to check signature");
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Patch boot image
|
||||
if (!Shell.sh("cd " + installDir, Utils.fmt(
|
||||
"KEEPFORCEENCRYPT=%b KEEPVERITY=%b sh update-binary indep boot_patch.sh %s",
|
||||
Data.keepEnc, Data.keepVerity, mBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return null;
|
||||
|
||||
Shell.Job job = Shell.sh("mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /");
|
||||
|
||||
File patched = new File(installDir, "new-boot.img");
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with test keys");
|
||||
File signed = new File(installDir, "signed.img");
|
||||
try (InputStream in = new SuFileInputStream(patched);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
|
||||
) {
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
}
|
||||
job.add("mv -f " + signed + " " + patched);
|
||||
}
|
||||
job.exec();
|
||||
return patched;
|
||||
}
|
||||
|
||||
private boolean outputBoot(File patched) throws IOException {
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
String fmt = mm.prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + fmt);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream out;
|
||||
switch (fmt) {
|
||||
case ".img.tar":
|
||||
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
((TarOutputStream) out).putNextEntry(new TarEntry(patched, "boot.img"));
|
||||
break;
|
||||
default:
|
||||
case ".img":
|
||||
out = new BufferedOutputStream(new FileOutputStream(dest));
|
||||
break;
|
||||
}
|
||||
try (InputStream in = new SuFileInputStream(patched)) {
|
||||
ShellUtils.pump(in, out);
|
||||
out.close();
|
||||
}
|
||||
Shell.sh("rm -f " + patched).exec();
|
||||
console.add("");
|
||||
console.add("****************************");
|
||||
console.add(" Patched image is placed in ");
|
||||
console.add(" " + dest + " ");
|
||||
console.add("****************************");
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
case DIRECT_MODE:
|
||||
if (!Shell.su(Utils.fmt("direct_install %s %s", installDir, mBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return false;
|
||||
if (!Data.keepVerity)
|
||||
Shell.su("find_dtbo_image", "patch_dtbo_image").to(console, logs).exec();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void postOTA() {
|
||||
SuFile bootctl = new SuFile(Const.MAGISK_PATH + "/.core/bootctl");
|
||||
try (InputStream in = mm.getResources().openRawResource(R.raw.bootctl);
|
||||
OutputStream out = new SuFileOutputStream(bootctl)) {
|
||||
ShellUtils.pump(in, out);
|
||||
Shell.su("post_ota " + bootctl.getParent()).exec();
|
||||
console.add("***************************************");
|
||||
console.add(" Next reboot will boot to second slot!");
|
||||
console.add("***************************************");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
installDir = new File("/data/adb/magisk");
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec();
|
||||
} else {
|
||||
installDir = new File(
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mm.createDeviceProtectedStorageContext() : mm)
|
||||
.getFilesDir().getParent()
|
||||
, "install");
|
||||
Shell.sh("rm -rf " + installDir).exec();
|
||||
installDir.mkdirs();
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
mBoot = new File(installDir, "boot.img").getAbsolutePath();
|
||||
if (!dumpBoot())
|
||||
return false;
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
console.add("- Detecting target image");
|
||||
mBoot = ShellUtils.fastCmd("find_boot_image", "echo \"$BOOTIMAGE\"");
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
String slot = ShellUtils.fastCmd("echo $SLOT");
|
||||
String target = (TextUtils.equals(slot, "_a") ? "_b" : "_a");
|
||||
console.add("- Target slot: " + target);
|
||||
console.add("- Detecting target image");
|
||||
mBoot = ShellUtils.fastCmd(
|
||||
"SLOT=" + target,
|
||||
"find_boot_image",
|
||||
"SLOT=" + slot,
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
break;
|
||||
case FIX_ENV_MODE:
|
||||
mBoot = "";
|
||||
break;
|
||||
}
|
||||
if (mBoot == null) {
|
||||
console.add("! Unable to detect target image");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == DIRECT_MODE || mode == SECOND_SLOT_MODE)
|
||||
console.add("- Target image: " + mBoot);
|
||||
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
String arch;
|
||||
|
||||
if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// 32-bit only
|
||||
if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
} else {
|
||||
if (abis.contains("x86_64")) arch = "x64";
|
||||
else if (abis.contains("arm64-v8a")) arch = "arm64";
|
||||
else if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
}
|
||||
|
||||
console.add("- Device platform: " + Build.SUPPORTED_ABIS[0]);
|
||||
|
||||
try {
|
||||
extractFiles(arch);
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
Shell.su("fix_env").exec();
|
||||
} else {
|
||||
File patched = patchBoot();
|
||||
if (patched == null)
|
||||
return false;
|
||||
if (!outputBoot(patched))
|
||||
return false;
|
||||
if (mode == SECOND_SLOT_MODE)
|
||||
postOTA();
|
||||
console.add("- All done!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
dialog.dismiss();
|
||||
Utils.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
// Running in FlashActivity
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
if (!result) {
|
||||
Shell.sh("rm -rf " + installDir).submit();
|
||||
console.add("! Installation failed");
|
||||
activity.reboot.setVisibility(View.GONE);
|
||||
}
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
|
||||
private String mTitle;
|
||||
private String mUrl;
|
||||
private InputStream is;
|
||||
|
||||
|
||||
public MarkDownWindow(Activity context, String title, String url) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
public MarkDownWindow(Activity context, String title, InputStream in) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
is = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
MagiskManager mm = Data.MM();
|
||||
String md;
|
||||
if (mUrl != null) {
|
||||
md = Utils.dlString(mUrl);
|
||||
} else {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
ShellUtils.pump(is, out);
|
||||
md = out.toString();
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
String css;
|
||||
try (
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
Data.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
css = out.toString();
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
Parser parser = Parser.builder().build();
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
Node doc = parser.parse(md);
|
||||
return String.format("<style>%s</style>%s", css, renderer.render(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String html) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setTitle(mTitle);
|
||||
|
||||
WebView wv = new WebView(getActivity());
|
||||
wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null);
|
||||
|
||||
alert.setView(wv);
|
||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
||||
|
||||
private WeakReference<Activity> weakActivity;
|
||||
|
||||
public ParallelTask() {}
|
||||
|
||||
public ParallelTask(Activity context) {
|
||||
weakActivity = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
protected Activity getActivity() {
|
||||
return weakActivity.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void exec(Params... params) {
|
||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
|
||||
}
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.androidnetworking.AndroidNetworking;
|
||||
import com.androidnetworking.common.ANRequest;
|
||||
import com.androidnetworking.common.ANResponse;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateRepos {
|
||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
private static final int CORE_POOL_SIZE = Math.max(2, CPU_COUNT - 1);
|
||||
private static final DateFormat dateFormat;
|
||||
|
||||
private MagiskManager mm;
|
||||
private Set<String> cached;
|
||||
private ExecutorService threadPool;
|
||||
|
||||
static {
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public UpdateRepos() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
private void waitTasks() {
|
||||
threadPool.shutdown();
|
||||
while (true) {
|
||||
try {
|
||||
if (threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS))
|
||||
break;
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadJSON(JSONArray array) throws JSONException, ParseException {
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject rawRepo = array.getJSONObject(i);
|
||||
String id = rawRepo.getString("name");
|
||||
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
|
||||
threadPool.execute(() -> {
|
||||
Repo repo = mm.repoDB.getRepo(id);
|
||||
try {
|
||||
if (repo == null)
|
||||
repo = new Repo(id);
|
||||
else
|
||||
cached.remove(id);
|
||||
repo.update(date);
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* We sort repos by last push, which means that we only need to check whether the
|
||||
* first page is updated to determine whether the online repo database is changed
|
||||
*/
|
||||
private boolean loadPage(int page) {
|
||||
ANRequest.GetRequestBuilder req = AndroidNetworking.get(Const.Url.REPO_URL)
|
||||
.addQueryParameter("page", String.valueOf(page + 1));
|
||||
if (page == 0) {
|
||||
String etag = mm.prefs.getString(Const.Key.ETAG_KEY, null);
|
||||
if (etag != null)
|
||||
req.addHeaders(Const.Key.IF_NONE_MATCH, etag);
|
||||
}
|
||||
ANResponse<JSONArray> res = req.build().executeForJSONArray();
|
||||
if (res.getOkHttpResponse().code() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||
return false;
|
||||
// Current page is the last page
|
||||
if (res.getResult() == null || res.getResult().length() == 0)
|
||||
return true;
|
||||
|
||||
try {
|
||||
loadJSON(res.getResult());
|
||||
} catch (JSONException | ParseException e) {
|
||||
// Should not happen, but if exception occurs, page load fails
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update ETAG
|
||||
if (page == 0) {
|
||||
String etag = res.getOkHttpResponse().header(Const.Key.ETAG_KEY);
|
||||
if (etag != null) {
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etag).apply();
|
||||
}
|
||||
}
|
||||
|
||||
String links = res.getOkHttpResponse().header(Const.Key.LINK_KEY);
|
||||
return links == null || !links.contains("next") || loadPage(page + 1);
|
||||
}
|
||||
|
||||
private boolean loadPages() {
|
||||
return loadPage(0);
|
||||
}
|
||||
|
||||
private void fullReload() {
|
||||
Cursor c = mm.repoDB.getRawCursor();
|
||||
while (c.moveToNext()) {
|
||||
Repo repo = new Repo(c);
|
||||
threadPool.execute(() -> {
|
||||
try {
|
||||
repo.update();
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(repo);
|
||||
}
|
||||
});
|
||||
}
|
||||
waitTasks();
|
||||
}
|
||||
|
||||
public void exec(boolean force) {
|
||||
Topic.reset(Topic.REPO_LOAD_DONE);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
cached = Collections.synchronizedSet(mm.repoDB.getRepoIDSet());
|
||||
threadPool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
|
||||
|
||||
if (loadPages()) {
|
||||
waitTasks();
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
} else if (force) {
|
||||
fullReload();
|
||||
}
|
||||
Topic.publish(Topic.REPO_LOAD_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
exec(false);
|
||||
}
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 dvdandroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
/**
|
||||
* @author dvdandroid
|
||||
*/
|
||||
public class AboutCardRow extends LinearLayout {
|
||||
|
||||
@BindView(android.R.id.title) TextView mTitle;
|
||||
@BindView(android.R.id.summary) TextView mSummary;
|
||||
@BindView(android.R.id.icon) ImageView mIcon;
|
||||
@BindView(R.id.container) View mView;
|
||||
|
||||
public AboutCardRow(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AboutCardRow(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AboutCardRow(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LayoutInflater.from(context).inflate(R.layout.info_item_row, this);
|
||||
new AboutCardRow_ViewBinding(this, this);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AboutCardRow, 0, 0);
|
||||
String title;
|
||||
Drawable icon;
|
||||
try {
|
||||
title = a.getString(R.styleable.AboutCardRow_text);
|
||||
icon = a.getDrawable(R.styleable.AboutCardRow_icon);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
mTitle.setText(title);
|
||||
mIcon.setImageDrawable(icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
mView.setOnClickListener(l);
|
||||
}
|
||||
|
||||
public void setSummary(String s) {
|
||||
mSummary.setVisibility(VISIBLE);
|
||||
mSummary.setText(s);
|
||||
}
|
||||
}
|
@@ -1,17 +1,18 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.NoUIActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
@@ -20,24 +21,32 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public abstract class BaseActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
||||
|
||||
public static final String INTENT_PERM = "perm_dialog";
|
||||
private static Runnable grantCallback;
|
||||
|
||||
protected static Runnable permissionGrantCallback;
|
||||
static int[] EMPTY_INT_ARRAY = new int[0];
|
||||
|
||||
private ActivityResultListener activityResultListener;
|
||||
public MagiskManager mm;
|
||||
public App app = App.self;
|
||||
|
||||
static {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {}
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
@@ -45,17 +54,13 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
Configuration config = base.getResources().getConfiguration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
applyOverrideConfiguration(config);
|
||||
mm = Data.MM();
|
||||
super.attachBaseContext(LocaleManager.getLocaleContext(base, LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Topic.subscribe(this);
|
||||
if (Data.isDarkTheme && getDarkTheme() != -1) {
|
||||
if (getDarkTheme() != -1 && (boolean) Config.get(Config.Key.DARK_THEME)) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -84,6 +89,14 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
||||
}
|
||||
}
|
||||
|
||||
public void runWithExternalRW(Runnable callback) {
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, callback);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
runWithPermission(this, permissions, callback);
|
||||
}
|
||||
|
||||
public static void runWithPermission(Context context, String[] permissions, Runnable callback) {
|
||||
boolean granted = true;
|
||||
for (String perm : permissions) {
|
||||
@@ -95,23 +108,13 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
||||
callback.run();
|
||||
} else {
|
||||
// Passed in context should be an activity if not granted, need to show dialog!
|
||||
permissionGrantCallback = callback;
|
||||
if (!(context instanceof BaseActivity)) {
|
||||
// Start NoUIActivity to show dialog
|
||||
Intent intent = new Intent(context, Data.classMap.get(NoUIActivity.class));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(INTENT_PERM, permissions);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
if (context instanceof BaseActivity) {
|
||||
grantCallback = callback;
|
||||
ActivityCompat.requestPermissions((BaseActivity) context, permissions, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
runWithPermission(this, permissions, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (activityResultListener != null)
|
||||
@@ -132,16 +135,23 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
||||
grant = false;
|
||||
}
|
||||
if (grant) {
|
||||
if (permissionGrantCallback != null) {
|
||||
permissionGrantCallback.run();
|
||||
if (grantCallback != null) {
|
||||
grantCallback.run();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, R.string.no_rw_storage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
permissionGrantCallback = null;
|
||||
grantCallback = null;
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferences getSharedPreferences(String name, int mode) {
|
||||
if (TextUtils.equals(name, getPackageName() + "_preferences"))
|
||||
return app.prefs;
|
||||
return super.getSharedPreferences(name, mode);
|
||||
}
|
||||
}
|
||||
|
@@ -2,22 +2,17 @@ package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
public abstract class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
|
||||
public MagiskManager mm;
|
||||
public App app = App.self;
|
||||
protected Unbinder unbinder = null;
|
||||
|
||||
public BaseFragment() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@@ -54,4 +49,7 @@ public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
public int[] getSubscribedTopics() {
|
||||
return BaseActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {}
|
||||
}
|
||||
|
@@ -0,0 +1,81 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
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.Topic;
|
||||
|
||||
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, Topic.AutoSubscriber {
|
||||
|
||||
public App app = App.self;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = super.onCreateView(inflater, container, savedInstanceState);
|
||||
app.prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
Topic.subscribe(this);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
app.prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return BaseActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@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,84 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Network;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ListenableWorker;
|
||||
|
||||
public abstract class DelegateWorker {
|
||||
|
||||
private ListenableWorker worker;
|
||||
|
||||
@NonNull
|
||||
public abstract ListenableWorker.Result doWork();
|
||||
|
||||
public void onStopped() {}
|
||||
|
||||
public void setActualWorker(ListenableWorker w) {
|
||||
worker = w;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Context getApplicationContext() {
|
||||
return worker.getApplicationContext();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public UUID getId() {
|
||||
return worker.getId();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Data getInputData() {
|
||||
return worker.getInputData();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Set<String> getTags() {
|
||||
return worker.getTags();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@RequiresApi(24)
|
||||
public List<Uri> getTriggeredContentUris() {
|
||||
return worker.getTriggeredContentUris();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@RequiresApi(24)
|
||||
public List<String> getTriggeredContentAuthorities() {
|
||||
return worker.getTriggeredContentAuthorities();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@RequiresApi(28)
|
||||
public Network getNetwork() {
|
||||
return worker.getNetwork();
|
||||
}
|
||||
|
||||
public int getRunAttemptCount() {
|
||||
return worker.getRunAttemptCount();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@MainThread
|
||||
public ListenableFuture<ListenableWorker.Result> startWork() {
|
||||
return worker.startWork();
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
return worker.isStopped();
|
||||
}
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class DownloadModuleService extends Service {
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (flags == 0 && running) {
|
||||
Utils.toast(R.string.dl_one_module, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
running = true;
|
||||
Shell.EXECUTOR.execute(() -> {
|
||||
Repo repo = intent.getParcelableExtra("repo");
|
||||
boolean install = intent.getBooleanExtra("install", false);
|
||||
dlProcessInstall(repo, install);
|
||||
stopSelf();
|
||||
});
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private void dlProcessInstall(Repo repo, boolean install) {
|
||||
File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename());
|
||||
ProgressNotification progress = new ProgressNotification(output.getName());
|
||||
startForeground(progress.hashCode(), progress.getNotification());
|
||||
try {
|
||||
InputStream in = Networking.get(repo.getZipUrl())
|
||||
.setDownloadProgressListener(progress)
|
||||
.execForInputStream().getResult();
|
||||
removeTopFolder(in, new BufferedOutputStream(new FileOutputStream(output)));
|
||||
if (install) {
|
||||
progress.dismiss();
|
||||
Intent intent = new Intent(this, ClassMap.get(FlashActivity.class));
|
||||
intent.setData(Uri.fromFile(output))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
progress.dlDone();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
progress.dlFail();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTopFolder(InputStream in, OutputStream out) throws IOException {
|
||||
try (ZipInputStream zin = new ZipInputStream(in);
|
||||
ZipOutputStream zout = new ZipOutputStream(out)) {
|
||||
ZipEntry entry;
|
||||
int off = -1;
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
if (off < 0)
|
||||
off = entry.getName().indexOf('/') + 1;
|
||||
String path = entry.getName().substring(off);
|
||||
if (path.isEmpty())
|
||||
continue;
|
||||
zout.putNextEntry(new ZipEntry(path));
|
||||
if (!entry.isDirectory())
|
||||
ShellUtils.pump(zin, zout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class EnvFixDialog extends CustomAlertDialog {
|
||||
|
||||
public EnvFixDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
setTitle(R.string.env_fix_title);
|
||||
setMessage(R.string.env_fix_msg);
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.yes, (d, i) -> new InstallMagisk(activity).exec());
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
}
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
public interface ExpandableView {
|
||||
|
||||
class Container {
|
||||
public ViewGroup expandLayout;
|
||||
ValueAnimator expandAnimator, collapseAnimator;
|
||||
boolean mExpanded = false;
|
||||
int expandHeight = 0;
|
||||
}
|
||||
|
||||
// Provide state info
|
||||
Container getContainer();
|
||||
|
||||
default void setupExpandable() {
|
||||
Container container = getContainer();
|
||||
container.expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
||||
new ViewTreeObserver.OnPreDrawListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (container.expandHeight == 0) {
|
||||
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
container.expandLayout.measure(widthSpec, heightSpec);
|
||||
container.expandHeight = container.expandLayout.getMeasuredHeight();
|
||||
}
|
||||
|
||||
container.expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
container.expandLayout.setVisibility(View.GONE);
|
||||
container.expandAnimator = slideAnimator(0, container.expandHeight);
|
||||
container.collapseAnimator = slideAnimator(container.expandHeight, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
default boolean isExpanded() {
|
||||
return getContainer().mExpanded;
|
||||
}
|
||||
|
||||
default void setExpanded(boolean expanded) {
|
||||
Container container = getContainer();
|
||||
container.mExpanded = expanded;
|
||||
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
|
||||
layoutParams.height = expanded ? container.expandHeight : 0;
|
||||
container.expandLayout.setLayoutParams(layoutParams);
|
||||
container.expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
default void expand() {
|
||||
Container container = getContainer();
|
||||
if (container.mExpanded) return;
|
||||
container.expandLayout.setVisibility(View.VISIBLE);
|
||||
container.expandAnimator.start();
|
||||
container.mExpanded = true;
|
||||
}
|
||||
|
||||
default void collapse() {
|
||||
Container container = getContainer();
|
||||
if (!container.mExpanded) return;
|
||||
container.collapseAnimator.start();
|
||||
container.mExpanded = false;
|
||||
}
|
||||
|
||||
default ValueAnimator slideAnimator(int start, int end) {
|
||||
Container container = getContainer();
|
||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
||||
|
||||
animator.addUpdateListener(valueAnimator -> {
|
||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
||||
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
|
||||
layoutParams.height = value;
|
||||
container.expandLayout.setLayoutParams(layoutParams);
|
||||
});
|
||||
return animator;
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.uicomponents.Notifications;
|
||||
import com.topjohnwu.magisk.uicomponents.Shortcuts;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.SuLogger;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class GeneralReceiver extends BroadcastReceiver {
|
||||
|
||||
private static SuLogger SU_LOGGER = new SuLogger() {
|
||||
@Override
|
||||
public String getMessage(Policy policy) {
|
||||
return App.self.getString(policy.policy == Policy.ALLOW ?
|
||||
R.string.su_allow_toast : R.string.su_deny_toast, policy.appName);
|
||||
}
|
||||
};
|
||||
|
||||
private String getPkg(Intent i) {
|
||||
return i.getData() == null ? "" : i.getData().getEncodedSchemeSpecificPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
App app = App.self;
|
||||
if (intent == null)
|
||||
return;
|
||||
String action = intent.getAction();
|
||||
if (action == null)
|
||||
return;
|
||||
switch (action) {
|
||||
case Intent.ACTION_BOOT_COMPLETED:
|
||||
String bootAction = intent.getStringExtra("action");
|
||||
if (bootAction == null)
|
||||
bootAction = "boot";
|
||||
switch (bootAction) {
|
||||
case "request":
|
||||
Intent i = new Intent(app, ClassMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", intent.getStringExtra("socket"))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
app.startActivity(i);
|
||||
break;
|
||||
case "log":
|
||||
SU_LOGGER.handleLogs(intent);
|
||||
break;
|
||||
case "notify":
|
||||
SU_LOGGER.handleNotify(intent);
|
||||
break;
|
||||
case "boot":
|
||||
default:
|
||||
/* Devices with DTBO might want to patch dtbo.img.
|
||||
* However, that is not possible if Magisk is installed by
|
||||
* patching boot image with Magisk Manager and flashed via
|
||||
* fastboot, since at that time we do not have root.
|
||||
* Check for dtbo status every boot time, and prompt user
|
||||
* to reboot if dtbo wasn't patched and patched by Magisk Manager.
|
||||
* */
|
||||
Shell.su("mm_patch_dtbo").submit(result -> {
|
||||
if (result.isSuccess())
|
||||
Notifications.dtboPatched();
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_REPLACED:
|
||||
// This will only work pre-O
|
||||
if (Config.get(Config.Key.SU_REAUTH)) {
|
||||
app.mDB.deletePolicy(getPkg(intent));
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
String pkg = getPkg(intent);
|
||||
app.mDB.deletePolicy(pkg);
|
||||
Shell.su("magiskhide --rm " + pkg).submit();
|
||||
break;
|
||||
case Intent.ACTION_LOCALE_CHANGED:
|
||||
Shortcuts.setup(context);
|
||||
break;
|
||||
case Const.Key.BROADCAST_MANAGER_UPDATE:
|
||||
Config.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK);
|
||||
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME));
|
||||
break;
|
||||
case Const.Key.BROADCAST_REBOOT:
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskInstallDialog extends CustomAlertDialog {
|
||||
public MagiskInstallDialog(BaseActivity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)));
|
||||
setMessage(mm.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(mm.getString(R.string.download_zip_only));
|
||||
options.add(mm.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(mm.getString(R.string.direct_install));
|
||||
String s = ShellUtils.fastCmd("grep_prop ro.build.ab_update");
|
||||
if (!s.isEmpty() && Boolean.parseBoolean(s)) {
|
||||
options.add(mm.getString(R.string.install_inactive_slot));
|
||||
}
|
||||
}
|
||||
new InstallMethodDialog(activity, options).show();
|
||||
});
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(Data.magiskNoteLink)) {
|
||||
setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (Data.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
Utils.openLink(activity, Uri.parse(Data.magiskNoteLink));
|
||||
} else {
|
||||
new MarkDownWindow(activity, null, Data.magiskNoteLink).exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.DlInstallManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ManagerInstallDialog extends CustomAlertDialog {
|
||||
|
||||
public ManagerInstallDialog(@NonNull BaseActivity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
String name = Utils.fmt("MagiskManager v%s(%d)",
|
||||
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
|
||||
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)));
|
||||
setMessage(mm.getString(R.string.repo_install_msg, name));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> DlInstallManager.upgrade(name));
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(Data.managerNoteLink)) {
|
||||
setNeutralButton(R.string.app_changelog, (d, i) ->
|
||||
new MarkDownWindow(activity, null, Data.managerNoteLink).exec());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
import com.topjohnwu.magisk.uicomponents.Notifications;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.ListenableWorker;
|
||||
|
||||
public class UpdateCheckService extends DelegateWorker {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ListenableWorker.Result doWork() {
|
||||
Shell.getShell();
|
||||
CheckUpdates.checkNow(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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class MagiskDB {
|
||||
|
||||
static final String POLICY_TABLE = "policies";
|
||||
static final String LOG_TABLE = "logs";
|
||||
static final String SETTINGS_TABLE = "settings";
|
||||
static final String STRINGS_TABLE = "strings";
|
||||
static final File LEGACY_MANAGER_DB =
|
||||
new File(Utils.fmt("/sbin/.magisk/db-%d/magisk.db", Const.USER_ID));
|
||||
|
||||
@NonNull
|
||||
public static MagiskDB getInstance() {
|
||||
if (LEGACY_MANAGER_DB.canWrite()) {
|
||||
return MagiskDBLegacy.newInstance();
|
||||
} else if (Shell.rootAccess()) {
|
||||
return Data.magiskVersionCode >= Const.MAGISK_VER.CMDLINE_DB ?
|
||||
new MagiskDBCmdline() : MagiskDBLegacy.newInstance();
|
||||
} else {
|
||||
return new MagiskDB();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearOutdated() {}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.uid);
|
||||
}
|
||||
|
||||
public void deletePolicy(String pkg) {}
|
||||
|
||||
public void deletePolicy(int uid) {}
|
||||
|
||||
public Policy getPolicy(int uid) { return null; }
|
||||
|
||||
public void updatePolicy(Policy policy) {}
|
||||
|
||||
public List<Policy> getPolicyList() { return Collections.emptyList(); }
|
||||
|
||||
public List<List<SuLogEntry>> getLogs() { return Collections.emptyList(); }
|
||||
|
||||
public void addLog(SuLogEntry log) {}
|
||||
|
||||
public void clearLogs() {}
|
||||
|
||||
public void setSettings(String key, int value) {}
|
||||
|
||||
public int getSettings(String key, int defaultValue) { return defaultValue; }
|
||||
|
||||
public void setStrings(String key, String value) {}
|
||||
|
||||
public String getStrings(String key, String defaultValue) { return defaultValue; }
|
||||
}
|
@@ -1,299 +0,0 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskDBLegacy extends MagiskDB {
|
||||
|
||||
private static final int DATABASE_VER = 6;
|
||||
private static final int OLD_DATABASE_VER = 5;
|
||||
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase db;
|
||||
|
||||
static MagiskDBLegacy newInstance() {
|
||||
try {
|
||||
return new MagiskDBLegacy();
|
||||
} catch (Exception e) {
|
||||
// Let's cleanup everything and try again
|
||||
Shell.su("db_clean '*'").exec();
|
||||
return new MagiskDBLegacy();
|
||||
}
|
||||
}
|
||||
|
||||
private MagiskDBLegacy() {
|
||||
pm = Data.MM().getPackageManager();
|
||||
db = openDatabase();
|
||||
db.disableWriteAheadLogging();
|
||||
int version = Data.magiskVersionCode >= Const.MAGISK_VER.DBVER_SIX ? DATABASE_VER : OLD_DATABASE_VER;
|
||||
int curVersion = db.getVersion();
|
||||
if (curVersion < version) {
|
||||
onUpgrade(db, curVersion);
|
||||
} else if (curVersion > DATABASE_VER) {
|
||||
/* Higher than we can possibly support */
|
||||
onDowngrade(db);
|
||||
}
|
||||
db.setVersion(version);
|
||||
clearOutdated();
|
||||
}
|
||||
|
||||
private SQLiteDatabase openDatabase() {
|
||||
MagiskManager mm = Data.MM();
|
||||
Context de = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
? mm.createDeviceProtectedStorageContext() : mm;
|
||||
if (!LEGACY_MANAGER_DB.canWrite()) {
|
||||
if (!Shell.rootAccess() || Data.magiskVersionCode < 0) {
|
||||
// We don't want the app to crash, create a db and return
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
}
|
||||
// Cleanup
|
||||
Shell.su("db_clean " + Const.USER_ID).exec();
|
||||
// Global database
|
||||
final SuFile GLOBAL_DB = new SuFile("/data/adb/magisk.db");
|
||||
mm.deleteDatabase("su.db");
|
||||
de.deleteDatabase("su.db");
|
||||
if (Data.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// We need some additional policies on old versions
|
||||
Shell.su("db_sepatch").exec();
|
||||
}
|
||||
if (!GLOBAL_DB.exists()) {
|
||||
Shell.su("db_init").exec();
|
||||
SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
|
||||
Shell.su("db_restore").exec();
|
||||
}
|
||||
Shell.su("db_setup " + Process.myUid()).exec();
|
||||
}
|
||||
// Not using legacy mode, open the mounted global DB
|
||||
return SQLiteDatabase.openOrCreateDatabase(LEGACY_MANAGER_DB, null);
|
||||
}
|
||||
|
||||
private void onUpgrade(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 3;
|
||||
}
|
||||
if (oldVersion == 1) {
|
||||
// We're dropping column app_name, rename and re-construct table
|
||||
db.execSQL(Utils.fmt("ALTER TABLE %s RENAME TO %s_old", POLICY_TABLE));
|
||||
|
||||
// Create the new tables
|
||||
createTables(db);
|
||||
|
||||
// Migrate old data to new tables
|
||||
db.execSQL(Utils.fmt("INSERT INTO %s SELECT " +
|
||||
"uid, package_name, policy, until, logging, notification FROM %s_old",
|
||||
POLICY_TABLE, POLICY_TABLE));
|
||||
db.execSQL(Utils.fmt("DROP TABLE %s_old", POLICY_TABLE));
|
||||
|
||||
Data.MM().deleteDatabase("sulog.db");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET time=time*1000", LOG_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 3) {
|
||||
db.execSQL(Utils.fmt("CREATE TABLE IF NOT EXISTS %s (key TEXT, value TEXT, PRIMARY KEY(key))", STRINGS_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 4) {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET uid=uid%%100000", POLICY_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 5) {
|
||||
setSettings(Const.Key.SU_FINGERPRINT,
|
||||
Data.MM().prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) ? 1 : 0);
|
||||
++oldVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything, we do not support downgrade
|
||||
private void onDowngrade(SQLiteDatabase db) {
|
||||
Utils.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + POLICY_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + STRINGS_TABLE);
|
||||
onUpgrade(db, 0);
|
||||
}
|
||||
|
||||
private void createTables(SQLiteDatabase db) {
|
||||
// Policies
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
|
||||
"(uid INT, package_name TEXT, policy INT, " +
|
||||
"until INT, logging INT, notification INT, " +
|
||||
"PRIMARY KEY(uid))");
|
||||
|
||||
// Logs
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
|
||||
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
|
||||
"to_uid INT, action INT, time INT, command TEXT)");
|
||||
|
||||
// Settings
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearOutdated() {
|
||||
// Clear outdated policies
|
||||
db.delete(POLICY_TABLE, Utils.fmt("until > 0 AND until < %d", System.currentTimeMillis() / 1000), null);
|
||||
// Clear outdated logs
|
||||
db.delete(LOG_TABLE, Utils.fmt("time < %d", System.currentTimeMillis() - Data.suLogTimeout * 86400000), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePolicy(String pkg) {
|
||||
db.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePolicy(int uid) {
|
||||
db.delete(POLICY_TABLE, Utils.fmt("uid=%d", uid), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Policy getPolicy(int uid) {
|
||||
Policy policy = null;
|
||||
try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid=%d", uid), null, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(c, values);
|
||||
policy = new Policy(values, pm);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(uid);
|
||||
return null;
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePolicy(Policy policy) {
|
||||
db.replace(POLICY_TABLE, null, policy.getContentValues());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> getPolicyList() {
|
||||
try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, null)) {
|
||||
List<Policy> ret = new ArrayList<>(c.getCount());
|
||||
while (c.moveToNext()) {
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(c, values);
|
||||
Policy policy = new Policy(values, pm);
|
||||
ret.add(policy);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// The app no longer exist, remove from DB
|
||||
deletePolicy(c.getInt(c.getColumnIndex("uid")));
|
||||
}
|
||||
}
|
||||
Collections.sort(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<SuLogEntry>> getLogs() {
|
||||
try (Cursor c = db.query(LOG_TABLE, null, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, "time DESC")) {
|
||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
||||
List<SuLogEntry> list = null;
|
||||
String dateString = null, newString;
|
||||
while (c.moveToNext()) {
|
||||
Date date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
||||
ret.add(list);
|
||||
}
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(c, values);
|
||||
list.add(new SuLogEntry(values));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLog(SuLogEntry log) {
|
||||
db.insert(LOG_TABLE, null, log.getContentValues());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLogs() {
|
||||
db.delete(LOG_TABLE, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
db.replace(SETTINGS_TABLE, null, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
int value = defaultValue;
|
||||
try (Cursor c = db.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getInt(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
db.delete(STRINGS_TABLE, "key=?", new String[] { key });
|
||||
} else {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
db.replace(STRINGS_TABLE, null, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
String value = defaultValue;
|
||||
try (Cursor c = db.query(STRINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getString(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
@@ -22,9 +22,9 @@ public class CustomAlertDialog extends AlertDialog.Builder {
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
private AlertDialog dialog;
|
||||
|
||||
private ViewHolder vh;
|
||||
protected AlertDialog dialog;
|
||||
protected ViewHolder vh;
|
||||
|
||||
public class ViewHolder {
|
||||
@BindView(R.id.dialog_layout) public LinearLayout dialogLayout;
|
@@ -0,0 +1,59 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.tasks.MagiskInstaller;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class EnvFixDialog extends CustomAlertDialog {
|
||||
|
||||
public EnvFixDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
setTitle(R.string.env_fix_title);
|
||||
setMessage(R.string.env_fix_msg);
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.yes, (d, i) -> {
|
||||
ProgressDialog pd = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.setup_title),
|
||||
activity.getString(R.string.setup_msg));
|
||||
new MagiskInstaller() {
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
installDir = new SuFile("/data/adb/magisk");
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec();
|
||||
return extractZip() && Shell.su("fix_env").exec().isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
pd.dismiss();
|
||||
Utils.toast(success ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
if (success) {
|
||||
// Relaunch the app
|
||||
try {
|
||||
Shell.getShell().close();
|
||||
} catch (IOException ignored) {}
|
||||
Intent intent = new Intent(activity, ClassMap.get(SplashActivity.class));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
}
|
||||
}.exec();
|
||||
});
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public class FingerprintAuthDialog extends CustomAlertDialog {
|
||||
|
||||
private Runnable callback;
|
||||
private DialogFingerprintHelper helper;
|
||||
|
||||
public FingerprintAuthDialog(@NonNull Activity activity, Runnable onSuccess) {
|
||||
super(activity);
|
||||
callback = onSuccess;
|
||||
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
|
||||
Resources.Theme theme = activity.getTheme();
|
||||
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
|
||||
fingerprint.setTint(ta.getColor(0, Color.GRAY));
|
||||
ta.recycle();
|
||||
vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
|
||||
vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
|
||||
vh.messageView.setGravity(Gravity.CENTER);
|
||||
setMessage(R.string.auth_fingerprint);
|
||||
setNegativeButton(R.string.close, (d, w) -> helper.cancel());
|
||||
setOnCancelListener(d -> helper.cancel());
|
||||
try {
|
||||
helper = new DialogFingerprintHelper();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
if (helper == null) {
|
||||
dialog.dismiss();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
helper.authenticate();
|
||||
dialog.show();
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
class DialogFingerprintHelper extends FingerprintHelper {
|
||||
|
||||
DialogFingerprintHelper() throws Exception {}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(R.string.auth_fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
dismiss();
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +1,22 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.androidnetworking.AndroidNetworking;
|
||||
import com.androidnetworking.error.ANError;
|
||||
import com.androidnetworking.interfaces.DownloadListener;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -34,7 +36,7 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
||||
downloadOnly(activity);
|
||||
break;
|
||||
case 2:
|
||||
intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
break;
|
||||
@@ -49,13 +51,13 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
||||
private void patchBoot(BaseActivity a) {
|
||||
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
|
||||
a.runWithPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, () ->
|
||||
a.runWithExternalRW(() ->
|
||||
a.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT &&
|
||||
resultCode == Activity.RESULT_OK && data != null) {
|
||||
Intent i = new Intent(a, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
||||
Intent i = new Intent(a, ClassMap.get(FlashActivity.class))
|
||||
.setData(data.getData())
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
a.startActivity(i);
|
||||
}
|
||||
@@ -64,27 +66,19 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
||||
}
|
||||
|
||||
private void downloadOnly(BaseActivity a) {
|
||||
a.runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
|
||||
a.runWithExternalRW(() -> {
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode);
|
||||
File zip = new File(Const.EXTERNAL_PATH, filename);
|
||||
ProgressNotification progress = new ProgressNotification(filename);
|
||||
AndroidNetworking
|
||||
.download(Data.magiskLink, Const.EXTERNAL_PATH.getPath(), filename)
|
||||
.build()
|
||||
Networking.get(Config.magiskLink)
|
||||
.setDownloadProgressListener(progress)
|
||||
.startDownload(new DownloadListener() {
|
||||
@Override
|
||||
public void onDownloadComplete() {
|
||||
progress.dlDone();
|
||||
SnackbarMaker.make(a,
|
||||
.setErrorHandler(((conn, e) -> progress.dlFail()))
|
||||
.getAsFile(zip, f -> {
|
||||
progress.dlDone();
|
||||
SnackbarMaker.make(a,
|
||||
a.getString(R.string.internal_storage, "/Download/" + filename),
|
||||
Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ANError anError) {
|
||||
progress.dlFail();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -95,7 +89,7 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
||||
.setMessage(R.string.install_inactive_slot_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
|
||||
activity.startActivity(intent);
|
||||
})
|
@@ -0,0 +1,50 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskInstallDialog extends CustomAlertDialog {
|
||||
public MagiskInstallDialog(BaseActivity a) {
|
||||
super(a);
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode);
|
||||
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)));
|
||||
setMessage(a.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(a.getString(R.string.download_zip_only));
|
||||
options.add(a.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(a.getString(R.string.direct_install));
|
||||
String s = ShellUtils.fastCmd("grep_prop ro.build.ab_update");
|
||||
if (!s.isEmpty() && Boolean.parseBoolean(s)) {
|
||||
options.add(a.getString(R.string.install_inactive_slot));
|
||||
}
|
||||
}
|
||||
new InstallMethodDialog(a, options).show();
|
||||
});
|
||||
if (!TextUtils.isEmpty(Config.magiskNoteLink)) {
|
||||
setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (Config.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
AppUtils.openLink(a, Uri.parse(Config.magiskNoteLink));
|
||||
} else {
|
||||
MarkDownWindow.show(a, null, Config.magiskNoteLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ManagerInstallDialog extends CustomAlertDialog {
|
||||
|
||||
public ManagerInstallDialog(@NonNull Activity a) {
|
||||
super(a);
|
||||
String name = Utils.fmt("MagiskManager v%s(%d)",
|
||||
Config.remoteManagerVersionString, Config.remoteManagerVersionCode);
|
||||
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)));
|
||||
setMessage(a.getString(R.string.repo_install_msg, name));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name));
|
||||
if (!TextUtils.isEmpty(Config.managerNoteLink)) {
|
||||
setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Config.managerNoteLink));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
@@ -7,14 +7,14 @@ import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.androidnetworking.AndroidNetworking;
|
||||
import com.androidnetworking.error.ANError;
|
||||
import com.androidnetworking.interfaces.DownloadListener;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
@@ -40,28 +40,20 @@ public class UninstallDialog extends CustomAlertDialog {
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!TextUtils.isEmpty(Data.uninstallerLink)) {
|
||||
if (!TextUtils.isEmpty(Config.uninstallerLink)) {
|
||||
setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
|
||||
ProgressNotification progress = new ProgressNotification(zip.getName());
|
||||
AndroidNetworking.download(Data.uninstallerLink, zip.getParent(), zip.getName())
|
||||
.build()
|
||||
Networking.get(Config.uninstallerLink)
|
||||
.setDownloadProgressListener(progress)
|
||||
.startDownload(new DownloadListener() {
|
||||
@Override
|
||||
public void onDownloadComplete() {
|
||||
progress.dismiss();
|
||||
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(Uri.fromFile(zip))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ANError anError) {
|
||||
progress.dlFail();
|
||||
}
|
||||
.setErrorHandler(((conn, e) -> progress.dlFail()))
|
||||
.getAsFile(zip, f -> {
|
||||
progress.dismiss();
|
||||
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(Uri.fromFile(f))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
activity.startActivity(intent);
|
||||
});
|
||||
});
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -27,7 +28,9 @@ public class LogFragment extends BaseFragment {
|
||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||
unbinder = new LogFragment_ViewBinding(this, v);
|
||||
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
}
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
|
@@ -1,9 +1,6 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -11,72 +8,58 @@ import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.EnvFixDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.MagiskInstallDialog;
|
||||
import com.topjohnwu.magisk.components.ManagerInstallDialog;
|
||||
import com.topjohnwu.magisk.components.UninstallDialog;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.dialogs.EnvFixDialog;
|
||||
import com.topjohnwu.magisk.dialogs.MagiskInstallDialog;
|
||||
import com.topjohnwu.magisk.dialogs.ManagerInstallDialog;
|
||||
import com.topjohnwu.magisk.dialogs.UninstallDialog;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
import com.topjohnwu.magisk.uicomponents.ArrowExpandedViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.uicomponents.SafetyNet;
|
||||
import com.topjohnwu.magisk.uicomponents.UpdateCardHolder;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.Fade;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class MagiskFragment extends BaseFragment
|
||||
implements SwipeRefreshLayout.OnRefreshListener, ExpandableView, Topic.Subscriber {
|
||||
implements SwipeRefreshLayout.OnRefreshListener, Topic.Subscriber {
|
||||
|
||||
private Container expandableContainer = new Container();
|
||||
private static boolean shownDialog = false;
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) public SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
@BindView(R.id.core_only_notice) CardView coreOnlyNotice;
|
||||
|
||||
@BindView(R.id.magisk_update) RelativeLayout magiskUpdate;
|
||||
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
|
||||
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
||||
@BindView(R.id.magisk_update_progress) ProgressBar magiskUpdateProgress;
|
||||
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
|
||||
@BindView(R.id.magisk_version) TextView magiskVersionText;
|
||||
|
||||
@BindView(R.id.safetyNet_card) CardView safetyNetCard;
|
||||
@BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
|
||||
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
|
||||
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
|
||||
@BindView(R.id.expand_layout) LinearLayout expandLayout;
|
||||
@BindView(R.id.cts_status_icon) ImageView ctsStatusIcon;
|
||||
@BindView(R.id.cts_status) TextView ctsStatusText;
|
||||
@BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
|
||||
@BindView(R.id.basic_status) TextView basicStatusText;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.linearLayout) LinearLayout root;
|
||||
|
||||
@BindView(R.id.install_option_card) CardView installOptionCard;
|
||||
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
||||
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
||||
@BindView(R.id.install_button) CardView installButton;
|
||||
@BindView(R.id.install_text) TextView installText;
|
||||
@BindView(R.id.install_option_expand) ViewGroup optionExpandLayout;
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
|
||||
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
||||
|
||||
@BindColor(R.color.red500) int colorBad;
|
||||
@@ -85,42 +68,52 @@ public class MagiskFragment extends BaseFragment
|
||||
@BindColor(R.color.green500) int colorNeutral;
|
||||
@BindColor(R.color.blue500) int colorInfo;
|
||||
|
||||
@OnClick(R.id.safetyNet_title)
|
||||
void safetyNet() {
|
||||
Runnable task = () -> {
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
new CheckSafetyNet(requireActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new CustomAlertDialog(requireActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
.setMessage(R.string.proprietary_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> task.run())
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OnClick(R.id.install_button)
|
||||
void install() {
|
||||
shownDialog = true;
|
||||
private UpdateCardHolder magisk;
|
||||
private UpdateCardHolder manager;
|
||||
private SafetyNet safetyNet;
|
||||
private Transition transition;
|
||||
private ExpandableViewHolder optionExpand;
|
||||
|
||||
private void magiskInstall(View v) {
|
||||
// Show Manager update first
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
new ManagerInstallDialog((BaseActivity) requireActivity()).show();
|
||||
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
new ManagerInstallDialog(requireActivity()).show();
|
||||
return;
|
||||
}
|
||||
new MagiskInstallDialog((BaseActivity) requireActivity()).show();
|
||||
}
|
||||
|
||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||
new MagiskInstallDialog((BaseActivity) getActivity()).show();
|
||||
private void managerInstall(View v) {
|
||||
new ManagerInstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
private void openLink(String url) {
|
||||
AppUtils.openLink(requireActivity(), Uri.parse(url));
|
||||
}
|
||||
|
||||
@OnClick(R.id.paypal)
|
||||
void paypal() {
|
||||
openLink(Const.Url.PAYPAL_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.patreon)
|
||||
void patreon() {
|
||||
openLink(Const.Url.PATREON_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.twitter)
|
||||
void twitter() {
|
||||
openLink(Const.Url.TWITTER_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.github)
|
||||
void github() {
|
||||
openLink(Const.Url.SOURCE_CODE_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.xda)
|
||||
void xda() {
|
||||
openLink(Const.Url.XDA_THREAD);
|
||||
}
|
||||
|
||||
@OnClick(R.id.uninstall_button)
|
||||
@@ -128,6 +121,13 @@ public class MagiskFragment extends BaseFragment
|
||||
new UninstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
@OnClick(R.id.arrow)
|
||||
void expandOptions() {
|
||||
if (optionExpand.isExpanded())
|
||||
optionExpand.collapse();
|
||||
else optionExpand.expand();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@@ -136,178 +136,196 @@ public class MagiskFragment extends BaseFragment
|
||||
unbinder = new MagiskFragment_ViewBinding(this, v);
|
||||
requireActivity().setTitle(R.string.magisk);
|
||||
|
||||
expandableContainer.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
optionExpand = new ArrowExpandedViewHolder(optionExpandLayout, arrow);
|
||||
safetyNet = new SafetyNet(v);
|
||||
magisk = new UpdateCardHolder(inflater, root);
|
||||
manager = new UpdateCardHolder(inflater, root);
|
||||
manager.setClickable(vv ->
|
||||
MarkDownWindow.show(requireActivity(), null,
|
||||
getResources().openRawResource(R.raw.changelog)));
|
||||
root.addView(magisk.itemView, 1);
|
||||
root.addView(manager.itemView, 2);
|
||||
|
||||
keepVerityChkbox.setChecked(Data.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(Data.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepEnc = checked);
|
||||
keepVerityChkbox.setChecked(Config.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Config.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(Config.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Config.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
updateUI();
|
||||
|
||||
magisk.install.setOnClickListener(this::magiskInstall);
|
||||
manager.install.setOnClickListener(this::managerInstall);
|
||||
if (Config.get(Config.Key.COREONLY)) {
|
||||
magisk.additional.setText(R.string.core_only_enabled);
|
||||
magisk.additional.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (!app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
manager.additional.setText("(" + app.getPackageName() + ")");
|
||||
manager.additional.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
transition = new TransitionSet()
|
||||
.setOrdering(TransitionSet.ORDERING_TOGETHER)
|
||||
.addTransition(new Fade(Fade.OUT))
|
||||
.addTransition(new ChangeBounds())
|
||||
.addTransition(new Fade(Fade.IN));
|
||||
|
||||
updateUI();
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
safetyNet.unbinder.unbind();
|
||||
magisk.unbinder.unbind();
|
||||
manager.unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
Data.loadMagiskInfo();
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
TransitionManager.beginDelayedTransition(root, transition);
|
||||
safetyNet.reset();
|
||||
magisk.reset();
|
||||
manager.reset();
|
||||
|
||||
Config.loadMagiskInfo();
|
||||
updateUI();
|
||||
|
||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||
magiskUpdateProgress.setVisibility(View.VISIBLE);
|
||||
magiskUpdateIcon.setVisibility(View.GONE);
|
||||
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
|
||||
Topic.reset(getSubscribedTopics());
|
||||
Data.remoteMagiskVersionString = null;
|
||||
Data.remoteMagiskVersionCode = -1;
|
||||
collapse();
|
||||
Config.remoteMagiskVersionString = null;
|
||||
Config.remoteMagiskVersionCode = -1;
|
||||
|
||||
shownDialog = false;
|
||||
|
||||
// Trigger state check
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
if (Networking.checkNetworkStatus(app)) {
|
||||
CheckUpdates.check();
|
||||
} else {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.SNET_CHECK_DONE, Topic.UPDATE_CHECK_DONE};
|
||||
return new int[] {Topic.UPDATE_CHECK_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
switch (topic) {
|
||||
case Topic.SNET_CHECK_DONE:
|
||||
updateSafetyNetUI((int) result[0]);
|
||||
break;
|
||||
case Topic.UPDATE_CHECK_DONE:
|
||||
updateCheckUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return expandableContainer;
|
||||
}
|
||||
|
||||
private boolean hasGms() {
|
||||
PackageManager pm = mm.getPackageManager();
|
||||
PackageInfo info;
|
||||
try {
|
||||
info = pm.getPackageInfo("com.google.android.gms", 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
return info.applicationInfo.enabled;
|
||||
updateCheckUI();
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
((MainActivity) requireActivity()).checkHideSection();
|
||||
|
||||
boolean hasNetwork = Download.checkNetworkStatus(mm);
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
|
||||
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
uninstallButton.setVisibility(hasRoot ? View.VISIBLE : View.GONE);
|
||||
coreOnlyNotice.setVisibility(mm.prefs.getBoolean(Const.Key.COREONLY, false) ? View.VISIBLE : View.GONE);
|
||||
|
||||
int image, color;
|
||||
|
||||
if (Data.magiskVersionCode < 0) {
|
||||
String status;
|
||||
if (Config.magiskVersionCode < 0) {
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
magiskVersionText.setText(R.string.magisk_version_error);
|
||||
status = getString(R.string.magisk_version_error);
|
||||
magisk.status.setText(status);
|
||||
magisk.currentVersion.setVisibility(View.GONE);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + Data.magiskVersionString));
|
||||
status = getString(R.string.magisk);
|
||||
magisk.currentVersion.setText(getString(R.string.current_installed,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
Config.magiskVersionString, Config.magiskVersionCode)));
|
||||
}
|
||||
magisk.statusIcon.setColorFilter(color);
|
||||
magisk.statusIcon.setImageResource(image);
|
||||
|
||||
magiskStatusIcon.setImageResource(image);
|
||||
magiskStatusIcon.setColorFilter(color);
|
||||
manager.statusIcon.setColorFilter(colorOK);
|
||||
manager.statusIcon.setImageResource(R.drawable.ic_check_circle);
|
||||
manager.currentVersion.setText(getString(R.string.current_installed,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
|
||||
if (!Networking.checkNetworkStatus(app)) {
|
||||
// No network, updateCheckUI will not be triggered
|
||||
magisk.status.setText(status);
|
||||
manager.status.setText(R.string.app_name);
|
||||
magisk.setValid(false);
|
||||
manager.setValid(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCheckUI() {
|
||||
int image, color;
|
||||
String status, button = "";
|
||||
|
||||
safetyNetCard.setVisibility(hasGms() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (Data.remoteMagiskVersionCode < 0) {
|
||||
if (Config.remoteMagiskVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
magiskUpdateText.setText(R.string.invalid_update_channel);
|
||||
installButton.setVisibility(View.GONE);
|
||||
status = getString(R.string.invalid_update_channel);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + Data.remoteMagiskVersionString));
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||
} else if (Data.magiskVersionCode > 0 && Data.remoteMagiskVersionCode > Data.magiskVersionCode) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||
magisk.latestVersion.setText(getString(R.string.latest_version,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode)));
|
||||
if (Config.remoteMagiskVersionCode > Config.magiskVersionCode) {
|
||||
color = colorInfo;
|
||||
image = R.drawable.ic_update;
|
||||
status = getString(R.string.magisk_update_title);
|
||||
button = getString(R.string.update);
|
||||
} else {
|
||||
installText.setText(R.string.install);
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
status = getString(R.string.magisk_up_to_date);
|
||||
button = getString(R.string.install);
|
||||
}
|
||||
}
|
||||
|
||||
magiskUpdateIcon.setImageResource(image);
|
||||
magiskUpdateIcon.setColorFilter(color);
|
||||
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
magiskUpdateProgress.setVisibility(View.GONE);
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (!shownDialog) {
|
||||
if (Data.remoteMagiskVersionCode > Data.magiskVersionCode
|
||||
|| Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
install();
|
||||
} else if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
!ShellUtils.fastCmdResult("env_check")) {
|
||||
new EnvFixDialog(requireActivity()).show();
|
||||
}
|
||||
if (Config.magiskVersionCode > 0) {
|
||||
// Only override status if Magisk is installed
|
||||
magisk.statusIcon.setImageResource(image);
|
||||
magisk.statusIcon.setColorFilter(color);
|
||||
magisk.status.setText(status);
|
||||
magisk.install.setText(button);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSafetyNetUI(int response) {
|
||||
safetyNetProgress.setVisibility(View.GONE);
|
||||
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
|
||||
if ((response & 0x0F) == 0) {
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_success);
|
||||
|
||||
boolean b;
|
||||
b = (response & ISafetyNetHelper.CTS_PASS) != 0;
|
||||
ctsStatusText.setText("ctsProfile: " + b);
|
||||
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
b = (response & ISafetyNetHelper.BASIC_PASS) != 0;
|
||||
basicStatusText.setText("basicIntegrity: " + b);
|
||||
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
expand();
|
||||
if (Config.remoteManagerVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
status = getString(R.string.invalid_update_channel);
|
||||
} else {
|
||||
@StringRes int resid;
|
||||
switch (response) {
|
||||
case ISafetyNetHelper.RESPONSE_ERR:
|
||||
resid = R.string.safetyNet_res_invalid;
|
||||
break;
|
||||
case ISafetyNetHelper.CONNECTION_FAIL:
|
||||
default:
|
||||
resid = R.string.safetyNet_api_error;
|
||||
break;
|
||||
manager.latestVersion.setText(getString(R.string.latest_version,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
Config.remoteManagerVersionString, Config.remoteManagerVersionCode)));
|
||||
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
color = colorInfo;
|
||||
image = R.drawable.ic_update;
|
||||
status = getString(R.string.manager_update_title);
|
||||
manager.install.setText(R.string.update);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
status = getString(R.string.manager_up_to_date);
|
||||
manager.install.setText(R.string.install);
|
||||
}
|
||||
safetyNetStatusText.setText(resid);
|
||||
}
|
||||
manager.statusIcon.setImageResource(image);
|
||||
manager.statusIcon.setColorFilter(color);
|
||||
manager.status.setText(status);
|
||||
|
||||
magisk.setValid(Config.remoteMagiskVersionCode > 0);
|
||||
manager.setValid(Config.remoteManagerVersionCode > 0);
|
||||
|
||||
TransitionManager.beginDelayedTransition(root, transition);
|
||||
|
||||
if (Config.remoteMagiskVersionCode < 0) {
|
||||
// Hide install related components
|
||||
installOptionCard.setVisibility(View.GONE);
|
||||
uninstallButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
// Show install related components
|
||||
installOptionCard.setVisibility(View.VISIBLE);
|
||||
uninstallButton.setVisibility(Shell.rootAccess() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
if (!shownDialog && Config.magiskVersionCode > 0 &&
|
||||
!Shell.su("env_check").exec().isSuccess()) {
|
||||
shownDialog = true;
|
||||
new EnvFixDialog(requireActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,10 +4,12 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
@@ -23,10 +25,9 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
SearchView search;
|
||||
|
||||
private ApplicationAdapter appAdapter;
|
||||
|
||||
private SearchView search;
|
||||
private ApplicationAdapter adapter;
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
|
||||
@Override
|
||||
@@ -41,22 +42,22 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
||||
|
||||
appAdapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
adapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(appAdapter::refresh);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
appAdapter.filter(query);
|
||||
adapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
appAdapter.filter(newText);
|
||||
adapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -71,6 +72,21 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
boolean showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||
menu.findItem(R.id.show_system).setChecked(showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.show_system) {
|
||||
boolean showSystem = !item.isChecked();
|
||||
item.setChecked(showSystem);
|
||||
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
adapter.filter(search.getQuery().toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,6 +97,6 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(search.getQuery().toString());
|
||||
adapter.filter(search.getQuery().toString());
|
||||
}
|
||||
}
|
||||
|
@@ -2,39 +2,36 @@ package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.StringListAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class MagiskLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.txtLog) TextView txtLog;
|
||||
@BindView(R.id.svLog) ScrollView svLog;
|
||||
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -42,7 +39,6 @@ public class MagiskLogFragment extends BaseFragment {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||
unbinder = new MagiskLogFragment_ViewBinding(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
txtLog.setTextIsSelectable(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -74,6 +70,7 @@ public class MagiskLogFragment extends BaseFragment {
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
clearLogs();
|
||||
rv.setAdapter(new MagiskLogAdapter(NOPList.getInstance()));
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
@@ -81,14 +78,8 @@ public class MagiskLogFragment extends BaseFragment {
|
||||
}
|
||||
|
||||
private void readLogs() {
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " | tail -n 5000").submit(result -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (result.getOut().isEmpty())
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
else
|
||||
txtLog.setText(TextUtils.join("\n", result.getOut()));
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
Shell.su("tail -n 5000 " + Const.MAGISK_LOG).submit(result -> {
|
||||
rv.setAdapter(new MagiskLogAdapter(result.getOut()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,12 +98,60 @@ public class MagiskLogFragment extends BaseFragment {
|
||||
}
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||
.submit(result ->
|
||||
SnackbarMaker.make(txtLog, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
SnackbarMaker.make(rv, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
private void clearLogs() {
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
SnackbarMaker.make(rv, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private class MagiskLogAdapter extends StringListAdapter<MagiskLogAdapter.ViewHolder> {
|
||||
|
||||
MagiskLogAdapter(List<String> list) {
|
||||
super(list);
|
||||
if (mList.isEmpty())
|
||||
mList.add(requireContext().getString(R.string.log_is_empty));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int itemLayoutRes() {
|
||||
return R.layout.list_item_console;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder createViewHolder(@NonNull View v) {
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateTextWidth(ViewHolder holder) {
|
||||
super.onUpdateTextWidth(holder);
|
||||
// Find the longest string and update accordingly
|
||||
int max = 0;
|
||||
String maxStr = "";
|
||||
for (String s : mList) {
|
||||
int len = s.length();
|
||||
if (len > max) {
|
||||
max = len;
|
||||
maxStr = s;
|
||||
}
|
||||
}
|
||||
holder.txt.setText(maxStr);
|
||||
super.onUpdateTextWidth(holder);
|
||||
}
|
||||
|
||||
public class ViewHolder extends StringListAdapter.ViewHolder {
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int textViewResId() {
|
||||
return R.id.txt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,8 +12,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
@@ -94,7 +94,7 @@ public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||
// Get the URI of the selected file
|
||||
Intent intent = new Intent(getActivity(), Data.classMap.get(FlashActivity.class));
|
||||
Intent intent = new Intent(getActivity(), ClassMap.get(FlashActivity.class));
|
||||
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
Utils.reboot();
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.su("/system/bin/reboot recovery").submit();
|
||||
@@ -118,7 +118,7 @@ public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
Shell.su("/system/bin/reboot bootloader").submit();
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.su("/system/bin/reboot upgrade").submit();
|
||||
Shell.su("/system/bin/reboot download").submit();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@@ -11,13 +11,12 @@ import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -51,11 +50,7 @@ public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
new UpdateRepos().exec(true);
|
||||
});
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> new UpdateRepos().exec(true));
|
||||
|
||||
requireActivity().setTitle(R.string.downloads);
|
||||
|
||||
@@ -69,17 +64,21 @@ public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
if (topic == Topic.MODULE_LOAD_DONE) {
|
||||
adapter = new ReposAdapter(mm.repoDB, (Map<String, Module>) result[0]);
|
||||
mm.repoDB.registerAdapter(adapter);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
switch (topic) {
|
||||
case Topic.MODULE_LOAD_DONE:
|
||||
adapter = new ReposAdapter(app.repoDB, (Map<String, Module>) result[0]);
|
||||
recyclerView.setAdapter(adapter);
|
||||
break;
|
||||
case Topic.REPO_LOAD_DONE:
|
||||
if (adapter != null)
|
||||
adapter.notifyDBChanged();
|
||||
break;
|
||||
}
|
||||
if (Topic.isPublished(getSubscribedTopics())) {
|
||||
if (Topic.isPublished(this)) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
boolean empty = adapter.getItemCount() == 0;
|
||||
recyclerView.setVisibility(empty ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(empty ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,19 +105,13 @@ public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(R.array.sorting_orders, Data.repoOrder, (d, which) -> {
|
||||
Data.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, Data.repoOrder).apply();
|
||||
.setSingleChoiceItems(R.array.sorting_orders,
|
||||
Config.get(Config.Key.REPO_ORDER), (d, which) -> {
|
||||
Config.set(Config.Key.REPO_ORDER, which);
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mm.repoDB.unregisterAdapter();
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +1,28 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
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 android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.DlInstallManager;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.components.BasePreferenceFragment;
|
||||
import com.topjohnwu.magisk.dialogs.FingerprintAuthDialog;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -32,43 +32,23 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.ListPreference;
|
||||
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.preference.SwitchPreference;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
Topic.Subscriber, Topic.AutoSubscriber {
|
||||
|
||||
private MagiskManager mm;
|
||||
public class SettingsFragment extends BasePreferenceFragment implements Topic.Subscriber {
|
||||
|
||||
private ListPreference updateChannel, autoRes, suNotification,
|
||||
requestTimeout, rootConfig, multiuserConfig, nsConfig;
|
||||
|
||||
private int rootState, namespaceState;
|
||||
private boolean showSuperuser;
|
||||
|
||||
private void prefsSync() {
|
||||
rootState = mm.mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
namespaceState = mm.mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
showSuperuser = Utils.showSuperUser();
|
||||
mm.prefs.edit()
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(rootState))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(namespaceState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(Data.multiuserState))
|
||||
.putBoolean(Const.Key.SU_FINGERPRINT, FingerprintHelper.useFingerPrint())
|
||||
.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
mm = Data.MM();
|
||||
prefsSync();
|
||||
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
|
||||
boolean showSuperuser = Utils.showSuperUser();
|
||||
app.prefs.edit()
|
||||
.putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
||||
.apply();
|
||||
|
||||
PreferenceScreen prefScreen = getPreferenceScreen();
|
||||
|
||||
@@ -82,12 +62,12 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
});
|
||||
Preference restoreManager = findPreference("restore");
|
||||
restoreManager.setOnPreferenceClickListener(pref -> {
|
||||
DlInstallManager.restore();
|
||||
DownloadApp.restore();
|
||||
return true;
|
||||
});
|
||||
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
app.prefs.edit().remove(Config.Key.ETAG_KEY).apply();
|
||||
app.repoDB.clearRepo();
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
@@ -98,33 +78,32 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
||||
rootConfig = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
||||
multiuserConfig = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||
nsConfig = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||
updateChannel = (ListPreference) findPreference(Config.Key.UPDATE_CHANNEL);
|
||||
rootConfig = (ListPreference) findPreference(Config.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Config.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Config.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Config.Key.SU_NOTIFICATION);
|
||||
multiuserConfig = (ListPreference) findPreference(Config.Key.SU_MULTIUSER_MODE);
|
||||
nsConfig = (ListPreference) findPreference(Config.Key.SU_MNT_NS);
|
||||
SwitchPreferenceCompat reauth = (SwitchPreferenceCompat) findPreference(Config.Key.SU_REAUTH);
|
||||
SwitchPreferenceCompat fingerprint = (SwitchPreferenceCompat) findPreference(Config.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||
String prev =String.valueOf(Data.updateChannel);
|
||||
int prev = Config.get(Config.Key.UPDATE_CHANNEL);
|
||||
int channel = Integer.parseInt((String) o);
|
||||
if (channel == Const.Value.CUSTOM_CHANNEL) {
|
||||
if (channel == Config.Value.CUSTOM_CHANNEL) {
|
||||
View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
url.setText(app.prefs.getString(Config.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
Config.set(Config.Key.CUSTOM_CHANNEL, url.getText().toString()))
|
||||
.setNegativeButton(R.string.close, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
||||
.setOnCancelListener(d ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
@@ -132,6 +111,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
|
||||
setSummary();
|
||||
|
||||
// Remove language setting when API < 17
|
||||
if (Build.VERSION.SDK_INT < 17)
|
||||
generalCatagory.removePreference(findPreference(Config.Key.LOCALE));
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserConfig);
|
||||
@@ -152,10 +135,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
}
|
||||
|
||||
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
||||
if (mm.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
if (app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (!Download.checkNetworkStatus(mm))
|
||||
if (!Networking.checkNetworkStatus(app))
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
@@ -182,51 +165,25 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
int i = 1;
|
||||
for (Locale locale : LocaleManager.locales) {
|
||||
entries[i] = locale.getDisplayName(locale);
|
||||
entryValues[i++] = locale.toLanguageTag();
|
||||
entryValues[i++] = LocaleManager.toLanguageTag(locale);
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mm.prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
Topic.subscribe(this);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mm.prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
switch (key) {
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
case Config.Key.ROOT_ACCESS:
|
||||
case Config.Key.SU_MULTIUSER_MODE:
|
||||
case Config.Key.SU_MNT_NS:
|
||||
app.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
}
|
||||
switch (key) {
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
rootState = Utils.getPrefsInt(prefs, key);
|
||||
break;
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
Data.multiuserState = Utils.getPrefsInt(prefs, key);
|
||||
break;
|
||||
case Const.Key.SU_MNT_NS:
|
||||
namespaceState = Utils.getPrefsInt(prefs, key);
|
||||
break;
|
||||
case Const.Key.DARK_THEME:
|
||||
case Config.Key.DARK_THEME:
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Const.Key.COREONLY:
|
||||
case Config.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
@@ -236,104 +193,101 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
case Config.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit();
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
LocaleManager.setLocale(mm);
|
||||
case Config.Key.LOCALE:
|
||||
LocaleManager.setLocale(app);
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
case Const.Key.CUSTOM_CHANNEL:
|
||||
case Config.Key.UPDATE_CHANNEL:
|
||||
case Config.Key.CUSTOM_CHANNEL:
|
||||
CheckUpdates.check();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
Utils.setupUpdateCheck();
|
||||
case Config.Key.CHECK_UPDATES:
|
||||
AppUtils.scheduleUpdateCheck();
|
||||
break;
|
||||
}
|
||||
Data.loadConfig();
|
||||
setSummary();
|
||||
setSummary(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
switch (key) {
|
||||
case Const.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreference) preference).isChecked();
|
||||
((SwitchPreference) preference).setChecked(!checked);
|
||||
FingerprintHelper.showAuthDialog(requireActivity(), () -> {
|
||||
((SwitchPreference) preference).setChecked(checked);
|
||||
mm.mDB.setSettings(key, checked ? 1 : 0);
|
||||
});
|
||||
case Config.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreferenceCompat) preference).isChecked();
|
||||
((SwitchPreferenceCompat) preference).setChecked(!checked);
|
||||
new FingerprintAuthDialog(requireActivity(), () -> {
|
||||
((SwitchPreferenceCompat) preference).setChecked(checked);
|
||||
Config.set(key, checked);
|
||||
}).show();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSummary(String key) {
|
||||
switch (key) {
|
||||
case Config.Key.UPDATE_CHANNEL:
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)
|
||||
[(int) Config.get(Config.Key.UPDATE_CHANNEL)]);
|
||||
break;
|
||||
case Config.Key.ROOT_ACCESS:
|
||||
rootConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)
|
||||
[(int) Config.get(Config.Key.ROOT_ACCESS)]);
|
||||
break;
|
||||
case Config.Key.SU_AUTO_RESPONSE:
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)
|
||||
[(int) Config.get(Config.Key.SU_AUTO_RESPONSE)]);
|
||||
break;
|
||||
case Config.Key.SU_NOTIFICATION:
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)
|
||||
[(int) Config.get(Config.Key.SU_NOTIFICATION)]);
|
||||
break;
|
||||
case Config.Key.SU_REQUEST_TIMEOUT:
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary,
|
||||
(int) Config.get(Config.Key.SU_REQUEST_TIMEOUT)));
|
||||
break;
|
||||
case Config.Key.SU_MULTIUSER_MODE:
|
||||
multiuserConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)
|
||||
[(int) Config.get(Config.Key.SU_MULTIUSER_MODE)]);
|
||||
break;
|
||||
case Config.Key.SU_MNT_NS:
|
||||
nsConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)
|
||||
[(int) Config.get(Config.Key.SU_MNT_NS)]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[Data.updateChannel]);
|
||||
rootConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)[rootState]);
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)[Data.suResponseType]);
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[Data.suNotificationType]);
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary,
|
||||
mm.prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||
multiuserConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[Data.multiuserState]);
|
||||
nsConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)[namespaceState]);
|
||||
setSummary(Config.Key.UPDATE_CHANNEL);
|
||||
setSummary(Config.Key.ROOT_ACCESS);
|
||||
setSummary(Config.Key.SU_AUTO_RESPONSE);
|
||||
setSummary(Config.Key.SU_NOTIFICATION);
|
||||
setSummary(Config.Key.SU_REQUEST_TIMEOUT);
|
||||
setSummary(Config.Key.SU_MULTIUSER_MODE);
|
||||
setSummary(Config.Key.SU_MNT_NS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.LOCALE_FETCH_DONE};
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user